[osgearth] 03/19: Imported Upstream version 2.5

Bas Couwenberg sebastic at xs4all.nl
Thu Feb 20 23:27:42 UTC 2014


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

sebastic-guest pushed a commit to branch master
in repository osgearth.

commit 1b16339ccac8eb6836f03703919eb7ce51201db6
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Jan 16 22:41:58 2014 +0100

    Imported Upstream version 2.5
---
 CMakeLists.txt                                     |  38 +-
 CMakeModules/FindCURL.cmake                        |   3 +
 CMakeModules/FindJavaScriptCore.cmake              |  46 +
 CMakeModules/FindLibNoise.cmake                    |  64 ++
 CMakeModules/FindMiniZip.cmake                     | 104 ++-
 CMakeModules/FindV8.cmake                          | 142 ++-
 CMakeModules/GetGitRevisionDescription.cmake       | 123 +++
 CMakeModules/GetGitRevisionDescription.cmake.in    |  38 +
 LICENSE.txt                                        |  21 +
 docs/CMakeLists.txt                                | 117 +++
 docs/source/about.rst                              |  26 +-
 docs/source/data.rst                               |   8 +-
 docs/source/developer/coordinate_systems.rst       |  74 ++
 docs/source/developer/index.rst                    |   1 +
 docs/source/developer/shader_composition.rst       |  55 +-
 docs/source/developer/utilities.rst                | 114 +++
 docs/source/faq.rst                                |  40 +-
 docs/source/ios.rst                                |   5 +
 docs/source/references/drivers/terrain/mp.rst      |  23 +-
 .../drivers/terrain/terrain_options_shared.rst     |   3 -
 docs/source/references/drivers/tile/gdal.rst       |   5 +-
 docs/source/references/drivers/tile/index.rst      |   1 +
 docs/source/references/drivers/tile/noise.rst      | 111 +++
 docs/source/references/earthfile.rst               |  87 +-
 docs/source/references/envvars.rst                 |  21 +-
 docs/source/references/symbology.rst               | 208 +++--
 docs/source/releasenotes.rst                       |  47 +
 docs/source/user/caching.rst                       |  28 +-
 docs/source/user/tools.rst                         |  15 +-
 src/applications/CMakeLists.txt                    |  11 +-
 .../osgearth_annotation/osgearth_annotation.cpp    |   2 +-
 src/applications/osgearth_city/osgearth_city.cpp   |  11 +-
 .../osgearth_contour/osgearth_contour.cpp          | 163 ----
 .../osgearth_controls/osgearth_controls.cpp        |  36 +-
 src/applications/osgearth_demo/CMakeLists.txt      |  28 +
 .../osgearth_demo.cpp}                             |  48 +-
 .../CMakeLists.txt                                 |   4 +-
 .../osgearth_detailtex/osgearth_detailtex.cpp      | 134 +++
 .../osgearth_elevation/osgearth_elevation.cpp      |   4 +-
 .../osgearth_featureeditor.cpp                     | 118 +--
 .../osgearth_features/osgearth_features.cpp        |   7 +-
 .../osgearth_imageoverlay.cpp                      |  20 +-
 src/applications/osgearth_manip/osgearth_manip.cpp |  94 +-
 .../osgearth_measure/osgearth_measure.cpp          |  11 +-
 .../osgearth_occlusionculling.cpp                  |   2 +-
 .../osgearth_overlayviewer.cpp                     |  18 +-
 .../osgearth_package/osgearth_package.cpp          |   8 +-
 .../osgearth_package_qt/ExportDialog.ui            |  10 +-
 .../osgearth_package_qt/PackageQtMainWindow        |  13 +-
 .../osgearth_package_qt/TMSExporter.cpp            |  52 +-
 src/applications/osgearth_package_qt/TMSExporter.h |  16 +-
 .../osgearth_package_qt/images/delete.png          | Bin 947 -> 667 bytes
 .../osgearth_package_qt/package_qt.cpp             |  16 +-
 src/applications/osgearth_qt/DemoMainWindow        |  24 +-
 src/applications/osgearth_qt/osgearth_qt.cpp       | 113 +--
 .../osgearth_qt_simple/osgearth_qt_simple.cpp      |   5 +-
 src/applications/osgearth_seed/osgearth_seed.cpp   | 116 ++-
 .../CMakeLists.txt                                 |   4 +-
 .../osgearth_sharedlayer/osgearth_sharedlayer.cpp  | 222 +++++
 .../CMakeLists.txt                                 |   4 +-
 .../osgearth_terraineffects.cpp                    | 228 +++++
 .../osgearth_terrainprofile.cpp                    |   2 +-
 src/applications/osgearth_tfs/osgearth_tfs.cpp     |  19 +-
 .../CMakeLists.txt                                 |   5 +-
 .../osgearth_tileindex/osgearth_tileindex.cpp      |  85 ++
 src/applications/osgearth_toc/osgearth_toc.cpp     |  97 +-
 .../osgearth_tracks/osgearth_tracks.cpp            |   4 +-
 .../osgearth_verticalscale.cpp                     | 132 ++-
 .../osgearth_viewer/osgearth_viewer.cpp            |  12 +-
 .../osgEarthViewerIOS.xcodeproj/project.pbxproj    |  52 +-
 .../osgEarthViewerIOS/ViewController.m             |  18 +-
 .../osgEarthViewerIOS/osgPlugins.h                 |   1 +
 src/osgEarth/AlphaEffect                           |  68 ++
 src/osgEarth/AlphaEffect.cpp                       | 115 +++
 .../AnnotationSettings.cpp => osgEarth/AutoScale}  |  28 +-
 src/osgEarth/AutoScale.cpp                         | 191 ++++
 src/osgEarth/CMakeLists.txt                        |  22 +
 src/osgEarth/Cache                                 |  10 +-
 src/osgEarth/Cache.cpp                             |  12 +
 src/osgEarth/CacheBin                              |  58 +-
 src/osgEarth/CacheEstimator                        | 114 +++
 src/osgEarth/CacheEstimator.cpp                    |  94 ++
 src/osgEarth/CachePolicy                           |  25 +-
 src/osgEarth/CachePolicy.cpp                       |  60 +-
 src/osgEarth/CacheSeed                             |  46 +-
 src/osgEarth/CacheSeed.cpp                         | 341 ++++----
 src/osgEarth/Capabilities                          |  21 +-
 src/osgEarth/Capabilities.cpp                      |  31 +-
 src/osgEarth/ClampableNode                         |  47 +-
 src/osgEarth/ClampableNode.cpp                     |  85 +-
 src/osgEarth/ClampingTechnique.cpp                 | 160 ++--
 src/osgEarth/ColorFilter                           |   2 +-
 src/osgEarth/CompositeTileSource.cpp               |   1 +
 src/osgEarth/Config                                |  21 +-
 src/osgEarth/Config.cpp                            | 142 ++-
 src/osgEarth/Containers                            |   4 -
 src/osgEarth/Cube                                  |  10 +-
 src/osgEarth/Cube.cpp                              |  12 +
 src/osgEarth/CullingUtils                          |  62 +-
 src/osgEarth/CullingUtils.cpp                      | 226 ++++-
 src/osgEarth/DateTime                              |  74 ++
 src/osgEarth/DateTime.cpp                          | 134 +++
 src/{osgEarthAnnotation => osgEarth}/Decluttering  |  35 +-
 .../Decluttering.cpp                               |  28 +-
 src/osgEarth/DepthOffset                           | 131 +--
 src/osgEarth/DepthOffset.cpp                       | 538 +++++-------
 src/osgEarth/Draggers.cpp                          |  18 +-
 src/osgEarth/DrapingTechnique                      |  11 +-
 src/osgEarth/DrapingTechnique.cpp                  | 440 +++++++---
 src/osgEarth/DrawInstanced                         |   3 +-
 src/osgEarth/DrawInstanced.cpp                     | 202 +++--
 src/osgEarth/ElevationLayer                        |  12 +-
 src/osgEarth/ElevationLayer.cpp                    | 153 +++-
 src/osgEarth/ElevationQuery.cpp                    | 130 ++-
 src/osgEarth/FadeEffect.cpp                        |  76 +-
 src/osgEarth/FileUtils                             |  58 ++
 src/osgEarth/FileUtils.cpp                         |  93 +-
 src/osgEarth/GeoData                               |  12 +
 src/osgEarth/GeoData.cpp                           | 346 +++++---
 src/osgEarth/HTTPClient                            |  34 +-
 src/osgEarth/HTTPClient.cpp                        | 173 ++--
 src/osgEarth/HeightFieldUtils                      |  72 +-
 src/osgEarth/HeightFieldUtils.cpp                  | 150 +++-
 src/osgEarth/IOTypes                               |  35 +-
 src/osgEarth/IOTypes.cpp                           |  18 +
 src/osgEarth/ImageLayer                            |  42 +-
 src/osgEarth/ImageLayer.cpp                        | 145 ++-
 src/osgEarth/ImageUtils                            |  20 +-
 src/osgEarth/ImageUtils.cpp                        | 119 ++-
 src/osgEarth/Locators                              |  17 +-
 src/osgEarth/Locators.cpp                          |  42 +-
 src/osgEarth/Map                                   |  25 +-
 src/osgEarth/Map.cpp                               |  68 +-
 src/osgEarth/MapModelChange                        |   1 +
 src/osgEarth/MapNode                               |  10 +-
 src/osgEarth/MapNode.cpp                           |  96 +-
 src/osgEarth/MapNodeOptions                        |  14 +
 src/osgEarth/MapNodeOptions.cpp                    |  92 +-
 src/osgEarth/MapOptions                            |   4 +-
 src/osgEarth/MaskNode                              |   6 +-
 src/osgEarth/MaskNode.cpp                          |   4 +
 src/osgEarth/MaskSource                            |  10 +-
 src/osgEarth/MaskSource.cpp                        |  18 +
 src/osgEarth/MemCache.cpp                          |  46 +-
 src/osgEarth/ModelLayer                            |  26 +-
 src/osgEarth/ModelLayer.cpp                        |  70 +-
 src/osgEarth/ModelSource                           |  10 +-
 src/osgEarth/ModelSource.cpp                       |  18 +-
 src/osgEarth/OverlayDecorator                      |  14 +-
 src/osgEarth/OverlayDecorator.cpp                  | 211 +++--
 src/osgEarth/OverlayNode                           |   2 +-
 src/osgEarth/OverlayNode.cpp                       |  93 +-
 src/osgEarth/Pickers                               |  24 +-
 src/osgEarth/Pickers.cpp                           |  81 +-
 src/osgEarth/PrimitiveIntersector                  | 127 +++
 src/osgEarth/PrimitiveIntersector.cpp              | 681 ++++++++++++++
 src/osgEarth/Profile                               |   2 +-
 src/osgEarth/Profile.cpp                           | 132 ++-
 src/osgEarth/Progress                              |   6 +-
 src/osgEarth/Registry.cpp                          |  35 +-
 src/osgEarth/ShaderFactory                         |  49 +-
 src/osgEarth/ShaderFactory.cpp                     |  22 +-
 src/osgEarth/ShaderGenerator                       |  50 +-
 src/osgEarth/ShaderGenerator.cpp                   | 351 +++++---
 src/osgEarth/ShaderUtils.cpp                       |   4 +
 src/osgEarth/SpatialReference                      |  48 +-
 src/osgEarth/SpatialReference.cpp                  | 122 +--
 src/osgEarth/StateSetCache                         |  14 +-
 src/osgEarth/StateSetCache.cpp                     |  67 ++
 src/osgEarth/TaskService.cpp                       |  18 -
 src/osgEarth/Terrain                               |  32 +-
 src/osgEarth/Terrain.cpp                           | 108 ++-
 src/osgEarth/TerrainEffect                         |  57 ++
 src/osgEarth/TerrainEngineNode                     |  45 +-
 src/osgEarth/TerrainEngineNode.cpp                 |  43 +-
 src/osgEarth/TerrainLayer                          |  20 +-
 src/osgEarth/TerrainLayer.cpp                      |  62 +-
 src/osgEarth/TerrainOptions                        |   3 +-
 src/osgEarth/TerrainOptions.cpp                    |   3 +-
 src/osgEarth/TextureCompositor.cpp                 |  14 +-
 src/osgEarth/TextureCompositorMulti.cpp            |   4 +-
 src/osgEarth/ThreadingUtils                        |   2 +-
 src/osgEarth/TileKey                               |  22 +-
 src/osgEarth/TileKey.cpp                           |  13 +
 src/osgEarth/TileSource                            |  68 +-
 src/osgEarth/TileSource.cpp                        |  43 +-
 src/osgEarth/TraversalData                         |   8 +-
 src/osgEarth/TraversalData.cpp                     |   6 +-
 src/osgEarth/URI                                   |   3 +-
 src/osgEarth/URI.cpp                               |  47 +-
 src/osgEarth/Utils                                 |  23 +
 src/osgEarth/Utils.cpp                             |  88 ++
 src/osgEarth/Version                               |   4 +-
 src/osgEarth/Version.cpp                           |  13 +-
 src/osgEarth/VersionGit.cpp.in                     |   7 +
 src/osgEarth/VirtualProgram                        | 120 ++-
 src/osgEarth/VirtualProgram.cpp                    | 974 +++++++++++++--------
 src/osgEarth/XmlUtils.cpp                          |  10 -
 src/osgEarthAnnotation/AnnotationData              |  19 +-
 src/osgEarthAnnotation/AnnotationEditing.cpp       |   6 +-
 src/osgEarthAnnotation/AnnotationNode              |   5 +-
 src/osgEarthAnnotation/AnnotationNode.cpp          |  45 +-
 src/osgEarthAnnotation/AnnotationRegistry.cpp      |   2 +-
 src/osgEarthAnnotation/AnnotationSettings          |  20 +-
 src/osgEarthAnnotation/AnnotationSettings.cpp      |   3 +-
 src/osgEarthAnnotation/AnnotationUtils             |  30 +-
 src/osgEarthAnnotation/AnnotationUtils.cpp         | 201 ++---
 src/osgEarthAnnotation/CMakeLists.txt              |  22 +-
 src/osgEarthAnnotation/CircleNode.cpp              |   9 +-
 src/osgEarthAnnotation/EllipseNode.cpp             |   9 +-
 src/osgEarthAnnotation/FeatureEditing              |  35 +-
 src/osgEarthAnnotation/FeatureEditing.cpp          |  70 +-
 src/osgEarthAnnotation/FeatureNode                 |  18 +-
 src/osgEarthAnnotation/FeatureNode.cpp             |  35 +-
 src/osgEarthAnnotation/ImageOverlay.cpp            |  19 +-
 src/osgEarthAnnotation/ImageOverlayEditor          |   9 +-
 src/osgEarthAnnotation/ImageOverlayEditor.cpp      |  15 +-
 src/osgEarthAnnotation/LabelNode                   |   2 +-
 src/osgEarthAnnotation/LabelNode.cpp               |   6 +-
 src/osgEarthAnnotation/LocalGeometryNode.cpp       |   7 +-
 src/osgEarthAnnotation/LocalizedNode               |  10 +-
 src/osgEarthAnnotation/LocalizedNode.cpp           |  85 +-
 src/osgEarthAnnotation/ModelNode.cpp               |  30 +-
 src/osgEarthAnnotation/OrthoNode                   |   9 +-
 src/osgEarthAnnotation/OrthoNode.cpp               |  85 +-
 src/osgEarthAnnotation/PlaceNode.cpp               |  55 +-
 src/osgEarthAnnotation/RectangleNode.cpp           |   8 +-
 src/osgEarthAnnotation/TrackNode.cpp               |   8 +-
 src/osgEarthDrivers/CMakeLists.txt                 |  10 +
 .../agglite/AGGLiteRasterizerTileSource.cpp        | 247 +++---
 src/osgEarthDrivers/arcgis/ArcGISOptions           |   9 +
 src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp  |  29 +-
 src/osgEarthDrivers/bing/BingOptions               | 113 +++
 src/osgEarthDrivers/bing/BingTileSource.cpp        | 309 +++++++
 src/osgEarthDrivers/bing/CMakeLists.txt            |  19 +
 .../cache_filesystem/FileSystemCache               |   2 +-
 .../cache_filesystem/FileSystemCache.cpp           | 280 ++++--
 src/osgEarthDrivers/debug/DebugTileSource.cpp      |   4 +-
 src/osgEarthDrivers/earth/EarthFileSerializer2.cpp |  20 +-
 src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp |  23 +-
 .../engine_byo/BYOTerrainEngineNode.cpp            |   5 +-
 src/osgEarthDrivers/engine_mp/CMakeLists.txt       |  13 +-
 src/osgEarthDrivers/engine_mp/CustomPagedLOD       |  73 --
 src/osgEarthDrivers/engine_mp/CustomPagedLOD.cpp   | 111 ---
 src/osgEarthDrivers/engine_mp/FileLocationCallback |   1 -
 src/osgEarthDrivers/engine_mp/KeyNodeFactory       |   8 +-
 .../engine_mp/LODFactorCallback.cpp                |  80 --
 src/osgEarthDrivers/engine_mp/MPGeometry           |  59 +-
 src/osgEarthDrivers/engine_mp/MPGeometry.cpp       | 321 +++++--
 .../engine_mp/MPTerrainEngineDriver.cpp            |  81 +-
 src/osgEarthDrivers/engine_mp/MPTerrainEngineNode  |  23 +-
 .../engine_mp/MPTerrainEngineNode.cpp              | 385 +++++---
 .../engine_mp/MPTerrainEngineOptions               |  40 +-
 .../engine_mp/SerialKeyNodeFactory.cpp             | 224 -----
 .../{SerialKeyNodeFactory => SingleKeyNodeFactory} |  41 +-
 .../engine_mp/SingleKeyNodeFactory.cpp             | 215 +++++
 src/osgEarthDrivers/engine_mp/TerrainNode          |   1 -
 src/osgEarthDrivers/engine_mp/TileGroup            |  71 ++
 src/osgEarthDrivers/engine_mp/TileGroup.cpp        | 193 ++++
 src/osgEarthDrivers/engine_mp/TileModel            | 142 ++-
 src/osgEarthDrivers/engine_mp/TileModel.cpp        | 231 +++++
 src/osgEarthDrivers/engine_mp/TileModelCompiler    |  22 +-
 .../engine_mp/TileModelCompiler.cpp                | 969 +++++++++++---------
 src/osgEarthDrivers/engine_mp/TileModelFactory     |  21 +-
 src/osgEarthDrivers/engine_mp/TileModelFactory.cpp | 130 ++-
 src/osgEarthDrivers/engine_mp/TileNode             |  92 +-
 src/osgEarthDrivers/engine_mp/TileNode.cpp         |  77 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry     |  28 +-
 src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp |  52 +-
 src/osgEarthDrivers/engine_mp/TilePagedLOD         |  68 ++
 src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp     | 171 ++++
 .../engine_osgterrain/OSGTerrainEngineNode.cpp     |   2 +-
 .../engine_quadtree/QuadTreeTerrainEngineOptions   |   7 +
 src/osgEarthDrivers/engine_quadtree/TileModel      |  11 +
 .../engine_quadtree/TileModelCompiler.cpp          |  45 +-
 .../engine_quadtree/TileModelFactory.cpp           |  17 +
 src/osgEarthDrivers/engine_quadtree/TileNode       |   1 +
 src/osgEarthDrivers/engine_quadtree/TileNode.cpp   |   9 +
 .../feature_ogr/FeatureCursorOGR.cpp               |  23 +-
 .../feature_ogr/FeatureSourceOGR.cpp               |   3 +-
 .../feature_wfs/FeatureSourceWFS.cpp               |  29 +-
 src/osgEarthDrivers/gdal/GDALOptions               |  17 +-
 src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp      | 117 +--
 src/osgEarthDrivers/kml/KMLReader.cpp              |   3 +-
 src/osgEarthDrivers/kml/KML_Geometry.cpp           |  96 +-
 src/osgEarthDrivers/kml/KML_LineString.cpp         |  13 +-
 src/osgEarthDrivers/kml/KML_LinearRing.cpp         |  13 +-
 src/osgEarthDrivers/kml/KML_Placemark.cpp          |  74 +-
 src/osgEarthDrivers/kml/KML_Polygon.cpp            |   6 +
 .../label_annotation/AnnotationLabelSource.cpp     | 161 ++--
 .../mbtiles/ReaderWriterMBTiles.cpp                |  66 +-
 .../FeatureStencilModelSource.cpp                  |  14 +-
 .../model_simple/SimpleModelOptions                |  17 +-
 .../model_simple/SimpleModelSource.cpp             |  57 +-
 src/osgEarthDrivers/noise/CMakeLists.txt           |  14 +
 src/osgEarthDrivers/noise/NoiseOptions             | 220 +++++
 src/osgEarthDrivers/noise/ReaderWriterNoise.cpp    | 318 +++++++
 .../ocean_surface/OceanCompositor.cpp              |  24 +-
 .../script_engine_javascriptcore/CMakeLists.txt    |  23 +
 .../script_engine_javascriptcore/JSWrappers}       |  21 +-
 .../script_engine_javascriptcore/JSWrappers.cpp    |  87 ++
 .../JavaScriptCoreEngine}                          |  35 +-
 .../JavaScriptCoreEngine.cpp                       | 126 +++
 .../JavaScriptCoreEngineFactory.cpp                |  50 ++
 .../script_engine_v8/CMakeLists.txt                |   4 +-
 src/osgEarthDrivers/script_engine_v8/JSWrappers    | 118 +--
 .../script_engine_v8/JSWrappers.cpp                | 809 +++++++++--------
 .../script_engine_v8/JavascriptEngineV8            |  17 +-
 .../script_engine_v8/JavascriptEngineV8.cpp        | 233 ++---
 src/osgEarthDrivers/script_engine_v8/V8Util        |   8 +-
 .../tilecache/ReaderWriterTileCache.cpp            |   2 +-
 src/osgEarthDrivers/tileindex/CMakeLists.txt       |  17 +
 .../tileindex/ReaderWriterTileIndex.cpp            | 186 ++++
 .../ArcGISOptions => tileindex/TileIndexOptions}   |  39 +-
 src/osgEarthDrivers/tms/ReaderWriterTMS.cpp        |  33 +-
 .../vpb/Copy of ReaderWriterVPB.cpp                | 669 --------------
 src/osgEarthDrivers/vpb/VPBOptions                 |  12 +-
 src/osgEarthDrivers/wms/ReaderWriterWMS.cpp        | 113 +--
 src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp    |   2 +-
 src/osgEarthFeatures/BuildGeometryFilter           |  40 +-
 src/osgEarthFeatures/BuildGeometryFilter.cpp       | 658 +++++++++-----
 src/osgEarthFeatures/BuildTextFilter               |   3 +-
 src/osgEarthFeatures/BuildTextFilter.cpp           |  36 +-
 src/osgEarthFeatures/ExtrudeGeometryFilter.cpp     | 114 +--
 src/osgEarthFeatures/Feature.cpp                   |   2 +-
 src/osgEarthFeatures/FeatureDrawSet                |   2 +-
 src/osgEarthFeatures/FeatureDrawSet.cpp            |   4 +
 src/osgEarthFeatures/FeatureModelGraph             |   4 +-
 src/osgEarthFeatures/FeatureModelGraph.cpp         |  60 +-
 src/osgEarthFeatures/FeatureModelSource.cpp        |  20 +-
 src/osgEarthFeatures/FeatureSource                 |   4 +-
 src/osgEarthFeatures/FeatureSource.cpp             |   8 +
 src/osgEarthFeatures/FeatureTileSource.cpp         |  30 +-
 src/osgEarthFeatures/Filter                        |  15 +-
 src/osgEarthFeatures/Filter.cpp                    |  55 ++
 src/osgEarthFeatures/FilterContext                 |  23 +-
 src/osgEarthFeatures/FilterContext.cpp             |  16 +-
 src/osgEarthFeatures/GeometryCompiler.cpp          |  66 +-
 src/osgEarthFeatures/LabelSource                   |  10 +-
 src/osgEarthFeatures/LabelSource.cpp               |   9 +
 src/osgEarthFeatures/MeshClamper.cpp               |   9 +
 src/osgEarthFeatures/PolygonizeLines               |  18 +-
 src/osgEarthFeatures/PolygonizeLines.cpp           | 219 ++---
 src/osgEarthFeatures/ScriptEngine                  |  10 +
 src/osgEarthFeatures/ScriptEngine.cpp              |  43 +-
 src/osgEarthFeatures/Session.cpp                   |   9 +-
 src/osgEarthFeatures/SubstituteModelFilter         |   2 +-
 src/osgEarthFeatures/SubstituteModelFilter.cpp     | 141 ++-
 src/osgEarthFeatures/TextSymbolizer.cpp            |  17 -
 src/osgEarthFeatures/TransformFilter.cpp           |  43 -
 src/osgEarthQt/AnnotationDialogs                   |   1 +
 src/osgEarthQt/CMakeLists.txt                      |   2 -
 src/osgEarthQt/LayerManagerWidget                  |  26 +-
 src/osgEarthQt/LayerManagerWidget.cpp              | 115 ++-
 src/osgEarthQt/MultiViewerWidget                   |  57 --
 src/osgEarthQt/MultiViewerWidget.cpp               | 159 ----
 src/osgEarthQt/images.qrc                          |   1 +
 src/osgEarthQt/images/close.png                    | Bin 0 -> 247 bytes
 src/osgEarthSymbology/AltitudeSymbol.cpp           |   2 +
 src/osgEarthSymbology/Expression                   |   3 +
 src/osgEarthSymbology/Expression.cpp               |   9 +
 src/osgEarthSymbology/ExtrusionSymbol.cpp          |   2 +
 src/osgEarthSymbology/Geometry                     |  17 +-
 src/osgEarthSymbology/Geometry.cpp                 |  40 +
 src/osgEarthSymbology/GeometryRasterizer           |   4 +-
 src/osgEarthSymbology/GeometryRasterizer.cpp       |  17 +-
 src/osgEarthSymbology/IconResource                 |   2 +
 src/osgEarthSymbology/IconResource.cpp             |  63 +-
 src/osgEarthSymbology/IconSymbol                   |  10 +
 src/osgEarthSymbology/IconSymbol.cpp               |  32 +-
 src/osgEarthSymbology/InstanceResource             |   3 +
 src/osgEarthSymbology/InstanceResource.cpp         |  18 +-
 src/osgEarthSymbology/InstanceSymbol.cpp           |   3 +-
 src/osgEarthSymbology/LineSymbol.cpp               |   9 +
 src/osgEarthSymbology/MarkerResource.cpp           |   3 -
 src/osgEarthSymbology/MarkerSymbol.cpp             |   2 +
 src/osgEarthSymbology/MeshConsolidator             |   2 +-
 src/osgEarthSymbology/MeshConsolidator.cpp         |   4 +-
 src/osgEarthSymbology/MeshSubdivider.cpp           |   9 +-
 src/osgEarthSymbology/ModelResource                |   2 +
 src/osgEarthSymbology/ModelResource.cpp            |  21 +
 src/osgEarthSymbology/ModelSymbol                  |   5 +
 src/osgEarthSymbology/ModelSymbol.cpp              |  28 +-
 src/osgEarthSymbology/PointSymbol.cpp              |   6 +-
 src/osgEarthSymbology/PolygonSymbol.cpp            |   2 +
 src/osgEarthSymbology/RenderSymbol                 |   5 +
 src/osgEarthSymbology/RenderSymbol.cpp             |  46 +-
 src/osgEarthSymbology/ResourceCache                |  16 +-
 src/osgEarthSymbology/ResourceCache.cpp            | 128 +--
 src/osgEarthSymbology/Skins.cpp                    |   2 +
 src/osgEarthSymbology/Stroke                       |  18 +-
 src/osgEarthSymbology/Stroke.cpp                   |   7 +-
 src/osgEarthSymbology/Style.cpp                    |  59 +-
 src/osgEarthSymbology/Symbol                       |  81 +-
 src/osgEarthSymbology/Symbol.cpp                   |  46 +
 src/osgEarthSymbology/TextSymbol                   |  21 +
 src/osgEarthSymbology/TextSymbol.cpp               |  72 +-
 src/osgEarthUtil/ArcGIS                            | 112 +++
 src/osgEarthUtil/ArcGIS.cpp                        | 168 ++++
 src/osgEarthUtil/BrightnessContrastColorFilter.cpp |   2 +-
 src/osgEarthUtil/CMYKColorFilter.cpp               |   2 +-
 src/osgEarthUtil/CMakeLists.txt                    |  24 +-
 src/osgEarthUtil/ChromaKeyColorFilter.cpp          |   2 +-
 src/osgEarthUtil/ContourMap                        |  79 ++
 src/osgEarthUtil/ContourMap.cpp                    | 226 +++++
 src/osgEarthUtil/Controls                          |  95 +-
 src/osgEarthUtil/Controls.cpp                      | 967 +++++++++++---------
 .../LODFactorCallback => osgEarthUtil/DateTime}    |  23 +-
 src/osgEarthUtil/DateTime.cpp                      |  85 ++
 src/osgEarthUtil/DetailTexture                     |  80 ++
 src/osgEarthUtil/DetailTexture.cpp                 | 252 ++++++
 src/osgEarthUtil/EarthManipulator                  |  42 +-
 src/osgEarthUtil/EarthManipulator.cpp              | 274 +++---
 src/osgEarthUtil/ExampleResources.cpp              | 132 ++-
 src/osgEarthUtil/FeatureQueryTool.cpp              |   4 +-
 src/osgEarthUtil/GLSLColorFilter.cpp               |   2 +-
 src/osgEarthUtil/GammaColorFilter.cpp              |   2 +-
 src/osgEarthUtil/GeodeticGraticule.cpp             |  71 +-
 src/osgEarthUtil/HSLColorFilter.cpp                |   2 +-
 src/osgEarthUtil/HTM                               | 195 +++++
 src/osgEarthUtil/HTM.cpp                           | 604 +++++++++++++
 src/osgEarthUtil/LODBlending                       |  84 ++
 src/osgEarthUtil/LODBlending.cpp                   | 249 ++++++
 src/osgEarthUtil/LinearLineOfSight                 |  23 +-
 src/osgEarthUtil/LinearLineOfSight.cpp             | 117 +--
 src/osgEarthUtil/MGRSGraticule.cpp                 |  10 +-
 src/osgEarthUtil/MeasureTool.cpp                   |  20 +-
 src/osgEarthUtil/MouseCoordsTool.cpp               |   6 +-
 src/osgEarthUtil/NormalMap                         |  78 ++
 src/osgEarthUtil/NormalMap.cpp                     | 209 +++++
 src/osgEarthUtil/RGBColorFilter.cpp                |   2 +-
 src/osgEarthUtil/RadialLineOfSight                 |  18 +-
 src/osgEarthUtil/RadialLineOfSight.cpp             | 101 +--
 src/osgEarthUtil/ShadowUtils.cpp                   |  19 +-
 src/osgEarthUtil/SkyNode                           |  31 +-
 src/osgEarthUtil/SkyNode.cpp                       | 165 ++--
 src/osgEarthUtil/SpatialData.cpp                   |  53 +-
 src/osgEarthUtil/TFSPackager                       |   9 +-
 src/osgEarthUtil/TFSPackager.cpp                   |  36 +-
 src/osgEarthUtil/TMS                               |  50 +-
 src/osgEarthUtil/TMS.cpp                           |  88 +-
 src/osgEarthUtil/TMSPackager                       |  22 +-
 src/osgEarthUtil/TMSPackager.cpp                   | 393 ++++++---
 src/osgEarthUtil/TileIndex                         |  68 ++
 src/osgEarthUtil/TileIndex.cpp                     | 143 +++
 src/osgEarthUtil/TileIndexBuilder                  |  75 ++
 src/osgEarthUtil/TileIndexBuilder.cpp              | 126 +++
 src/osgEarthUtil/UTMGraticule                      |   1 -
 src/osgEarthUtil/UTMGraticule.cpp                  |  13 +-
 src/osgEarthUtil/VerticalScale                     |  65 ++
 src/osgEarthUtil/VerticalScale.cpp                 | 148 ++++
 src/osgEarthUtil/WFS.cpp                           |  13 +-
 tests/arcgisonline-utm.earth                       |  36 -
 tests/arcgisonline.earth                           |  18 +-
 tests/bing.earth                                   |  24 +
 tests/boston.earth                                 |  27 +-
 tests/byo.earth                                    |  12 -
 tests/cloudmade.earth                              |  22 -
 tests/{readymap.earth => detail_texture.earth}     |  24 +-
 ...re_overlay.earth => feature_draped_lines.earth} |   9 +-
 ...y_polys.earth => feature_draped_polygons.earth} |   1 +
 tests/feature_geom.earth                           |  14 +-
 ..._inline.earth => feature_inline_geometry.earth} |   6 +-
 tests/feature_labels.earth                         |  16 +-
 tests/feature_model_scatter.earth                  |  18 +-
 tests/feature_models.earth                         |  26 +-
 tests/feature_occlusion_culling.earth              |  51 ++
 tests/feature_overlay.earth                        |   6 +-
 tests/feature_rasterize.earth                      |  40 +-
 tests/feature_rasterize_2.earth                    |   6 +-
 tests/feature_scripted_styling.earth               |   4 +-
 tests/feature_scripted_styling_2.earth             |  12 +-
 tests/feature_scripting.earth                      |   1 -
 ...tyling_2.earth => feature_style_selector.earth} |  23 +-
 tests/feature_tfs.earth                            |   5 +-
 tests/feature_wfs.earth                            |  11 +-
 tests/fractal_detail.earth                         |  58 ++
 tests/glsl_filter.earth                            |   5 +-
 tests/hires-inset.earth                            |  30 +-
 tests/{shadows.earth => lod_blending.earth}        |  35 +-
 tests/mapquest_open_aerial.earth                   |   3 -
 ..._open_aerial.earth => mapquest_with_srtm.earth} |  10 +-
 tests/mask.earth                                   |   4 +-
 tests/mb_tiles.earth                               |  10 +-
 tests/min_max_resolutions.earth                    |  34 +-
 tests/multiple_heightfields.earth                  |  26 +-
 tests/noise.earth                                  |  27 +
 tests/normalmap.earth                              |  43 +
 tests/ocean.earth                                  |   7 +-
 tests/readymap.earth                               |  18 +-
 tests/refresh.earth                                |  33 -
 tests/simple_model.earth                           |  24 +-
 tests/vertical_datum.earth                         |   4 +-
 tests/vertical_scale.earth                         |  29 +
 tests/vpb_earth_bayarea.earth                      |  24 -
 tests/vpb_with_inset.earth                         |   1 +
 tests/wms-t_nexrad_animated.earth                  |   7 +
 tests/wms_metacarta.earth                          |  22 -
 tests/wms_nexrad.earth                             |   1 +
 499 files changed, 22687 insertions(+), 10220 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86f7542..34cd375 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@ SET_PROPERTY( GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets" )
 PROJECT(OSGEARTH)
 
 SET(OSGEARTH_MAJOR_VERSION 2)
-SET(OSGEARTH_MINOR_VERSION 3)
+SET(OSGEARTH_MINOR_VERSION 5)
 SET(OSGEARTH_PATCH_VERSION 0)
 SET(OSGEARTH_SOVERSION     0)
 
@@ -43,10 +43,17 @@ SET(OPENTHREADS_SONAMES TRUE)
 
 SET(OpenThreads_SOURCE_DIR ${OSGEARTH_SOURCE_DIR})
 
+# Good place to look for the OSG 3rd party libs
+SET(THIRD_PARTY_DIR "" CACHE PATH "OSG 3rd-party dependency folder")
+
 # We have some custom .cmake scripts not in the official distribution.
 # Maybe this can be used override existing behavior if needed?
 SET(CMAKE_MODULE_PATH "${OSGEARTH_SOURCE_DIR}/CMakeModules;${CMAKE_MODULE_PATH}")
 
+# Attempt to detect the GIT revision number.
+include(GetGitRevisionDescription)
+get_git_head_revision(GIT_REFSPEC OSGEARTH_GIT_SHA1)
+
 # IPHONE_PORT at tom
 # Trying to get CMake to generate an XCode IPhone project, current efforts are to get iphoneos sdk 3.1 working
 # Added option which needs manually setting to select the IPhone SDK for building. We can only have one of the below 
@@ -56,7 +63,7 @@ OPTION(OSG_BUILD_PLATFORM_IPHONE_SIMULATOR "Enable IPhoneSDK Simulator support"
 IF(OSG_BUILD_PLATFORM_IPHONE OR OSG_BUILD_PLATFORM_IPHONE_SIMULATOR)
   
   #you need to manually set the default sdk version here
-  SET (IPHONE_SDKVER "6.0")
+  SET (IPHONE_SDKVER "6.1")
   #the below is taken from ogre, it states the gcc stuff needs to happen before PROJECT() is called. I've no clue if we even need it
   # Force gcc <= 4.2 on iPhone
   include(CMakeForceCompiler)
@@ -106,17 +113,20 @@ FIND_PACKAGE(GDAL)
 FIND_PACKAGE(GEOS)
 FIND_PACKAGE(Sqlite3)
 FIND_PACKAGE(ZLIB)
+
+SET(V8_DIR "" CACHE PATH "set to base V8 install path")
 FIND_PACKAGE(V8)
 
-OPTION(OSGEARTH_USE_QT "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" ON)
+FIND_PACKAGE(JavaScriptCore)
+FIND_PACKAGE(LibNoise)
 
-IF(OSGEARTH_USE_QT)
-    FIND_PACKAGE(Qt4 4.6)
-    IF (QT4_FOUND)
-        INCLUDE(${QT_USE_FILE})
-        SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
-    ENDIF (QT4_FOUND)
-ENDIF()
+FIND_PACKAGE(Qt4)
+IF (QT4_FOUND)
+    INCLUDE(${QT_USE_FILE})
+    SET(QT_ALL_LIBRARIES ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTWEBKIT_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTOPENGL_LIBRARY})
+ENDIF (QT4_FOUND)
+
+OPTION(OSGEARTH_USE_QT "Enable to use Qt (build Qt-dependent libraries, plugins and examples)" ON)
 
 SET (WITH_EXTERNAL_TINYXML FALSE CACHE BOOL "Use bundled or system wide version of TinyXML")
 IF (WITH_EXTERNAL_TINYXML)
@@ -139,6 +149,7 @@ ENDIF(UNIX)
 #ADD_DEFINITIONS(-D)
 # Platform specific definitions
 
+
 IF(WIN32)
   IF(MSVC)
         # This option is to enable the /MP switch for Visual Studio 2005 and above compilers
@@ -303,7 +314,7 @@ IF(APPLE)
     IF(OSG_BUILD_PLATFORM_IPHONE OR OSG_BUILD_PLATFORM_IPHONE_SIMULATOR)
 
         IF(OSG_BUILD_PLATFORM_IPHONE)
-            SET(CMAKE_OSX_ARCHITECTURES "armv7;armv7s" CACHE STRING "Build architectures for iOS" FORCE)
+            SET(CMAKE_OSX_ARCHITECTURES "armv7" CACHE STRING "Build architectures for iOS" FORCE)
             SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=4.1 -mno-thumb -arch armv7 -pipe -no-cpp-precomp" CACHE STRING "Flags used by the compiler during all build types." FORCE)
         ELSE()
             #simulator uses i386 architectures
@@ -381,6 +392,11 @@ ADD_CUSTOM_TARGET(uninstall
 
 
 
+#------------------------------------------------------------------------------
+# Docs.
+add_subdirectory(docs)
+
+
 
 #------------------------------------------------------------------------------
 # Packaging setup.
diff --git a/CMakeModules/FindCURL.cmake b/CMakeModules/FindCURL.cmake
index 14cbd85..ec4231c 100644
--- a/CMakeModules/FindCURL.cmake
+++ b/CMakeModules/FindCURL.cmake
@@ -12,6 +12,7 @@ FIND_PATH(CURL_INCLUDE_DIR curl.h curl/curl.h
 
 FIND_PATH(CURL_INCLUDE_DIR curl.h curl/curl.h
   PATHS
+  ${THIRD_PARTY_DIR}/include
   /usr/local/include/curl
   /usr/local/include/CURL
   /usr/local/include
@@ -36,6 +37,7 @@ FIND_PATH(CURL_INCLUDE_DIR curl.h curl/curl.h
 FIND_LIBRARY(CURL_LIBRARY
   NAMES curl curllib CURL libcurl
   PATHS
+  ${THIRD_PARTY_DIR}/lib
   $ENV{CURL_DIR}
   NO_DEFAULT_PATH
   PATH_SUFFIXES lib64 lib
@@ -66,6 +68,7 @@ FIND_LIBRARY(CURL_LIBRARY
 FIND_LIBRARY(CURL_LIBRARY_DEBUG
   NAMES curlD curld curllibD curllibd CURLD libcurlD libcurld
   PATHS
+  ${THIRD_PARTY_DIR}/lib
   $ENV{CURL_DIR}
   NO_DEFAULT_PATH
   PATH_SUFFIXES lib64 lib
diff --git a/CMakeModules/FindJavaScriptCore.cmake b/CMakeModules/FindJavaScriptCore.cmake
new file mode 100644
index 0000000..1bca250
--- /dev/null
+++ b/CMakeModules/FindJavaScriptCore.cmake
@@ -0,0 +1,46 @@
+# Locate JavaScriptCore
+# This module defines
+# JAVASCRIPTCORE_LIBRARY
+# JAVASCRIPTCORE_FOUND, if false, do not try to link to JavaScriptCore
+# JAVASCRIPTCORE_INCLUDE_DIR, where to find the headers
+
+FIND_PATH(JAVASCRIPTCORE_INCLUDE_DIR JavaScriptCore.h
+    ${JAVASCRIPTCORE_DIR}/include
+    $ENV{JAVASCRIPTCORE_DIR}/include
+    $ENV{JAVASCRIPTCORE_DIR}
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/include
+    /usr/include
+    /sw/include # Fink
+    /opt/local/include # DarwinPorts
+    /opt/csw/include # Blastwave
+    /opt/include
+    /usr/freeware/include
+    /devel
+)
+
+FIND_LIBRARY(JAVASCRIPTCORE_LIBRARY
+    NAMES libJavaScriptCore
+    PATHS
+    ${JAVASCRIPTCORE_DIR}
+    ${JAVASCRIPTCORE_DIR}/lib
+    $ENV{JAVASCRIPTCORE_DIR}
+    $ENV{JAVASCRIPTCORE_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+SET(JAVASCRIPTCORE_FOUND "NO")
+IF(JAVASCRIPTCORE_LIBRARY AND JAVASCRIPTCORE_INCLUDE_DIR)
+    SET(JAVASCRIPTCORE_FOUND "YES")
+ENDIF(JAVASCRIPTCORE_LIBRARY AND JAVASCRIPTCORE_INCLUDE_DIR)
+
+
diff --git a/CMakeModules/FindLibNoise.cmake b/CMakeModules/FindLibNoise.cmake
new file mode 100644
index 0000000..99d006b
--- /dev/null
+++ b/CMakeModules/FindLibNoise.cmake
@@ -0,0 +1,64 @@
+# Locate libnoise.
+# This module defines
+# LIBNOISE_LIBRARY
+# LIBNOISE_FOUND, if false, do not try to link to libnoise
+# LIBNOISE_INCLUDE_DIR, where to find the headers
+
+FIND_PATH(LIBNOISE_INCLUDE_DIR noise.h
+  $ENV{LIBNOISE_DIR}
+  NO_DEFAULT_PATH
+    PATH_SUFFIXES include
+)
+
+FIND_PATH(LIBNOISE_INCLUDE_DIR noise.h
+  PATHS
+  ~/Library/Frameworks/noise/Headers
+  /Library/Frameworks/noise/Headers
+  /usr/local/include/noise
+  /usr/local/include/noise
+  /usr/local/include
+  /usr/include/noise
+  /usr/include/noise
+  /usr/include
+  /sw/include/noise 
+  /sw/include/noise 
+  /sw/include # Fink
+  /opt/local/include/noise
+  /opt/local/include/noise
+  /opt/local/include # DarwinPorts
+  /opt/csw/include/noise
+  /opt/csw/include/noise
+  /opt/csw/include # Blastwave
+  /opt/include/noise
+  /opt/include/noise
+  /opt/include  
+)
+
+FIND_LIBRARY(LIBNOISE_LIBRARY
+  NAMES libnoise noise
+  PATHS
+    $ENV{LIBNOISE_DIR}
+    NO_DEFAULT_PATH
+    PATH_SUFFIXES lib64 lib
+)
+
+FIND_LIBRARY(LIBNOISE_LIBRARY
+  NAMES libnoise
+  PATHS
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local
+    /usr
+    /sw
+    /opt/local
+    /opt/csw
+    /opt
+    /usr/freeware    
+  PATH_SUFFIXES lib64 lib
+)
+
+SET(LIBNOISE_FOUND "NO")
+IF(LIBNOISE_LIBRARY AND LIBNOISE_INCLUDE_DIR)
+  SET(LIBNOISE_FOUND "YES")
+ENDIF(LIBNOISE_LIBRARY AND LIBNOISE_INCLUDE_DIR)
+
diff --git a/CMakeModules/FindMiniZip.cmake b/CMakeModules/FindMiniZip.cmake
index c80ebbf..345386d 100644
--- a/CMakeModules/FindMiniZip.cmake
+++ b/CMakeModules/FindMiniZip.cmake
@@ -5,50 +5,66 @@
 # MINIZIP_INCLUDE_DIR, where to find the headers
 #
 
-FIND_PATH(MINIZIP_INCLUDE_DIR zip.h
-    ${CMAKE_SOURCE_DIR}/src/3rdparty/minizip
-    $ENV{MINIZIP_DIR}/include
-    $ENV{MINIZIP_DIR}
-    $ENV{OSGDIR}/include
-    $ENV{OSGDIR}
-    $ENV{OSG_ROOT}/include
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/include
-    /usr/include
-    /sw/include # Fink
-    /opt/local/include # DarwinPorts
-    /opt/csw/include # Blastwave
-    /opt/include
-    [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/include
-    /usr/freeware/include
-)
+# prefer pkg-config
+IF(UNIX)
+	INCLUDE(FindPkgConfig)
+	IF(PKG_CONFIG_FOUND)
+		pkg_check_modules(MINIZIP QUIET minizip)
+		IF(MINIZIP_FOUND)
+			# pkgconfig does not define the singular names
+			SET(MINIZIP_LIBRARY ${MINIZIP_LIBRARIES} CACHE STRING "Minizip libraries to link against")
+			SET(MINIZIP_INCLUDE_DIR ${MINIZIP_INCLUDE_DIRS} CACHE STRING "Minizip include dirs to look for headers")
+			link_directories(${MINIZIP_LIBRARY_DIRS})
+			ADD_DEFINITIONS(-DOSGEARTH_HAVE_MINIZIP)
+		ENDIF(MINIZIP_FOUND)
+	ENDIF(PKG_CONFIG_FOUND)
+ENDIF(UNIX)
 
-FIND_LIBRARY(MINIZIP_LIBRARY 
-    NAMES minizip
-    PATHS
-    ${CMAKE_SOURCE_DIR}/src/3rdparty/minizip
-    $ENV{MINIZIP_DIR}/lib
-    $ENV{MINIZIP_DIR}
-    $ENV{OSGDIR}/lib
-    $ENV{OSGDIR}
-    $ENV{OSG_ROOT}/lib
-    ~/Library/Frameworks
-    /Library/Frameworks
-    /usr/local/lib
-    /usr/lib
-    /sw/lib
-    /opt/local/lib
-    /opt/csw/lib
-    /opt/lib
-    [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/lib
-    /usr/freeware/lib64
-)
-
-SET(MINIZIP_FOUND "NO")
-IF(MINIZIP_LIBRARY AND MINIZIP_INCLUDE_DIR)
-    SET(MINIZIP_FOUND "YES")
-    ADD_DEFINITIONS(-DOSGEARTH_HAVE_MINIZIP)
-ENDIF(MINIZIP_LIBRARY AND MINIZIP_INCLUDE_DIR)
+# fallback logic
+IF(NOT MINIZIP_FOUND)
+	FIND_PATH(MINIZIP_INCLUDE_DIR zip.h
+		${CMAKE_SOURCE_DIR}/src/3rdparty/minizip
+		$ENV{MINIZIP_DIR}/include
+		$ENV{MINIZIP_DIR}
+		$ENV{OSGDIR}/include
+		$ENV{OSGDIR}
+		$ENV{OSG_ROOT}/include
+		~/Library/Frameworks
+		/Library/Frameworks
+		/usr/local/include
+		/usr/include
+		/sw/include # Fink
+		/opt/local/include # DarwinPorts
+		/opt/csw/include # Blastwave
+		/opt/include
+		[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/include
+		/usr/freeware/include
+	)
 
+	FIND_LIBRARY(MINIZIP_LIBRARY 
+		NAMES minizip
+		PATHS
+		${CMAKE_SOURCE_DIR}/src/3rdparty/minizip
+		$ENV{MINIZIP_DIR}/lib
+		$ENV{MINIZIP_DIR}
+		$ENV{OSGDIR}/lib
+		$ENV{OSGDIR}
+		$ENV{OSG_ROOT}/lib
+		~/Library/Frameworks
+		/Library/Frameworks
+		/usr/local/lib
+		/usr/lib
+		/sw/lib
+		/opt/local/lib
+		/opt/csw/lib
+		/opt/lib
+		[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/lib
+		/usr/freeware/lib64
+	)
 
+	SET(MINIZIP_FOUND "NO")
+	IF(MINIZIP_LIBRARY AND MINIZIP_INCLUDE_DIR)
+		SET(MINIZIP_FOUND "YES")
+		ADD_DEFINITIONS(-DOSGEARTH_HAVE_MINIZIP)
+	ENDIF(MINIZIP_LIBRARY AND MINIZIP_INCLUDE_DIR)
+ENDIF(NOT MINIZIP_FOUND)
diff --git a/CMakeModules/FindV8.cmake b/CMakeModules/FindV8.cmake
index e8038ba..9f5684d 100644
--- a/CMakeModules/FindV8.cmake
+++ b/CMakeModules/FindV8.cmake
@@ -20,11 +20,145 @@ FIND_PATH(V8_INCLUDE_DIR v8.h
     /devel
 )
 
-FIND_LIBRARY(V8_LIBRARY
-    NAMES v8 libv8
+FIND_LIBRARY(V8_BASE_LIBRARY
+    NAMES v8_base v8_base.ia32 libv8_base
     PATHS
     ${V8_DIR}
     ${V8_DIR}/lib
+    ${V8_DIR}/build/Release/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_BASE_LIBRARY_DEBUG
+    NAMES v8_base v8_base.ia32 libv8_base
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Debug/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_SNAPSHOT_LIBRARY
+    NAMES v8_snapshot libv8_snapshot
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Release/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_SNAPSHOT_LIBRARY_DEBUG
+    NAMES v8_snapshot libv8_snapshot
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Debug/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_ICUUC_LIBRARY
+    NAMES icuuc libicuuc
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Release/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_ICUUC_LIBRARY_DEBUG
+    NAMES icuuc libicuuc
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Debug/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_ICUI18N_LIBRARY
+    NAMES icui18n libicui18n
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Release/lib
+    $ENV{V8_DIR}
+    $ENV{V8_DIR}/lib
+    ~/Library/Frameworks
+    /Library/Frameworks
+    /usr/local/lib
+    /usr/lib
+    /sw/lib
+    /opt/local/lib
+    /opt/csw/lib
+    /opt/lib
+    /usr/freeware/lib64
+)
+
+FIND_LIBRARY(V8_ICUI18N_LIBRARY_DEBUG
+    NAMES icui18n libicui18n
+    PATHS
+    ${V8_DIR}
+    ${V8_DIR}/lib
+    ${V8_DIR}/build/Debug/lib
     $ENV{V8_DIR}
     $ENV{V8_DIR}/lib
     ~/Library/Frameworks
@@ -39,8 +173,8 @@ FIND_LIBRARY(V8_LIBRARY
 )
 
 SET(V8_FOUND "NO")
-IF(V8_LIBRARY AND V8_INCLUDE_DIR)
+IF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
     SET(V8_FOUND "YES")
-ENDIF(V8_LIBRARY AND V8_INCLUDE_DIR)
+ENDIF(V8_BASE_LIBRARY AND V8_SNAPSHOT_LIBRARY AND V8_ICUUC_LIBRARY AND V8_ICUI18N_LIBRARY AND V8_INCLUDE_DIR)
 
 
diff --git a/CMakeModules/GetGitRevisionDescription.cmake b/CMakeModules/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000..1bf0230
--- /dev/null
+++ b/CMakeModules/GetGitRevisionDescription.cmake
@@ -0,0 +1,123 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+#  get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
+#
+# Returns the refspec and sha hash of the current head revision
+#
+#  git_describe(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe on the source tree, and adjusting
+# the output so that it tests false if an error occurs.
+#
+#  git_get_exact_tag(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe --exact-match on the source tree,
+# and adjusting the output so that it tests false if there was no exact
+# matching tag.
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik at iastate.edu> <abiryan at ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if(__get_git_revision_description)
+	return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+function(get_git_head_revision _refspecvar _hashvar)
+	set(GIT_PARENT_DIR "${CMAKE_SOURCE_DIR}")
+	set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+	while(NOT EXISTS "${GIT_DIR}")	# .git dir not found, search parent directories
+		set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
+		get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
+		if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
+			# We have reached the root directory, we are not in git
+			set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+			set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+			return()
+		endif()
+		set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+	endwhile()
+	set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+	if(NOT EXISTS "${GIT_DATA}")
+		file(MAKE_DIRECTORY "${GIT_DATA}")
+	endif()
+
+	if(NOT EXISTS "${GIT_DIR}/HEAD")
+		return()
+	endif()
+	set(HEAD_FILE "${GIT_DATA}/HEAD")
+	configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
+
+	configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
+		"${GIT_DATA}/grabRef.cmake"
+		@ONLY)
+	include("${GIT_DATA}/grabRef.cmake")
+
+	set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+	set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
+
+function(git_describe _var)
+	if(NOT GIT_FOUND)
+		find_package(Git QUIET)
+	endif()
+	get_git_head_revision(refspec hash)
+	if(NOT GIT_FOUND)
+		set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
+		return()
+	endif()
+	if(NOT hash)
+		set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
+		return()
+	endif()
+
+	# TODO sanitize
+	#if((${ARGN}" MATCHES "&&") OR
+	#	(ARGN MATCHES "||") OR
+	#	(ARGN MATCHES "\\;"))
+	#	message("Please report the following error to the project!")
+	#	message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
+	#endif()
+
+	#message(STATUS "Arguments to execute_process: ${ARGN}")
+
+	execute_process(COMMAND
+		"${GIT_EXECUTABLE}"
+		describe
+		${hash}
+		${ARGN}
+		WORKING_DIRECTORY
+		"${CMAKE_SOURCE_DIR}"
+		RESULT_VARIABLE
+		res
+		OUTPUT_VARIABLE
+		out
+		ERROR_QUIET
+		OUTPUT_STRIP_TRAILING_WHITESPACE)
+	if(NOT res EQUAL 0)
+		set(out "${out}-${res}-NOTFOUND")
+	endif()
+
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+function(git_get_exact_tag _var)
+	git_describe(out --exact-match ${ARGN})
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
diff --git a/CMakeModules/GetGitRevisionDescription.cmake.in b/CMakeModules/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000..888ce13
--- /dev/null
+++ b/CMakeModules/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,38 @@
+# 
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik at iastate.edu> <abiryan at ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(HEAD_HASH)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if(HEAD_CONTENTS MATCHES "ref")
+	# named branch
+	string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+	if(EXISTS "@GIT_DIR@/${HEAD_REF}")
+		configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+	elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
+		configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+		set(HEAD_HASH "${HEAD_REF}")
+	endif()
+else()
+	# detached HEAD
+	configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+if(NOT HEAD_HASH)
+	file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+	string(STRIP "${HEAD_HASH}" HEAD_HASH)
+endif()
diff --git a/LICENSE.txt b/LICENSE.txt
index 65c5ca8..830e7f0 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,3 +1,24 @@
+OSGEARTH SOFTWARE LICENSE
+
+The osgEarth library and included programs are provided under the terms
+of the GNU Library General Public License (LGPL - included below) with the
+following exceptions and stipulations:
+
+1. Static linking of applications and modules to the osgEarth library does NOT
+   constitute a derivative work and does not require the author to provide source
+   code for the application or module.
+
+2. If you link the application or module to a modified version of the osgEarth
+   library, then the modifications to osgEarth must be provided under the terms of
+   the LGPL.
+   
+3. The text herein comprises the definitive licensing information for the osgEarth
+   library, and supercedes any licensing text found in osgEarth source files,
+   header files, or documentation.
+
+
+   ----
+
                    GNU LESSER GENERAL PUBLIC LICENSE
                        Version 3, 29 June 2007
 
diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt
new file mode 100644
index 0000000..f1ee836
--- /dev/null
+++ b/docs/CMakeLists.txt
@@ -0,0 +1,117 @@
+### DOCUMENTATION ###
+
+set( DOCS_ROOT
+    source/about.rst
+    source/data.rst
+    source/faq.rst
+    source/howtos.rst
+    source/index.rst
+    source/install.rst
+    source/ios.rst
+    source/releasenotes.rst
+    source/startup.rst
+)
+
+set( DOCS_DEVELOPER
+    source/developer/coordinate_systems.rst
+    source/developer/custom_driver.rst
+    source/developer/index.rst
+    source/developer/maps.rst
+    source/developer/qt_integration.rst
+    source/developer/shader_composition.rst
+    source/developer/utilities.rst
+)
+
+set( DOCS_REFERENCES
+    source/references/colorfilters.rst
+    source/references/earthfile.rst
+    source/references/envvars.rst
+    source/references/index.rst
+    source/references/symbology.rst
+)
+
+set( DOCS_REFRENCES_DRIVERS
+    source/references/drivers/index.rst
+)
+
+set( DOCS_REFERENCES_DRIVERS_FEATURE
+    source/references/drivers/feature/index.rst
+    source/references/drivers/feature/ogr.rst
+    source/references/drivers/feature/tfs.rst
+    source/references/drivers/feature/wfs.rst
+)
+
+set( DOCS_REFERENCES_DRIVERS_LOADERS
+    source/references/drivers/loaders/index.rst
+)
+
+set( DOCS_REFERENCES_DRIVERS_MODEL
+    source/references/drivers/model/feature_geom.rst
+    source/references/drivers/model/feature_model_shared_blocks.rst
+    source/references/drivers/model/feature_model_shared_props.rst
+    source/references/drivers/model/feature_stencil.rst
+    source/references/drivers/model/index.rst
+    source/references/drivers/model/simple.rst
+)
+
+set( DOCS_REFERENCES_DRIVERS_TERRAIN
+    source/references/drivers/terrain/index.rst
+    source/references/drivers/terrain/mp.rst
+    source/references/drivers/terrain/terrain_options_shared.rst
+)
+
+set( DOCS_REFERENCES_DRIVERS_TILE
+    source/references/drivers/tile/agglite.rst
+    source/references/drivers/tile/arcgis.rst
+    source/references/drivers/tile/debug.rst
+    source/references/drivers/tile/gdal.rst
+    source/references/drivers/tile/index.rst
+    source/references/drivers/tile/noise.rst
+    source/references/drivers/tile/osg.rst
+    source/references/drivers/tile/tilecache.rst
+    source/references/drivers/tile/tileservice.rst
+    source/references/drivers/tile/tms.rst
+    source/references/drivers/tile/vpb.rst
+    source/references/drivers/tile/wcs.rst
+    source/references/drivers/tile/wms.rst
+    source/references/drivers/tile/xyz.rst
+)
+
+set( DOCS_USER
+    source/user/annotations.rst
+    source/user/caching.rst
+    source/user/earthfiles.rst
+    source/user/elevation.rst
+    source/user/features.rst
+    source/user/imagery.rst
+    source/user/index.rst
+    source/user/maps.rst
+    source/user/profile.rst
+    source/user/spatialreference.rst
+    source/user/tools.rst
+)
+
+source_group( Developer                    FILES ${DOCS_DEVELOPER} )
+source_group( References                   FILES ${DOCS_REFERENCES} )
+source_group( References\\Drivers          FILES ${DOCS_REFERENCES_DRIVERS} )
+source_group( References\\Drivers\\Feature FILES ${DOCS_REFERENCES_DRIVERS_FEATURE} )
+source_group( References\\Drivers\\Loaders FILES ${DOCS_REFERENCES_DRIVERS_LOADERS} )
+source_group( References\\Drivers\\Model   FILES ${DOCS_REFERENCES_DRIVERS_MODEL} )
+source_group( References\\Drivers\\Terrain FILES ${DOCS_REFERENCES_DRIVERS_TERRAIN} )
+source_group( References\\Drivers\\Tile    FILES ${DOCS_REFERENCES_DRIVERS_TILE} )
+source_group( User                         FILES ${DOCS_USER} )
+
+add_custom_target( 
+    Documentation
+    SOURCES
+        ${DOCS_ROOT}
+        ${DOCS_DEVELOPER}
+        ${DOCS_REFERENCES}
+        ${DOCS_REFERENCES_DRIVERS}
+        ${DOCS_REFERENCES_DRIVERS_FEATURE}
+        ${DOCS_REFERENCES_DRIVERS_LOADERS}
+        ${DOCS_REFERENCES_DRIVERS_MODEL}
+        ${DOCS_REFERENCES_DRIVERS_TERRAIN}
+        ${DOCS_REFERENCES_DRIVERS_TILE}
+        ${DOCS_USER}
+)
diff --git a/docs/source/about.rst b/docs/source/about.rst
index d869d5e..c97d955 100644
--- a/docs/source/about.rst
+++ b/docs/source/about.rst
@@ -45,13 +45,21 @@ to testing, adding features, and fixing bugs.
 **Support Forum**
 
     The best way to interact with the osgEarth team and the user community is
-    through the `support forum`_. Here are a couple guidelines for using the
-    board:
+    through the `support forum`_. **Please read** and follow these guidelines for
+    using the forum:
 
-    * Please sign up for an account and use your real name. You can participate
-      anonymously, but using your real name helps build a stronger community.
+    * Sign up for an account and use your real name. You can participate
+      anonymously, but using your real name helps build a stronger community
+      (and makes it more likely that we will get to your question sooner).
+      
     * Limit yourself to *one topic* per post. Asking multiple questions in one
       post makes it too hard to keep track of responses.
+      
+    * Always include as much supporting information as possible. Post an
+      *earth file* or *short code snippet*. Post the output to ``osgearth_version --caps``.
+      Post the output to ``gdalinfo`` if you are having trouble with a GeoTIFF
+      or other data file. List everything you have tried so far.
+      
     * Be patient!
 
 **OSG Forum**
@@ -63,8 +71,8 @@ to testing, adding features, and fixing bugs.
     
 **Social Media**
 
-* Follow `@pelicanmapping`_ on twitter for updates.
-* Add our `Google+ Page`_ to your circles for gallery shots.
+    * Follow `@pelicanmapping`_ on twitter for updates.
+    * Add our `Google+ Page`_ to your circles for gallery shots.
 
 **Professional Services**
 
@@ -94,6 +102,12 @@ This means that:
        include the associated copyright notices and license information
        unaltered and intact.
        
+    4. *iOS / static linking exception*: The LGPL requires that anything statically
+       linked to an LGPL library (like osgEarth) also be released under the LGPL.
+       We grant an exception to the LGPL in this case. If you statically link 
+       osgEarth with your proprietary code, you are *NOT* required to release your
+       own code under the LGPL.
+       
 That's it.
 
     
diff --git a/docs/source/data.rst b/docs/source/data.rst
index 538554e..12ac3e1 100644
--- a/docs/source/data.rst
+++ b/docs/source/data.rst
@@ -8,8 +8,12 @@ Help us add useful sources of Free data to this list.
 
 **Raster data**
 
+    * `ReadyMap.org`_ - Free 15m imagery, elevation, and street tiles for osgEarth developers
+    
     * MapQuest_ - MapQuest open aerial imagery and rasterized OpenStreetMap layers
     
+    * `Bing Maps`_ - Microsoft's worldwide imagery and map data ($)
+    
     * `USGS National Map`_ - Elevation, orthoimagery, hydrography, geographic names, boundaries,
       transportation, structures, and land cover products for the US.
     
@@ -55,9 +59,11 @@ Help us add useful sources of Free data to this list.
 .. _NASA BlueMarble:            http://visibleearth.nasa.gov/view_cat.php?categoryID=1484
 .. _Natural Earth:              http://www.naturalearthdata.com/
 .. _NRL GIDB:                   http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet
-.. _+SRTM30+:                   ftp://topex.ucsd.edu/pub/srtm30_plus/
+.. _SRTM30+:                    ftp://topex.ucsd.edu/pub/srtm30_plus/
 .. _USGS National Map:          http://nationalmap.gov/viewer.html
 .. _Virtual Terrain Project:    http://vterrain.org/Imagery/WholeEarth/
+.. _Bing Maps:                  http://www.microsoft.com/maps/choose-your-bing-maps-API.aspx
+.. _ReadyMap.org:               http://readymap.org/index_orig.html
 
 ----
 
diff --git a/docs/source/developer/coordinate_systems.rst b/docs/source/developer/coordinate_systems.rst
new file mode 100644
index 0000000..86b056a
--- /dev/null
+++ b/docs/source/developer/coordinate_systems.rst
@@ -0,0 +1,74 @@
+Coordinate Systems
+==================
+
+Between OpenGL, OSG, and osgEarth, there are several different coordinate systems
+and reference frames in use and it can get confusing sometimes which is which.
+Here we will cover some of the basics.
+
+OpenSceneGraph/OpenGL Coordinate Spaces
+---------------------------------------
+Here is a brief explaination of the various coordinate systems used in OpenGL and
+OSG. For a more detailed explaination (with pictures!) we direct you to read this
+excellent tutorial on the subject:
+
+    `OpenGL Transformation`_
+
+Model Coordinates
+.................
+Model (or Object) space refers to the actual coordinates in the geometry (like
+terrain tiles, an airplane model, etc). In OSG, model coordinates might be absolute
+or they might be transformed with an OSG ``Transform``.
+
+We will often refer to two types of Model coordinates: *world* and *local*.
+
+*World coordinates* are expressed in absolute terms; they are not transformed.
+*Local coordinates* have been transformed to make them relative to some reference
+point (in *world* coordinates).
+
+Why use local coordinates? Because OpenGL hardware can only handle 32-bit values for
+vertex locations. But in a system like osgEarth, we need to represent locations with
+large values and we cannot do that without exceeding the limits of 32-bit precision.
+The solution is to use *local coordinates*. OSG uses a double-precision ``MatrixTransform``
+to create a local origin (0,0,0), and then we can express our data relative to that.
+
+View Coordinates
+................
+View space (sometimes called *camera* or *eye* space) express the position of
+geometry relative to the camera itself. The camera is at the origin (0,0,0) and
+the coordinate axes are::
+
+    +X : Right
+    +Y : Up
+    -Z : Forward (direction the camera is looking)
+
+In osgEarth, View space is used quite a bit in *vertex shaders* -- they operate on
+the GPU which is limited to 32-bit precision, and View space has a *local origin*
+at the camera.
+
+Clip Coordinates
+................
+*Clip* coordinate are what you get after applying the view volume (also know as the
+camera frustum). The frustum defines the limits of what you can see from the eyepoint.
+The resulting coordinates are in this system::
+
+    +X : Right
+    +Y : Up
+    +Z : Forward
+    
+Clip spaces uses 4-dimensional homogeneous coordinates. The range of values in clip
+space encompasses the camera frustum and is expressed thusly::
+
+    X : [-w..w] (-w = left,   +w = right)
+    Y : [-w..w] (-w = bottom, +w = top)
+    Z : [-w..w] (-w = near,   +w = far)
+    W : perspective divisor
+    
+Note that the Z value, which represents *depth*, is non-linear. There is much more 
+precision closer to the near plane.
+
+Clip space is useful in a *shader* when you need to sample or manipulator *depth*
+information in the scene.
+
+
+.. _`OpenGL Transformation`: http://www.songho.ca/opengl/gl_transform.html
+
diff --git a/docs/source/developer/index.rst b/docs/source/developer/index.rst
index b0212dc..c9f5158 100644
--- a/docs/source/developer/index.rst
+++ b/docs/source/developer/index.rst
@@ -7,5 +7,6 @@ Developer Topics
    maps
    utilities
    shader_composition
+   coordinate_systems
    custom_driver
    qt_integration
diff --git a/docs/source/developer/shader_composition.rst b/docs/source/developer/shader_composition.rst
index 3a7ac65..02c2005 100644
--- a/docs/source/developer/shader_composition.rst
+++ b/docs/source/developer/shader_composition.rst
@@ -160,6 +160,50 @@ You can alter the vertex, but you *must* leave it in the same space.
 :CLIP:   Post-projected clip space. CLIP space lies in the [-w..w] range along all
          three axis, and is the result of transforming the original vertex by
          ``gl_ModelViewProjectionMatrix``.
+         
+         
+Shader Variables
+~~~~~~~~~~~~~~~~
+
+There are some built-in shader variables that osgEarth installs and that you can 
+access from your shader functions.
+
+    *Important: Shader variables starting with the prefix ``oe_`` or ``osgearth_``
+    are reserved for osgEarth internal use.*
+
+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_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
+  :oe_layer_opacity:     (float) Opacity [0..1] of the active layer
+
+Vertex attributes:
+
+  :oe_terrain_attr:      (vec4) elements 0-2 hold the unit height vector for a terrain
+                         vertex, and element 3 holds the raw terrain elevation value
+  :oe_terrain_attr2:     (vec4) element 0 holds the *parent* tile's elevation value;
+                         elements 1-3 are currently unused.
+
+
+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
+another visible layer.
+
+You can do this using *shared image layers*. In the ``Map``, mark an image layer as
+*shared* (using ``ImageLayerOptions::shared()``) and the renderer will make it available
+to all the other layers in a secondary sampler.
+
+    Please refer to ``osgearth_sharedlayer.cpp`` for a usage example!
 
 
 Customizing the Shader Factory
@@ -186,14 +230,3 @@ 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.
-
-
-System Uniforms
----------------
-
-In addition the the OSG system uniforms (which all start with "osg_"), osgEarth
-provides various uniforms. They are:
-
-  :osgearth_LightingEnabled:     whether GL lighting is enabled (bool)
-  :osgearth_CameraElevation:     distance from camera to ellipsoid/Z=0 (float)
-
diff --git a/docs/source/developer/utilities.rst b/docs/source/developer/utilities.rst
index 47995ad..f531f36 100644
--- a/docs/source/developer/utilities.rst
+++ b/docs/source/developer/utilities.rst
@@ -5,6 +5,25 @@ The osgEarth *Utils* namespace includes a variety of useful classes for interact
 with the map. None of these are strictly necessary for using osgEarth, but they do
 make it easier to perform some common operations.
 
+
+AutoScale
+---------
+
+*AutoScale* is a special *Render Bin* that will scale geometry from meters to pixels.
+That is: if you have an object that is 10 meters across, *AutoScale* will draw it in
+the space of 10 pixels (at scale 1.0) regardless of its distance from the camera.
+The effect is similar to OSG's ``AutoTransform::setAutoScaleToScreen`` method but is
+done in a shader and does not require any special nodes.
+
+To activate auto-scaling on a node::
+
+    node->getOrCreateStateSet()->setRenderBinDetails( 0, osgEarth::AUTO_SCALE_BIN );
+    
+And to deactivate it:
+
+    node->getOrCreateStateSet()->setRenderBinToInherit();
+
+
 DataScanner
 -----------
 
@@ -28,6 +47,46 @@ The ``extensions`` parameter lets you filter files by extension. For example, pa
 with a comma.
 
 
+DetailTexture
+-------------
+
+``DetailTexture`` is a terrain controller that will apply a non-geospatial texture
+cross the terrain. This is an old trick that you can use to generate "noise" that makes
+a low resolution terrain appear more detailed::
+
+    DetailTexture* detail = new DetailTexture();
+    detail->setImage( osgDB::readImageFile("mytexture.jpg") );
+    detail->setIntensity( 0.5f );
+    detail->setImageUnit( 4 );
+    mapnode->getTerrainEngine()->addEffect( detail );
+
+Try the example. Zoom in fairly close to the terrain to see the effect::
+
+    osgearth_detailtex readymap.earth
+
+
+
+LOD Blending
+------------
+
+``LODBlending`` is a terrain controller that will attempt to smoothly morph vertices
+and image textures from one LOD to the next as you zoom in or out. Basic usage is::
+
+    LODBlending* effect = new LODBlending();
+    mapnode->getTerrainEngine()->addEffect( effect );
+
+Caveats: It requires that the terrain elevation tile size dimensions be odd-numbered
+(e.g., 15x15). You can use the ``MapOptions::elevationTileSize`` property to configure
+this, or set ``elevation_tile_size`` in your earth file::
+
+    <map>
+        <options elevation_tile_size="15" ...
+
+For a demo, run this example and zoom into a mountainous area::
+
+    osgearth_viewer lod_blending.earth
+
+
 Formatters
 ----------
 
@@ -108,3 +167,58 @@ For your convenience, ``MouseCoordsTool`` also comes with a stock callback that
 print the coords to ``osgEarthUtil::Controls::LabelControl``. You can even pass a
 ``LabelControl`` to the contructor to make it even easier.
 
+
+NormalMap
+---------
+
+The ``NormalMap`` effect will use an ``ImageLayer`` as a bump map texture, adding
+apparent detail to the terrain. 
+
+A *normal map* is a kind of *bump map* in which each texel represents an XYZ normal
+vector instead of an RGB color value. The GPU can then use this information to apply
+lighting to the terrain on a per-pixel basis instead of per-vertex, rendering a
+more detailed-looking surface with the same number of triangles.
+
+First you need to create a normal map layer. You can use the **noise** driver to do
+this. The setup looks like this in the earth file::
+
+    <image name="bump" driver="noise" shared="true" visible="false">
+        <normal_map>true</normal_map>
+    </image>
+    
+The **noise driver** generates Perlin noise; this will will the image with pseudo-
+random normal vectors. (Setting ``normal_map`` to ``true`` is what tells the driver
+to make normal vectors instead of RGB values. You should also set ``shared`` to 
+``true``; this will make the normal map available to the shader pipeline so that it
+can do the custom lighting calculations.)
+
+Once you have the image layer set up, install the ``NormalMap`` terrain effect and 
+point it at our normal map layer. From the earth file::
+
+    <map>
+        ...
+        <external>
+            <normal_map layer="bump"/>
+        </external>
+
+Or from code::
+
+    NormalMap* normalMap = new NormalMap();
+    normalMap->setNormalMapLayer( myBumpLayer );
+    mapnode->getTerrainEngine()->addEffect( normalMap );
+    
+Please refer to the **normalmap.earth** example for a demo.
+
+
+VerticalScale
+-------------
+
+``VerticalScale`` scales the height values of the terrain. Basic usage is::
+
+    VerticalScale* scale = new VerticalScale();
+    scale->setScale( 2.0 );
+    mapnode->getTerrainEngine()->addEffect( scale );
+
+For a demo, run this example::
+
+    osgearth_verticalscale readymap.earth
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index b7293ef..f947ffa 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -9,6 +9,8 @@ FAQ
 * `Licensing`_
 
 
+----
+
 Common Usage
 ------------
 
@@ -68,6 +70,38 @@ How do make the terrain transparent?
         options.color() = osg::Vec4(1,1,1,0);
 
 
+How do I set the resolution of terrain tiles?
+.............................................
+
+    Each tile is a grid of vertices. The number of vertices can vary depending on source data
+    and settings. By default (when you have no elevation data) it is an 15x15 grid, tessellated
+    into triangles.
+    
+    If you do have elevation data, osgEarth will use the tile size of the first elevation layer 
+    to decide on the overall tile size for the terrain.
+
+    You can control this in a couple ways. If you have elevation data, you can set the
+    ``tile_size`` property on the elevation layer. For example::
+    
+        <elevation name="srtm" driver="gdal">
+            <url>...</url>
+            <tile_size>31</tile_size>
+        </elevation>
+        
+    That will read data as a grid of 31x31 vertices. If this is your first elevation layer,
+    osgEarth will render tiles at a resolution of 31x31.
+
+    Or, you can expressly set the terrain's tile size overall by using the Map options.
+    osgEarth will then resample all elevation data to the size you specify::
+
+        <map>
+            <options>
+                <elevation_tile_size>31</elevation_tile_size>
+                ...
+
+
+----
+
 Other Terrain Formats
 ---------------------
 
@@ -111,6 +145,8 @@ Can osgEarth load TerraPage or MetaFlight?
 .. _VirtualPlanetBuilder:	http://www.openscenegraph.com/index.php/documentation/tools/virtual-planet-builder
 
 
+----
+
 Community and Support
 ---------------------
 
@@ -159,7 +195,9 @@ Can I hire someone to help me with osgEarth?
     
 .. _contact form:   http://pelicanmapping.com/?page_id=2
 
-	
+
+----
+
 Licensing
 ---------
 
diff --git a/docs/source/ios.rst b/docs/source/ios.rst
index a4e6aba..c2c19ec 100644
--- a/docs/source/ios.rst
+++ b/docs/source/ios.rst
@@ -15,6 +15,11 @@ The steps to buld for use on devices are as follows:
 #. **Get Dependencies**
      | Prebuilt dependencies courtesy of `Thomas Hogarth <http://www.hogbox.co.uk>`_ can be downloaded here: https://s3.amazonaws.com/pelican-downloads/ios-3rdParty.zip
 
+#. **Javascript Support**
+     | Unlike osgEarth on the desktop which uses the V8 engine, osgEarth on iOS depends on the JavaScriptCore engine for scripting support.
+     | The JavaScriptCore code with projects for iOS can be found on GitHub here: https://github.com/phoboslab/JavaScriptCore-iOS
+     | As noted on that page, a prebuilt library can be found in the source tree of the `Ejecta project <https://github.com/phoboslab/Ejecta/tree/master/Source/lib>`_
+
 #. **Download OSG trunk**
    ::
       mkdir osgearth-build
diff --git a/docs/source/references/drivers/terrain/mp.rst b/docs/source/references/drivers/terrain/mp.rst
index 5b9804c..0669061 100644
--- a/docs/source/references/drivers/terrain/mp.rst
+++ b/docs/source/references/drivers/terrain/mp.rst
@@ -7,8 +7,14 @@ Example usage::
 
     <map>
         <options>
-            <terrain driver          = "mp"
-                     normalize_edges = "true" />
+            <terrain driver                   = "mp"
+                     skirt_ratio              = "0.05"
+                     color                    = "#ffffffff"
+                     normalize_edges          = "true"
+                     incremental_update       = "false"
+                     quick_release_gl_objects = "true"
+                     min_tile_range_factor    = "6.0"
+                     cluster_culling          = "true" />
 
 Properties:
 
@@ -16,15 +22,20 @@ Properties:
                                 gaps between adjacent tiles with different levels of
                                 detail. This property sets the ratio of skirt height to
                                 the width of the tile.
-    :normalize_edges:           Post-process the normal vectors on tile boundaries to 
-                                smooth them across tiles, making the tile boundaries
-                                less visible when not using imagery.
     :color:                     Color of the underlying terrain (without imagery) in
                                 HTML format. Default = "#ffffffff" (opaque white). You
                                 can adjust the alpha to get transparency.
+    :normalize_edges:           Post-process the normal vectors on tile boundaries to 
+                                smooth them across tiles, making the tile boundaries
+                                less visible when not using imagery.
+    :incremental_update:        When enabled, only visible tiles update when the map
+                                model changes (i.e., when layers are added or removed).
+                                Non-visible terrain tiles (like those at lower LODs)
+                                don't update until they come into view.
     :quick_release_gl_objects:  When true, installs a module that releases GL resources
                                 immediately when a tile pages out. This can prevent
                                 memory run-up when traversing a paged terrain at high
-                                speed.
+                                speed. Disabling quick-release may help achieve a more
+                                consistent frame rate.
     
 .. include:: terrain_options_shared.rst
diff --git a/docs/source/references/drivers/terrain/terrain_options_shared.rst b/docs/source/references/drivers/terrain/terrain_options_shared.rst
index 59238e9..3619f14 100644
--- a/docs/source/references/drivers/terrain/terrain_options_shared.rst
+++ b/docs/source/references/drivers/terrain/terrain_options_shared.rst
@@ -1,8 +1,5 @@
 Common Properties:
 
-    :sample_ratio:              Ratio at which to resample the number of height values
-                                in each elevation tile. (The preferred approach is to use
-                                MapOptions.elevation_tile_size instead.)
     :min_tile_range_factor:     The "maximum visible distance" ratio for all tiles. The 
                                 maximum visible distance is computed as tile radius * 
                                 this value. (default = 6.0)
diff --git a/docs/source/references/drivers/tile/gdal.rst b/docs/source/references/drivers/tile/gdal.rst
index b50859f..9187046 100644
--- a/docs/source/references/drivers/tile/gdal.rst
+++ b/docs/source/references/drivers/tile/gdal.rst
@@ -40,8 +40,9 @@ Properties:
                         any built in overviews or wavelet compression in the source file but can 
                         cause artifacts on neighboring tiles.  Interpolating the imagery can look nicer
                         but will be much slower.
-    :warp_profile:      The "warp profile" is a way to tell the GDAL driver to keep the original SRS and geotransform of the source data
-                        but use a Warped VRT to make the data appear to conform to the given profile.  This is useful for merging multiple 
+    :warp_profile:      The "warp profile" is a way to tell the GDAL driver to keep the original SRS
+                        and geotransform of the source data but use a Warped VRT to make the data
+                        appear to conform to the given profile.  This is useful for merging multiple
                         files that may be in different projections using the composite driver.
     
 Also see:
diff --git a/docs/source/references/drivers/tile/index.rst b/docs/source/references/drivers/tile/index.rst
index 5482eea..cc018bb 100644
--- a/docs/source/references/drivers/tile/index.rst
+++ b/docs/source/references/drivers/tile/index.rst
@@ -11,6 +11,7 @@ terrain engine. It can produce image tiles, elevation grid tiles, or both.
    arcgis_map_cache
    debug
    gdal
+   noise
    osg
    tilecache
    tileservice
diff --git a/docs/source/references/drivers/tile/noise.rst b/docs/source/references/drivers/tile/noise.rst
new file mode 100644
index 0000000..0b1951d
--- /dev/null
+++ b/docs/source/references/drivers/tile/noise.rst
@@ -0,0 +1,111 @@
+Noise
+==========================================
+The noise plugin procedurally generates fractal terrain based on a Perlin noise generator called `libnoise`_.
+We will explain how it works here, but you can also refer the the libnoise documentation for the meaning and
+application of the properties below.
+
+There are lots of ways to use the ``noise`` driver. After the properties list there are 
+a few examples of how to use it.
+       
+Basic Properties:
+
+    :resolution:        The linear distance (usually meters) over which to generate one cycle of
+                        noise data.
+    :scale:             The amount of offset to apply to noise values within a cycle. The default is
+                        1.0, which means you will get noise data between [-1...1].
+    :octaves:           Number of times to refine the noise data by adding levels of detail,
+                        i.e. how deep the noise generator will recurse within the resolution span.
+                        A higher number will create more detail as you zoom in closer. Default is 4.
+    :offset:            For heightfields, set this to true to generate offset values instead of absolute
+                        elevation heights. They will be added to the heights from another absolute
+                        elevation layer.
+
+Advanced Properties:
+
+    :frequency:         The reciprocal of the *resolution* above. (Since osgEarth is a mapping SDK,
+                        it is usually more intuitive to specifiy the resolution and leave this empty.)
+    :persistence:       Rate at which the *scale* decreases as the noise function traverses each
+                        higher octave. Scale(octave N+1) = Scale(octave N) * Persistence.
+    :lacunarity:        Rate at which the *frequency* increases as the noise function traverses each
+                        higher octave of detail. Freq(octave N+1) = Freq(octave N) * Lacunarity.
+    :seed:              Seeds the random number generator. The noise driver is "coherent", meaning that
+                        (among other things) it generates the same values given the same random
+                        seed. Alter this to alter the pattern.
+    :min_elevation:     The minimum elevation value to generate when creating height fields. This clamps
+                        height data to create a "floor".
+    :max_elevation:     The maximum elevation value to generate when createing height fields. This clamps
+                        height data to create a "ceiling".
+    :normal_map:        Set this to true (for an image layer) to create a bump map normal texture that you
+                        can use with the ``NormalMap`` terrain effect.
+
+Also see:
+
+    ``noise.earth``, ``fractal_detail.earth``, and ``normalmap.earth`` samples in the repo ``tests`` folder.
+
+
+Examples
+--------
+
+Create a worldwide procedural elevation layer::
+
+    <elevation driver="noise">
+        <resolution>3185500</resolution>   <!-- 1/4 earth's diameter -->
+        <scale>5000</scale>                <!-- vary heights by +/- 5000m over the resolution -->
+        <octaves>12</octaves>              <!-- detail recursion level -->
+    </elevation>
+
+Make it a little more interesting by tweaking the recursion properties::
+
+    <elevation driver="noise">
+        <resolution>3185500</resolution>   <!-- 1/4 earth's diameter -->
+        <scale>5000</scale>                <!-- vary heights by +/- 5000m over the resolution -->
+        <octaves>12</octaves>              <!-- detail recursion level -->
+        <persistence>0.49</persistence>    <!-- don't reduce the scale as quickly = noisier -->
+        <lacunarity>3.0</lacunarity>       <!-- increase the frequency faster = lumpier -->
+    </elevation>
+
+Look at the noise itself by creating an image layer. Looks like clouds::
+
+    <image driver="noise">
+        <resolution>3185500</resolution>   <!-- 1/4 earth's diameter -->
+        <octaves>12</octaves>              <!-- detail recursion level -->
+    </image>
+
+Use ``noise`` to create an offset layer to add detail to real elevation data::
+
+    <!-- Real elevation data -->
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+    
+    <elevation driver="noise" name="detail">
+        <offset>true</offset>             <!-- treat this as offset data -->
+        <tile_size>31</tile_size>         <!-- size of the tiles to create -->
+        <resolution>250</resolution>      <!-- not far from the resolution of our real data -->
+        <scale>20</scale>                 <!-- vary heights by 20m over 250m -->
+        <octaves>4</octaves>              <!-- add some additional detail -->
+    </elevation>
+
+Instead of creating offset elevation data, we can fake it with a *normal map*. A normal map
+is an invisible texture that simulates the normal vectors you'd get if you used real 
+elevation data::
+
+    <image name="normalmap" driver="noise">
+        <shared>true</shared>             <!-- share this layer so our effect can find it -->
+        <visible>false</visible>          <!-- we don't want to see the actual texture -->
+        <normal_map>true</normal_map>     <!-- create a normal map please -->
+        <tile_size>128</tile_size>        <!-- 128x128 texture -->
+        <resolution>250</resolution>      <!-- resolution of the noise function -->
+        <scale>20</scale>                 <!-- maximum height offset -->
+        <octaves>4</octaves>              <!-- level of detail -->
+    </image>
+    
+    ...
+    <external>
+       <normal_map layer="normalmap"/>    <!-- Install the terrain effect so we can see it -->
+       <sky hours="17"/>                  <!-- Must have lighting as well -->
+    </external>
+
+
+
+.. _libnoise:           http://libnoise.sourceforge.net/
diff --git a/docs/source/references/earthfile.rst b/docs/source/references/earthfile.rst
index 39b7a64..ddd9617 100644
--- a/docs/source/references/earthfile.rst
+++ b/docs/source/references/earthfile.rst
@@ -42,10 +42,12 @@ the entire map.
 .. parsed-literal::
 
     <map>
-        <options lighting                = "true"
-                 elevation_interpolation = "bilinear"
-                 elevation_tile_size     = "8"
-                 overlay_texture_size    = "4096" >
+        <options lighting                 = "true"
+                 elevation_interpolation  = "bilinear"
+                 elevation_tile_size      = "8"
+                 overlay_texture_size     = "4096"
+                 overlay_blending         = "true"
+                 overlay_resolution_ratio = "5.0" >
 
             <:ref:`profile <Profile>`>
             <:ref:`proxy <ProxySettings>`>
@@ -53,24 +55,32 @@ the entire map.
             <:ref:`cache_policy <CachePolicy>`>
             <:ref:`terrain <TerrainOptions>`>
 
-+------------------------+--------------------------------------------------------------------+
-| 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.     |
-+------------------------+--------------------------------------------------------------------+
-| elevation_interpolation| Algorithm to use when resampling elevation source data:            |
-|                        |   :nearest:     Nearest neighbor                                   |
-|                        |   :average:     Averages the neighoring values                     |
-|                        |   :bilinear:    Linear interpolation in both axes                  |
-|                        |   :triangulate: Interp follows triangle slope                      |
-+------------------------+--------------------------------------------------------------------+
-| elevation_tile_size    | Forces the number of posts to render for each terrain tile. By     |
-|                        | default, the engine will use the size of the largest available     |
-|                        | source.                                                            |
-+------------------------+--------------------------------------------------------------------+
-| overlay_texture_size   | Sets the texture size to use for draping (projective texturing)    |
-+------------------------+--------------------------------------------------------------------+
++--------------------------+--------------------------------------------------------------------+
+| 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.     |
++--------------------------+--------------------------------------------------------------------+
+| elevation_interpolation  | Algorithm to use when resampling elevation source data:            |
+|                          |   :nearest:     Nearest neighbor                                   |
+|                          |   :average:     Averages the neighoring values                     |
+|                          |   :bilinear:    Linear interpolation in both axes                  |
+|                          |   :triangulate: Interp follows triangle slope                      |
++--------------------------+--------------------------------------------------------------------+
+| elevation_tile_size      | Forces the number of posts to render for each terrain tile. By     |
+|                          | default, the engine will use the size of the largest available     |
+|                          | source.                                                            |
++--------------------------+--------------------------------------------------------------------+
+| overlay_texture_size     | Sets the texture size to use for draping (projective texturing)    |
++--------------------------+--------------------------------------------------------------------+
+| overlay_blending         | Whether overlay geometry blends with the terrain during draping    |
+|                          | (projective texturing                                              |
++--------------------------+--------------------------------------------------------------------+
+| overlay_resolution_ratio | For draped geometry, the ratio of the resolution of the projective |
+|                          | texture near the camera versus the resolution far from the camera. |
+|                          | Increase the value to improve appearance close to the camera while |
+|                          | sacrificing appearance of farther geometry.                        |
++--------------------------+--------------------------------------------------------------------+
 
 
 .. _TerrainOptions:
@@ -85,7 +95,6 @@ These options control the rendering of the terrain surface.
         <options>
             <terrain driver                = "mp"
                      lighting              = "true"
-                     sample_ratio          = "1.0"
                      skirt_ratio           = "0.05"
                      min_tile_range_factor = "6"
                      min_lod               = "0"
@@ -105,12 +114,6 @@ These options control the rendering of the terrain surface.
 | lighting              | Whether to enable GL_LIGHTING on the terrain. By default this is   |
 |                       | unset, meaning it will inherit the lighting mode of the scene.     |
 +-----------------------+--------------------------------------------------------------------+
-| sample_ratio          | Ratio of vertices per tile to the number of hieght samples in the  |
-|                       | source elevation data. Default = 1.0. You can reduce this number in|
-|                       | order to forcably downsample the terrain. *NOTE* that it is usually|
-|                       | better to set the ``elevation_tile_size`` property in the map      |
-|                       | options.                                                           |
-+-----------------------+--------------------------------------------------------------------+
 | skirt_ratio           | Ratio of the height of a terrain tile "skirt" to the extent of the |
 |                       | tile. The *skirt* is geometry that hides gaps between adjacent     |
 |                       | tiles with different levels of detail.                             |
@@ -163,7 +166,11 @@ An *image layer* is a raster image overlaid on the map's geometry.
                min_resolution = "100.0"
                max_resolution = "0.0"
                enabled        = "true"
-               visible        = "true" >
+               visible        = "true"
+               shared         = "false"
+               feather_pixels = "false"
+               min_filter     = "LINEAR"
+               mag_filter     = "LINEAR" >
 
             <:ref:`cache_policy <CachePolicy>`>
             <:ref:`color_filters <ColorFilterChain>`>
@@ -207,6 +214,20 @@ An *image layer* is a raster image overlaid on the map's geometry.
 +-----------------------+--------------------------------------------------------------------+
 | visible               | Whether to draw the layer.                                         |
 +-----------------------+--------------------------------------------------------------------+
+| shared                | Generates a secondary, dedicated sampler for this layer so that it |
+|                       | may be accessed globally by custom shaders.                        |
++-----------------------+--------------------------------------------------------------------+
+| 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.                  |
++-----------------------+--------------------------------------------------------------------+
+| min_filter            | OpenGL texture minification filter to use for this layer.          |
+|                       | Options are NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST,               |
+|                       | NEAREST_MIPMIP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR |
++-----------------------+--------------------------------------------------------------------+
+| mag_filter            | OpenGL texture magnification filter to use for this layer.         |
+|                       | Options are the same as for ``min_filter`` above.                  |
++-----------------------+--------------------------------------------------------------------+
 
 
 .. _ElevationLayer:
@@ -225,7 +246,8 @@ will composite all elevation data into a single heightmap and use that to build
                    max_level      = "23"
                    min_resolution = "100.0"
                    max_resolution = "0.0"
-                   enabled        = "true" >
+                   enabled        = "true"
+                   offset         = "false" >
 
 
 +-----------------------+--------------------------------------------------------------------+
@@ -251,6 +273,9 @@ will composite all elevation data into a single heightmap and use that to build
 |                       | load time; it is just an easy way of "commenting out" a layer in   |
 |                       | the earth file.                                                    |
 +-----------------------+--------------------------------------------------------------------+
+| offset                | Indicates that the height values in this layer are relative        |
+|                       | offsets rather than true terrain height samples.                   |
++-----------------------+--------------------------------------------------------------------+
 
 
 .. _ModelLayer:
diff --git a/docs/source/references/envvars.rst b/docs/source/references/envvars.rst
index 1396ea9..3002365 100644
--- a/docs/source/references/envvars.rst
+++ b/docs/source/references/envvars.rst
@@ -12,11 +12,13 @@ Debugging:
 
     :OSGEARTH_NOTIFY_LEVEL:     Similar to ``OSG_NOTIFY_LEVEL``, sets the verbosity for
                                 console output. Values are ``DEBUG``, ``INFO``, ``NOTICE``,
-                                and ``WARN``. Default is ``NOTICE``.
-    :OSGEARTH_MERGE_SHADERS:    Consolidate all shaders within a shader program; this is
-                                required for GLES so this is useful for testing, and also
-                                makes shader dumps more readable (set to 1)
-    :OSGEARTH_DUMP_SHADERS:     Prints composited shader code to the console (set to 1)
+                                and ``WARN``. Default is ``NOTICE``. (This is distinct from
+                                OSG's notify level.)
+    :OSGEARTH_MERGE_SHADERS:    Consolidate all shaders within a single shader program; this
+                                is required for GLES (mobile devices) and is therefore useful
+                                for testing. It also makes shader dumps more readable. (set to 1)
+    :OSGEARTH_DUMP_SHADERS:     Prints composited shader code to the console (set to 1).
+                                Setting this will also activate shader merging (above).
 
 Rendering:
 
@@ -39,3 +41,12 @@ Misc:
     :OSGEARTH_USE_PBUFFER_TEST: Directs the osgEarth platform Capabilities analyzer to
                                 create a PBUFFER-based graphics context for collecting
                                 GL support information. (set to 1)
+
+Performance:
+
+    :OSG_NUM_DATABASE_THREADS:      Sets the total number of threads that the OSG DatabasePager
+                                    will use to load terrain tiles and feature data tiles.
+    :OSG_NUM_HTTP_DATABASE_THREADS: Sets the number of threads in the Pager's thread pool (see
+                                    above) that should be used for "high-latency" operations.
+                                    (Usually this means operations that download data from the
+                                    network, hence the "HTTP" in the variable name.)
diff --git a/docs/source/references/symbology.rst b/docs/source/references/symbology.rst
index 9bd4458..7be2503 100644
--- a/docs/source/references/symbology.rst
+++ b/docs/source/references/symbology.rst
@@ -84,6 +84,16 @@ control the color and style of the vector data.
 |                       | rounded corner. Value is the ratio of |                            |
 |                       | line width to corner segment length.  |                            |
 +-----------------------+---------------------------------------+----------------------------+
+| stroke-stipple-pattern| Stippling pattern bitmask. Each set   | integer (65535)            |
+|                       | bit represents an "on" pixel in the   |                            |
+|                       | pattern.                              |                            |
++-----------------------+---------------------------------------+----------------------------+
+| stroke-stipple-factor | Stipple factor for pixel-width lines. | integer (1)                |
+|                       | Number of times to repeat each bit in |                            |
+|                       | the stippling pattern                 |                            |
++-----------------------+---------------------------------------+----------------------------+
+| point-fill            | Fill color for a point.               | HTML color                 |
++-----------------------+---------------------------------------+----------------------------+
 | point-size            | Size for a GL point geometry          | float (1.0)                |
 +-----------------------+---------------------------------------+----------------------------+
 
@@ -123,6 +133,9 @@ the terrain under its location.
 | altitude-scale        | Scale factor to apply to geometry Z                                |
 +-----------------------+--------------------------------------------------------------------+
 
+Tip: You can also use a shortcut to activate draping or GPU clamping; set ``altitude-clamping``
+to either ``terrain-drape`` or ``terrain-gpu``.
+
 
 Extrusion
 ---------
@@ -206,50 +219,56 @@ Icons are used for different things, the most common being:
  * Point model substitution - replace geometry with icons
  * Place annotations
 
-+-------------------------+--------------------------------------------------------------------+
-| Property                | Description                                                        |
-+=========================+====================================================================+
-| icon                    | URI of the icon image. (uri-string)                                |
-+-------------------------+--------------------------------------------------------------------+
-| icon-library            | Name of a *resource library* containing the icon (optional)        |
-+-------------------------+--------------------------------------------------------------------+
-| icon-placement          | For model substitution, describes how osgEarth should replace      |
-|                         | geometry with icons:                                               |
-|                         |    :vertex:   Replace each vertex in the geometry with an icon.    |
-|                         |    :interval: Place icons at regular intervals along the geometry, |
-|                         |               according to the ``icon-density`` property.          |
-|                         |    :random:   Place icons randomly within the geometry, according  |
-|                         |               to the ``icon-density`` property.                    |
-|                         |    :centroid: Place a single icon at the centroid of the geometry. |
-+-------------------------+--------------------------------------------------------------------+
-| icon-density            | For ``icon-placement`` settings of ``interval`` or ``random``,     |
-|                         | this property is hint as to how many instances osgEarth should     |
-|                         | place. The unit is approximately "units per km" (for linear data)  |
-|                         | or "units per square km" for polygon data. (float)                 |
-+-------------------------+--------------------------------------------------------------------+
-| icon-scale              | Scales the icon by this amount (float)                             |
-+-------------------------+--------------------------------------------------------------------+
-| icon-heading            | Rotates the icon along its central axis (float, degrees)           |
-+-------------------------+--------------------------------------------------------------------+
-| icon-declutter          | Activate *decluttering* for this icon. osgEarth will attempt to    |
-|                         | automatically show or hide things so they don't overlap on the     |
-|                         | screen. (boolean)                                                  |
-+-------------------------+--------------------------------------------------------------------+
-| icon-align              | Sets the icon's location relative to its anchor point. The valid   |
-|                         | values are in the form "horizontal-vertical", and are:             |
-|                         |   * ``left-top``                                                   |
-|                         |   * ``left-center``                                                |
-|                         |   * ``left-bottom``                                                |
-|                         |   * ``center-top``                                                 |
-|                         |   * ``center-center``                                              |
-|                         |   * ``center-bottom``                                              |
-|                         |   * ``right-top``                                                  |
-|                         |   * ``right-center``                                               |
-|                         |   * ``right-bottom``                                               |
-+-------------------------+--------------------------------------------------------------------+
-| icon-random-seed        | For random placement operations, set this seed so that the         |
-|                         | randomization is repeatable each time you run the app. (integer)   |
-+-------------------------+--------------------------------------------------------------------+
++--------------------------------+--------------------------------------------------------------------+
+| Property                       | Description                                                        |
++================================+====================================================================+
+| icon                           | URI of the icon image. (uri-string)                                |
++--------------------------------+--------------------------------------------------------------------+
+| icon-library                   | Name of a *resource library* containing the icon (optional)        |
++--------------------------------+--------------------------------------------------------------------+
+| icon-placement                 | For model substitution, describes how osgEarth should replace      |
+|                                | geometry with icons:                                               |
+|                                |    :vertex:   Replace each vertex in the geometry with an icon.    |
+|                                |    :interval: Place icons at regular intervals along the geometry, |
+|                                |               according to the ``icon-density`` property.          |
+|                                |    :random:   Place icons randomly within the geometry, according  |
+|                                |               to the ``icon-density`` property.                    |
+|                                |    :centroid: Place a single icon at the centroid of the geometry. |
++--------------------------------+--------------------------------------------------------------------+
+| icon-density                   | For ``icon-placement`` settings of ``interval`` or ``random``,     |
+|                                | this property is hint as to how many instances osgEarth should     |
+|                                | place. The unit is approximately "units per km" (for linear data)  |
+|                                | or "units per square km" for polygon data. (float)                 |
++--------------------------------+--------------------------------------------------------------------+
+| icon-scale                     | Scales the icon by this amount (float)                             |
++--------------------------------+--------------------------------------------------------------------+
+| icon-heading                   | Rotates the icon along its central axis (float, degrees)           |
++--------------------------------+--------------------------------------------------------------------+
+| icon-declutter                 | Activate *decluttering* for this icon. osgEarth will attempt to    |
+|                                | automatically show or hide things so they don't overlap on the     |
+|                                | screen. (boolean)                                                  |
++--------------------------------+--------------------------------------------------------------------+
+| icon-align                     | Sets the icon's location relative to its anchor point. The valid   |
+|                                | values are in the form "horizontal-vertical", and are:             |
+|                                |   * ``left-top``                                                   |
+|                                |   * ``left-center``                                                |
+|                                |   * ``left-bottom``                                                |
+|                                |   * ``center-top``                                                 |
+|                                |   * ``center-center``                                              |
+|                                |   * ``center-bottom``                                              |
+|                                |   * ``right-top``                                                  |
+|                                |   * ``right-center``                                               |
+|                                |   * ``right-bottom``                                               |
++--------------------------------+--------------------------------------------------------------------+
+| icon-random-seed               | For random placement operations, set this seed so that the         |
+|                                | randomization is repeatable each time you run the app. (integer)   |
++--------------------------------+--------------------------------------------------------------------+
+| icon-occlusion-cull            | Whether to occlusion cull the text so they do not display          |
+|                                | when line of sight is obstructed by terrain                        | 
++--------------------------------+--------------------------------------------------------------------+
+| icon-occlusion-cull-altitude   | The viewer altitude (MSL) to start occlusion culling               |
+|                                | when line of sight is obstructed by terrain                        |
++--------------------------------+--------------------------------------------------------------------+
  
 
 Model
@@ -335,49 +354,60 @@ Text
 
 The *text symbol* (SDK: ``TextSymbol``) controls the existance and appearance of text labels.
 
-+-------------------------+--------------------------------------------------------------------+
-| Property                | Description                                                        |
-+=========================+====================================================================+
-| fill                    | Foreground color of the text (HTML color)                          |
-+-------------------------+--------------------------------------------------------------------+
-| text-size               | Size of the text (float, pixels)                                   |
-+-------------------------+--------------------------------------------------------------------+
-| text-font               | Name of the font to use (system-dependent). For example, use       |
-|                         | "arialbd" on Windows for Arial Bold.                               |
-+-------------------------+--------------------------------------------------------------------+
-| text-halo               | Outline color of the text; Omit this propery altogether for no     |
-|                         | outline. (HTML Color)                                              |
-+-------------------------+--------------------------------------------------------------------+
-| text-halo-offset        | Outline thickness (float, pixels)                                  |
-+-------------------------+--------------------------------------------------------------------+
-| text-align              | Alignment of the text string relative to its anchor point:         |
-|                         |   * ``left-top``                                                   |
-|                         |   * ``left-center``                                                |
-|                         |   * ``left-bottom``                                                |
-|                         |   * ``left-base-line``                                             |
-|                         |   * ``left-bottom-base-line``                                      |
-|                         |   * ``center-top``                                                 |
-|                         |   * ``center-center``                                              |
-|                         |   * ``center-bottom``                                              |
-|                         |   * ``center-base-line``                                           |
-|                         |   * ``center-bottom-base-line``                                    |
-|                         |   * ``right-top``                                                  |
-|                         |   * ``right-center``                                               |
-|                         |   * ``right-bottom``                                               |
-|                         |   * ``right-base-line``                                            |
-|                         |   * ``right-bottom-base-line``                                     |
-|                         |   * ``base-line``                                                  |
-+-------------------------+--------------------------------------------------------------------+
-| text-content            | The actual text string to display (string-expr)                    |
-+-------------------------+--------------------------------------------------------------------+
-| text-encoding           | Character encoding of the text content:                            |
-|                         |   * ``utf-8``                                                      |
-|                         |   * ``utf-16``                                                     |
-|                         |   * ``utf-32``                                                     |
-|                         |   * ``ascii``                                                      |
-+-------------------------+--------------------------------------------------------------------+
-| text-declutter          | Activate *decluttering* for this icon. osgEarth will attempt to    |
-|                         | automatically show or hide things so they don't overlap on the     |
-|                         | screen. (boolean)                                                  |
-+-------------------------+--------------------------------------------------------------------+
++--------------------------------+--------------------------------------------------------------------+
+| Property                       | Description                                                        |
++================================+====================================================================+
+| text-fill                      | Foreground color of the text (HTML color)                          |
++--------------------------------+--------------------------------------------------------------------+
+| text-size                      | Size of the text (float, pixels)                                   |
++--------------------------------+--------------------------------------------------------------------+
+| text-font                      | Name of the font to use (system-dependent). For example, use       |
+|                                | "arialbd" on Windows for Arial Bold.                               |
++--------------------------------+--------------------------------------------------------------------+
+| text-halo                      | Outline color of the text; Omit this propery altogether for no     |
+|                                | outline. (HTML Color)                                              |
++--------------------------------+--------------------------------------------------------------------+
+| text-halo-offset               | Outline thickness (float, pixels)                                  |
++--------------------------------+--------------------------------------------------------------------+
+| text-align                     | Alignment of the text string relative to its anchor point:         |
+|                                |   * ``left-top``                                                   |
+|                                |   * ``left-center``                                                |
+|                                |   * ``left-bottom``                                                |
+|                                |   * ``left-base-line``                                             |
+|                                |   * ``left-bottom-base-line``                                      |
+|                                |   * ``center-top``                                                 |
+|                                |   * ``center-center``                                              |
+|                                |   * ``center-bottom``                                              |
+|                                |   * ``center-base-line``                                           |
+|                                |   * ``center-bottom-base-line``                                    |
+|                                |   * ``right-top``                                                  |
+|                                |   * ``right-center``                                               |
+|                                |   * ``right-bottom``                                               |
+|                                |   * ``right-base-line``                                            |
+|                                |   * ``right-bottom-base-line``                                     |
+|                                |   * ``base-line``                                                  |
++--------------------------------+--------------------------------------------------------------------+
+| text-layout                    | Layout of text:                                                    |
+|                                |   * ``ltr``                                                        |
+|                                |   * ``rtl``                                                        |
+|                                |   * ``vertical``                                                   |
++--------------------------------+--------------------------------------------------------------------+
+| text-content                   | The actual text string to display (string-expr)                    |
++--------------------------------+--------------------------------------------------------------------+
+| text-encoding                  | Character encoding of the text content:                            |
+|                                |   * ``utf-8``                                                      |
+|                                |   * ``utf-16``                                                     |
+|                                |   * ``utf-32``                                                     |
+|                                |   * ``ascii``                                                      |
++--------------------------------+--------------------------------------------------------------------+
+| text-declutter                 | Activate *decluttering* for this icon. osgEarth will attempt to    |
+|                                | automatically show or hide things so they don't overlap on the     |
+|                                | screen. (boolean)                                                  |
++--------------------------------+--------------------------------------------------------------------+
+| text-occlusion-cull            | Whether to occlusion cull the text so they do not display          |
+|                                | when line of sight is obstructed by terrain                        | 
++--------------------------------+--------------------------------------------------------------------+
+| text-occlusion-cull-altitude   | The viewer altitude (MSL) to start occlusion culling               |
+|                                | when line of sight is obstructed by terrain                        |
++--------------------------------+--------------------------------------------------------------------+
 
diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes.rst
index 44db6bb..cf1555c 100644
--- a/docs/source/releasenotes.rst
+++ b/docs/source/releasenotes.rst
@@ -1,6 +1,53 @@
 Release Notes
 =============
 
+Version 2.5 (November 2013)
+---------------------------
+
+Terrain Engine
+
+The terrain engine ("MP") has undergone many performance updates. We focused on geometry
+optimization and GL state optimization, bypassing some the OSG mechnisms and going straight
+to GL to make things as fast as possible.
+
+MP has a new optional "incremental update" feature. By default, when you change the
+map model (add/remove layers etc.) osgEarth will rebuild the terrain in its entirely. With
+incremental update enabled, it will only rebuild tiles that are visible. Tiles not currently
+visible (like those at lower LODs) don't update until they actually become visible.
+
+Caching
+
+Caching got a couple improvements. The cache seeder (osgearth_cache) is now multi-threaded
+(as it the TMS packager utility). The filesystem cache also supports expiration policies
+for cached items, including map tiles.
+
+JavaScript
+
+We updated osgEarth to work with the newest Google V8 JavaScript interpreter API. We also
+now support JavaScriptCore as a JS interpreter for OSX/iOS devices (where V8 is not
+available).
+
+Terrain Effects
+
+A new TerrainEffect API makes it easy to add custom shaders to the terrain. osgEarth has
+several of these built in, including NormalMap, DetailTexture, LODBlending, and ContourMap.
+
+New Drivers
+
+There is a new Bing Maps driver. Bing requires an API key, which you can get at the Bing site.
+
+We also added a new LibNOISE driver. It generates parametric noise that you can use as
+terrain elevation data, or to add fractal detail to existing terrain, or to generate 
+noise patterns for detail texturing.
+
+Other Goodies
+
+* Shared Layers allow access multiple samplers from a custom shader
+* A new "AUTO_SCALE" render bin scales geometry to the screen without using an AutoTransform node.
+* PlaceNodes and LabelNodes now support localized occlusion culling.
+* The Controls utility library works on iOS/GLES now.
+
+
 Version 2.4 (April 2013)
 ------------------------
 
diff --git a/docs/source/user/caching.rst b/docs/source/user/caching.rst
index d6c0731..5f269d5 100644
--- a/docs/source/user/caching.rst
+++ b/docs/source/user/caching.rst
@@ -33,7 +33,8 @@ In code this would look like this::
     MapOptions mapOptions;
     mapOptions.cache() = cacheOptions;
     
-Or, you can use an environment variable that will apply to all earth files::
+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_PATH=folder_name
 
@@ -47,8 +48,8 @@ Caching Policies
 ----------------
 Once you have a cache set up, osgEarth will use it be default for all your
 imagery and elevation layers. If you want to override this behavior, you can
-use a *cache policy*. A cache policy tells osgEarth how a certain object 
-should treat the cache.
+use a *cache policy*. A cache policy tells osgEarth if and how a certain object 
+should utilize the cache.
 
 In an *earth file* you can do this by using the ``cache_policy`` block. Here 
 we apply it to the entire map::
@@ -72,14 +73,24 @@ The values for cache policy *usage* are:
     :cache_only:        If a cache if set up, ONLY use data in the cache; never go 
                         to the data source.
 
+You can also direct the cache to expire objects. By default, cached data never expires,
+but you can use the ``max_age`` property to tell it how long to treat an object as valid::
+
+    <cache_policy max_age="3600"/>
+    
+Specify the maximum age in seconds. The example above will expire objects that are more
+than one hour old.
+
+
 Environment Variables
 ---------------------
 Sometimes it's more convenient to control caching from the environment,
 especially during development. Here are some environment variables you can use.
 
-    :OSGEARTH_CACHE_PATH:   Root folder for a file system cache.
-    :OSGEARTH_NO_CACHE:     Enables a ``no_cache`` policy for any osgEarth map. (set to 1)
-    :OSGEARTH_CACHE_ONLY:   Enabled a ``cache_only`` policy for any osgEarth map. (set to 1)
+    :OSGEARTH_CACHE_PATH:    Root folder for a file system cache.
+    :OSGEARTH_NO_CACHE:      Enables a ``no_cache`` policy for any osgEarth map. (set to 1)
+    :OSGEARTH_CACHE_ONLY:    Enabled a ``cache_only`` policy for any osgEarth map. (set to 1)
+    :OSGEARTH_CACHE_MAX_AGE: Set the cache to expire objects more than this number of seconds old.
 
 **Note**: environment variables *override* the cache settings in an *earth file*!
 
@@ -92,3 +103,8 @@ this task. ``osgearth_cache`` will take an Earth file and populate any caches
 it finds.
 
     Type ``osgearth_cache --help`` on the command line for usage information.
+
+**Note**: The cache is a transient, "black box" designed to improve
+performance in certain situations. It is not inteded as a distributable data
+repository. In many cases you can move a cache folder from one environment to another
+and it will work, but osgEarth does not *guarantee* such behavior.
diff --git a/docs/source/user/tools.rst b/docs/source/user/tools.rst
index 1794aa9..dffb64d 100644
--- a/docs/source/user/tools.rst
+++ b/docs/source/user/tools.rst
@@ -78,6 +78,11 @@ The most common usage of osgearth_cache is to populate a cache in a non-interact
 +-------------------------------------+--------------------------------------------------------------------+
 | ``--seed``                          | Seeds the cache in a .earth file                                   |
 +-------------------------------------+--------------------------------------------------------------------+
+| ``--estimate``                      | Print out an estimation of the number of tiles, disk space and     |
+|                                     | time it will take to perform this seed operation                   |
++-------------------------------------+--------------------------------------------------------------------+
+| ``--threads``                       |The number of threads to use for the seed operation (default=1)     |
++-------------------------------------+--------------------------------------------------------------------+
 | ``--min-level level``               | Lowest LOD level to seed (default=0)                               |
 +-------------------------------------+--------------------------------------------------------------------+
 | ``--max-level level``               | Highest LOD level to seed (default=highest available)              |
@@ -85,12 +90,17 @@ The most common usage of osgearth_cache is to populate a cache in a non-interact
 | ``--bounds xmin ymin xmax ymax``    | Geospatial bounding box to seed                                    |
 |                                     | (in map coordinates; default=entire map                            |
 +-------------------------------------+--------------------------------------------------------------------+
+| ``--index shapefile``               | Loads a shapefile (.shp) and uses the feature extents to set the   |
+|                                     | cache seeding bounding box(es). For each feature in the shapefile, |
+|                                     | adds a bounding box (similar to ``--bounds``) to constrain the     |
+|                                     | region you wish to cache.                                          |
++-------------------------------------+--------------------------------------------------------------------+
 | ``--cache-path path``               | Overrides the cache path in the .earth file                        |
 +-------------------------------------+--------------------------------------------------------------------+
 | ``--cache-type type``               | Overrides the cache type in the .earth file                        |
 +-------------------------------------+--------------------------------------------------------------------+
 | ``--purge``                         | Purges a layer cache in a .earth file                              |
-+-------------------------------------+--------------------------------------------------------------------+       
++-------------------------------------+--------------------------------------------------------------------+
 
 osgearth_package
 ----------------
@@ -120,6 +130,9 @@ osgearth_package creates a redistributable `TMS`_ based package from an earth fi
 +------------------------------------+--------------------------------------------------------------------+
 | ``--keep-empties``                 | writes out fully transparent image tiles (normally discarded)      |
 +------------------------------------+--------------------------------------------------------------------+
+| ``--continue-single-color``        | continues to subdivide single color tiles,                         |
+|                                    | subdivision typicall stops on single color images                  |
++------------------------------------+--------------------------------------------------------------------+
 | ``--db-options``                   | db options string to pass to the image writer                      |
 |                                    | in quotes (e.g., "JPEG_QUALITY 60")                                |
 +------------------------------------+--------------------------------------------------------------------+
diff --git a/src/applications/CMakeLists.txt b/src/applications/CMakeLists.txt
index b131dce..82ec977 100644
--- a/src/applications/CMakeLists.txt
+++ b/src/applications/CMakeLists.txt
@@ -13,6 +13,7 @@ IF(NOT OSGCORE_BUNDLED)
 ENDIF(NOT OSGCORE_BUNDLED)
 SET(OPENSCENEGRAPH_APPLICATION_DIR ${PROJECT_SOURCE_DIR})
 
+
 #OpenThreads, osg, osgDB and osgUtil are included elsewhere.
 SET(TARGET_COMMON_LIBRARIES
     osgEarth
@@ -35,6 +36,7 @@ ADD_SUBDIRECTORY(osgearth_boundarygen)
 ADD_SUBDIRECTORY(osgearth_backfill)
 ADD_SUBDIRECTORY(osgearth_overlayviewer)
 ADD_SUBDIRECTORY(osgearth_version)
+ADD_SUBDIRECTORY(osgearth_tileindex)
 IF (QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
     ADD_SUBDIRECTORY(osgearth_package_qt)
 ENDIF()
@@ -71,19 +73,22 @@ ADD_SUBDIRECTORY(osgearth_featuremanip)
 ADD_SUBDIRECTORY(osgearth_featurequery)
 ADD_SUBDIRECTORY(osgearth_occlusionculling)
 ADD_SUBDIRECTORY(osgearth_colorfilter)
-ADD_SUBDIRECTORY(osgearth_contour)
-ADD_SUBDIRECTORY(osgearth_verticalscale)
+#ADD_SUBDIRECTORY(osgearth_verticalscale)
 ADD_SUBDIRECTORY(osgearth_sequencecontrol)
 ADD_SUBDIRECTORY(osgearth_minimap)
+#ADD_SUBDIRECTORY(osgearth_detailtex)
+ADD_SUBDIRECTORY(osgearth_sharedlayer)
+ADD_SUBDIRECTORY(osgearth_terraineffects)
 
 IF(NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "3.1.0")
     ADD_SUBDIRECTORY(osgearth_shadow)
 ENDIF()
 
 IF (QT4_FOUND AND NOT ANDROID AND OSGEARTH_USE_QT)
-    ADD_SUBDIRECTORY(osgearth_qt)
+#    ADD_SUBDIRECTORY(osgearth_qt)
     ADD_SUBDIRECTORY(osgearth_qt_simple)
     ADD_SUBDIRECTORY(osgearth_qt_windows)
+    ADD_SUBDIRECTORY(osgearth_demo)
 ENDIF()
 
 #ADD_SUBDIRECTORY(osgearth_silverlining)
diff --git a/src/applications/osgearth_annotation/osgearth_annotation.cpp b/src/applications/osgearth_annotation/osgearth_annotation.cpp
index b29d071..60676bb 100644
--- a/src/applications/osgearth_annotation/osgearth_annotation.cpp
+++ b/src/applications/osgearth_annotation/osgearth_annotation.cpp
@@ -18,6 +18,7 @@
 */
 
 #include <osgEarth/MapNode>
+#include <osgEarth/Decluttering>
 #include <osgEarth/ECEF>
 
 #include <osgEarthUtil/EarthManipulator>
@@ -36,7 +37,6 @@
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthAnnotation/LocalGeometryNode>
 #include <osgEarthAnnotation/FeatureNode>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthAnnotation/HighlightDecoration>
 #include <osgEarthAnnotation/ScaleDecoration>
 
diff --git a/src/applications/osgearth_city/osgearth_city.cpp b/src/applications/osgearth_city/osgearth_city.cpp
index f520d31..48e0ad6 100644
--- a/src/applications/osgearth_city/osgearth_city.cpp
+++ b/src/applications/osgearth_city/osgearth_city.cpp
@@ -26,6 +26,7 @@
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/SkyNode>
 #include <osgEarthDrivers/tms/TMSOptions>
+#include <osgEarthDrivers/xyz/XYZOptions>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 #include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
 
@@ -62,13 +63,17 @@ main(int argc, char** argv)
     
     // a style for the building data:
     Style buildingStyle;
-    buildingStyle.setName( "default" );
+    buildingStyle.setName( "buildings" );
 
     ExtrusionSymbol* extrusion = buildingStyle.getOrCreate<ExtrusionSymbol>();
     extrusion->heightExpression() = NumericExpression( "3.5 * max( [story_ht_], 1 )" );
     extrusion->flatten() = true;
     extrusion->wallStyleName() = "building-wall";
     extrusion->roofStyleName() = "building-roof";
+    extrusion->wallGradientPercentage() = 0.8;
+
+    PolygonSymbol* poly = buildingStyle.getOrCreate<PolygonSymbol>();
+    poly->fill()->color() = Color::White;
 
     // a style for the wall textures:
     Style wallStyle;
@@ -99,8 +104,8 @@ main(int argc, char** argv)
 
     // set up a paging layout for incremental loading.
     FeatureDisplayLayout layout;
-    layout.tileSizeFactor() = 45.0;
-    layout.addLevel( FeatureLevel(0.0f, 20000.0f) );
+    layout.tileSizeFactor() = 52.0;
+    layout.addLevel( FeatureLevel(0.0f, 20000.0f, "buildings") );
 
     // create a model layer that will render the buildings according to our style sheet.
     FeatureGeomModelOptions fgm_opt;
diff --git a/src/applications/osgearth_contour/osgearth_contour.cpp b/src/applications/osgearth_contour/osgearth_contour.cpp
deleted file mode 100644
index 30007f1..0000000
--- a/src/applications/osgearth_contour/osgearth_contour.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-/**
- * This sample shows how to use osgEarth's built-in elevation data attributes
- * to apply contour-coloring to the terrain.
- */
-#include <osg/Notify>
-#include <osgViewer/Viewer>
-#include <osgEarth/VirtualProgram>
-#include <osgEarth/Registry>
-#include <osgEarth/TerrainEngineNode>
-#include <osgEarthUtil/EarthManipulator>
-#include <osgEarthUtil/ExampleResources>
-#include <osg/TransferFunction>
-#include <osg/Texture1D>
-
-using namespace osgEarth;
-using namespace osgEarth::Util;
-
-//-------------------------------------------------------------------------
-
-// In the vertex shader, we use a vertex attribute that's genreated by the
-// terrain engine. The attribute contains a vec4 which holds the unit
-// extrusion vector in indexes[0,1,2] and the raw height in index[3].
-// We just read the height, remap it to [0..1] and send it to the 
-// fragment shader.
-
-const char* vertexShader =
-    "attribute vec4  osgearth_elevData; \n"
-    "uniform   float contour_xferMin; \n"
-    "uniform   float contour_xferRange; \n"
-    "uniform   float contour_xferMax; \n"
-    "varying   float contour_lookup; \n"
-
-    "void setupContour(inout vec4 VertexModel) \n"
-    "{ \n"
-    "    float height = osgearth_elevData[3]; \n"
-    "    float height_normalized = (height-contour_xferMin)/contour_xferRange; \n"
-    "    contour_lookup = clamp( height_normalized, 0.0, 1.0 ); \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.
-
-const char* fragmentShader =
-    "uniform   sampler1D contour_colorMap; \n"
-    "varying   float     contour_lookup; \n"
-
-    "void colorContour( inout vec4 color ) \n"
-    "{ \n"
-    "    color = texture1D( contour_colorMap, contour_lookup ); \n"
-    "} \n";
-
-
-
-// Build the stateset necessary for drawing contours.
-osg::StateSet* createStateSet( osg::TransferFunction1D* xfer, int unit )
-{
-    osg::StateSet* stateSet = new osg::StateSet();
-
-    // Create a 1D texture from the transfer function's image.
-    osg::Texture* tex = new osg::Texture1D( xfer->getImage() );
-    tex->setResizeNonPowerOfTwoHint( false );
-    tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-    tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-    tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
-    stateSet->setTextureAttributeAndModes( unit, tex, osg::StateAttribute::ON );
-
-    // Tell the shader program where to find it.
-    stateSet->getOrCreateUniform( "contour_colorMap", osg::Uniform::SAMPLER_1D )->set( unit );
-
-    // Install the shaders. We also bind osgEarth's elevation data attribute, which the 
-    // terrain engine automatically generates at the specified location.
-    VirtualProgram* vp = new VirtualProgram();
-    vp->setFunction( "setupContour", vertexShader,   ShaderComp::LOCATION_VERTEX_MODEL);
-    vp->setFunction( "colorContour", fragmentShader, ShaderComp::LOCATION_FRAGMENT_COLORING );
-    vp->addBindAttribLocation( "osgearth_elevData", osg::Drawable::ATTRIBUTE_6 );
-    stateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
-
-    // Install some uniforms that tell the shader the height range of the color map.
-    stateSet->getOrCreateUniform( "contour_xferMin",   osg::Uniform::FLOAT )->set( xfer->getMinimum() );
-    stateSet->getOrCreateUniform( "contour_xferRange", osg::Uniform::FLOAT )->set( xfer->getMaximum() - xfer->getMinimum() );
-
-    return stateSet;
-};
-
-
-int main(int argc, char** argv)
-{
-    osg::ArgumentParser arguments(&argc, argv);
-
-    // create a viewer:
-    osgViewer::Viewer viewer(arguments);
-
-    // Tell osgEarth to use the "quadtree" terrain driver by default.
-    // Elevation data attribution is only available in this driver!
-    osgEarth::Registry::instance()->setDefaultTerrainEngineDriverName( "quadtree" );
-
-    // install our default manipulator (do this before calling load)
-    viewer.setCameraManipulator( new EarthManipulator() );
-
-    // load an earth file, and support all or our example command-line options
-    // and earth file <external> tags    
-    osg::Node* node = MapNodeHelper().load( arguments, &viewer );
-    if ( node )
-    {
-        MapNode* mapNode = MapNode::findMapNode(node);
-        if ( !mapNode )
-            return -1;
-
-        if ( mapNode->getMap()->getNumElevationLayers() == 0 )
-            OE_WARN << "No elevation layers! The contour will be very boring." << std::endl;
-
-        // Set up a transfer function for the elevation contours.
-        osg::ref_ptr<osg::TransferFunction1D> xfer = new osg::TransferFunction1D();
-        xfer->setColor( -3000.0f, osg::Vec4f(0,0,0.5,1), false );
-        xfer->setColor(   -10.0f, osg::Vec4f(0,0,0,1),   false );
-        xfer->setColor(    10.0f, osg::Vec4f(0,1,0,1),   false );
-        xfer->setColor(  1500.0f, osg::Vec4f(1,0,0,1),   false );
-        xfer->setColor(  3000.0f, osg::Vec4f(1,1,1,1),   false );
-        xfer->updateImage();
-
-        // request an available texture unit:
-        int unit;
-        mapNode->getTerrainEngine()->getTextureCompositor()->reserveTextureImageUnit(unit);
-
-        // install the contour shaders:
-        osg::Group* root = new osg::Group();
-        root->setStateSet( createStateSet(xfer.get(), unit) );
-        root->addChild( node );
-        
-        viewer.setSceneData( root );
-        viewer.run();
-    }
-    else
-    {
-        OE_NOTICE 
-            << "\nUsage: " << argv[0] << " file.earth" << std::endl
-            << MapNodeHelper().usage() << std::endl;
-    }
-
-    return 0;
-}
diff --git a/src/applications/osgearth_controls/osgearth_controls.cpp b/src/applications/osgearth_controls/osgearth_controls.cpp
index a7c4d54..6804d2c 100644
--- a/src/applications/osgearth_controls/osgearth_controls.cpp
+++ b/src/applications/osgearth_controls/osgearth_controls.cpp
@@ -26,6 +26,7 @@
 #include <osgEarth/Registry>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
+#include <osgEarthUtil/ExampleResources>
 #include <osgEarthSymbology/Color>
 
 using namespace osgEarth::Symbology;
@@ -42,13 +43,12 @@ int main(int argc, char** argv)
     osgViewer::Viewer viewer(arguments);
 
     osg::Group* root = new osg::Group();
-    osg::Node* node = osgDB::readNodeFiles( arguments );
+    osg::Node* node = osgEarth::Util::MapNodeHelper().load(arguments, &viewer);
     if ( node )
         root->addChild( node );
 
     // create a surface to house the controls
     ControlCanvas* cs = ControlCanvas::get( &viewer );
-    root->addChild( cs );
 
     viewer.setSceneData( root );
     viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator );
@@ -90,14 +90,22 @@ struct RotateImage : public ControlEventHandler
     }
 };
 
+struct SetImageOpacity : public ControlEventHandler
+{
+    void onValueChanged( Control* control, float value )
+    {
+        s_imageControl->setOpacity( value );
+    }
+};
+
 void
 createControls( ControlCanvas* cs )
 {
     // a container centered on the screen, containing an image and a text label.
     {
         VBox* center = new VBox();
-        center->setFrame( new RoundedFrame() );
-        center->getFrame()->setBackColor( 1,1,1,0.5 );
+        center->setBorderColor( 1, 1, 1, 1 );
+        center->setBackColor( .6,.5,.4,0.5 );
         center->setPadding( 10 );
         center->setHorizAlign( Control::ALIGN_CENTER );
         center->setVertAlign( Control::ALIGN_CENTER );
@@ -109,7 +117,6 @@ createControls( ControlCanvas* cs )
             s_imageControl = new ImageControl( image.get() );
             s_imageControl->setHorizAlign( Control::ALIGN_CENTER );
             s_imageControl->setFixSizeForRotation( true );
-            //imageCon->addEventHandler( new ImageRotationHandler );
             center->addControl( s_imageControl );
             center->setHorizAlign( Control::ALIGN_CENTER );
         }
@@ -138,6 +145,22 @@ createControls( ControlCanvas* cs )
         }
         center->addControl( rotateBox );
 
+        // Opacity slider:
+        HBox* opacityBox = new HBox();
+        opacityBox->setChildVertAlign( Control::ALIGN_CENTER );
+        opacityBox->setHorizFill( true );
+        opacityBox->setBackColor( Color::Green );
+        {
+            opacityBox->addControl( new LabelControl("Opacity: ") );
+
+            HSliderControl* opacitySlider = new HSliderControl( 0.0, 1.0, 1.0 );
+            opacitySlider->addEventHandler( new SetImageOpacity() );
+            opacitySlider->setHeight( 8.0f );
+            opacitySlider->setHorizFill( true, 200 );
+            opacityBox->addControl( opacitySlider );
+        }
+        center->addControl( opacityBox );
+
         cs->addControl( center );
     }
 
@@ -199,8 +222,7 @@ createControls( ControlCanvas* cs )
     // a centered hbox container along the bottom on the screen.
     {
         HBox* bottom = new HBox();
-        bottom->setFrame( new RoundedFrame() );
-        bottom->getFrame()->setBackColor(0,0,0,0.5);
+        bottom->setBackColor(0,0,0,0.5);        
         bottom->setMargin( 10 );
         bottom->setChildSpacing( 145 );
         bottom->setVertAlign( Control::ALIGN_BOTTOM );
diff --git a/src/applications/osgearth_demo/CMakeLists.txt b/src/applications/osgearth_demo/CMakeLists.txt
new file mode 100644
index 0000000..a5a0cea
--- /dev/null
+++ b/src/applications/osgearth_demo/CMakeLists.txt
@@ -0,0 +1,28 @@
+INCLUDE( ${QT_USE_FILE} )
+
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${QT_INCLUDES})
+
+SET(MOC_HDRS
+)
+
+QT4_WRAP_CPP( MOC_SRCS ${MOC_HDRS} OPTIONS "-f" )
+
+SET(TARGET_H
+    ${LIB_QT_RCS}
+)
+
+SET(TARGET_SRC
+    ${MOC_SRCS}
+    ${LIB_RC_SRCS}
+    osgearth_demo.cpp
+)
+
+SET(TARGET_ADDED_LIBRARIES
+    osgEarthQt
+    ${QT_QTCORE_LIBRARY}
+    ${QT_QTGUI_LIBRARY}
+    ${QT_QTOPENGL_LIBRARY}
+)
+
+#### end var setup  ###
+SETUP_APPLICATION(osgearth_demo)
diff --git a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp b/src/applications/osgearth_demo/osgearth_demo.cpp
similarity index 54%
copy from src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
copy to src/applications/osgearth_demo/osgearth_demo.cpp
index cebb296..f7a44b9 100644
--- a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
+++ b/src/applications/osgearth_demo/osgearth_demo.cpp
@@ -18,10 +18,13 @@
 */
 
 #include <osg/Notify>
-#include <osgViewer/CompositeViewer>
+#include <osg/DisplaySettings>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/Controls>
 #include <osgEarthQt/ViewerWidget>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <QtGui/QApplication>
 #include <QtGui/QMainWindow>
 #include <QtGui/QStatusBar>
@@ -32,6 +35,7 @@
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
+using namespace osgEarth::Util::Controls;
 using namespace osgEarth::QtGui;
 
 //------------------------------------------------------------------
@@ -40,8 +44,7 @@ int
 usage( const std::string& msg )
 {
     OE_NOTICE << msg << std::endl << std::endl;
-    OE_NOTICE << "USAGE: osgearth_qt_simple file.earth" << std::endl;
-        
+    OE_NOTICE << "USAGE: osgearth_demo file.earth [options]" << std::endl;
     return -1;
 }
 
@@ -50,19 +53,43 @@ usage( const std::string& msg )
 int
 main(int argc, char** argv)
 {
-    osg::ArgumentParser arguments(&argc,argv);
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);
-
+    // allocate pager threads based on CPU configuration.
+    int cores = Registry::capabilities().getNumProcessors();
+    osg::DisplaySettings::instance()->setNumOfDatabaseThreadsHint( osg::clampAbove(cores, 2) );
+    osg::DisplaySettings::instance()->setNumOfHttpDatabaseThreadsHint( osg::clampAbove(cores/2, 1) );
 
+    // parse the command line.
+    osg::ArgumentParser arguments(&argc,argv);
     osgViewer::Viewer viewer(arguments);
+
+    // set up the motion model.
     viewer.setCameraManipulator( new EarthManipulator() );
 
-    // load an earth file
+    // simple UI.
+    VBox* vbox = new VBox();
+    vbox->setMargin( 3 );
+    vbox->setChildSpacing( 5 );
+    vbox->setAbsorbEvents( true );
+    vbox->setHorizAlign( Control::ALIGN_RIGHT );
+    vbox->setVertAlign ( Control::ALIGN_BOTTOM );
+
+    // Control instructions:
+    Grid* grid = vbox->addControl( new Grid() );
+    grid->setControl( 0, 0, new LabelControl("Pan:"));
+    grid->setControl( 1, 0, new LabelControl("Left mouse"));
+    grid->setControl( 0, 1, new LabelControl("Zoom:"));
+    grid->setControl( 1, 1, new LabelControl("Right mouse / scroll"));
+    grid->setControl( 0, 2, new LabelControl("Rotate:") );
+    grid->setControl( 1, 2, new LabelControl("Middle mouse"));
+    grid->setControl( 0, 3, new LabelControl("Go to:"));
+    grid->setControl( 1, 3, new LabelControl("Double-click"));
+
+    ControlCanvas::get(&viewer)->addControl(vbox);
+
+    // Load an earth file
     osg::Node* node = MapNodeHelper().load(arguments, &viewer);
     if ( !node )
         return usage( "Failed to load earth file." );
-
     viewer.setSceneData( node );
 
 
@@ -77,10 +104,11 @@ main(int argc, char** argv)
     QWidget* viewerWidget = new ViewerWidget( &viewer );
     
     QMainWindow win;
+    win.setWindowTitle( "osgEarth -- The #1 Open Source Terrain SDK for Mission-Critical Applications" );
     win.setCentralWidget( viewerWidget );
     win.setGeometry(100, 100, 1024, 800);
 
-    win.statusBar()->showMessage(QString("Quite possibly the world's simplest osgEarthQt app."));
+    win.statusBar()->showMessage(QString("osgEarth.   Terrain on Demand.   Copyright 2013 Pelican Mapping.   Please visit http://osgearth.org"));
 
     win.show();
     app.exec();
diff --git a/src/applications/osgearth_contour/CMakeLists.txt b/src/applications/osgearth_detailtex/CMakeLists.txt
similarity index 69%
copy from src/applications/osgearth_contour/CMakeLists.txt
copy to src/applications/osgearth_detailtex/CMakeLists.txt
index 6d08b89..efa4c05 100644
--- a/src/applications/osgearth_contour/CMakeLists.txt
+++ b/src/applications/osgearth_detailtex/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_contour.cpp )
+SET(TARGET_SRC osgearth_detailtex.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_contour)
+SETUP_APPLICATION(osgearth_detailtex)
diff --git a/src/applications/osgearth_detailtex/osgearth_detailtex.cpp b/src/applications/osgearth_detailtex/osgearth_detailtex.cpp
new file mode 100644
index 0000000..1da58f3
--- /dev/null
+++ b/src/applications/osgearth_detailtex/osgearth_detailtex.cpp
@@ -0,0 +1,134 @@
+/* -*-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/>
+*/
+
+/**
+ * This sample shows how to apply a detail texture to osgEarth's terrain.
+ */
+#include <osg/Notify>
+#include <osgViewer/Viewer>
+#include <osgEarth/URI>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/DetailTexture>
+#include <osg/Image>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+namespace ui = osgEarth::Util::Controls;
+
+int 
+usage(const char* msg)
+{
+    OE_NOTICE << msg << std::endl;
+    OE_NOTICE <<
+        "Usage:\n"
+        "    --image <filename>                  : detail texture image file\n"
+        "    --lod <lod>                         : LOD at which to start detail texturing\n"
+        "    --uniform oe_dtex_intensity 0 1     : control the intensity of the detail texture\n"
+        << std::endl;
+    return 0;
+}
+
+
+struct App
+{
+    DetailTexture* dt;
+};
+
+
+// Build a slider to adjust the vertical scale
+ui::Control* createUI( App& app )
+{
+    struct SetIntensity : public ui::ControlEventHandler {
+        App& _app;
+        SetIntensity(App& app) : _app(app) {}
+        void onValueChanged(ui::Control*, float value) {
+            _app.dt->setIntensity(value);
+        }
+    };
+
+    ui::VBox* vbox = new VBox();
+
+    vbox->addControl( new LabelControl(Stringify() << "Detail texture starts at LOD: " << app.dt->getStartLOD()) );
+
+    ui::HBox* hbox = vbox->addControl( new ui::HBox() );
+    hbox->setChildVertAlign( ui::Control::ALIGN_CENTER );
+    hbox->addControl( new ui::LabelControl("Intensity:") );
+    ui::HSliderControl* intensity = hbox->addControl( new ui::HSliderControl(0.0, 1.0, 0.25, new SetIntensity(app)) );
+    intensity->setHorizFill( true, 200 );
+    hbox->addControl( new ui::LabelControl(intensity) );
+
+    return vbox;
+}
+
+
+int main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc, argv);
+
+    if (arguments.read("--help"))
+        return usage(argv[0]);
+
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
+
+    // install our default manipulator (do this before calling load)
+    viewer.setCameraManipulator( new EarthManipulator() );
+
+    // Set up the app and read options:
+    App app;
+    app.dt = new DetailTexture();
+
+    std::string filename( "../data/noise3.png" );
+    arguments.read( "--image", filename );
+    osg::Image* image = URI(filename).getImage();
+    if ( !image )
+        return usage( "Failed to load image" );
+    app.dt->setImage( image );
+
+    unsigned startLOD;
+    if ( arguments.read("--lod", startLOD) )
+        app.dt->setStartLOD(startLOD);
+
+    // Create the UI:
+    ui::Control* demoui = createUI(app);
+
+    // load an earth file, and support all or our example command-line options
+    // and earth file <external> tags    
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer, demoui );
+    if ( node )
+    {
+        MapNode* mapNode = MapNode::findMapNode(node);
+        if ( !mapNode )
+            return -1;
+
+        // install our detail texturer.
+        TerrainEngineNode* terrain = mapNode->getTerrainEngine();
+        terrain->addEffect( app.dt );
+
+        viewer.setSceneData( node );
+        viewer.run();
+    }
+    else
+    {
+        return usage("no earth file");
+    }
+
+    return 0;
+}
diff --git a/src/applications/osgearth_elevation/osgearth_elevation.cpp b/src/applications/osgearth_elevation/osgearth_elevation.cpp
index 876ae8b..df40b4d 100644
--- a/src/applications/osgearth_elevation/osgearth_elevation.cpp
+++ b/src/applications/osgearth_elevation/osgearth_elevation.cpp
@@ -90,9 +90,9 @@ struct QueryElevationHandler : public osgGA::GUIEventHandler
 
                 s_posLabel->setText( Stringify()
                     << std::fixed << std::setprecision(2) 
-                    << s_f.format(mapPoint.y())
+                    << s_f.format(mapPointGeodetic.y())
                     << ", " 
-                    << s_f.format(mapPoint.x()) );
+                    << s_f.format(mapPointGeodetic.x()) );
 
                 s_mslLabel->setText( Stringify() << out_hamsl );
                 s_haeLabel->setText( Stringify() << mapPointGeodetic.z() );
diff --git a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
index a08fca5..11b91c4 100644
--- a/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
+++ b/src/applications/osgearth_featureeditor/osgearth_featureeditor.cpp
@@ -29,9 +29,6 @@
 #include <osgEarth/Utils>
 
 #include <osgEarthSymbology/Style>
-#include <osgEarthFeatures/FeatureModelGraph>
-#include <osgEarthFeatures/FeatureListSource>
-#include <osgEarthFeatures/GeometryCompiler>
 
 #include <osgEarthDrivers/gdal/GDALOptions>
 #include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
@@ -65,10 +62,9 @@ static int s_fid = 0;
 
 static osg::ref_ptr< AddPointHandler > s_addPointHandler;
 static osg::ref_ptr< osg::Node > s_editor;
-static osg::ref_ptr< Feature > s_activeFeature;
+static osg::ref_ptr< FeatureNode > s_featureNode;
 static osgViewer::Viewer* s_viewer;
 static osg::ref_ptr< osg::Group > s_root;
-static osg::ref_ptr< FeatureListSource > s_source;
 static osg::ref_ptr< MapNode > s_mapNode;
 
 Grid* createToolBar()
@@ -86,8 +82,7 @@ Grid* createToolBar()
 
 struct AddVertsModeHandler : public ControlEventHandler
 {
-    AddVertsModeHandler( FeatureModelGraph* featureGraph)
-        : _featureGraph( featureGraph )
+    AddVertsModeHandler()        
     {
     }
 
@@ -99,30 +94,25 @@ struct AddVertsModeHandler : public ControlEventHandler
             s_root->removeChild( s_editor.get() );
             s_editor = NULL;
 
-            Style* style = _featureGraph->getStyles()->getDefaultStyle();
-            if ( style )
-            {            
-                style->get<LineSymbol>()->stroke()->stipple().unset();
-                _featureGraph->dirty();
-            }
+            // Unset the stipple on the line
+            Style style = s_featureNode->getStyle();
+            style.get<LineSymbol>()->stroke()->stipple().unset();
+            s_featureNode->setStyle( style );            
         }
 
         //Add the new add point handler
-        if (!s_addPointHandler.valid() && s_activeFeature.valid())
-        {
-            s_addPointHandler = new AddPointHandler(s_activeFeature.get(), s_source.get(), s_mapNode->getMap()->getProfile()->getSRS());
+        if (!s_addPointHandler.valid())
+        {            
+            s_addPointHandler = new AddPointHandler( s_featureNode.get() );
             s_addPointHandler->setIntersectionMask( 0x1 );
             s_viewer->addEventHandler( s_addPointHandler.get() );
         }        
     }
-
-    osg::ref_ptr< FeatureModelGraph > _featureGraph;
 };
 
 struct EditModeHandler : public ControlEventHandler
 {
-    EditModeHandler( FeatureModelGraph* featureGraph)
-        : _featureGraph( featureGraph )
+    EditModeHandler()        
     { 
     }
 
@@ -135,39 +125,33 @@ struct EditModeHandler : public ControlEventHandler
             s_addPointHandler = NULL;
         }        
 
-        if (!s_editor.valid() && s_activeFeature.valid())
-        {
-            Style* style = _featureGraph->getStyles()->getDefaultStyle();
-            if ( style )
-            {
-                style->get<LineSymbol>()->stroke()->stipple() = 0x00FF;
-                _featureGraph->dirty();
-            }
-            s_editor = new FeatureEditor(s_activeFeature.get(), s_source.get(), s_mapNode.get());
-            s_root->addChild( s_editor.get() );
+        if (!s_editor.valid())
+        {            
+            Style style = s_featureNode->getStyle();
+            style.getOrCreate<LineSymbol>()->stroke()->stipple() = 0x00FF;            
+            s_featureNode->setStyle( style );            
+            s_editor = new FeatureEditor( s_featureNode );
+            s_root->addChild( s_editor.get() );            
         }
-    }
-
-    osg::ref_ptr< FeatureModelGraph > _featureGraph;
+    }    
 };
 
 struct ChangeStyleHandler : public ControlEventHandler
 {
-    ChangeStyleHandler( FeatureModelGraph* features, StyleSheet* styleSheet) 
-        : _features( features), _styleSheet(styleSheet)
+    ChangeStyleHandler(const Style &style) 
+        : _style( style )
     {
         //nop
     }
 
     void onClick( Control* control, int mouseButtonMask ) {
-        _features->setStyles( _styleSheet.get() );
+        s_featureNode->setStyle( _style );        
     }
 
-    osg::ref_ptr< FeatureModelGraph > _features;
-    osg::ref_ptr< StyleSheet >        _styleSheet;
+    Style _style;  
 };
 
-StyleSheet* buildStyleSheet( const osg::Vec4 &color, float width )
+Style buildStyle( const osg::Vec4 &color, float width )
 {
     // Define a style for the feature data. Since we are going to render the
     // vectors as lines, configure the line symbolizer:
@@ -175,14 +159,18 @@ StyleSheet* buildStyleSheet( const osg::Vec4 &color, float width )
 
     LineSymbol* ls = style.getOrCreateSymbol<LineSymbol>();
     ls->stroke()->color() = color;
-    ls->stroke()->width() = width;
+    ls->stroke()->width() = width;    
+    ls->tessellation() = 10;
+
+    AltitudeSymbol* as = style.getOrCreate<AltitudeSymbol>();
+    as->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+    as->technique() = AltitudeSymbol::TECHNIQUE_SCENE;
 
-    //AltitudeSymbol* as = style.getOrCreate<AltitudeSymbol>();
-    //as->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+    RenderSymbol* rs = style.getOrCreateSymbol<RenderSymbol>();
+    rs->depthOffset()->enabled() = true;
+    rs->depthOffset()->minBias() = 1000;
 
-    StyleSheet* styleSheet = new StyleSheet();
-    styleSheet->addStyle( style );
-    return styleSheet;
+    return style;    
 }
 
 //
@@ -213,39 +201,21 @@ int main(int argc, char** argv)
 
         s_mapNode = new MapNode( map, mapNodeOptions );
     }
-    s_mapNode->setNodeMask( 0x01 );
+    s_mapNode->setNodeMask( 0x01 );    
 
         
-    // Define a style for the feature data. Since we are going to render the
-    // vectors as lines, configure the line symbolizer:
-    StyleSheet* styleSheet = buildStyleSheet( Color::Yellow, 2.0f );
+    // Define a style for the feature data.
+    Style style = buildStyle( Color::Yellow, 2.0f );    
 
-    // create a feature list source with the map extents as the default extent.
-    s_source = new FeatureListSource( s_mapNode->getMap()->getProfile()->getExtent() );
-
-    LineString* line = new LineString();
-    line->push_back( osg::Vec3d(-60, 20, 0) );
-    line->push_back( osg::Vec3d(-120, 20, 0) );
-    line->push_back( osg::Vec3d(-120, 60, 0) );
-    line->push_back( osg::Vec3d(-60, 60, 0) );
+    LineString* line = new LineString();    
     Feature* feature = new Feature(line, s_mapNode->getMapSRS(), Style(), s_fid++);
-    s_source->insertFeature( feature );
-    s_activeFeature = feature;
+    s_featureNode = new FeatureNode( s_mapNode, feature );    
+    s_featureNode->setStyle( style );
   
     s_root = new osg::Group;
     s_root->addChild( s_mapNode.get() );
 
-    Session* session = new Session(s_mapNode->getMap(), styleSheet, s_source.get());
-
-    FeatureModelGraph* graph = new FeatureModelGraph( 
-        session,
-        FeatureModelSourceOptions(), 
-        new GeomFeatureNodeFactory() );
-
-    graph->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
-    graph->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-
-    s_root->addChild( graph );
+    s_root->addChild( s_featureNode );
 
     //Setup the controls
     ControlCanvas* canvas = ControlCanvas::get( &viewer );
@@ -254,16 +224,14 @@ int main(int argc, char** argv)
     canvas->addControl( toolbar );
     canvas->setNodeMask( 0x1 << 1 );
 
-
-
     int col = 0;
     LabelControl* addVerts = new LabelControl("Add Verts");
     toolbar->setControl(col++, 0, addVerts );    
-    addVerts->addEventHandler( new AddVertsModeHandler( graph ));
+    addVerts->addEventHandler( new AddVertsModeHandler());
     
     LabelControl* edit = new LabelControl("Edit");
     toolbar->setControl(col++, 0, edit );    
-    edit->addEventHandler(new EditModeHandler( graph ));
+    edit->addEventHandler(new EditModeHandler());
 
     unsigned int row = 0;
     Grid *styleBar = createToolBar( );
@@ -286,7 +254,7 @@ int main(int argc, char** argv)
         {
             Control* l = new Control();            
             l->setBackColor( color );
-            l->addEventHandler(new ChangeStyleHandler(graph, buildStyleSheet( color, widths[j] ) ));
+            l->addEventHandler(new ChangeStyleHandler(buildStyle( color, widths[j] ) ));
             l->setSize(w,5 * widths[j]);
             styleBar->setControl(j, r, l);
         }
diff --git a/src/applications/osgearth_features/osgearth_features.cpp b/src/applications/osgearth_features/osgearth_features.cpp
index 44ff332..7ce2b65 100644
--- a/src/applications/osgearth_features/osgearth_features.cpp
+++ b/src/applications/osgearth_features/osgearth_features.cpp
@@ -68,15 +68,15 @@ int main(int argc, char** argv)
     if ( arguments.read("--help") )
         return usage( argv[0] );
 
-    if ( arguments.read("--stencil") )
-        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
-
     bool useRaster  = arguments.read("--rasterize");
     bool useOverlay = arguments.read("--overlay");
     bool useStencil = arguments.read("--stencil");
     bool useMem     = arguments.read("--mem");
     bool useLabels  = arguments.read("--labels");
 
+    if ( useStencil )
+        osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
+
     osgViewer::Viewer viewer(arguments);
 
     // Start by creating the map:
@@ -155,7 +155,6 @@ int main(int argc, char** argv)
         geomOptions.enableLighting() = false;
 
         ModelLayerOptions layerOptions( "my features", geomOptions );
-        layerOptions.overlay() = useOverlay;
         map->addModelLayer( new ModelLayer(layerOptions) );
     }
 
diff --git a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
index f2787bf..e151c2d 100644
--- a/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
+++ b/src/applications/osgearth_imageoverlay/osgearth_imageoverlay.cpp
@@ -27,16 +27,13 @@
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/Controls>
 #include <osgEarth/Utils>
+#include <osgEarth/VirtualProgram>
 
 #include <osg/ImageStream>
 #include <osgDB/FileNameUtils>
-#include <osg/Version>
-#include <osgEarth/Version>
 
 #include <osgEarthAnnotation/ImageOverlay>
-#if OSG_MIN_VERSION_REQUIRED(2,9,6)
 #include <osgEarthAnnotation/ImageOverlayEditor>
-#endif
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
@@ -106,7 +103,6 @@ struct EditHandler : public ControlEventHandler
       _editor(editor){ }
 
     void onClick( Control* control, int mouseButtonMask ) {        
-#if OSG_MIN_VERSION_REQUIRED(2,9,6)
         if (_editor->getNodeMask() != ~0)
         {
             static_cast<LabelControl*>(control)->setText( "Finish" );
@@ -117,11 +113,8 @@ struct EditHandler : public ControlEventHandler
             static_cast<LabelControl*>(control)->setText( "Edit" );
             _editor->setNodeMask(0);
         }
-
-#else
-        OE_NOTICE << "Use OSG 2.9.6 or greater to use editing" << std::endl;
-#endif
     }
+
     ImageOverlay* _overlay;
     osgViewer::Viewer* _viewer;
     osg::Node* _editor;
@@ -240,17 +233,12 @@ main(int argc, char** argv)
             ImageOverlay* overlay = new ImageOverlay(mapNode);
             overlay->setImage( image );
             overlay->setBounds(imageBounds[i]);
-
+            
             root->addChild( overlay );
 
 
             //Create a new ImageOverlayEditor and set it's node mask to 0 to hide it initially
-#if OSG_MIN_VERSION_REQUIRED(2,9,6)
-            osg::Node* editor = new ImageOverlayEditor( overlay);
-#else
-            //Just make an empty group for pre-2.9.6
-            osg::Node* editor = new osg::Group;
-#endif
+            osg::Node* editor = new ImageOverlayEditor( overlay, moveVert);
             editor->setNodeMask( 0 );
             root->addChild( editor );      
             
diff --git a/src/applications/osgearth_manip/osgearth_manip.cpp b/src/applications/osgearth_manip/osgearth_manip.cpp
index 3b52271..9d5c5be 100644
--- a/src/applications/osgearth_manip/osgearth_manip.cpp
+++ b/src/applications/osgearth_manip/osgearth_manip.cpp
@@ -36,6 +36,7 @@
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthAnnotation/LocalGeometryNode>
+#include <osgEarthAnnotation/LabelNode>
 #include <osgEarthSymbology/Style>
 
 using namespace osgEarth::Util;
@@ -52,7 +53,7 @@ namespace
      */
     Control* createHelp( osgViewer::View* view )
     {
-        static char* text[] =
+        const char* text[] =
         {
             "left mouse :",        "pan",
             "middle mouse :",      "rotate",
@@ -64,7 +65,9 @@ namespace
             "shift-right-mouse :", "locked panning",
             "u :",                 "toggle azimuth lock",
             "c :",                 "toggle perspective/ortho",
-            "t :",                 "toggle tethering"
+            "t :",                 "toggle tethering",
+            "a :",                 "toggle viewpoint arcing",
+            "z :",                 "toggle throwing"
         };
 
         Grid* g = new Grid();
@@ -151,6 +154,38 @@ namespace
 
 
     /**
+     * Handler to toggle "viewpoint transtion arcing", which causes the camera to "arc"
+     * as it travels from one viewpoint to another.
+     */
+    struct ToggleArcViewpointTransitionsHandler : public osgGA::GUIEventHandler
+    {
+        ToggleArcViewpointTransitionsHandler(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)
+            {
+                bool arc = _manip->getSettings()->getArcViewpointTransitions();
+                _manip->getSettings()->setArcViewpointTransitions(!arc);
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Arc viewpoint transitions"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
+
+
+    /**
      * Toggles the projection matrix between perspective and orthographic.
      */
     struct ToggleProjectionHandler : public osgGA::GUIEventHandler
@@ -186,15 +221,48 @@ namespace
 
 
     /**
+     * Toggles the throwing feature.
+     */
+    struct ToggleThrowingHandler : public osgGA::GUIEventHandler
+    {
+        ToggleThrowingHandler(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)
+            {
+                bool throwing = _manip->getSettings()->getThrowingEnabled();
+                _manip->getSettings()->setThrowingEnabled( !throwing );
+                aa.requestRedraw();
+                return true;
+            }
+            return false;
+        }
+
+        void getUsage(osg::ApplicationUsage& usage) const
+        {
+            using namespace std;
+            usage.addKeyboardMouseBinding(string(1, _key), string("Toggle throwing"));
+        }
+
+        char _key;
+        osg::ref_ptr<EarthManipulator> _manip;
+    };
+
+
+    /**
      * 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 )
-            : _manip(manip), _lat0(55.0), _lon0(45.0), _lat1(-55.0), _lon1(-45.0)
+        Simulator( osg::Group* root, EarthManipulator* manip, MapNode* mapnode )
+            : _manip(manip), _mapnode(mapnode), _lat0(55.0), _lon0(45.0), _lat1(-55.0), _lon1(-45.0)
         {
-            osg::Node* geode = AnnotationUtils::createSphere( 100.0, osg::Vec4(1,1,1,1) );
+            osg::Node* geode = AnnotationUtils::createSphere( 25.0, osg::Vec4(1,.7,.4,1) );
             
             _xform = new osg::MatrixTransform();
             _xform->addChild( geode );
@@ -203,6 +271,13 @@ namespace
             _cam->setRenderOrder( osg::Camera::NESTED_RENDER, 1 );
             _cam->addChild( _xform );
 
+            Style style;
+            style.getOrCreate<TextSymbol>()->size() = 32.0f;
+            style.getOrCreate<TextSymbol>()->declutter() = false;
+            _label = new LabelNode(_mapnode, GeoPoint(), "Hello World", style);
+            _label->setDynamic( true );
+            _cam->addChild( _label );
+
             root->addChild( _cam.get() );
         }
 
@@ -217,10 +292,11 @@ namespace
                 osg::Vec3d world;
                 p.toWorld( world );
                 _xform->setMatrix( osg::Matrix::translate(world) );
+                _label->setPosition( p );
             }
             else if ( ea.getEventType() == ea.KEYDOWN && ea.getKey() == 't' )
             {
-                _manip->setTetherNode( _manip->getTetherNode() ? 0L : _cam.get() );
+                _manip->setTetherNode( _manip->getTetherNode() ? 0L : _xform.get() );
                 if ( _manip->getTetherNode() )
                 {
                     _manip->getSettings()->setArcViewpointTransitions( false );
@@ -232,10 +308,12 @@ namespace
             return false;
         }
 
+        MapNode*                           _mapnode;
         EarthManipulator*                  _manip;
         osg::ref_ptr<osg::Camera>          _cam;
         osg::ref_ptr<osg::MatrixTransform> _xform;
         double                             _lat0, _lon0, _lat1, _lon1;
+        LabelNode*                         _label;
     };
 }
 
@@ -278,7 +356,7 @@ int main(int argc, char** argv)
     }
 
     // Simulator for tethering:
-    viewer.addEventHandler( new Simulator(root, manip) );
+    viewer.addEventHandler( new Simulator(root, manip, mapNode) );
     manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_PAN );
     manip->getSettings()->getBreakTetherActions().push_back( EarthManipulator::ACTION_GOTO );
 
@@ -295,6 +373,8 @@ int main(int argc, char** argv)
     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));
 
     return viewer.run();
 }
diff --git a/src/applications/osgearth_measure/osgearth_measure.cpp b/src/applications/osgearth_measure/osgearth_measure.cpp
index a649c4d..2d07978 100644
--- a/src/applications/osgearth_measure/osgearth_measure.cpp
+++ b/src/applications/osgearth_measure/osgearth_measure.cpp
@@ -31,6 +31,7 @@
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/SkyNode>
 #include <osgEarthUtil/MouseCoordsTool>
+#include <osgEarthUtil/ExampleResources>
 #include <osgEarthSymbology/Color>
 
 #include <osgEarthUtil/MeasureTool>
@@ -99,8 +100,10 @@ main(int argc, char** argv)
     osg::ArgumentParser arguments(&argc,argv);
     osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
 
+    osgViewer::Viewer viewer(arguments);
+
     // load the .earth file from the command line.
-    osg::Node* earthNode = osgDB::readNodeFiles( arguments );
+    osg::Node* earthNode = MapNodeHelper().load( arguments, &viewer );
     if (!earthNode)
     {
         OE_NOTICE << "Unable to load earth model." << std::endl;
@@ -115,9 +118,6 @@ main(int argc, char** argv)
     }
 
     earthNode->setNodeMask( 0x1 );
-
-
-    osgViewer::Viewer viewer(arguments);
     
     osgEarth::Util::EarthManipulator* earthManip = new EarthManipulator();
     viewer.setCameraManipulator( earthManip );
@@ -142,8 +142,7 @@ main(int argc, char** argv)
     grid->setChildSpacing( 10 );
     grid->setChildVertAlign( Control::ALIGN_CENTER );
     grid->setAbsorbEvents( true );
-    grid->setVertAlign( Control::ALIGN_BOTTOM );
-    grid->setFrame(new RoundedFrame());
+    grid->setVertAlign( Control::ALIGN_BOTTOM );    
 
     canvas->addControl( grid );
 
diff --git a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
index 98d64c0..e86eaba 100644
--- a/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
+++ b/src/applications/osgearth_occlusionculling/osgearth_occlusionculling.cpp
@@ -18,6 +18,7 @@
 */
 
 #include <osgEarth/MapNode>
+#include <osgEarth/Decluttering>
 #include <osgEarth/ECEF>
 
 #include <osgEarthUtil/EarthManipulator>
@@ -33,7 +34,6 @@
 #include <osgEarthAnnotation/RectangleNode>
 #include <osgEarthAnnotation/EllipseNode>
 #include <osgEarthAnnotation/PlaceNode>
-#include <osgEarthAnnotation/Decluttering>
 
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
diff --git a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
index e815441..09672a9 100644
--- a/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
+++ b/src/applications/osgearth_overlayviewer/osgearth_overlayviewer.cpp
@@ -37,7 +37,7 @@ using namespace osgEarth::Symbology;
 
 static CheckBoxControl* s_cameraCheck;
 //static CheckBoxControl* s_overlayCheck;
-//static CheckBoxControl* s_intersectionCheck;
+static CheckBoxControl* s_intersectionCheck;
 static CheckBoxControl* s_rttCheck;
 
 namespace
@@ -108,7 +108,7 @@ namespace
 
                     toggle(_parent, "camera", s_cameraCheck->getValue());
                     //toggle(_parent, "overlay", s_overlayCheck->getValue());
-                    //toggle(_parent, "intersection", s_intersectionCheck->getValue());
+                    toggle(_parent, "intersection", s_intersectionCheck->getValue());
                     toggle(_parent, "rtt", s_rttCheck->getValue());
 
                     aa.requestRedraw();
@@ -141,11 +141,11 @@ setupOverlayView( osgViewer::View* view, osg::Group* parent, MapNode* mapNode )
         //    overlayBox->addControl(new LabelControl("Overlay", Color("#00ffff")));
         //}
 
-        //HBox* isectBox = v->addControl(new HBox());
-        //{
-        //    isectBox->addControl(s_intersectionCheck = new CheckBoxControl(true, new Toggle(parent,"intersection")));
-        //    isectBox->addControl(new LabelControl("Intersection",Color("#ff7f00")));
-        //}
+        HBox* isectBox = v->addControl(new HBox());
+        {
+            isectBox->addControl(s_intersectionCheck = new CheckBoxControl(true, new Toggle(parent,"intersection")));
+            isectBox->addControl(new LabelControl("Intersection",Color("#ff7f00")));
+        }
 
         HBox* rttBox = v->addControl(new HBox());
         {
@@ -185,10 +185,10 @@ main(int argc, char** argv)
         mainView->setSceneData( node );
 
         osg::Group* group = new osg::Group();
-        group->addChild( MapNode::findMapNode(node) );
+        group->addChild( MapNode::get(node) );
         overlayView->setSceneData( group );
 
-        setupOverlayView( overlayView, group, MapNode::findMapNode(node) );
+        setupOverlayView( overlayView, group, MapNode::get(node) );
 
         return viewer.run();
     }
diff --git a/src/applications/osgearth_package/osgearth_package.cpp b/src/applications/osgearth_package/osgearth_package.cpp
index 46e074e..3c17b1e 100644
--- a/src/applications/osgearth_package/osgearth_package.cpp
+++ b/src/applications/osgearth_package/osgearth_package.cpp
@@ -56,7 +56,7 @@ usage( const std::string& msg = "" )
         << "USAGE: osgearth_package <earth_file>" << std::endl
         << std::endl
         << "         --tms                              : make a TMS repo\n"
-        << "            <earth_file>                    : earth file defining layers to export (requied)\n"
+        << "            <earth_file>                    : earth file defining layers to export (required)\n"
         << "            --out <path>                    : root output folder of the TMS repo (required)\n"
         << "            [--bounds xmin ymin xmax ymax]* : bounds to package (in map coordinates; default=entire map)\n"
         << "            [--max-level <num>]             : max LOD level for tiles (all layers; default=inf)\n"
@@ -64,6 +64,7 @@ usage( const std::string& msg = "" )
         << "            [--ext <extension>]             : overrides the image file extension (e.g. jpg)\n"
         << "            [--overwrite]                   : overwrite existing tiles\n"
         << "            [--keep-empties]                : writes out fully transparent image tiles (normally discarded)\n"
+        << "            [--continue-single-color]       : continues to subdivide single color tiles, subdivision typicall stops on single color images\n"
         << "            [--db-options]                : db options string to pass to the image writer in quotes (e.g., \"JPEG_QUALITY 60\")\n"
         << std::endl
         << "         [--quiet]               : suppress progress output" << std::endl;
@@ -156,6 +157,8 @@ makeTMS( osg::ArgumentParser& args )
     // whether to keep 'empty' tiles
     bool keepEmpties = args.read("--keep-empties");    
 
+    bool continueSingleColor = args.read("--continue-single-color");
+
     // load up the map
     osg::ref_ptr<MapNode> mapNode = MapNode::load( args );
     if ( !mapNode.valid() )
@@ -174,6 +177,7 @@ makeTMS( osg::ArgumentParser& args )
     packager.setVerbose( verbose );
     packager.setOverwrite( overwrite );
     packager.setKeepEmptyImageTiles( keepEmpties );
+    packager.setSubdivideSingleColorImageTiles( continueSingleColor );
 
     if ( maxLevel != ~0 )
         packager.setMaxLevel( maxLevel );
@@ -221,7 +225,7 @@ makeTMS( osg::ArgumentParser& args )
             }
 
             std::string layerRoot = osgDB::concatPaths( rootFolder, layerFolder );
-            TMSPackager::Result r = packager.package( layer, layerRoot, extension );
+            TMSPackager::Result r = packager.package( layer, layerRoot, 0L, extension );
             if ( r.ok )
             {
                 // save to the output map if requested:
diff --git a/src/applications/osgearth_package_qt/ExportDialog.ui b/src/applications/osgearth_package_qt/ExportDialog.ui
index bed70fc..4d97da1 100644
--- a/src/applications/osgearth_package_qt/ExportDialog.ui
+++ b/src/applications/osgearth_package_qt/ExportDialog.ui
@@ -115,18 +115,24 @@
           <property name="text">
            <string>Max level (no max level = infinity):</string>
           </property>
+          <property name="checked">
+           <bool>true</bool>
+          </property>
          </widget>
         </item>
         <item>
          <widget class="QSpinBox" name="maxLevelSpinBox">
           <property name="enabled">
-           <bool>false</bool>
+           <bool>true</bool>
           </property>
           <property name="minimum">
            <number>1</number>
           </property>
           <property name="maximum">
-           <number>20</number>
+           <number>99</number>
+          </property>
+          <property name="value">
+           <number>10</number>
           </property>
          </widget>
         </item>
diff --git a/src/applications/osgearth_package_qt/PackageQtMainWindow b/src/applications/osgearth_package_qt/PackageQtMainWindow
index 545f601..91f7df6 100644
--- a/src/applications/osgearth_package_qt/PackageQtMainWindow
+++ b/src/applications/osgearth_package_qt/PackageQtMainWindow
@@ -18,7 +18,6 @@
 */
 
 #include <osgEarthQt/Common>
-#include <osgEarthQt/MultiViewerWidget>
 #include <osgEarthQt/DataManager>
 #include <osgEarthQt/LayerManagerWidget>
 #include <osgEarthQt/MapCatalogWidget>
@@ -103,7 +102,7 @@ class PackageQtMainWindow : public QMainWindow
 public:
 
   PackageQtMainWindow(osgEarth::QtGui::ViewerWidget* viewerWidget, PackageQt::SceneController* controller, PackageQt::TMSExporter* exporter)
-        : _viewerWidget(viewerWidget), _controller(controller), _exporter(exporter), _compositeViewerWidget(0L), _lastDir("")
+        : _viewerWidget(viewerWidget), _controller(controller), _exporter(exporter), _lastDir("")
     {
         _manager = new osgEarth::QtGui::DataManager(_controller->mapNode());
 
@@ -279,16 +278,9 @@ protected:
     {
         if (_viewerWidget)
         {
-            //_viewerWidget->getViewer()->setSceneData(0);
-            //_viewerWidget->getViewer()->frame();
             _viewerWidget->getViewer()->setDone(true);
         }
 
-        if (_compositeViewerWidget)
-        {
-            _compositeViewerWidget/*->getViewer()*/->setDone(true);
-        }
-
         event->accept();
     }
 
@@ -405,14 +397,11 @@ private:
 
     osg::ref_ptr<osgEarth::QtGui::DataManager> _manager;
     osgEarth::QtGui::ViewerWidget* _viewerWidget;
-    osgEarth::QtGui::MultiViewerWidget* _compositeViewerWidget;
     osgEarth::QtGui::ViewVector _views;
 
     QAction *_openEarthFileAction;
     QAction *_addImageLayerAction;
-    //QAction *_addImageFolderAction;
     QAction *_addElevationLayerAction;
-    //QAction *_addElevationFolderAction;
     QAction *_exportAction;
     QAction *_bboxAction;
     QAction *_bboxClearAction;
diff --git a/src/applications/osgearth_package_qt/TMSExporter.cpp b/src/applications/osgearth_package_qt/TMSExporter.cpp
index c766b14..cc155bd 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.cpp
+++ b/src/applications/osgearth_package_qt/TMSExporter.cpp
@@ -49,7 +49,7 @@ namespace
   /** Packaging task for a single layer. */
   struct PackageLayer
   {
-    void init(osgEarth::Map* map, osgEarth::ImageLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, std::vector< osgEarth::Bounds >& bounds)
+    void init(osgEarth::Map* map, osgEarth::ImageLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, osgEarth::ProgressCallback* callback, std::vector< osgEarth::Bounds >& bounds)
     {
       _map = map;
       _imageLayer = layer;
@@ -62,10 +62,11 @@ namespace
       _maxLevel = maxLevel;
       _extension = extension;
       _bounds = bounds;
+      _callback = callback;
       _packageResult.ok = false;
     }
 
-    void init(osgEarth::Map* map, osgEarth::ElevationLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, std::vector< osgEarth::Bounds >& bounds)
+    void init(osgEarth::Map* map, osgEarth::ElevationLayer* layer, osgDB::Options* options, const std::string& rootFolder, const std::string& layerFolder, bool verbose, bool overwrite, bool keepEmpties, unsigned int maxLevel, const std::string& extension, osgEarth::ProgressCallback* callback, std::vector< osgEarth::Bounds >& bounds)
     {
       _map = map;
       _elevationLayer = layer;
@@ -78,6 +79,7 @@ namespace
       _maxLevel = maxLevel;
       _extension = extension;
       _bounds = bounds;
+      _callback = callback;
       _packageResult.ok = false;
     }
 
@@ -109,14 +111,14 @@ namespace
         if (_verbose)
           OE_NOTICE << LC << "Packaging image layer \"" << _layerFolder << "\"" << std::endl;
 
-        _packageResult = packager.package( _imageLayer.get(), layerRoot, _extension );
+        _packageResult = packager.package( _imageLayer.get(), layerRoot, _callback, _extension );
       }
       else if (_elevationLayer.valid())
       {
         if (_verbose)
           OE_NOTICE << LC << "Packaging elevation layer \"" << _layerFolder << "\"" << std::endl;
 
-        _packageResult = packager.package( _elevationLayer.get(), layerRoot );
+        _packageResult = packager.package( _elevationLayer.get(), layerRoot, _callback );
       }
     }
 
@@ -132,6 +134,7 @@ namespace
     unsigned int _maxLevel;
     std::string _extension;
     std::vector< osgEarth::Bounds > _bounds;
+    osg::ref_ptr<osgEarth::ProgressCallback> _callback;
     TMSPackager::Result _packageResult;
   };
 }
@@ -208,8 +211,7 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& path, std::vecto
               layerFolder = Stringify() << "image_layer_" << imageCount;
 
           ParallelTask<PackageLayer>* task = new ParallelTask<PackageLayer>( &semaphore );
-          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, bounds);
-          task->setProgressCallback(new PackageLayerProgressCallback(this));
+          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, new PackageLayerProgressCallback(this, taskCount), bounds);
           tasks.push_back(task);
           taskCount++;
       }
@@ -230,8 +232,7 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& path, std::vecto
               layerFolder = Stringify() << "elevation_layer_" << elevCount;
 
           ParallelTask<PackageLayer>* task = new ParallelTask<PackageLayer>( &semaphore );
-          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, bounds);
-          task->setProgressCallback(new PackageLayerProgressCallback(this));
+          task->init(map, layer, options, rootFolder, layerFolder, true, overwrite, _keepEmpties, _maxLevel, extension, new PackageLayerProgressCallback(this, taskCount), bounds);
           tasks.push_back(task);
           taskCount++;
       }
@@ -240,9 +241,10 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& path, std::vecto
 
   // Run all the tasks in parallel
   _totalTasks = taskCount;
-  _completedTasks = 0;
+  _percentComplete = 0.0;
 
   semaphore.reset( _totalTasks );
+  _taskProgress = std::vector<double>(_totalTasks, 0.0);
 
   for( TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i )
         _taskService->add( i->get() );
@@ -311,18 +313,34 @@ int TMSExporter::exportTMS(MapNode* mapNode, const std::string& path, std::vecto
   return elevCount + imageCount;
 }
 
-void TMSExporter::packageTaskComplete()
+void TMSExporter::packageTaskProgress(int id, double complete)
 {
-  OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
+  if (_progress.valid())
+  {
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
 
-  _completedTasks++;
+    _percentComplete += complete - _taskProgress[id];
+    _taskProgress[id] = complete;
 
-  //if ( _progress.valid() && (_progress->isCanceled() || _progress->reportProgress(_completedTasks, _totalTasks)) )
-  //{
-  //    //Canceled
-  //}
+    _progress->reportProgress(_percentComplete, _totalTasks);
+  }
+}
 
+void TMSExporter::packageTaskComplete(int id)
+{
   if (_progress.valid())
-    _progress->reportProgress(_completedTasks, _totalTasks);
+  {
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock( _m );
+
+    _percentComplete += 1.0 - _taskProgress[id];
+    _taskProgress[id] = 1.0;
+
+    //if ( _progress.valid() && (_progress->isCanceled() || _progress->reportProgress(_completedTasks, _totalTasks)) )
+    //{
+    //    //Canceled
+    //}
+
+    _progress->reportProgress(_percentComplete, _totalTasks);
+  }
 }
 
diff --git a/src/applications/osgearth_package_qt/TMSExporter.h b/src/applications/osgearth_package_qt/TMSExporter.h
index a09ebee..421adc3 100644
--- a/src/applications/osgearth_package_qt/TMSExporter.h
+++ b/src/applications/osgearth_package_qt/TMSExporter.h
@@ -54,7 +54,8 @@ namespace PackageQt
   protected:
     friend class PackageLayerProgressCallback;
 
-    void packageTaskComplete();
+    void packageTaskProgress(int id, double percentComplete);
+    void packageTaskComplete(int id);
 
   private:
 
@@ -67,7 +68,8 @@ namespace PackageQt
 
     osg::ref_ptr<osgEarth::TaskService> _taskService;
     int _totalTasks;
-    int _completedTasks;
+    double _percentComplete;
+    std::vector<double> _taskProgress;
     osg::ref_ptr<osgEarth::ProgressCallback> _progress;
   };
 
@@ -101,8 +103,8 @@ namespace PackageQt
   class PackageLayerProgressCallback : public osgEarth::ProgressCallback
   {
   public:
-    PackageLayerProgressCallback(TMSExporter* exporter)
-      : _exporter(exporter)
+    PackageLayerProgressCallback(TMSExporter* exporter, int id)
+      : _exporter(exporter), _id(id)
     {
     }
 
@@ -110,17 +112,21 @@ namespace PackageQt
 
     bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
     {
+      if (_exporter)
+        _exporter->packageTaskProgress(_id, current / total);
+
       return false;
     }
 
     void onCompleted()
     {
       if (_exporter)
-        _exporter->packageTaskComplete();
+        _exporter->packageTaskComplete(_id);
     }
 
   private:
     TMSExporter* _exporter;
+    int _id;
   };
 }
 
diff --git a/src/applications/osgearth_package_qt/images/delete.png b/src/applications/osgearth_package_qt/images/delete.png
index c64d0fb..a922341 100644
Binary files a/src/applications/osgearth_package_qt/images/delete.png and b/src/applications/osgearth_package_qt/images/delete.png differ
diff --git a/src/applications/osgearth_package_qt/package_qt.cpp b/src/applications/osgearth_package_qt/package_qt.cpp
index 3d3a808..d6ac11e 100644
--- a/src/applications/osgearth_package_qt/package_qt.cpp
+++ b/src/applications/osgearth_package_qt/package_qt.cpp
@@ -19,6 +19,8 @@
 
 #include <osg/Notify>
 #include <osgDB/FileUtils>
+#include <osgGA/StateSetManipulator>
+#include <osgViewer/ViewerEventHandlers>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/MapNode>
 #include <osgEarthQt/ViewerWidget>
@@ -60,13 +62,17 @@ int main(int argc, char** argv)
   HTTPClient::setUserAgent( "osgearth_package_qt/1.0" );
 
   //setup log file
-  char *appData = getenv("APPDATA");
+#ifdef _WIN32
+  const char *appData = getenv("APPDATA");
+#else
+  const char *appData = "/tmp";
+#endif
 
-  std::string logDir = std::string(appData) + "\\osgEarthPackageQt";
+  std::string logDir = std::string(appData) + "/osgEarthPackageQt";
   if (!osgDB::fileExists(logDir))
     osgDB::makeDirectory(logDir);
 
-  std::string logPath = logDir + "\\log.txt";
+  std::string logPath = logDir + "/log.txt";
   std::ofstream* log = new std::ofstream( logPath.c_str() );
   std::cout.rdbuf( log->rdbuf() );
   std::cerr.rdbuf( log->rdbuf() );
@@ -104,7 +110,11 @@ int main(int argc, char** argv)
     mainView = views[0];
 
   if (mainView.valid())
+  {
     mainView->getCamera()->setNearFarRatio(0.00002);
+    mainView->addEventHandler( new osgGA::StateSetManipulator(mainView->getCamera()->getOrCreateStateSet()) );
+    mainView->addEventHandler( new osgViewer::StatsHandler() );
+  }
 
   //create the SceneController, if no earth file is specified a blank
   //globe will be loaded
diff --git a/src/applications/osgearth_qt/DemoMainWindow b/src/applications/osgearth_qt/DemoMainWindow
index 2e5cee2..bccbf5e 100644
--- a/src/applications/osgearth_qt/DemoMainWindow
+++ b/src/applications/osgearth_qt/DemoMainWindow
@@ -19,7 +19,6 @@
 
 #include <osgEarthQt/AnnotationToolbar>
 #include <osgEarthQt/Common>
-#include <osgEarthQt/MultiViewerWidget>
 #include <osgEarthQt/DataManager>
 #include <osgEarthQt/MapCatalogWidget>
 #include <osgEarthQt/TerrainProfileWidget>
@@ -45,7 +44,7 @@ class DemoMainWindow : public QMainWindow
 public:
 
     DemoMainWindow(osgEarth::QtGui::DataManager* manager, osgEarth::MapNode* mapNode, osg::Group* annotationRoot)
-        : _manager(manager), _mapNode(mapNode), _annoRoot(annotationRoot), _layerAdded(false), _terrainProfileDock(0L), _viewerWidget(0L), _compositeViewerWidget(0L)
+        : _manager(manager), _mapNode(mapNode), _annoRoot(annotationRoot), _layerAdded(false), _terrainProfileDock(0L), _viewerWidget(0L)
     {
         initUi(); 
     }
@@ -61,17 +60,6 @@ public:
         _annotationToolbar->setActiveViews(_views);
     }
 
-    void setViewerWidget(osgEarth::QtGui::MultiViewerWidget* viewerWidget, const osgEarth::QtGui::ViewVector& views)
-    {
-        setCentralWidget(viewerWidget);
-        _compositeViewerWidget = viewerWidget;
-
-        _views.clear();
-        _views.insert(_views.end(), views.begin(), views.end());
-
-        _annotationToolbar->setActiveViews(_views);
-    }
-
     void setTerrainProfileWidget(osgEarth::QtGui::TerrainProfileWidget* widget)
     {
         if (!_terrainProfileDock)
@@ -145,16 +133,9 @@ protected:
     {
         if (_viewerWidget)
         {
-            //_viewerWidget->getViewer()->setSceneData(0);
-            //_viewerWidget->getViewer()->frame();
             _viewerWidget->getViewer()->setDone(true);
         }
 
-        if (_compositeViewerWidget)
-        {
-            _compositeViewerWidget/*->getViewer()*/->setDone(true);
-        }
-
         event->accept();
     }
 
@@ -206,9 +187,6 @@ private:
     osg::ref_ptr<osg::Group> _annoRoot;
     osg::ref_ptr<osgEarth::ImageLayer> _testLayer;
     osgEarth::QtGui::ViewerWidget* _viewerWidget;
-    osgEarth::QtGui::MultiViewerWidget* _compositeViewerWidget;
-    //osg::ref_ptr<osgEarth::QtGui::ViewerWidget> _viewerWidget;
-    //osg::ref_ptr<osgEarth::QtGui::MultiViewerWidget> _compositeViewerWidget;
     osgEarth::QtGui::AnnotationToolbar* _annotationToolbar;
     osgEarth::QtGui::ViewVector _views;
     bool _layerAdded;
diff --git a/src/applications/osgearth_qt/osgearth_qt.cpp b/src/applications/osgearth_qt/osgearth_qt.cpp
index a4812f0..8a5115c 100644
--- a/src/applications/osgearth_qt/osgearth_qt.cpp
+++ b/src/applications/osgearth_qt/osgearth_qt.cpp
@@ -26,7 +26,6 @@
 #include <osgEarthAnnotation/ScaleDecoration>
 #include <osgEarthAnnotation/TrackNode>
 #include <osgEarthQt/ViewerWidget>
-#include <osgEarthQt/MultiViewerWidget>
 #include <osgEarthQt/LayerManagerWidget>
 #include <osgEarthQt/MapCatalogWidget>
 #include <osgEarthQt/DataManager>
@@ -205,10 +204,6 @@ main(int argc, char** argv)
     osg::ArgumentParser arguments(&argc,argv);
     osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);
 
-    std::string compNum;
-    bool composite = arguments.read("--multi", compNum);
-    int numViews = composite ? osgEarth::as<int>(compNum, 4) : 1;
-
     std::string stylesheet;
     bool styled = arguments.read("--stylesheet", stylesheet);
 
@@ -249,89 +244,63 @@ main(int argc, char** argv)
     osgEarth::QtGui::ViewVector views;
     osg::ref_ptr<osgViewer::ViewerBase> viewer;
 
-    // create viewer widget
-    if (composite)
-    {
-      osgEarth::QtGui::MultiViewerWidget* viewerWidget = new osgEarth::QtGui::MultiViewerWidget(root);
-
-      osgViewer::View* primary = viewerWidget->createViewWidget(root);
-      primary->getCamera()->addCullCallback(new osgEarth::Util::AutoClipPlaneCullCallback(mapNode));
-      views.push_back(primary);
-
-      for (int i=0; i < numViews - 1; i++)
-      {
-        osgViewer::View* view = viewerWidget->createViewWidget(root, primary);
-        view->getCamera()->addCullCallback(new osgEarth::Util::AutoClipPlaneCullCallback(mapNode));
-        views.push_back(view);
-      }
-
-      //viewerWidget->setGeometry(50, 50, 1024, 768);
-      appWin.setViewerWidget(viewerWidget, views);
+    osgEarth::QtGui::ViewerWidget* viewerWidget = 0L;
 
-      viewer = viewerWidget;
+    if ( testUseExistingViewer )
+    {
+        // tests: create a pre-existing viewer and install that in the widget.
+        osgViewer::Viewer* v = new osgViewer::Viewer();
+        v->setSceneData(root);
+        v->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
+        v->setCameraManipulator(new osgEarth::Util::EarthManipulator());
+        viewerWidget = new osgEarth::QtGui::ViewerWidget(v);
     }
 
     else
     {
-        osgEarth::QtGui::ViewerWidget* viewerWidget = 0L;
-
-        if ( testUseExistingViewer )
-        {
-            // tests: create a pre-existing viewer and install that in the widget.
-            osgViewer::Viewer* v = new osgViewer::Viewer();
-            v->setSceneData(root);
-            v->setThreadingModel(osgViewer::Viewer::DrawThreadPerContext);
-            v->setCameraManipulator(new osgEarth::Util::EarthManipulator());
-            viewerWidget = new osgEarth::QtGui::ViewerWidget(v);
-        }
-
-        else
-        {
-            // tests: implicity creating a viewer.
-            viewerWidget = new osgEarth::QtGui::ViewerWidget( root );
-        }
+        // tests: implicity creating a viewer.
+        viewerWidget = new osgEarth::QtGui::ViewerWidget( root );
+    }
 
-      //osgEarth::QtGui::ViewerWidget* viewerWidget = new osgEarth::QtGui::ViewerWidget(root);
-      //viewerWidget->setGeometry(50, 50, 1024, 768);
+    //osgEarth::QtGui::ViewerWidget* viewerWidget = new osgEarth::QtGui::ViewerWidget(root);
+    //viewerWidget->setGeometry(50, 50, 1024, 768);
 
-      viewerWidget->getViews( views );
+    viewerWidget->getViews( views );
 
-      for(osgEarth::QtGui::ViewVector::iterator i = views.begin(); i != views.end(); ++i )
-      {
-          i->get()->getCamera()->addCullCallback(new osgEarth::Util::AutoClipPlaneCullCallback(mapNode));
-      }
-      appWin.setViewerWidget(viewerWidget);
+    for(osgEarth::QtGui::ViewVector::iterator i = views.begin(); i != views.end(); ++i )
+    {
+        i->get()->getCamera()->addCullCallback(new osgEarth::Util::AutoClipPlaneCullCallback(mapNode));
+    }
+    appWin.setViewerWidget(viewerWidget);
 
-      if (mapNode.valid())
-      {
+    if (mapNode.valid())
+    {
         const Config& externals = mapNode->externalConfig();
 
         if (mapNode->getMap()->isGeocentric())
         {
-          // Sky model.
-          Config skyConf = externals.child("sky");
-
-          double hours = skyConf.value("hours", 12.0);
-          s_sky = new osgEarth::Util::SkyNode(mapNode->getMap());
-          s_sky->setDateTime(2011, 3, 6, hours);
-          for(osgEarth::QtGui::ViewVector::iterator i = views.begin(); i != views.end(); ++i )
-              s_sky->attach( *i );
-          //s_sky->attach(viewerWidget->getViewer());
-          root->addChild(s_sky);
-
-          // Ocean surface.
-          if (externals.hasChild("ocean"))
-          {
-            s_ocean = new osgEarth::Drivers::OceanSurfaceNode(mapNode.get(), externals.child("ocean"));
-            if (s_ocean)
-              root->addChild(s_ocean);
-          }
+            // Sky model.
+            Config skyConf = externals.child("sky");
+
+            double hours = skyConf.value("hours", 12.0);
+            s_sky = new osgEarth::Util::SkyNode(mapNode->getMap());
+            s_sky->setDateTime( DateTime(2011, 3, 6, hours) );
+            for(osgEarth::QtGui::ViewVector::iterator i = views.begin(); i != views.end(); ++i )
+                s_sky->attach( *i );
+            root->addChild(s_sky);
+
+            // Ocean surface.
+            if (externals.hasChild("ocean"))
+            {
+                s_ocean = new osgEarth::Drivers::OceanSurfaceNode(mapNode.get(), externals.child("ocean"));
+                if (s_ocean)
+                    root->addChild(s_ocean);
+            }
         }
-      }
-
-      viewer = viewerWidget->getViewer();
     }
 
+    viewer = viewerWidget->getViewer();
+
     // activate "on demand" rendering if requested:
     if ( on_demand )
     {
diff --git a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
index cebb296..e855baf 100644
--- a/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
+++ b/src/applications/osgearth_qt_simple/osgearth_qt_simple.cpp
@@ -25,6 +25,8 @@
 #include <QtGui/QApplication>
 #include <QtGui/QMainWindow>
 #include <QtGui/QStatusBar>
+#include <QtGui/QMdiArea>
+#include <QtGui/QMdiSubWindow>
 
 #ifdef Q_WS_X11
 #include <X11/Xlib.h>
@@ -56,6 +58,7 @@ main(int argc, char** argv)
 
 
     osgViewer::Viewer viewer(arguments);
+    viewer.setRunFrameScheme( viewer.ON_DEMAND );
     viewer.setCameraManipulator( new EarthManipulator() );
 
     // load an earth file
@@ -75,7 +78,7 @@ main(int argc, char** argv)
     QApplication app(argc, argv);
 
     QWidget* viewerWidget = new ViewerWidget( &viewer );
-    
+
     QMainWindow win;
     win.setCentralWidget( viewerWidget );
     win.setGeometry(100, 100, 1024, 800);
diff --git a/src/applications/osgearth_seed/osgearth_seed.cpp b/src/applications/osgearth_seed/osgearth_seed.cpp
index c52aab4..b3f49b3 100644
--- a/src/applications/osgearth_seed/osgearth_seed.cpp
+++ b/src/applications/osgearth_seed/osgearth_seed.cpp
@@ -26,15 +26,18 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapFrame>
 #include <osgEarth/Cache>
+#include <osgEarth/CacheEstimator>
 #include <osgEarth/CacheSeed>
 #include <osgEarth/MapNode>
 #include <osgEarth/Registry>
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
 
 #include <iostream>
 #include <sstream>
 #include <iterator>
 
 using namespace osgEarth;
+using namespace osgEarth::Drivers;
 
 #define LC "[osgearth_cache] "
 
@@ -43,6 +46,8 @@ int seed( osg::ArgumentParser& args );
 int purge( osg::ArgumentParser& args );
 int usage( const std::string& msg );
 int message( const std::string& msg );
+std::string prettyPrintTime( double seconds );
+std::string prettyPrintSize( double mb );
 
 
 int
@@ -75,11 +80,14 @@ usage( const std::string& msg )
         << "    --list file.earth                   ; Lists info about the cache in a .earth file" << std::endl
         << std::endl
         << "    --seed file.earth                   ; Seeds the cache in a .earth file"  << std::endl
+        << "        [--estimate]                    ; Print out an estimation of the number of tiles, disk space and time it will take to perform this seed operation" << std::endl
         << "        [--min-level level]             ; Lowest LOD level to seed (default=0)" << std::endl
         << "        [--max-level level]             ; Highest LOD level to seed (defaut=highest available)" << std::endl
         << "        [--bounds xmin ymin xmax ymax]* ; Geospatial bounding box to seed (in map coordinates; default=entire map)" << std::endl
+        << "        [--index shapefile]             ; Use the feature extents in a shapefile to set the bounding boxes for seeding" << std::endl
         << "        [--cache-path path]             ; Overrides the cache path in the .earth file" << std::endl
         << "        [--cache-type type]             ; Overrides the cache type in the .earth file" << std::endl
+        << "        [--threads]                     ; The number of threads to use for the seed operation (default=1)" << std::endl
         << std::endl
         << "    --purge file.earth                  ; Purges a layer cache in a .earth file (interactive)" << std::endl
         << std::endl;
@@ -96,10 +104,10 @@ int message( const std::string& msg )
     return 0;
 }
 
-
 int
 seed( osg::ArgumentParser& args )
 {    
+
     //Read the min level
     unsigned int minLevel = 0;
     while (args.read("--min-level", minLevel));
@@ -107,6 +115,11 @@ seed( osg::ArgumentParser& args )
     //Read the max level
     unsigned int maxLevel = 5;
     while (args.read("--max-level", maxLevel));
+
+    bool estimate = args.read("--estimate");        
+
+    unsigned int threads = 1;
+    while (args.read("--threads", threads));
     
 
     std::vector< Bounds > bounds;
@@ -137,10 +150,62 @@ seed( osg::ArgumentParser& args )
     MapNode* mapNode = MapNode::findMapNode( node.get() );
     if ( !mapNode )
         return usage( "Input file was not a .earth file" );
+    
+    // Read in an index shapefile
+    std::string index;
+    while (args.read("--index", index))
+    {        
+        //Open the feature source
+        OGRFeatureOptions featureOpt;
+        featureOpt.url() = index;        
+
+        osg::ref_ptr< FeatureSource > features = FeatureSourceFactory::create( featureOpt );
+        features->initialize();
+        features->getFeatureProfile();
+
+        osg::ref_ptr< FeatureCursor > cursor = features->createFeatureCursor();
+        while (cursor.valid() && cursor->hasMore())
+        {
+            osg::ref_ptr< Feature > feature = cursor->nextFeature();
+            osgEarth::Bounds featureBounds = feature->getGeometry()->getBounds();
+            GeoExtent ext( feature->getSRS(), featureBounds );
+            ext = ext.transform( mapNode->getMapSRS() );
+            bounds.push_back( ext.bounds() );            
+        }
+    }
+
+    // If they requested to do an estimate then don't do the the seed, just print out the estimated values.
+    if (estimate)
+    {        
+        CacheEstimator est;
+        est.setMinLevel( minLevel );
+        est.setMaxLevel( maxLevel );
+        est.setProfile( mapNode->getMap()->getProfile() );
+
+        for (unsigned int i = 0; i < bounds.size(); i++)
+        {
+            GeoExtent extent(mapNode->getMapSRS(), bounds[i]);
+            OE_DEBUG << "Adding extent " << extent.toString() << std::endl;
+            est.addExtent( extent );
+        } 
+
+        unsigned int numTiles = est.getNumTiles();
+        double size = est.getSizeInMB();
+        double time = est.getTotalTimeInSeconds();
+        std::cout << "Cache Estimation " << std::endl
+                  << "---------------- " << std::endl
+                  << "Total number of tiles: " << numTiles << std::endl
+                  << "Size on disk:          " << prettyPrintSize( size ) << std::endl
+                  << "Total time:            " << prettyPrintTime( time ) << std::endl;
+
+        return 0;
+    }
 
     CacheSeed seeder;
     seeder.setMinLevel( minLevel );
     seeder.setMaxLevel( maxLevel );
+    seeder.setNumThreads( threads );
+
 
     for (unsigned int i = 0; i < bounds.size(); i++)
     {
@@ -148,12 +213,21 @@ seed( osg::ArgumentParser& args )
         OE_DEBUG << "Adding extent " << extent.toString() << std::endl;
         seeder.addExtent( extent );
     }    
+
     if (verbose)
     {
         seeder.setProgressCallback(new ConsoleProgressCallback);
     }
+
+
+    osg::Timer_t start = osg::Timer::instance()->tick();
+
     seeder.seed( mapNode->getMap() );
 
+    osg::Timer_t end = osg::Timer::instance()->tick();
+
+    OE_NOTICE << "Completed seeding in " << prettyPrintTime( osg::Timer::instance()->delta_s( start, end ) ) << std::endl;
+
     return 0;
 }
 
@@ -224,7 +298,7 @@ int
 purge( osg::ArgumentParser& args )
 {
     //return usage( "Sorry, but purge is not yet implemented." );
-
+    
     osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles( args );
     if ( !node.valid() )
         return usage( "Failed to read .earth file." );
@@ -347,3 +421,41 @@ purge( osg::ArgumentParser& args )
 
     return 0;
 }
+
+/**
+ * Gets the total number of seconds formatted as H:M:S
+ */
+std::string prettyPrintTime( double seconds )
+{
+    int hours = (int)floor(seconds / (3600.0) );
+    seconds -= hours * 3600.0;
+
+    int minutes = (int)floor(seconds/60.0);
+    seconds -= minutes * 60.0;
+
+    std::stringstream buf;
+    buf << hours << ":" << minutes << ":" << seconds;
+    return buf.str();
+}
+
+/**
+ * Gets a pretty printed version of the given size in MB.
+ */
+std::string prettyPrintSize( double mb )
+{
+    std::stringstream buf;
+    // Convert to terabytes
+    if ( mb > 1024 * 1024 )
+    {
+        buf << (mb / (1024.0*1024.0)) << " TB";
+    }
+    else if (mb > 1024)
+    {
+        buf << (mb / 1024.0) << " GB";
+    }
+    else 
+    {
+        buf << mb << " MB";
+    }
+    return buf.str();
+}
diff --git a/src/applications/osgearth_contour/CMakeLists.txt b/src/applications/osgearth_sharedlayer/CMakeLists.txt
similarity index 68%
copy from src/applications/osgearth_contour/CMakeLists.txt
copy to src/applications/osgearth_sharedlayer/CMakeLists.txt
index 6d08b89..5ae4214 100644
--- a/src/applications/osgearth_contour/CMakeLists.txt
+++ b/src/applications/osgearth_sharedlayer/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_contour.cpp )
+SET(TARGET_SRC osgearth_sharedlayer.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_contour)
+SETUP_APPLICATION(osgearth_sharedlayer)
\ No newline at end of file
diff --git a/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
new file mode 100644
index 0000000..522b698
--- /dev/null
+++ b/src/applications/osgearth_sharedlayer/osgearth_sharedlayer.cpp
@@ -0,0 +1,222 @@
+/* -*-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/>
+*/
+
+/**
+ * This sample demonstrates the use of shared layers. A shared image layer
+ * is one that becomes available to other image layers during the rendering
+ * phase. Thus if you share a layer, you can access it in a customer shader
+ * and use it to modulate another layer.
+ *
+ * In this example, we render a masking layer from a shapefile using the
+ * rasterization (agglite) driver. We don't draw this layer directly; instead
+ * we use it as a mask and use that mask to modulate the imagery layer from
+ * a custom shader, drawing random colors where the mask exists.
+ */
+#include <osgViewer/Viewer>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ColorFilter>
+#include <osgEarth/StringUtils>
+#include <osgEarthSymbology/StyleSheet>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <osgEarthDrivers/agglite/AGGLiteOptions>
+#include <osgEarthDrivers/tms/TMSOptions>
+
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Features;
+using namespace osgEarth::Symbology;
+using namespace osgEarth::Drivers;
+
+
+// forward declarations.
+int usage( const std::string& msg );
+ImageLayer* createImageryLayer();
+ImageLayer* createSharedLayer(const std::string& shapefile);
+
+
+
+/**
+ * A custom ColorFilter that will modulate the color of a normal
+ * ImageLayer by using data from a second "shared" image layer.
+ */
+class MyColorFilter : public osgEarth::ColorFilter
+{
+public:
+    // Create the color filter, supplying the shared layer that we plan
+    // to access
+    MyColorFilter(ImageLayer* maskLayer)
+    {
+        _maskLayer = maskLayer;
+    }
+
+    // This runs when the color filter is first installed.
+    void install(osg::StateSet* stateSet) const
+    {
+        // When we added the shared layer, osgEarth reserves a special
+        // image unit for it. Retrieve that now sine we need it in order
+        // to use its sampler.
+        int unit = _maskLayer->shareImageUnit().get();
+
+        // Make a vertex shader that will access the texture coordinates
+        // for our shared layer.
+        std::string vs = Stringify()
+            << "varying vec4 mask_layer_texc; \n"
+            << "void my_filter_vertex(inout vec4 VertexMODEL) \n"
+            << "{ \n"
+            << "    mask_layer_texc = gl_MultiTexCoord" << unit << "; \n"
+            <<"} \n";
+
+        // Make the fragment shader that will modulate the visible layer.
+        // This is is simple -- it just maxes out the red component wherever
+        // the shared "mask" layer exceed a certain alpha value.
+        std::string fs =
+            "uniform sampler2D mask_layer_tex; \n"
+            "varying vec4 mask_layer_texc; \n"
+            "void my_color_filter(inout vec4 color) \n"
+            "{ \n"
+            "    vec4 mask_texel = texture2D(mask_layer_tex, mask_layer_texc.st); \n"
+            "    if ( mask_texel.a >= 0.5 ) \n"
+            "    { \n"
+            "        color.r = 1.0; \n"
+            "    } \n"
+            "} \n";
+
+        // getOrCreate ensures we don't overwrite an existing VP.
+        VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
+
+        // install the vertex function
+        vp->setFunction( "my_filter_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL );
+
+        // install the color filter entry point. This is just a regular shader function,
+        // not a pipeline injection, so just use setShader.
+        vp->setShader( "my_color_filter", new osg::Shader(osg::Shader::FRAGMENT, fs) );
+
+        // finally, bind our sampler uniform to the allocated image unit.
+        stateSet->getOrCreateUniform("mask_layer_tex", osg::Uniform::SAMPLER_2D)->set( unit );
+    }
+
+    // The ColorFilter pipeline needs to know which function to call.
+    std::string getEntryPointFunctionName() const
+    {
+        return "my_color_filter";
+    }
+
+    ImageLayer* _maskLayer;
+};
+
+
+
+// main program.
+int main(int argc, char** argv)
+{
+    // set up the viewer
+    osg::ArgumentParser arguments(&argc,argv);
+    osgViewer::Viewer viewer(arguments);
+
+    // make sure we have a shape file.
+    std::string shapefile;
+    if ( !arguments.read("--shapefile", shapefile) )
+        return usage("Missing required --shapefile argument");
+
+    // install a motion model
+    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
+
+    // create a visible imagery layer:
+    ImageLayer* imagery = createImageryLayer();
+
+    // create a masking layer using the shapefile:
+    ImageLayer* sharedLayer = createSharedLayer( shapefile );
+
+    // create a new map and add our two layers.
+    MapNode* mapnode = new MapNode();
+    mapnode->getMap()->addImageLayer( imagery );
+    mapnode->getMap()->addImageLayer( sharedLayer );
+
+    // make a custom color-filter shader that will modulate the imagery
+    // using the texture from the shared layer. (Using a ColorFilter 
+    // will apply the effect to just one layer; if you want to apply it
+    // to all layers, you can just create a VirtualProgram and apply that
+    // the the mapnode->getTerrainEngine() state set.)
+    ColorFilter* filter = new MyColorFilter( sharedLayer );
+    imagery->addColorFilter( filter );
+
+    // done!
+    viewer.setSceneData( mapnode );
+    MapNodeHelper().configureView(&viewer);
+    return viewer.run();
+}
+
+
+// Creates a visible imagery layer that we will modulate.
+ImageLayer* createImageryLayer()
+{
+    TMSOptions tms;
+    tms.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
+    return new ImageLayer( "imagery", tms );
+}
+
+
+// Creates the shared "masking" layer based on a shapefile.
+ImageLayer* createSharedLayer(const std::string& url)
+{
+    // configure the OGR driver to read the shapefile
+    OGRFeatureOptions ogr;
+    ogr.url() = url;
+
+    // configure a rasterization driver to render the shapes to an
+    // image layer.
+    AGGLiteOptions agg;
+    agg.featureOptions() = ogr;
+    agg.styles() = new StyleSheet();
+
+    // Render shapes as opaque white.
+    Style style;
+    style.getOrCreate<PolygonSymbol>()->fill()->color() = Color::White;
+    agg.styles()->addStyle( style );
+
+    // configure the image layer. We set it to "shared" to tell osgEarth to
+    // generate a special image unit, making this layer's textures available
+    // to all other layers. We also set "visible" to false sine we don't
+    // want to actually render this layer in this example.
+    ImageLayerOptions layer( "mask", agg );
+    layer.shared()  = true;
+    layer.visible() = false;
+    layer.cachePolicy() = CachePolicy::NO_CACHE;
+
+    return new ImageLayer(layer);
+}
+
+
+// help/error information.
+int usage( const std::string& msg )
+{    
+    OE_NOTICE
+        << msg << "\n\n"
+        << "USAGE: osgearth_sharedlayer \n"
+        "             --shapefile <shapefile> \n"
+        << std::endl;
+
+    return -1;
+}
diff --git a/src/applications/osgearth_contour/CMakeLists.txt b/src/applications/osgearth_terraineffects/CMakeLists.txt
similarity index 66%
copy from src/applications/osgearth_contour/CMakeLists.txt
copy to src/applications/osgearth_terraineffects/CMakeLists.txt
index 6d08b89..0cbcbc9 100644
--- a/src/applications/osgearth_contour/CMakeLists.txt
+++ b/src/applications/osgearth_terraineffects/CMakeLists.txt
@@ -1,7 +1,7 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_contour.cpp )
+SET(TARGET_SRC osgearth_terraineffects.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_contour)
+SETUP_APPLICATION(osgearth_terraineffects)
\ No newline at end of file
diff --git a/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
new file mode 100644
index 0000000..1aafd30
--- /dev/null
+++ b/src/applications/osgearth_terraineffects/osgearth_terraineffects.cpp
@@ -0,0 +1,228 @@
+/* -*-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/>
+*/
+
+/**
+ * This sample tests all the various built-in TerrainEffect classes and
+ * lets you toggle them and try them together.
+ */
+#include <osg/Notify>
+#include <osgViewer/Viewer>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/ExampleResources>
+#include <osgEarthUtil/Controls>
+
+#include <osgEarthUtil/ContourMap>
+#include <osgEarthUtil/DetailTexture>
+#include <osgEarthUtil/LODBlending>
+#include <osgEarthUtil/NormalMap>
+#include <osgEarthUtil/VerticalScale>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Symbology;
+namespace ui = osgEarth::Util::Controls;
+
+
+int 
+usage(const char* msg)
+{
+    OE_NOTICE << msg << std::endl;
+    return 0;
+}
+
+
+struct App
+{
+    TerrainEngineNode*           engine;
+
+    osg::ref_ptr<ContourMap>     contourMap;
+    osg::ref_ptr<DetailTexture>  detailTexture;
+    osg::ref_ptr<LODBlending>    lodBlending;
+    osg::ref_ptr<NormalMap>      normalMap;
+    osg::ref_ptr<VerticalScale>  verticalScale;
+
+    App()
+    {
+        contourMap = new ContourMap();
+
+        detailTexture = new DetailTexture();
+        detailTexture->setImage( osgDB::readImageFile("../data/noise3.png") );
+
+        lodBlending = new LODBlending();
+        normalMap = new NormalMap();
+        verticalScale = new VerticalScale();
+    }
+};
+
+
+#define SET_FLOAT( effect, func ) \
+struct func : public ui::ControlEventHandler { \
+    App& _app; \
+    func (App& app) : _app(app) { } \
+    void onValueChanged(ui::Control*, float value) { \
+        _app. effect -> func (value); \
+    } \
+};
+
+#define TOGGLE( effect ) \
+struct Toggle : public ui::ControlEventHandler { \
+    App& _app; \
+    Toggle (App& app) : _app(app) { } \
+    void onValueChanged(ui::Control*, bool value) { \
+        if ( value ) _app.engine->addEffect( _app. effect .get() ); \
+        else _app.engine->removeEffect( _app. effect .get() ); \
+    } \
+};
+
+struct ContourMapController {
+    TOGGLE   ( contourMap );
+    SET_FLOAT( contourMap, setOpacity );
+    ContourMapController(App& app, ui::Grid* grid) {
+        int r = grid->getNumRows();
+        grid->setControl(0, r, new ui::LabelControl("ContourMap"));
+        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   opacity:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 1.0, 0.5, new setOpacity(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+    }
+};
+
+struct DetailTextureController {
+    TOGGLE   ( detailTexture );
+    SET_FLOAT( detailTexture, setIntensity );
+    DetailTextureController(App& app, ui::Grid* grid) {
+        int r = grid->getNumRows();
+        grid->setControl(0, r, new ui::LabelControl("DetailTexture"));
+        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   intensity:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 1.0, 0.5, new setIntensity(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+    }
+};
+
+struct LODBlendingController {
+    TOGGLE   ( lodBlending );
+    SET_FLOAT( lodBlending, setDelay );
+    SET_FLOAT( lodBlending, setDuration );
+    SET_FLOAT( lodBlending, setVerticalScale );
+    LODBlendingController(App& app, ui::Grid* grid) {
+        int r = grid->getNumRows();
+        grid->setControl(0, r, new ui::LabelControl("LOD Blending"));
+        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   delay:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 2.0, 1.0, new setDelay(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   duration:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 5.0, 1.0, new setDuration(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   vertical scale:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 5.0, 1.0, new setVerticalScale(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+    }
+};
+
+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 );
+    VerticalScaleController(App& app, ui::Grid* grid) {
+        int r = grid->getNumRows();
+        grid->setControl(0, r, new ui::LabelControl("VerticalScale"));
+        grid->setControl(1, r, new ui::CheckBoxControl(false, new Toggle(app)));
+        ++r;
+        grid->setControl(0, r, new ui::LabelControl("   scale:"));
+        grid->setControl(1, r, new ui::HSliderControl(0.0, 10.0, 1.0, new setScale(app)));
+        grid->setControl(2, r, new ui::LabelControl(grid->getControl(1, r)));
+    }
+};
+
+
+// Build a slider to adjust the vertical scale
+ui::Control* createUI( App& app )
+{
+    Grid* grid = new Grid();
+    grid->setAbsorbEvents( true );
+    grid->setControl(0, 0, new LabelControl("Terrain Effects", Color::Yellow) );
+    grid->setControl(1, 0, new LabelControl(""));
+    grid->getControl(1, 0)->setHorizFill( true, 250 );
+
+    ContourMapController    (app, grid);
+    DetailTextureController (app, grid);
+    LODBlendingController   (app, grid);
+    NormalMapController     (app, grid);
+    VerticalScaleController (app, grid);
+
+    return grid;
+}
+
+
+int main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc, argv);
+
+    if (arguments.read("--help"))
+        return usage(argv[0]);
+
+    // create a viewer:
+    osgViewer::Viewer viewer(arguments);
+
+    // install our default manipulator (do this before calling load)
+    viewer.setCameraManipulator( new EarthManipulator() );
+
+    // Set up the app create the UI:
+    App app;
+    ui::Control* demo_ui = createUI(app);
+
+    // load an earth file, and support all or our example command-line options
+    // and earth file <external> tags    
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer, demo_ui );
+    if ( node )
+    {
+        MapNode* mapNode = MapNode::get(node);
+        if ( !mapNode )
+            return -1;
+
+        app.engine = mapNode->getTerrainEngine();
+
+        viewer.setSceneData( node );
+        viewer.run();
+    }
+    else
+    {
+        return usage("no earth file");
+    }
+
+    return 0;
+}
diff --git a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
index cdd4ea5..11c2e52 100644
--- a/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
+++ b/src/applications/osgearth_terrainprofile/osgearth_terrainprofile.cpp
@@ -312,7 +312,7 @@ public:
           ls->tessellation() = 20;
 
           style.getOrCreate<AltitudeSymbol>()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
-          style.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_GPU;
+          style.getOrCreate<AltitudeSymbol>()->technique() = AltitudeSymbol::TECHNIQUE_SCENE;
 
           feature->style() = style;
 
diff --git a/src/applications/osgearth_tfs/osgearth_tfs.cpp b/src/applications/osgearth_tfs/osgearth_tfs.cpp
index cbcb3f4..4367b25 100644
--- a/src/applications/osgearth_tfs/osgearth_tfs.cpp
+++ b/src/applications/osgearth_tfs/osgearth_tfs.cpp
@@ -51,7 +51,8 @@ usage( const std::string& msg )
         << "    --expression       ; The expression to run on the feature source, specific to the feature source" << std::endl
         << "    --order-by         ; Sort the features, if not already included in the expression. Append DESC for descending order!" << std::endl
         << "    --crop             ; Crops features instead of doing a centroid check.  Features can be added to multiple tiles when cropping is enabled" << std::endl
-        << "    --dest-srs         ;The destination SRS string in any format osgEarth can understand (wkt, proj4, epsg).  If none is specified the source data SRS will be used" << std::endl
+        << "    --dest-srs         ; The destination SRS string in any format osgEarth can understand (wkt, proj4, epsg).  If none is specified the source data SRS will be used" << std::endl
+        << "    --bounds minx miny maxx maxy ; The bounding box to use as Level 0.  Feature extent will be used by default" << std::endl
         << std::endl;
 
     return -1;
@@ -105,6 +106,17 @@ int main(int argc, char** argv)
 
     std::string destSRS;
     while(arguments.read("--dest-srs", destSRS));
+
+    // Custom bounding box
+    Bounds bounds;
+    double xmin=DBL_MAX, ymin=DBL_MAX, xmax=DBL_MIN, ymax=DBL_MIN;
+    while (arguments.read("--bounds", xmin, ymin, xmax, ymax ))
+    {
+        bounds.xMin() = xmin;
+        bounds.yMin() = ymin;
+        bounds.xMax() = xmax;
+        bounds.yMax() = ymax;
+    }
     
     std::string filename;
 
@@ -114,6 +126,7 @@ int main(int argc, char** argv)
         if (!arguments.isOption(pos))
         {
             filename  = arguments[ pos ];
+            break;
         }
     }
 
@@ -177,6 +190,10 @@ 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.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_contour/CMakeLists.txt b/src/applications/osgearth_tileindex/CMakeLists.txt
similarity index 69%
rename from src/applications/osgearth_contour/CMakeLists.txt
rename to src/applications/osgearth_tileindex/CMakeLists.txt
index 6d08b89..3444b52 100644
--- a/src/applications/osgearth_contour/CMakeLists.txt
+++ b/src/applications/osgearth_tileindex/CMakeLists.txt
@@ -1,7 +1,8 @@
 INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} )
+
 SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGVIEWER_LIBRARY OPENTHREADS_LIBRARY)
 
-SET(TARGET_SRC osgearth_contour.cpp )
+SET(TARGET_SRC osgearth_tileindex.cpp )
 
 #### end var setup  ###
-SETUP_APPLICATION(osgearth_contour)
+SETUP_APPLICATION(osgearth_tileindex)
\ No newline at end of file
diff --git a/src/applications/osgearth_tileindex/osgearth_tileindex.cpp b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
new file mode 100644
index 0000000..8a6a4f0
--- /dev/null
+++ b/src/applications/osgearth_tileindex/osgearth_tileindex.cpp
@@ -0,0 +1,85 @@
+/* -*-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/Notify>
+#include <osg/ArgumentParser>
+#include <osgEarth/Notify>
+#include <osg/Timer>
+
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include <osgEarth/Progress>
+#include <osgEarthUtil/TileIndexBuilder>
+
+
+
+using namespace osgDB;
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+int main(int argc, char** argv)
+{
+    osg::ArgumentParser arguments(&argc,argv);
+
+    std::string indexFilename = "index.shp";
+    while (arguments.read("--index", indexFilename));
+
+    OE_NOTICE << "index name = " << indexFilename << std::endl;
+
+    std::vector< std::string > filenames;
+
+    //The rest of the arguments are filenames
+    for(int pos=1;pos<arguments.argc();++pos)
+    {
+        if (!arguments.isOption(pos))
+        {
+            filenames.push_back( arguments[ pos ] );            
+        }
+    }    
+
+	if (filenames.empty())
+	{
+		OE_NOTICE << "Please specify a directory or list of files to build an index for" << std::endl;
+		return 1;
+	}
+
+    // Open or create the index file
+    if (osgDB::fileExists( indexFilename ) )
+    {
+        OE_NOTICE << indexFilename << " exists, cannot update existing index" << std::endl;
+        return 1;
+    }
+
+    osg::Timer_t start = osg::Timer::instance()->tick();
+
+    TileIndexBuilder builder;
+    builder.setProgressCallback( new ConsoleProgressCallback() );
+    for (unsigned int i = 0; i < filenames.size(); i++)
+    {
+        builder.getFilenames().push_back( filenames[i] );
+    }        
+    builder.build( indexFilename );
+
+    osg::Timer_t end = osg::Timer::instance()->tick();
+    OE_NOTICE << "Built index " << indexFilename << " in " << osg::Timer::instance()->delta_s( start, end) << "s" << std::endl;
+
+
+    return 0;
+}
diff --git a/src/applications/osgearth_toc/osgearth_toc.cpp b/src/applications/osgearth_toc/osgearth_toc.cpp
index 6a1ef2b..4ea6c7b 100644
--- a/src/applications/osgearth_toc/osgearth_toc.cpp
+++ b/src/applications/osgearth_toc/osgearth_toc.cpp
@@ -21,6 +21,7 @@
 #include <osgEarth/MapNode>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/Controls>
+#include <osgEarthUtil/ExampleResources>
 #include <osgViewer/Viewer>
 #include <osgViewer/ViewerEventHandlers>
 #include <osgGA/StateSetManipulator>
@@ -29,7 +30,7 @@
 using namespace osgEarth;
 using namespace osgEarth::Util::Controls;
 
-osg::Node* createControlPanel( osgViewer::View* );
+void createControlPanel( osgViewer::View* );
 void updateControlPanel();
 
 static osg::ref_ptr<Map> s_activeMap;
@@ -37,6 +38,7 @@ static osg::ref_ptr<Map> s_inactiveMap;
 static Grid* s_masterGrid;
 static Grid* s_imageBox;
 static Grid* s_elevationBox;
+static Grid* s_modelBox;
 static bool s_updateRequired = true;
 
 //------------------------------------------------------------------------
@@ -69,8 +71,15 @@ main( int argc, char** argv )
 {
     osg::ArgumentParser arguments( &argc,argv );
 
+    // configure the viewer.
+    osgViewer::Viewer viewer( arguments );
+
+    // install a motion model
+    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
+
     // Load an earth file 
-    osgEarth::MapNode* mapNode = MapNode::load( arguments );
+    osg::Node* loaded = osgEarth::Util::MapNodeHelper().load(arguments, &viewer);
+    osgEarth::MapNode* mapNode = osgEarth::MapNode::get(loaded);
     if ( !mapNode ) {
         OE_WARN << "No osgEarth MapNode found in the loaded file(s)." << std::endl;
         return -1;
@@ -83,28 +92,18 @@ main( int argc, char** argv )
     // a Map to hold inactive layers (layers that have been removed from the displayed Map)
     s_inactiveMap = new Map();
     s_inactiveMap->addMapCallback( new MyMapListener() );
-    
-
-    // configure the viewer.
-    osgViewer::Viewer viewer( arguments );
 
     osg::Group* root = new osg::Group();
 
     // install the control panel
-    root->addChild( createControlPanel( &viewer ) );
-    root->addChild( mapNode );
+    createControlPanel( &viewer );
+    root->addChild( loaded );
 
     // update the control panel with the two Maps:
     updateControlPanel();
-    
-    viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );
-    viewer.addEventHandler( new osgViewer::StatsHandler() );
 
     viewer.setSceneData( root );
 
-    // install a proper manipulator
-    viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
-
     // install our control panel updater
     viewer.addUpdateOperation( new UpdateOperation() );
 
@@ -133,6 +132,26 @@ struct LayerOpacityHandler : public ControlEventHandler
     ImageLayer* _layer;
 };
 
+struct ModelLayerVisibleHandler : public ControlEventHandler
+{
+    ModelLayerVisibleHandler( ModelLayer* layer ) : _layer(layer) { }
+    void onValueChanged( Control* control, bool value )
+    {
+        _layer->setVisible( value );
+    }
+    ModelLayer* _layer;
+};
+
+struct ModelLayerOpacityHandler : public ControlEventHandler
+{
+    ModelLayerOpacityHandler( ModelLayer* layer ) : _layer(layer) { }
+    void onValueChanged( Control* control, float value )
+    {
+        _layer->setOpacity( value );
+    }
+    ModelLayer* _layer;
+};
+
 struct AddLayerHandler : public ControlEventHandler
 {
     AddLayerHandler( TerrainLayer* layer ) : _layer(layer) { }
@@ -199,7 +218,7 @@ struct MoveLayerHandler : public ControlEventHandler
 //------------------------------------------------------------------------
 
 
-osg::Node*
+void
 createControlPanel( osgViewer::View* view )
 {
     ControlCanvas* canvas = ControlCanvas::get( view );
@@ -235,9 +254,18 @@ createControlPanel( osgViewer::View* view )
     s_elevationBox->setVertAlign( Control::ALIGN_BOTTOM );
     s_masterGrid->setControl( 1, 0, s_elevationBox );
 
-    canvas->addControl( s_masterGrid );
+    //The image layers
+    s_modelBox = new Grid();
+    s_modelBox->setBackColor(0,0,0,0.5);
+    s_modelBox->setMargin( 10 );
+    s_modelBox->setPadding( 10 );
+    s_modelBox->setChildSpacing( 10 );
+    s_modelBox->setChildVertAlign( Control::ALIGN_CENTER );
+    s_modelBox->setAbsorbEvents( true );
+    s_modelBox->setVertAlign( Control::ALIGN_BOTTOM );
+    s_masterGrid->setControl( 2, 0, s_modelBox );
 
-    return canvas;
+    canvas->addControl( s_masterGrid );
 }
 
 void
@@ -294,6 +322,26 @@ createLayerItem( Grid* grid, int gridRow, int layerIndex, int numLayers, Terrain
 }
 
 void
+createModelLayerItem( Grid* grid, int gridRow, ModelLayer* layer, bool isActive )
+{
+    // a checkbox to enable/disable the layer:
+    CheckBoxControl* enabled = new CheckBoxControl( layer->getVisible() );
+    enabled->addEventHandler( new ModelLayerVisibleHandler(layer) );
+    grid->setControl( 0, gridRow, enabled );
+
+    // the layer name
+    LabelControl* name = new LabelControl( layer->getName() );
+    grid->setControl( 1, gridRow, name );
+
+    // an opacity slider
+    HSliderControl* opacity = new HSliderControl( 0.0f, 1.0f, layer->getOpacity() );
+    opacity->setWidth( 125 );
+    opacity->setHeight( 12 );
+    opacity->addEventHandler( new ModelLayerOpacityHandler(layer) );
+    grid->setControl( 2, gridRow, opacity );
+}
+
+void
 updateControlPanel()
 {
     // erase all child controls and just rebuild them b/c we're lazy.
@@ -307,7 +355,7 @@ updateControlPanel()
     s_imageBox->setControl( 1, row++, activeLabel );
 
     // the active map layers:
-    MapFrame mapf( s_activeMap.get() );
+    MapFrame mapf( s_activeMap.get(), Map::ENTIRE_MODEL );
     int layerNum = mapf.imageLayers().size()-1;
     for( ImageLayerVector::const_reverse_iterator i = mapf.imageLayers().rbegin(); i != mapf.imageLayers().rend(); ++i )
         createLayerItem( s_imageBox, row++, layerNum--, mapf.imageLayers().size(), i->get(), true );
@@ -351,4 +399,17 @@ updateControlPanel()
         }
     }
 
+
+
+    //Rebuild the model layers
+    s_modelBox->clearControls();
+
+    row = 0;
+
+    activeLabel = new LabelControl( "Model Layers", 20, osg::Vec4f(1,1,0,1) );
+    s_modelBox->setControl( 1, row++, activeLabel );
+
+    // the active map layers:
+    for( ModelLayerVector::const_reverse_iterator i = mapf.modelLayers().rbegin(); i != mapf.modelLayers().rend(); ++i )
+        createModelLayerItem( s_modelBox, row++, i->get(), true );
 }
diff --git a/src/applications/osgearth_tracks/osgearth_tracks.cpp b/src/applications/osgearth_tracks/osgearth_tracks.cpp
index a2eb227..8faf385 100644
--- a/src/applications/osgearth_tracks/osgearth_tracks.cpp
+++ b/src/applications/osgearth_tracks/osgearth_tracks.cpp
@@ -23,13 +23,14 @@
 #include <osgEarth/GeoMath>
 #include <osgEarth/Units>
 #include <osgEarth/StringUtils>
+#include <osgEarth/Decluttering>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/MGRSFormatter>
 #include <osgEarthUtil/Controls>
 #include <osgEarthUtil/AnnotationEvents>
+#include <osgEarthUtil/HTM>
 #include <osgEarthAnnotation/TrackNode>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthSymbology/Color>
 
@@ -318,6 +319,7 @@ 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 );
 
diff --git a/src/applications/osgearth_verticalscale/osgearth_verticalscale.cpp b/src/applications/osgearth_verticalscale/osgearth_verticalscale.cpp
index 50022cc..c2c5976 100644
--- a/src/applications/osgearth_verticalscale/osgearth_verticalscale.cpp
+++ b/src/applications/osgearth_verticalscale/osgearth_verticalscale.cpp
@@ -29,74 +29,62 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 #include <osgEarthUtil/Controls>
+#include <osgEarthUtil/VerticalScale>
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
 
 //-------------------------------------------------------------------------
 
-// In the vertex shader, we use a vertex attribute that's genreated by the
-// terrain engine. In this example it's called "osgearth_elevData" but you 
-// can give it any name you want, as long as it's bound to the proper
-// attribute location (see code). 
-//
-// The attribute contains a vec4 which holds the unit "up vector" in 
-// indexes[0,1,2] and the original raw height in index[3].
-//
-// Here, we use the vertical scale uniform to move the vertex up or down
-// along its up vector, thereby scaling the terrain's elevation. The code
-// is intentionally verbose for clarity.
-
-const char* vertexShader =
-    "attribute vec4  osgearth_elevData; \n"
-    "uniform   float verticalScale;     \n"
-
-    "void applyVerticalScale(inout vec4 VertexMODEL) \n"
-    "{ \n"
-    "    vec3  upVector = osgearth_elevData.xyz;                   \n"
-    "    float elev     = osgearth_elevData.w;                     \n"
-    "    vec3  offset   = upVector * elev * (verticalScale - 1.0); \n"
-    "    VertexMODEL   += vec4(offset/VertexMODEL.w, 0.0); \n"
-    "} \n";
-
-
-// Build the stateset necessary for scaling elevation data.
-osg::StateSet* createStateSet()
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Symbology;
+namespace ui = osgEarth::Util::Controls;
+
+
+int 
+usage(const char* msg)
 {
-    osg::StateSet* stateSet = new osg::StateSet();
+    OE_NOTICE << msg << std::endl;
+    return 0;
+}
+
+
+struct App
+{
+    VerticalScale* scaler;
+};
 
-    // Install the shaders. We also bind osgEarth's elevation data attribute, which the 
-    // terrain engine automatically generates at the specified location.
-    VirtualProgram* vp = new VirtualProgram();
-    vp->setFunction( "applyVerticalScale", vertexShader, ShaderComp::LOCATION_VERTEX_MODEL );
-    vp->addBindAttribLocation( "osgearth_elevData", osg::Drawable::ATTRIBUTE_6 );
-    stateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
 
-    return stateSet;
+struct SetScale : public ui::ControlEventHandler {
+    App& _app;
+    SetScale(App& app) : _app(app) {}
+    void onValueChanged(ui::Control*, float value) {
+        _app.scaler->setScale(value);
+    }
 };
 
 
+
 // Build a slider to adjust the vertical scale
-osgEarth::Util::Controls::Control* createUI( osg::Uniform* scaler )
+ui::Control* createUI( App& app )
 {
-    using namespace osgEarth::Util::Controls;
-
-    struct ApplyVerticalScale : public ControlEventHandler {
-        osg::Uniform* _u;
-        ApplyVerticalScale(osg::Uniform* u) : _u(u) { }
-        void onValueChanged(Control*, float value) {
-            _u->set( value );
-        }
-    };
-
-    HBox* hbox = new HBox();
-    hbox->setChildVertAlign( Control::ALIGN_CENTER );
-    hbox->addControl( new LabelControl("Scale:") );
-    HSliderControl* slider = hbox->addControl( new HSliderControl(0.0, 5.0, 1.0, new ApplyVerticalScale(scaler)) );
-    slider->setHorizFill( true, 200 );
-    hbox->addControl( new LabelControl(slider) );
-
-    return hbox;
+    ui::VBox* vbox = new VBox();
+    vbox->setAbsorbEvents( true );
+
+    vbox->addControl( new LabelControl(Stringify() << "Vertical Scale Example", Color::Yellow) );
+
+    Grid* grid = vbox->addControl( new Grid() );
+
+    int r = 0;
+    grid->setControl( 0, r, new ui::LabelControl("Scale:") );
+    grid->setControl( 1, r, new ui::HSliderControl(0.0, 10.0, 1.0, new SetScale(app)) );
+    grid->setControl( 2, r, new ui::LabelControl(grid->getControl(1, r)) );
+
+    grid->getControl(1, r)->setHorizFill( true, 200 );
+
+    return vbox;
 }
 
 
@@ -104,46 +92,40 @@ int main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc, argv);
 
+    if (arguments.read("--help"))
+        return usage(argv[0]);
+
     // create a viewer:
     osgViewer::Viewer viewer(arguments);
 
-    // Tell osgEarth to use the "quadtree" terrain driver by default.
-    // Elevation data attribution is only available in this driver!
-    osgEarth::Registry::instance()->setDefaultTerrainEngineDriverName( "quadtree" );
-
     // install our default manipulator (do this before calling load)
     viewer.setCameraManipulator( new EarthManipulator() );
 
-    osg::Uniform* verticalScale = new osg::Uniform(osg::Uniform::FLOAT, "verticalScale");
-    verticalScale->set( 1.0f );
-    osgEarth::Util::Controls::Control* ui = createUI( verticalScale );
+    // Set up the app and read available options:
+    App app;
+
+    // Create the UI:
+    ui::Control* demo_ui = createUI(app);
 
     // load an earth file, and support all or our example command-line options
     // and earth file <external> tags    
-    osg::Node* node = MapNodeHelper().load( arguments, &viewer, ui );
+    osg::Node* node = MapNodeHelper().load( arguments, &viewer, demo_ui );
     if ( node )
     {
-        MapNode* mapNode = MapNode::findMapNode(node);
+        MapNode* mapNode = MapNode::get(node);
         if ( !mapNode )
             return -1;
 
-        if ( mapNode->getMap()->getNumElevationLayers() == 0 )
-            OE_WARN << "No elevation layers! Scaling will be very boring." << std::endl;
-
-        // install the shader program and install our controller uniform:
-        osg::Group* root = new osg::Group();
-        root->setStateSet( createStateSet() );
-        root->getStateSet()->addUniform( verticalScale );
-        root->addChild( node );
+        // attach the effect to the terrain.
+        app.scaler = new VerticalScale();
+        mapNode->getTerrainEngine()->addEffect( app.scaler );
 
-        viewer.setSceneData( root );
+        viewer.setSceneData( node );
         viewer.run();
     }
     else
     {
-        OE_NOTICE 
-            << "\nUsage: " << argv[0] << " file.earth" << std::endl
-            << MapNodeHelper().usage() << std::endl;
+        return usage("no earth file");
     }
 
     return 0;
diff --git a/src/applications/osgearth_viewer/osgearth_viewer.cpp b/src/applications/osgearth_viewer/osgearth_viewer.cpp
index d6ee38b..de97bee 100644
--- a/src/applications/osgearth_viewer/osgearth_viewer.cpp
+++ b/src/applications/osgearth_viewer/osgearth_viewer.cpp
@@ -29,11 +29,13 @@ using namespace osgEarth;
 using namespace osgEarth::Util;
 using namespace osgEarth::Annotation;
 
-int usage(char** argv)
+int
+usage(const char* name)
 {
     OE_NOTICE 
-        << "\nUsage: " << argv[0] << " file.earth" << std::endl
+        << "\nUsage: " << name << " file.earth" << std::endl
         << MapNodeHelper().usage() << std::endl;
+
     return 0;
 }
 
@@ -42,8 +44,9 @@ main(int argc, char** argv)
 {
     osg::ArgumentParser arguments(&argc,argv);
 
+    // help?
     if ( arguments.read("--help") )
-        return usage( argv );
+        return usage(argv[0]);
 
     if ( arguments.read("--stencil") )
         osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 );
@@ -66,12 +69,13 @@ 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
     {
-        return usage(argv);
+        return usage(argv[0]);
     }
     return 0;
 }
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj
index 0173b61..d95596c 100644
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj
+++ b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS.xcodeproj/project.pbxproj
@@ -7,6 +7,11 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		1F823FFE173C1D20003B519D /* libosgdb_osgearth_engine_mp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */; };
+		1F8566A817666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */; };
+		1F8566AA17666E67005BCD2B /* libJavaScriptCore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566A917666E67005BCD2B /* libJavaScriptCore.a */; };
+		1F8566AC17666EC1005BCD2B /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8566AB17666EC1005BCD2B /* libicucore.dylib */; };
+		1FA5C122177B608000A161FF /* gdal_data in Resources */ = {isa = PBXBuildFile; fileRef = 1FA5C121177B608000A161FF /* gdal_data */; };
 		90283E5615C6FB3E00620EEF /* tests in Resources */ = {isa = PBXBuildFile; fileRef = 90283E5415C6FB3E00620EEF /* tests */; };
 		90283E5715C6FB3E00620EEF /* data in Resources */ = {isa = PBXBuildFile; fileRef = 90283E5515C6FB3E00620EEF /* data */; };
 		90283E5D15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90283E5C15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a */; };
@@ -174,6 +179,11 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_engine_mp.a; path = ../../../lib/Release/libosgdb_osgearth_engine_mp.a; sourceTree = "<group>"; };
+		1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_scriptengine_javascriptcore.a; path = ../../../lib/Release/libosgdb_osgearth_scriptengine_javascriptcore.a; sourceTree = "<group>"; };
+		1F8566A917666E67005BCD2B /* libJavaScriptCore.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libJavaScriptCore.a; path = ../../../../3rdParty/all/libJavaScriptCore.a; sourceTree = "<group>"; };
+		1F8566AB17666EC1005BCD2B /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
+		1FA5C121177B608000A161FF /* gdal_data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = gdal_data; path = ../../../../../3rdParty/gdal_data; sourceTree = "<group>"; };
 		90283E5415C6FB3E00620EEF /* tests */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tests; path = ../../../../tests; sourceTree = "<group>"; };
 		90283E5515C6FB3E00620EEF /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = data; path = ../../../../data; sourceTree = "<group>"; };
 		90283E5C15C7091A00620EEF /* libosgdb_osgearth_engine_quadtree.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libosgdb_osgearth_engine_quadtree.a; path = ../../../lib/Release/libosgdb_osgearth_engine_quadtree.a; sourceTree = "<group>"; };
@@ -354,6 +364,10 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1F8566AC17666EC1005BCD2B /* libicucore.dylib in Frameworks */,
+				1F8566AA17666E67005BCD2B /* libJavaScriptCore.a in Frameworks */,
+				1F8566A817666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a in Frameworks */,
+				1F823FFE173C1D20003B519D /* libosgdb_osgearth_engine_mp.a in Frameworks */,
 				904E22771691CC42002D66FD /* Accelerate.framework in Frameworks */,
 				904E22781691CC42002D66FD /* MobileCoreServices.framework in Frameworks */,
 				907D037E15B86F8700575110 /* libosgdb_shp.a in Frameworks */,
@@ -544,6 +558,7 @@
 			isa = PBXGroup;
 			children = (
 				90DABDDB15CEFF9700D0F609 /* moon_1024x512.jpg */,
+				1FA5C121177B608000A161FF /* gdal_data */,
 				90283E5415C6FB3E00620EEF /* tests */,
 				90283E5515C6FB3E00620EEF /* data */,
 			);
@@ -554,6 +569,10 @@
 		9051FFEF15B1EDFD00D9ABD3 = {
 			isa = PBXGroup;
 			children = (
+				1F8566AB17666EC1005BCD2B /* libicucore.dylib */,
+				1F8566A917666E67005BCD2B /* libJavaScriptCore.a */,
+				1F8566A717666C29005BCD2B /* libosgdb_osgearth_scriptengine_javascriptcore.a */,
+				1F823FFD173C1D20003B519D /* libosgdb_osgearth_engine_mp.a */,
 				9051000815B1EDFD00D9ABD3 /* osgEarthViewerIOS */,
 				905100D215B21FFD00D9ABD3 /* Resources */,
 				90A0DD7715B7BBAB004FACEE /* Libs */,
@@ -873,6 +892,7 @@
 				90283E5715C6FB3E00620EEF /* data in Resources */,
 				90B8676715C8894900F5CDC3 /* StartViewerController.xib in Resources */,
 				90DABDDC15CEFF9700D0F609 /* moon_1024x512.jpg in Resources */,
+				1FA5C122177B608000A161FF /* gdal_data in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -970,27 +990,23 @@
 		9051002415B1EDFD00D9ABD3 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Thomas Hogarth (35UD7TG27V)";
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Jefferey Smith (KN8HX7RLT9)";
 				GCC_PRECOMPILE_PREFIX_HEADER = YES;
 				GCC_PREFIX_HEADER = "osgEarthViewerIOS/osgEarthViewerIOS-Prefix.pch";
 				"GCC_THUMB_SUPPORT[arch=armv6]" = "";
 				HEADER_SEARCH_PATHS = (
 					../../,
-					"../../../../osg-ios/include",
+					../../../../osg/OpenSceneGraph/include,
 				);
 				INFOPLIST_FILE = "osgEarthViewerIOS/osgEarthViewerIOS-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 4.3;
+				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
 				LIBRARY_SEARCH_PATHS = (
-					"\"$(SRCROOT)/../../../../3rdParty/freetype-ios-universal/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/proj4-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/gdal-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/curl-ios-device/lib\"",
+					"\"$(SRCROOT)/../../../../3rdParty/all\"",
 					"\"$(SRCROOT)/../../../lib/Release\"",
-					"\"$(SRCROOT)/../../../../3rdParty/geos-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../osg-ios/lib\"",
+					"\"$(SRCROOT)/../../../../osg/OpenSceneGraph/lib\"",
 				);
 				PRODUCT_NAME = osgEarth;
-				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "C5A8E349-91DB-4E98-9612-789DB499534E";
+				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "B0AE9D26-0AD5-4EE2-ADC5-8559501E74A9";
 				VALID_ARCHS = armv7;
 				WRAPPER_EXTENSION = app;
 			};
@@ -999,7 +1015,7 @@
 		9051002515B1EDFD00D9ABD3 /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Thomas Hogarth (35UD7TG27V)";
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Pelican Ventures, Inc";
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -1007,21 +1023,17 @@
 				"GCC_THUMB_SUPPORT[arch=armv6]" = "";
 				HEADER_SEARCH_PATHS = (
 					../../,
-					"../../../../osg-ios/include",
+					../../../../osg/OpenSceneGraph/include,
 				);
 				INFOPLIST_FILE = "osgEarthViewerIOS/osgEarthViewerIOS-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 4.3;
+				IPHONEOS_DEPLOYMENT_TARGET = 5.1;
 				LIBRARY_SEARCH_PATHS = (
-					"\"$(SRCROOT)/../../../../3rdParty/freetype-ios-universal/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/proj4-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/gdal-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../3rdParty/curl-ios-device/lib\"",
+					"\"$(SRCROOT)/../../../../3rdParty/all\"",
 					"\"$(SRCROOT)/../../../lib/Release\"",
-					"\"$(SRCROOT)/../../../../3rdParty/geos-ios-device/lib\"",
-					"\"$(SRCROOT)/../../../../osg-ios/lib\"",
+					"\"$(SRCROOT)/../../../../osg/OpenSceneGraph/lib\"",
 				);
 				PRODUCT_NAME = osgEarth;
-				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "C5A8E349-91DB-4E98-9612-789DB499534E";
+				"PROVISIONING_PROFILE[sdk=iphoneos*]" = "3948EA51-CDCD-4B29-A73F-67C55D186433";
 				VALID_ARCHS = armv7;
 				WRAPPER_EXTENSION = app;
 			};
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m
index cbc2575..80d7630 100644
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m
+++ b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/ViewController.m
@@ -10,6 +10,7 @@
 #include "osgPlugins.h"
 
 #include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
 
 #include <osgViewer/api/IOS/GraphicsWindowIOS>
 
@@ -18,9 +19,6 @@
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/ExampleResources>
 
-#include "EarthMultiTouchManipulator.h"
-#include "GLES2ShaderGenVisitor.h"
-
 using namespace osgEarth;
 using namespace osgEarth::Util;
 
@@ -61,9 +59,11 @@ using namespace osgEarth::Util;
     
     // This chunk inverts the Y axis.
     osgEarth::Util::EarthManipulator* manip = new osgEarth::Util::EarthManipulator();
-    osgEarth::Util::EarthManipulator::ActionOptions options;
-    options.add(osgEarth::Util::EarthManipulator::OPTION_SCALE_Y, -1.0);
-    manip->getSettings()->bindMouse(osgEarth::Util::EarthManipulator::ACTION_PAN, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON, 0L, options);
+    //osgEarth::Util::EarthManipulator::ActionOptions options;
+    //options.add(osgEarth::Util::EarthManipulator::OPTION_SCALE_Y, -1.0);
+    //manip->getSettings()->bindMouse(osgEarth::Util::EarthManipulator::ACTION_EARTH_DRAG, osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON, 0L, options);
+    manip->getSettings()->setThrowingEnabled(true);
+    manip->getSettings()->setThrowDecayRate(0.1);
     _viewer->setCameraManipulator( manip );
     
     osg::Node* node = osgDB::readNodeFile(osgDB::findDataFile("tests/" + _file));
@@ -115,9 +115,13 @@ using namespace osgEarth::Util;
 {
     [super viewDidLoad];
     
+    std::string fullPath = osgDB::findDataFile("gdal_data/gdal_datum.csv");
+    std::string dataPath = osgDB::getFilePath(fullPath);
+    
+    setenv("GDAL_DATA", dataPath.c_str(), 1);
+    
     osg::setNotifyLevel(osg::DEBUG_FP);
     osgEarth::setNotifyLevel(osg::DEBUG_FP);
-
     
     //get screen scale
     UIScreen* screen = [UIScreen mainScreen];
diff --git a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h
index a3d132e..0dddc1c 100755
--- a/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h
+++ b/src/applications/osgearth_viewerIOS/osgEarthViewerIOS/osgPlugins.h
@@ -81,3 +81,4 @@ USE_OSGPLUGIN(earth)
 USE_OSGPLUGIN(osgearth_cache_filesystem)
 USE_OSGPLUGIN(osgearth_arcgis)
 USE_OSGPLUGIN(osgearth_osg)
+USE_OSGPLUGIN(osgearth_scriptengine_javascriptcore)
diff --git a/src/osgEarth/AlphaEffect b/src/osgEarth/AlphaEffect
new file mode 100644
index 0000000..dba3b41
--- /dev/null
+++ b/src/osgEarth/AlphaEffect
@@ -0,0 +1,68 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_ALPHA_EFFECT_H
+#define OSGEARTH_ALPHA_EFFECT_H
+
+#include <osgEarth/Common>
+#include <osg/StateSet>
+#include <osg/Uniform>
+#include <osg/observer_ptr>
+
+namespace osgEarth
+{
+    /**
+     * Shader effect that lets you adjust the alpha channel.
+     */
+    class OSGEARTH_EXPORT AlphaEffect : public osg::Referenced
+    {
+    public:
+        /** constructs a new effect */
+        AlphaEffect();
+
+        /** contructs a new effect and attaches it to a stateset. */
+        AlphaEffect(osg::StateSet* stateset);
+
+    public:
+        /** The alpha channel value [0..1] */
+        void setAlpha(float value);
+        float getAlpha() const;
+
+    public:
+        /** attach this effect to a stateset. */
+        void attach(osg::StateSet* stateset);
+
+        /** detach this effect from any attached statesets. */
+        void detach();
+        /** detach this effect from a stateset. */
+        void detach(osg::StateSet* stateset);
+
+    protected:
+        virtual ~AlphaEffect();
+
+        typedef std::list< osg::observer_ptr<osg::StateSet> > StateSetList;
+
+        StateSetList _statesets;
+        osg::ref_ptr<osg::Uniform>       _alphaUniform;
+
+        void init();
+    };
+
+} // namespace osgEarth::Util
+
+#endif // OSGEARTH_ALPHA_EFFECT_H
diff --git a/src/osgEarth/AlphaEffect.cpp b/src/osgEarth/AlphaEffect.cpp
new file mode 100644
index 0000000..2a97b26
--- /dev/null
+++ b/src/osgEarth/AlphaEffect.cpp
@@ -0,0 +1,115 @@
+/* -*-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/AlphaEffect>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarth/VirtualProgram>
+
+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();
+}
+
+AlphaEffect::AlphaEffect(osg::StateSet* stateset)
+{
+    init();
+    attach( stateset );
+}
+
+void
+AlphaEffect::init()
+{
+    _alphaUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_alphaeffect_alpha");
+    _alphaUniform->set( 1.0f );
+}
+
+AlphaEffect::~AlphaEffect()
+{
+    detach();
+}
+
+void
+AlphaEffect::setAlpha(float value)
+{
+    _alphaUniform->set( value );
+}
+
+float
+AlphaEffect::getAlpha() const
+{
+    float value;
+    _alphaUniform->get(value);
+    return value;
+}
+
+void
+AlphaEffect::attach(osg::StateSet* stateset)
+{
+    if ( stateset )
+    {
+        _statesets.push_back(stateset);
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setFunction( "oe_alphaeffect_fragment", fragment, ShaderComp::LOCATION_FRAGMENT_COLORING, 2 );
+        stateset->addUniform( _alphaUniform.get() );
+    }
+}
+
+void
+AlphaEffect::detach()
+{
+    for (StateSetList::iterator it = _statesets.begin(); it != _statesets.end(); ++it)
+    {
+        osg::ref_ptr<osg::StateSet> stateset;
+        if ( (*it).lock(stateset) )
+        {
+            detach( stateset );
+            (*it) = 0L;
+        }
+    }
+
+    _statesets.clear();
+}
+
+void
+AlphaEffect::detach(osg::StateSet* stateset)
+{
+    if ( stateset )
+    {
+        stateset->removeUniform( _alphaUniform.get() );
+        VirtualProgram* vp = VirtualProgram::get( stateset );
+        if ( vp )
+        {
+            vp->removeShader( "oe_alphaeffect_fragment" );
+        }
+    }
+}
diff --git a/src/osgEarthAnnotation/AnnotationSettings.cpp b/src/osgEarth/AutoScale
similarity index 58%
copy from src/osgEarthAnnotation/AnnotationSettings.cpp
copy to src/osgEarth/AutoScale
index cf6b156..9c9ae40 100644
--- a/src/osgEarthAnnotation/AnnotationSettings.cpp
+++ b/src/osgEarth/AutoScale
@@ -17,13 +17,27 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 
-#include <osgEarthAnnotation/AnnotationSettings>
+#ifndef OSGEARTH_AUTO_SCALE_H
+#define OSGEARTH_AUTO_SCALE_H 1
 
-using namespace osgEarth::Annotation;
+#include <osgEarth/Common>
 
-//---------------------------------------------------------------------------
+namespace osgEarth
+{
+    /**
+     * Usage: set your render bin to AUTO_SCALE_BIN and your
+     * geometry will automatically draw at one pixel per meter.
+     *
+     * To enable:
+     *
+     *    node->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN);
+     *
+     * And to disable:
+     *
+     *    node->getOrCreateStateSet()->setRenderBinToInherit();
+     */
+    extern OSGEARTH_EXPORT const std::string AUTO_SCALE_BIN;
+}
 
-// static defaults
-bool AnnotationSettings::_continuousClamping = true;
-bool AnnotationSettings::_autoDepthOffset = true;
-double AnnotationSettings::_occlusionQueryMaxRange = 200000.0;
+
+#endif // OSGEARTH_AUTO_SCALE_H
diff --git a/src/osgEarth/AutoScale.cpp b/src/osgEarth/AutoScale.cpp
new file mode 100644
index 0000000..7e752e1
--- /dev/null
+++ b/src/osgEarth/AutoScale.cpp
@@ -0,0 +1,191 @@
+/* -*-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/AutoScale>
+#include <osgEarth/ThreadingUtils>
+#include <osgEarth/Utils>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderFactory>
+#include <osgUtil/RenderBin>
+
+#define LC "[AutoScale] "
+
+using namespace osgEarth;
+
+#define AUTO_SCALE_BIN_NAME "osgEarth::AutoScale"
+const std::string osgEarth::AUTO_SCALE_BIN = AUTO_SCALE_BIN_NAME;
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform float oe_autoscale_zp; \n"
+
+#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
+        "    float d        = length(cp.xyz); \n"
+        "    vec3  cpn      = cp.xyz/d; \n"
+        "    vec3  off      = VertexVIEW.xyz - cp.xyz; \n"
+
+        "    float dp = (d * oe_autoscale_zp) / z; \n"
+        "    cp.xyz   = cpn * dp; \n"
+
+        "    VertexVIEW.z *= (oe_autoscale_zp/z); \n"
+        "    VertexVIEW.xy = cp.xy + off.xy; \n"
+
+        "    vec3 push      = normalize(VertexVIEW.xyz); \n"
+        "    VertexVIEW.xyz = push * z; \n"
+        "} \n";
+
+
+    class AutoScaleRenderBin : public osgUtil::RenderBin
+    {
+    public:
+        osg::ref_ptr<osg::Uniform>  _zp;
+
+        // support cloning (from RenderBin):
+        virtual osg::Object* cloneType() const { return new AutoScaleRenderBin(); }
+        virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new AutoScaleRenderBin(*this,copyop); } // note only implements a clone of type.
+        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const AutoScaleRenderBin*>(obj)!=0L; }
+        virtual const char* libraryName() const { return "osgEarth"; }
+        virtual const char* className() const { return "AutoScaleRenderBin"; }
+
+
+        // constructs the prototype for this render bin.
+        AutoScaleRenderBin() : osgUtil::RenderBin()
+        {
+            //OE_NOTICE << LC << "AUTOSCALE: created bin." << std::endl;
+
+            this->setName( osgEarth::AUTO_SCALE_BIN );
+
+            _stateset = new osg::StateSet();
+
+            VirtualProgram* vp = VirtualProgram::getOrCreate(_stateset.get());
+            vp->setFunction( "oe_autoscale_vertex", vs, ShaderComp::LOCATION_VERTEX_VIEW );
+            //_stateset->setAttributeAndModes( vp, osg::StateAttribute::ON );
+
+            _zp = _stateset->getOrCreateUniform("oe_autoscale_zp", osg::Uniform::FLOAT);
+        }
+
+        AutoScaleRenderBin( const AutoScaleRenderBin& rhs, const osg::CopyOp& op )
+            : osgUtil::RenderBin( rhs, op ),
+              _zp      ( rhs._zp.get() )
+        {
+            //nop
+            //OE_NOTICE << LC << "AUTOSCALE: cloned bin." << std::endl;
+        }
+
+        /**
+         * Draws a bin. Most of this code is copied from osgUtil::RenderBin::drawImplementation.
+         * The modifications are (a) skipping code to render child bins, (b) setting a bin-global
+         * projection matrix in orthographic space, and (c) calling our custom "renderLeaf()" method 
+         * instead of RenderLeaf::render()
+         * (override)
+         */
+        void drawImplementation( osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous)
+        {
+            //OE_NOTICE << LC << "AUTOSCALE: leaves = " << getNumLeaves(this) << std::endl;
+
+            // apply a window-space projection matrix.
+            const osg::Viewport* vp = renderInfo.getCurrentCamera()->getViewport();
+            if ( vp )
+            {
+                float vpw = 0.5f*(float)vp->width();
+
+                double fovy, aspectRatio, n, f;
+                renderInfo.getCurrentCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, n, f);
+                float hfovd2 = (float)(0.5*fovy*aspectRatio);
+
+                _zp->set( vpw / tanf(osg::DegreesToRadians(hfovd2)) );
+            }
+    
+            osgUtil::RenderBin::drawImplementation(renderInfo, previous);
+        }
+
+        // debugging - counts the total # of leaves in this bin and its children
+        static unsigned getNumLeaves(osgUtil::RenderBin* bin)
+        {
+            unsigned here = bin->getRenderLeafList().size();
+            for( RenderBinList::iterator i = bin->getRenderBinList().begin(); i != bin->getRenderBinList().end(); ++i )
+                here += getNumLeaves( i->second.get() );
+            return here;
+        }
+    };
+}
+
+/** static registration of the bin */
+extern "C" void osgEarth_AutoScaleBin_registration(void) {}
+static osgEarthRegisterRenderBinProxy<AutoScaleRenderBin> s_regbin(AUTO_SCALE_BIN_NAME);
diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt
index 3617471..8ff7ad5 100644
--- a/src/osgEarth/CMakeLists.txt
+++ b/src/osgEarth/CMakeLists.txt
@@ -21,8 +21,11 @@ SET(LIB_NAME osgEarth)
 
 SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 SET(LIB_PUBLIC_HEADERS
+    AlphaEffect
+    AutoScale
     Bounds
     Cache
+    CacheEstimator
     CacheBin
     CachePolicy
     CacheSeed
@@ -36,6 +39,8 @@ SET(LIB_PUBLIC_HEADERS
     Containers
     Cube
     CullingUtils
+    DateTime
+    Decluttering
     DepthOffset
     DPLineSegmentIntersector
     Draggers
@@ -86,6 +91,7 @@ SET(LIB_PUBLIC_HEADERS
     OverlayDecorator
     OverlayNode
     Pickers
+    PrimitiveIntersector
     Profile
     Progress
     Random
@@ -100,6 +106,7 @@ SET(LIB_PUBLIC_HEADERS
     StringUtils
     TaskService
     Terrain
+    TerrainEffect
     TerrainLayer
     TerrainOptions
     TerrainEngineNode
@@ -139,11 +146,23 @@ IF (NOT TINYXML_FOUND)
     )
 ENDIF (NOT TINYXML_FOUND)
 
+# auto-generate the VersionGit file to include the git SHA1 variable.
+configure_file(
+    "${CMAKE_CURRENT_SOURCE_DIR}/VersionGit.cpp.in"
+    "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp" 
+    @ONLY)
+    
+set(VERSION_GIT_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/VersionGit.cpp")
+
 ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}
     ${TINYXML_SRC}
+    ${VERSION_GIT_SOURCE}
+    AlphaEffect.cpp
+    AutoScale.cpp
     Bounds.cpp
     Cache.cpp
+    CacheEstimator.cpp
     CachePolicy.cpp
     CacheSeed.cpp
     Capabilities.cpp
@@ -154,6 +173,8 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     Config.cpp
     Cube.cpp
     CullingUtils.cpp
+    DateTime.cpp
+    Decluttering.cpp
     DepthOffset.cpp
     Draggers.cpp
     DPLineSegmentIntersector.cpp
@@ -199,6 +220,7 @@ ADD_LIBRARY(${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     OverlayDecorator.cpp
     OverlayNode.cpp
     Pickers.cpp
+    PrimitiveIntersector.cpp
     Profile.cpp
     Progress.cpp
     Random.cpp
diff --git a/src/osgEarth/Cache b/src/osgEarth/Cache
index 8939041..4cc63b9 100644
--- a/src/osgEarth/Cache
+++ b/src/osgEarth/Cache
@@ -27,11 +27,11 @@
 #include <osgEarth/ThreadingUtils>
 
 namespace osgEarth
-{    
+{
     /**
      * Base class for Cache implementation options.
      */
-    class CacheOptions : public DriverConfigOptions // no export (header only)
+    class OSGEARTH_EXPORT CacheOptions : public DriverConfigOptions
     {
     public:
         CacheOptions( const ConfigOptions& options =ConfigOptions() )
@@ -41,7 +41,7 @@ namespace osgEarth
         }
 
         /** dtor */
-        virtual ~CacheOptions() { }
+        virtual ~CacheOptions();
 
     public:
         virtual Config getConfig() const {
@@ -74,7 +74,7 @@ namespace osgEarth
         META_Object( osgEarth, Cache );
 
         /** dtor */
-        virtual ~Cache() { }
+        virtual ~Cache();
 
     public:
         /**
@@ -146,7 +146,7 @@ namespace osgEarth
         const CacheOptions& getCacheOptions( const osgDB::ReaderWriter::Options* options ) const;
 
         /** dtor */
-        virtual ~CacheDriver() { }
+        virtual ~CacheDriver();
     };
 
 //----------------------------------------------------------------------
diff --git a/src/osgEarth/Cache.cpp b/src/osgEarth/Cache.cpp
index 907c175..04ae37d 100644
--- a/src/osgEarth/Cache.cpp
+++ b/src/osgEarth/Cache.cpp
@@ -30,6 +30,10 @@ using namespace osgEarth::Threading;
 
 #define LC "[Cache] "
 
+CacheOptions::~CacheOptions()
+{
+}
+
 //------------------------------------------------------------------------
 
 Cache::Cache( const CacheOptions& options ) :
@@ -39,6 +43,10 @@ _options( options )
     //nop
 }
 
+Cache::~Cache()
+{
+}
+
 Cache::Cache( const Cache& rhs, const osg::CopyOp& op ) :
 osg::Object( rhs, op )
 {
@@ -106,3 +114,7 @@ CacheDriver::getCacheOptions( const osgDB::ReaderWriter::Options* rwopt ) const
 {
     return *static_cast<const CacheOptions*>( rwopt->getPluginData( CACHE_OPTIONS_TAG ) );
 }
+
+CacheDriver::~CacheDriver()
+{
+}
diff --git a/src/osgEarth/CacheBin b/src/osgEarth/CacheBin
index f9fbab2..d661807 100644
--- a/src/osgEarth/CacheBin
+++ b/src/osgEarth/CacheBin
@@ -34,6 +34,14 @@ namespace osgEarth
     class /*no export*/ CacheBin : public osg::Referenced
     {
     public:
+        /** returned by the getRecordStatus() function */
+        enum RecordStatus {
+            STATUS_NOT_FOUND,   // record is not in the cache
+            STATUS_OK,          // record is in the cache and newer than the test time
+            STATUS_EXPIRED      // record is in the cache and older than the test time
+        };
+
+    public:
         /**
          * Constructs a caching bin.
          * @param binID  Name of this caching bin (unique withing a Cache)
@@ -51,30 +59,24 @@ namespace osgEarth
 
         /**
          * Reads an object from the cache bin.
-         * @param key    Lookup key to read
-         * @param maxAge Maximum age of the record; return 0L if expired.
+         * @param key     Lookup key to read
+         * @param minTime Fail if the entry's timestamp is older than this
          */
-        virtual ReadResult readObject(
-            const std::string&         key,
-            double                     maxAge =DBL_MAX ) =0;
+        virtual ReadResult readObject(const std::string& key, TimeStamp minTime) =0;
 
         /**
          * Reads an image from the cache bin.
-         * @param key    Lookup key to read
-         * @param maxAge Maximum age of the record; return 0L if expired.
+         * @param key     Lookup key to read
+         * @param minTime Fail if the entry's timestamp is older than this
          */
-        virtual ReadResult readImage(
-            const std::string&        key,
-            double                    maxAge =DBL_MAX ) =0;
+        virtual ReadResult readImage(const std::string& key, TimeStamp minTime) =0;
 
         /**
          * Reads a string buffer from the cache bin.
-         * @param key    Lookup key to read
-         * @param maxAge Maximum age of the record; return 0L if expired.
+         * @param key    Lookup key to read.
+         * @param minTime Fail if the entry's timestamp is older than this.
          */
-        virtual ReadResult readString(
-            const std::string&          key,
-            double                      maxAge =DBL_MAX ) =0;
+        virtual ReadResult readString(const std::string& key, TimeStamp minTime) =0;
 
         /**
          * Writes an object (or an image) to the cache bin.
@@ -87,12 +89,30 @@ namespace osgEarth
             const Config&      metadata =Config() ) =0;
 
         /**
+         * Gets the status of a key, i.e. not found, valid or expired.
+         * Pass in a minTime = 0 to simply check whether the record exists.
+         * @param key     Lookup key to check for
+         * @param minTime Return EXPIRED if the key exists but is older than this
+         */
+        virtual RecordStatus getRecordStatus(const std::string& key, TimeStamp minTime) =0;
+
+        /**
          * Checks whether a key exists in the cache.
-         * (Default implementation just tries to read the object)
+         * @deprecated - Please use getRecordStatus instead
          */
-        virtual bool isCached( 
-            const std::string& key, 
-            double             maxAge =DBL_MAX ) =0;
+        //bool isCached(const std::string& key) { 
+        //    return getRecordStatus(key, 0) == STATUS_OK; }
+
+        /**
+         * Purge an entry from the cache bin
+         */
+        virtual bool remove(const std::string& key) =0;
+
+        /**
+         * Update a record's timestamp to "now", as if it were a
+         * new entry.
+         */
+        virtual bool touch(const std::string& key) =0;
 
         /**
          * Reads custom metadata from the cache.
diff --git a/src/osgEarth/CacheEstimator b/src/osgEarth/CacheEstimator
new file mode 100644
index 0000000..6e1b1de
--- /dev/null
+++ b/src/osgEarth/CacheEstimator
@@ -0,0 +1,114 @@
+/* -*-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_CACHEESTIMATOR_H
+#define OSGEARTH_CACHEESTIMATOR_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/Profile>
+
+namespace osgEarth
+{      
+
+    /**
+     * A simple tool used for estimating the size of a cache seed operation.
+     * This provides a ROUGH estimate intended to provide a quick reality check before performing a cache operation.   
+     * If you see that a cache operation is going to generate 15TB of data and take 10 years to run you might want to think before running
+     * it or at least be prepared to make an extra cup of coffee.
+     */
+    class OSGEARTH_EXPORT CacheEstimator
+    {
+    public:
+        CacheEstimator();
+
+        /**
+        * Sets the minimum level to seed to
+        */
+        void setMinLevel(const unsigned int& minLevel) {_minLevel = minLevel;}
+
+        /**
+        * Gets the minimum level to seed to.
+        */
+        const unsigned int getMinLevel() const {return _minLevel;}
+
+        /**
+        * Sets the maximum level to seed to
+        */
+        void setMaxLevel(const unsigned int& maxLevel) {_maxLevel = maxLevel;}
+
+        /**
+        * Gets the maximum level to cache to.
+        */
+        const unsigned int getMaxLevel() const {return _maxLevel;}
+
+        /**
+        *Adds an extent to cache
+        */
+        void addExtent( const GeoExtent& value );
+       
+
+        /**
+         * Gets or sets the Profile used for this Cache.  Defaults to a global-geodetic profile
+         */
+        const osgEarth::Profile* getProfile() const { return _profile.get(); }
+        void setProfile( const osgEarth::Profile* profile ) { _profile = profile; }
+
+        /**
+         * Gets or sets the approximate size in MB for each tile
+         */
+        double getSizeInMBPerTile() const { return _sizeInMBPerTile;}
+        void setSizeInMBPerTile( double sizeInMBPerTile ) { _sizeInMBPerTile = sizeInMBPerTile; }
+
+        /**
+         * Gets or sets the approximate amount of processing time in seconds it will take for each tile
+         */
+        double getTimeInSecondsPerTile() const { return _timeInSecondsPerTile;}
+        void setTimeInSecondsPerTile( double timeInSecondsPerTile ) { _timeInSecondsPerTile = timeInSecondsPerTile; }
+
+        /**
+         * Gets the estimated total number of tiles that will be cached
+         */
+        unsigned int getNumTiles() const;
+
+        /**
+         * Get the estimated size of the output cache in MB.  
+         * This is a ROUGH estimate based on the _sizeInMBPerTile setting.
+         */
+        double getSizeInMB() const;
+
+        /**
+         * Get an estimate on the amount of time it will take to process the cache.
+         * This is a ROUGH estimate based on the _timeInSecondsPerTile setting
+         */
+        double getTotalTimeInSeconds() const;
+
+
+    protected:
+
+        osg::ref_ptr< const osgEarth::Profile > _profile;
+        unsigned int _minLevel;
+        unsigned int _maxLevel;        
+        std::vector< GeoExtent > _extents;
+        double _sizeInMBPerTile;
+        double _timeInSecondsPerTile;
+
+    };
+}
+
+#endif // OSGEARTH_BOUNDS_H
diff --git a/src/osgEarth/CacheEstimator.cpp b/src/osgEarth/CacheEstimator.cpp
new file mode 100644
index 0000000..a1f6d24
--- /dev/null
+++ b/src/osgEarth/CacheEstimator.cpp
@@ -0,0 +1,94 @@
+/* -*-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/CacheEstimator>
+#include <osgEarth/Registry>
+#include <osgEarth/TileKey>
+
+using namespace osgEarth;
+
+CacheEstimator::CacheEstimator():
+_minLevel (0),
+_maxLevel (12),
+_profile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() )
+{    
+    // By default we can give them a somewhat worse case estimate since it's going to be next to impossible to know what the real size of the data is going to be due to the fact that it's 
+    // dependant on the dataset itself as well as compression.  So lets just default to about 130 kb per tile to start with.
+    // the goal of the size estimator is just to provide a reality check before you start to do a cache.  If you see that it's going to take 10TB of disk space to 
+    // perform a cache you will think twice before starting it.
+    _sizeInMBPerTile = 0.13;
+
+    //Rough guess, assume it takes 1/10 of a second to process a tile.
+    _timeInSecondsPerTile = 0.1;
+}
+
+void
+CacheEstimator::addExtent( const GeoExtent& value)
+{
+    _extents.push_back( value );
+}
+
+unsigned int
+CacheEstimator::getNumTiles() const
+{
+    unsigned int total = 0;
+
+    for (unsigned int level = _minLevel; level <= _maxLevel; level++)
+    {
+        double coverageRatio = 0.0;
+
+        if (_extents.empty())
+        {
+            unsigned int wide, high;
+            _profile->getNumTiles( level, wide, high );
+            total += (wide * high);
+        }
+        else
+        {
+            for (std::vector< GeoExtent >::const_iterator itr = _extents.begin(); itr != _extents.end(); itr++)
+            {
+                const GeoExtent& extent = *itr;
+                double boundsArea = extent.area();
+
+                TileKey ll = _profile->createTileKey(extent.xMin(), extent.yMin(), level);
+                TileKey ur = _profile->createTileKey(extent.xMax(), extent.yMax(), level);
+
+                if (!ll.valid() || !ur.valid()) continue;
+                
+                int tilesWide = ur.getTileX() - ll.getTileX() + 1;
+                int tilesHigh = ll.getTileY() - ur.getTileY() + 1;
+                int tilesAtLevel = tilesWide * tilesHigh;                
+                total += tilesAtLevel;
+            }
+        }
+    }
+    return total;
+}
+
+double CacheEstimator::getSizeInMB() const
+{
+    return getNumTiles() * _sizeInMBPerTile;
+}
+
+double CacheEstimator::getTotalTimeInSeconds() const
+{
+    return getNumTiles() * _timeInSecondsPerTile;
+}
+
+
+
diff --git a/src/osgEarth/CachePolicy b/src/osgEarth/CachePolicy
index 8221b86..2997773 100644
--- a/src/osgEarth/CachePolicy
+++ b/src/osgEarth/CachePolicy
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
+#include <osgEarth/DateTime>
 #include <osgDB/Options>
 
 namespace osgEarth
@@ -53,11 +54,11 @@ namespace osgEarth
         /** constructs an invalid CachePolicy. */
         CachePolicy();
 
-        /** constructs a caching policy. */
-        CachePolicy( const Usage& usage );
+        /** copy ctor */
+        CachePolicy(const CachePolicy& rhs);
 
         /** constructs a caching policy. */
-        CachePolicy( const Usage& usage, double maxAgeSeconds );
+        CachePolicy( const Usage& usage );
 
         /** constructs a CachePolicy from a config options */
         CachePolicy( const Config& conf );
@@ -68,6 +69,9 @@ namespace osgEarth
         /** Stores this cache policy in a DB Options. */
         void apply( osgDB::Options* options );
 
+        /** Gets the oldest timestamp for which to accept a cache record */
+        TimeStamp getMinAcceptTime() const;
+
         /** dtor */
         virtual ~CachePolicy() { }
 
@@ -76,8 +80,13 @@ namespace osgEarth
         const optional<Usage>& usage() const { return _usage; }
 
         /** Gets the age limit for a cache record (in seconds) */
-        optional<double>& maxAge() { return _maxAge; }
-        const optional<double>& maxAge() const { return _maxAge; }
+        optional<TimeSpan>& maxAge() { return _maxAge; }
+        const optional<TimeSpan>& maxAge() const { return _maxAge; }
+
+        /** Gets the age limit for a cache record (as an absolute timestamp) */
+        optional<TimeStamp>& minTime() { return _minTime; }
+        const optional<TimeStamp>& minTime() const { return _minTime; }
+
 
     public: // convenience functions.
 
@@ -95,6 +104,7 @@ namespace osgEarth
             return ! operator==(rhs);
         }
 
+        // returns a readable string describing usage
         std::string usageString() const;
 
     public: // config
@@ -102,8 +112,9 @@ namespace osgEarth
         void fromConfig( const Config& conf );
 
     private:
-        optional<Usage>   _usage;
-        optional<double>  _maxAge;
+        optional<Usage>     _usage;
+        optional<TimeSpan>  _maxAge;
+        optional<TimeStamp> _minTime;
     };
 }
 
diff --git a/src/osgEarth/CachePolicy.cpp b/src/osgEarth/CachePolicy.cpp
index f91e1b3..455974d 100644
--- a/src/osgEarth/CachePolicy.cpp
+++ b/src/osgEarth/CachePolicy.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/CachePolicy>
+#include <limits.h>
 
 using namespace osgEarth;
 
@@ -30,31 +31,34 @@ CachePolicy CachePolicy::CACHE_ONLY( CachePolicy::USAGE_CACHE_ONLY );
 //------------------------------------------------------------------------
 
 CachePolicy::CachePolicy() :
-_maxAge( DBL_MAX )
+_maxAge ( INT_MAX ),
+_minTime( 0 )
 {
     _usage = USAGE_READ_WRITE;
 }
 
 CachePolicy::CachePolicy( const Usage& usage ) :
-_usage ( usage ),
-_maxAge( DBL_MAX )
+_usage  ( usage ),
+_maxAge ( INT_MAX ),
+_minTime( 0 )
 {
     _usage = usage; // explicity init the optional<>
 }
 
-CachePolicy::CachePolicy( const Usage& usage, double maxAge ) :
-_usage( usage ),
-_maxAge( maxAge )
+CachePolicy::CachePolicy( const Config& conf ) :
+_usage  ( USAGE_READ_WRITE ),
+_maxAge ( INT_MAX ),
+_minTime( 0 )
 {
-    _usage  = usage; // explicity init the optional<>
-    _maxAge = maxAge;
+    fromConfig( conf );
 }
 
-CachePolicy::CachePolicy( const Config& conf ) :
-_usage ( USAGE_READ_WRITE ),
-_maxAge( DBL_MAX )
+CachePolicy::CachePolicy(const CachePolicy& rhs) :
+_usage  ( rhs._usage ),
+_maxAge ( rhs._maxAge ),
+_minTime( rhs._minTime )
 {
-    fromConfig( conf );
+    //nop
 }
 
 bool
@@ -84,12 +88,22 @@ CachePolicy::apply( osgDB::Options* dbOptions )
     }
 }
 
+TimeStamp
+CachePolicy::getMinAcceptTime() const
+{
+    return
+        _minTime.isSet() ? _minTime.value() :
+        _maxAge.isSet()  ? DateTime().asTimeStamp() - _maxAge.value() :
+        0;
+}
+
 bool
 CachePolicy::operator == (const CachePolicy& rhs) const
 {
     return 
         (_usage.get() == rhs._usage.get()) &&
-        (_maxAge.get() == rhs._maxAge.get());
+        (_maxAge.get() == rhs._maxAge.get()) &&
+        (_minTime.get() == rhs._minTime.get());
 }
 
 std::string
@@ -105,22 +119,24 @@ CachePolicy::usageString() const
 void
 CachePolicy::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "usage", "read_write", _usage, USAGE_READ_WRITE );
-    conf.getIfSet( "usage", "read_only",  _usage, USAGE_READ_ONLY );
-    conf.getIfSet( "usage", "cache_only", _usage, USAGE_CACHE_ONLY );
-    conf.getIfSet( "usage", "no_cache",   _usage, USAGE_NO_CACHE );
-    conf.getIfSet( "usage", "none",       _usage, USAGE_NO_CACHE );
+    conf.getIfSet( "usage", "read_write",   _usage, USAGE_READ_WRITE );
+    conf.getIfSet( "usage", "read_only",    _usage, USAGE_READ_ONLY );
+    conf.getIfSet( "usage", "cache_only",   _usage, USAGE_CACHE_ONLY );
+    conf.getIfSet( "usage", "no_cache",     _usage, USAGE_NO_CACHE );
+    conf.getIfSet( "usage", "none",         _usage, USAGE_NO_CACHE );
     conf.getIfSet( "max_age", _maxAge );
+    conf.getIfSet( "min_time", _minTime );
 }
 
 Config
 CachePolicy::getConfig() const
 {
     Config conf( "cache_policy" );
-    conf.addIfSet( "usage", "read_write", _usage, USAGE_READ_WRITE );
-    conf.addIfSet( "usage", "read_only",  _usage, USAGE_READ_ONLY );
-    conf.addIfSet( "usage", "cache_only", _usage, USAGE_CACHE_ONLY );
-    conf.addIfSet( "usage", "no_cache",   _usage, USAGE_NO_CACHE );
+    conf.addIfSet( "usage", "read_write",   _usage, USAGE_READ_WRITE );
+    conf.addIfSet( "usage", "read_only",    _usage, USAGE_READ_ONLY );
+    conf.addIfSet( "usage", "cache_only",   _usage, USAGE_CACHE_ONLY );
+    conf.addIfSet( "usage", "no_cache",     _usage, USAGE_NO_CACHE );
     conf.addIfSet( "max_age", _maxAge );
+    conf.addIfSet( "min_time", _minTime );
     return conf;
 }
diff --git a/src/osgEarth/CacheSeed b/src/osgEarth/CacheSeed
index 0937cc4..ec43bf2 100644
--- a/src/osgEarth/CacheSeed
+++ b/src/osgEarth/CacheSeed
@@ -24,17 +24,41 @@
 #include <osgEarth/Map>
 #include <osgEarth/TileKey>
 #include <osgEarth/Progress>
+#include <OpenThreads/Mutex>
+#include <OpenThreads/Atomic>
 
 namespace osgEarth
 {
+    class CacheSeed;
+
+    class CacheTileOperation : public osg::Operation
+    {
+    public:
+        CacheTileOperation(const MapFrame& mapFrame, CacheSeed& cacheSeed, const TileKey& key);
+        virtual void operator()(osg::Object*);
+
+        // The MapFrame to operate on
+        const MapFrame& _mapFrame;
+
+        // The parent CacheSeed
+        CacheSeed& _cacheSeed;
+
+        // The TileKey to process
+        const TileKey _key;
+    };
+
     /**
     * Utility class for seeding a cache
     */
     class OSGEARTH_EXPORT CacheSeed
     {
     public:
+        friend class CacheTileOperation;
+
         CacheSeed();
 
+		CacheSeed( const CacheSeed& rhs);
+
         /** dtor */
         virtual ~CacheSeed() { }
 
@@ -58,6 +82,14 @@ namespace osgEarth
         */
         const unsigned int getMaxLevel() const {return _maxLevel;}
 
+        /*
+         * The number of threads to use when seeding
+         */
+        unsigned int getNumThreads() const;
+        void setNumThreads( unsigned int numThreads );
+
+        std::vector< GeoExtent >& getExtents() { return _extents; }
+
         /**
         *Adds an extent to cache
         */
@@ -75,20 +107,28 @@ namespace osgEarth
 
     protected:
 
-        void incrementCompleted( unsigned int total ) const;
+        void incrementCompleted() const;
+
+        void reportProgress( const std::string& msg ) const;
 
         unsigned int _minLevel;
         unsigned int _maxLevel;
 
         unsigned int _total;
-        unsigned int _completed;
+        OpenThreads::Atomic _completed;
+
+        unsigned int _numThreads;
 
         osg::ref_ptr<ProgressCallback> _progress;
 
-        void processKey( const MapFrame& mapf, const TileKey& key ) const;
         bool cacheTile( const MapFrame& mapf, const TileKey& key ) const;
 
         std::vector< GeoExtent > _extents;
+
+        // The work queue to pass seed operations to
+        osg::ref_ptr< osg::OperationQueue > _queue;  
+
+        OpenThreads::Mutex _mutex;
     };
 }
 
diff --git a/src/osgEarth/CacheSeed.cpp b/src/osgEarth/CacheSeed.cpp
index 035bd3b..e1b7039 100644
--- a/src/osgEarth/CacheSeed.cpp
+++ b/src/osgEarth/CacheSeed.cpp
@@ -18,6 +18,7 @@
 */
 
 #include <osgEarth/CacheSeed>
+#include <osgEarth/CacheEstimator>
 #include <osgEarth/MapFrame>
 #include <OpenThreads/ScopedLock>
 #include <limits.h>
@@ -27,16 +28,103 @@
 using namespace osgEarth;
 using namespace OpenThreads;
 
+
+
+
+
+/******************************************************************/
+CacheTileOperation::CacheTileOperation(const MapFrame& mapFrame, CacheSeed& cacheSeed, const TileKey& key):
+_mapFrame( mapFrame ),
+_cacheSeed( cacheSeed ),
+_key( key )
+{
+}
+
+void CacheTileOperation::operator()(osg::Object*)
+{
+    unsigned int x, y, lod;
+    _key.getTileXY(x, y);
+    lod = _key.getLevelOfDetail();
+
+    bool gotData = true;
+
+    if ( _cacheSeed.getMinLevel() <= lod && _cacheSeed.getMaxLevel() >= lod )
+    {
+        gotData = _cacheSeed.cacheTile( _mapFrame, _key );
+        if (gotData)
+        {                
+            _cacheSeed.incrementCompleted();
+            _cacheSeed.reportProgress( std::string("Cached tile: ") + _key.str() );
+        }       
+    }
+
+    if ( gotData && lod <= _cacheSeed.getMaxLevel() )
+    {
+        TileKey k0 = _key.createChildKey(0);
+        TileKey k1 = _key.createChildKey(1);
+        TileKey k2 = _key.createChildKey(2);
+        TileKey k3 = _key.createChildKey(3); 
+
+        bool intersectsKey = false;
+        if ( _cacheSeed.getExtents().empty()) intersectsKey = true;
+        else
+        {
+            for (unsigned int i = 0; i < _cacheSeed.getExtents().size(); ++i)
+            {
+                const GeoExtent& extent = _cacheSeed.getExtents()[i];
+                if (extent.intersects( k0.getExtent() ) ||
+                    extent.intersects( k1.getExtent() ) ||
+                    extent.intersects( k2.getExtent() ) ||
+                    extent.intersects( k3.getExtent() ))
+                {
+                    intersectsKey = true;
+                }
+
+            }
+        }
+
+        //Check to see if the bounds intersects ANY of the tile's children.  If it does, then process all of the children
+        //for this level
+        if (intersectsKey)
+        {
+            // Queue the task up for the children
+            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k0) );
+            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k1) );
+            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k2) );
+            _cacheSeed._queue.get()->add( new CacheTileOperation( _mapFrame, _cacheSeed, k3) );                
+        }
+    }
+}
+
+/******************************************************************/
+
+
+
+
+
 CacheSeed::CacheSeed():
 _minLevel (0),
 _maxLevel (12),
 _total    (0),
-_completed(0)
+_completed(0),
+_numThreads(1)
+{
+}
+
+CacheSeed::CacheSeed( const CacheSeed& rhs):
+_minLevel( rhs._minLevel),
+_maxLevel( rhs._maxLevel),
+_numThreads( rhs._numThreads )
 {
 }
 
 void CacheSeed::seed( Map* map )
 {
+    // We must do this to avoid an error message in OpenSceneGraph b/c the findWrapper method doesn't appear to be threadsafe.
+    // This really isn't a big deal b/c this only effects data that is already cached.
+    osgDB::ObjectWrapper* wrapper = osgDB::Registry::instance()->getObjectWrapperManager()->findWrapper( "osg::Image" );
+
+    osg::Timer_t startTime = osg::Timer::instance()->tick();
     if ( !map->getCache() )
     {
         OE_WARN << LC << "Warning: No cache defined; aborting." << std::endl;
@@ -74,10 +162,10 @@ void CacheSeed::seed( Map* map )
         {
             OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl;
         }
-        else if ( src->getCachePolicyHint() == CachePolicy::NO_CACHE )
-        {
-            OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
-        }
+        //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE )
+        //{
+        //    OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
+        //}
         else if ( !layer->getCache() )
         {
             OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl;
@@ -107,10 +195,10 @@ void CacheSeed::seed( Map* map )
         {
             OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl;
         }
-        else if ( src->getCachePolicyHint() == CachePolicy::NO_CACHE )
-        {
-            OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
-        }
+        //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE )
+        //{
+        //    OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl;
+        //}
         else if ( !layer->getCache() )
         {
             OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl;
@@ -139,201 +227,98 @@ void CacheSeed::seed( Map* map )
 
     OE_NOTICE << LC << "Maximum cache level will be " << _maxLevel << std::endl;
 
-    osg::Timer_t startTime = osg::Timer::instance()->tick();
     //Estimate the number of tiles
     _total = 0;    
+    CacheEstimator est;
+    est.setMinLevel( _minLevel );
+    est.setMaxLevel( _maxLevel );
+    est.setProfile( map->getProfile() ); 
+    for (unsigned int i = 0; i < _extents.size(); i++)
+    {                
+        est.addExtent( _extents[ i ] );
+    } 
+    _total = est.getNumTiles();
 
-    for (unsigned int level = _minLevel; level <= _maxLevel; level++)
-    {
-        double coverageRatio = 0.0;
-
-        if (_extents.empty())
-        {
-            unsigned int wide, high;
-            map->getProfile()->getNumTiles( level, wide, high );
-            _total += (wide * high);
-        }
-        else
-        {
-            for (std::vector< GeoExtent >::const_iterator itr = _extents.begin(); itr != _extents.end(); itr++)
-            {
-                const GeoExtent& extent = *itr;
-                double boundsArea = extent.area();
-
-                TileKey ll = map->getProfile()->createTileKey(extent.xMin(), extent.yMin(), level);
-                TileKey ur = map->getProfile()->createTileKey(extent.xMax(), extent.yMax(), level);
+    OE_INFO << "Processing ~" << _total << " tiles" << std::endl;
 
-                int tilesWide = ur.getTileX() - ll.getTileX() + 1;
-                int tilesHigh = ll.getTileY() - ur.getTileY() + 1;
-                int tilesAtLevel = tilesWide * tilesHigh;
-                //OE_NOTICE << "Tiles at level " << level << "=" << tilesAtLevel << std::endl;
 
-                bool hasData = false;
+    // Initialize the operations queue
+    _queue = new osg::OperationQueue;
 
-                for (ImageLayerVector::const_iterator itr = mapf.imageLayers().begin(); itr != mapf.imageLayers().end(); itr++)
-                {
-                    TileSource* src = itr->get()->getTileSource();
-                    if (src)
-                    {
-                        if (src->hasDataAtLOD( level ))
-                        {
-                            //Compute the percent coverage of this dataset on the current extent
-                            if (src->getDataExtents().size() > 0)
-                            {
-                                double cov = 0.0;
-                                for (unsigned int j = 0; j < src->getDataExtents().size(); j++)
-                                {
-                                    GeoExtent b = src->getDataExtents()[j].transform( extent.getSRS());
-                                    GeoExtent intersection = b.intersectionSameSRS( extent );
-                                    if (intersection.isValid())
-                                    {
-                                        double coverage = intersection.area() / boundsArea;
-                                        cov += coverage; //Assumes the extents aren't overlapping                            
-                                    }
-                                }
-                                if (coverageRatio < cov) coverageRatio = cov;
-                            }
-                            else
-                            {
-                                //We have no way of knowing how much coverage we have
-                                coverageRatio = 1.0;
-                            }
-                            hasData = true;
-                            break;
-                        }
-                    }
-                }
+    osg::Timer_t endTime = osg::Timer::instance()->tick();
 
-                for (ElevationLayerVector::const_iterator itr = mapf.elevationLayers().begin(); itr != mapf.elevationLayers().end(); itr++)
-                {
-                    TileSource* src = itr->get()->getTileSource();
-                    if (src)
-                    {
-                        if (src->hasDataAtLOD( level ))
-                        {
-                            //Compute the percent coverage of this dataset on the current extent
-                            if (src->getDataExtents().size() > 0)
-                            {
-                                double cov = 0.0;
-                                for (unsigned int j = 0; j < src->getDataExtents().size(); j++)
-                                {
-                                    GeoExtent b = src->getDataExtents()[j].transform( extent.getSRS());
-                                    GeoExtent intersection = b.intersectionSameSRS( extent );
-                                    if (intersection.isValid())
-                                    {
-                                        double coverage = intersection.area() / boundsArea;
-                                        cov += coverage; //Assumes the extents aren't overlapping                            
-                                    }
-                                }
-                                if (coverageRatio < cov) coverageRatio = cov;
-                            }
-                            else
-                            {
-                                //We have no way of knowing how much coverage we have
-                                coverageRatio = 1.0;
-                            }
-                            hasData = true;
-                            break;
-                        }
-                    }
-                }
+    // Start the threads
+    std::vector< osg::ref_ptr< osg::OperationsThread > > threads;
+    for (unsigned int i = 0; i < _numThreads; i++)
+    {        
+        osg::OperationsThread* thread = new osg::OperationsThread();
+        thread->setOperationQueue(_queue.get());
+        thread->start();
+        threads.push_back( thread );
+    }
 
-                //Adjust the coverage ratio by a fudge factor to try to keep it from being too small,
-                //tiles are either processed or not and the ratio is exact so will cover tiles partially
-                //and potentially be too small
-                double adjust = 4.0;
-                coverageRatio = osg::clampBetween(coverageRatio * adjust, 0.0, 1.0);
+    OE_NOTICE << "Startup time " << osg::Timer::instance()->delta_s( startTime, endTime ) << std::endl;
 
-                //OE_NOTICE << level <<  " CoverageRatio = " << coverageRatio << std::endl;
+    
+    // Add the root keys to the queue
+    for (unsigned int i = 0; i < keys.size(); ++i)
+    {
+        //processKey( mapf, keys[i] );
+        _queue.get()->add( new CacheTileOperation( mapf, *this, keys[i]) );
+    }    
 
-                if (hasData)
+    bool done = false;
+    while (!done)
+    {
+        OpenThreads::Thread::microSleep(500000); // sleep for half a second
+        done = true;
+        if (_queue->getNumOperationsInQueue() > 0)
+        {
+            done = false;
+            continue;
+        }
+        else
+        {
+            // Make sure no threads are currently working on an operation, which actually might add MORE operations since we are doing a quadtree traversal
+            for (unsigned int i = 0; i < threads.size(); i++)
+            {
+                if (threads[i]->getCurrentOperation())
                 {
-                    _total += (int)ceil(coverageRatio * (double)tilesAtLevel );
+                    done = false;
+                    continue;
                 }
             }
         }
-    }
-
-    //Adjust the # of tiles again to be bigger than computed to avoid giving false hope
-    _total *= 2;
-    osg::Timer_t endTime = osg::Timer::instance()->tick();
-    //OE_NOTICE << "Counted tiles in " << osg::Timer::instance()->delta_s(startTime, endTime) << " s" << std::endl;
-
-    OE_INFO << "Processing ~" << _total << " tiles" << std::endl;
-
-    for (unsigned int i = 0; i < keys.size(); ++i)
-    {
-        processKey( mapf, keys[i] );
-    }
+    }    
 
     _total = _completed;
 
     if ( _progress.valid()) _progress->reportProgress(_completed, _total, 0, 1, "Finished");
 }
 
-void CacheSeed::incrementCompleted( unsigned int total ) const
-{    
-    CacheSeed* nonconst_this = const_cast<CacheSeed*>(this);
-    nonconst_this->_completed += total;
+unsigned int CacheSeed::getNumThreads() const
+{
+    return _numThreads;
 }
 
-void
-CacheSeed::processKey(const MapFrame& mapf, const TileKey& key ) const
+void CacheSeed::setNumThreads( unsigned int numThreads )
 {
-    unsigned int x, y, lod;
-    key.getTileXY(x, y);
-    lod = key.getLevelOfDetail();
-
-    bool gotData = true;
-
-    if ( _minLevel <= lod && _maxLevel >= lod )
-    {
-        gotData = cacheTile( mapf, key );
-        if (gotData)
-        {
-        incrementCompleted( 1 );
-        }
-
-        if ( _progress.valid() && _progress->isCanceled() )
-            return; // Task has been cancelled by user
+    _numThreads = numThreads;
+}
 
-        if ( _progress.valid() && gotData && _progress->reportProgress(_completed, _total, std::string("Cached tile: ") + key.str()) )
-            return; // Canceled
-    }
+void CacheSeed::incrementCompleted( ) const
+{            
+    CacheSeed* nonconst_this = const_cast<CacheSeed*>(this);    
+    ++nonconst_this->_completed;    
+}
 
-    if ( gotData && lod <= _maxLevel )
+void CacheSeed::reportProgress( const std::string& message ) const
+{
+    if ( _progress.valid() )
     {
-        TileKey k0 = key.createChildKey(0);
-        TileKey k1 = key.createChildKey(1);
-        TileKey k2 = key.createChildKey(2);
-        TileKey k3 = key.createChildKey(3); 
-
-        bool intersectsKey = false;
-        if (_extents.empty()) intersectsKey = true;
-        else
-        {
-            for (unsigned int i = 0; i < _extents.size(); ++i)
-            {
-                if (_extents[i].intersects( k0.getExtent() ) ||
-                    _extents[i].intersects( k1.getExtent() ) ||
-                    _extents[i].intersects( k2.getExtent() ) ||
-                    _extents[i].intersects( k3.getExtent() ))
-                {
-                    intersectsKey = true;
-                }
-
-            }
-        }
-
-        //Check to see if the bounds intersects ANY of the tile's children.  If it does, then process all of the children
-        //for this level
-        if (intersectsKey)
-        {
-            processKey(mapf, k0);
-            processKey(mapf, k1);
-            processKey(mapf, k2);
-            processKey(mapf, k3);
-        }
+        CacheSeed* nonconst_this = const_cast<CacheSeed*>(this);    
+        OpenThreads::ScopedLock< OpenThreads::Mutex > lock( nonconst_this->_mutex );
+        _progress->reportProgress(_completed, _total, message );
     }
 }
 
diff --git a/src/osgEarth/Capabilities b/src/osgEarth/Capabilities
index b9ebd20..2e2861a 100644
--- a/src/osgEarth/Capabilities
+++ b/src/osgEarth/Capabilities
@@ -21,9 +21,12 @@
 #define OSGEARTH_CAPABILITIES_H 1
 
 #include <osgEarth/Common>
+#include <osg/Uniform>
 
 namespace osgEarth
 {
+    class VirtualProgram;
+
     /**
      * Stores information about the hardware and graphics system capbilities.
      * The osgEarth::Registry stores a singleton Capabilities object that you can 
@@ -38,9 +41,12 @@ namespace osgEarth
         /** maximum # of texture image units exposed in a GPU fragment shader */
         int getMaxGPUTextureUnits() const { return _maxGPUTextureUnits; }
 
-        /** maximum # of texture coordinate sets available in a GPU fragment shader */
+        /** maximum # of texture coordinate indices available in a GPU fragment shader */
         int getMaxGPUTextureCoordSets() const { return _maxGPUTextureCoordSets; }
 
+        /** maximum # of vertex attributes available in a shader */
+        int getMaxGPUAttribs() const { return _maxGPUAttribs; }
+
         /** maximum supported size (in pixels) of a texture */
         int getMaxTextureSize() const { return _maxTextureSize; }
 
@@ -102,12 +108,21 @@ namespace osgEarth
         /** whether the GPU supports Uniform Buffer Objects */
         bool supportsUniformBufferObjects() const { return _supportsUniformBufferObjects; }
 
+        /** whether the GPU can handle non-power-of-two textures. */
+        bool supportsNonPowerOfTwoTextures() const { return _supportsNonPowerOfTwoTextures; }
+
         /** maximum size of a uniform buffer block, in bytes */
         int getMaxUniformBlockSize() const { return _maxUniformBlockSize; }
 
         /** whether to prefer display lists over VBOs for static geometry. */
         bool preferDisplayListsForStaticGeometry() const { return _preferDLforStaticGeom; }
 
+        /** number of logical CPUs available. */
+        int getNumProcessors() const { return _numProcessors; }
+
+        /** whether the GPU supports writing to the depth fragment */
+        bool supportsFragDepthWrite() const { return _supportsFragDepthWrite; }
+
     protected:
         Capabilities();
 
@@ -118,6 +133,7 @@ namespace osgEarth
         int  _maxFFPTextureUnits;
         int  _maxGPUTextureUnits;
         int  _maxGPUTextureCoordSets;
+        int  _maxGPUAttribs;
         int  _maxTextureSize;
         int  _maxFastTextureSize;
         int  _maxLights;
@@ -135,8 +151,11 @@ namespace osgEarth
         bool _supportsOcclusionQuery;
         bool _supportsDrawInstanced;
         bool _supportsUniformBufferObjects;
+        bool _supportsNonPowerOfTwoTextures;
         int  _maxUniformBlockSize;
         bool _preferDLforStaticGeom;
+        int  _numProcessors;
+        bool _supportsFragDepthWrite;
         std::string _vendor;
         std::string _renderer;
         std::string _version;
diff --git a/src/osgEarth/Capabilities.cpp b/src/osgEarth/Capabilities.cpp
index 8c89dc3..f7a3a08 100644
--- a/src/osgEarth/Capabilities.cpp
+++ b/src/osgEarth/Capabilities.cpp
@@ -24,6 +24,7 @@
 #include <osg/GL2Extensions>
 #include <osg/Texture>
 #include <osgViewer/Version>
+#include <OpenThreads/Thread>
 
 using namespace osgEarth;
 
@@ -99,6 +100,7 @@ Capabilities::Capabilities() :
 _maxFFPTextureUnits     ( 1 ),
 _maxGPUTextureUnits     ( 1 ),
 _maxGPUTextureCoordSets ( 1 ),
+_maxGPUAttribs          ( 1 ),
 _maxTextureSize         ( 256 ),
 _maxFastTextureSize     ( 256 ),
 _maxLights              ( 1 ),
@@ -109,14 +111,18 @@ _supportsTextureArrays  ( false ),
 _supportsMultiTexture   ( false ),
 _supportsStencilWrap    ( true ),
 _supportsTwoSidedStencil( false ),
+_supportsTexture3D      ( false ),
 _supportsTexture2DLod   ( false ),
 _supportsMipmappedTextureUpdates( false ),
 _supportsDepthPackedStencilBuffer( false ),
 _supportsOcclusionQuery ( false ),
 _supportsDrawInstanced  ( false ),
 _supportsUniformBufferObjects( false ),
+_supportsNonPowerOfTwoTextures( false ),
 _maxUniformBlockSize    ( 0 ),
-_preferDLforStaticGeom  ( true )
+_preferDLforStaticGeom  ( true ),
+_numProcessors          ( 1 ),
+_supportsFragDepthWrite ( false )
 {
     // little hack to force the osgViewer library to link so we can create a graphics context
     osgViewerGetVersion();
@@ -126,6 +132,9 @@ _preferDLforStaticGeom  ( true )
     if ( ::getenv( "OSGEARTH_DISABLE_ATI_WORKAROUNDS" ) != 0L )
         enableATIworkarounds = false;
 
+    // logical CPUs (cores)
+    _numProcessors = OpenThreads::GetNumberOfProcessors();
+
     // create a graphics context so we can query OpenGL support:
     MyGraphicsContext mgc;
 
@@ -153,13 +162,10 @@ _preferDLforStaticGeom  ( true )
         OE_INFO << LC << "  Max GPU texture units = " << _maxGPUTextureUnits << std::endl;
 
         glGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &_maxGPUTextureCoordSets );
-#if defined(OSG_GLES2_AVAILABLE)
-        int maxVertAttributes = 0;
-        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertAttributes);
-        _maxGPUTextureCoordSets = maxVertAttributes - 5; //-5 for vertex, normal, color, tangent and binormal
-#endif
-        OE_INFO << LC << "  Max GPU texture coordinate sets = " << _maxGPUTextureCoordSets << std::endl;
+        OE_INFO << LC << "  Max GPU texture coord indices = " << _maxGPUTextureCoordSets << std::endl;
 
+        glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &_maxGPUAttribs );
+        OE_INFO << LC << "  Max GPU attributes = " << _maxGPUAttribs << std::endl;
 
         glGetIntegerv( GL_DEPTH_BITS, &_depthBits );
         OE_INFO << LC << "  Depth buffer bits = " << _depthBits << std::endl;
@@ -250,6 +256,17 @@ _preferDLforStaticGeom  ( true )
             _supportsUniformBufferObjects = false;
         }
 
+        _supportsNonPowerOfTwoTextures =
+            osg::isGLExtensionSupported( id, "GL_ARB_texture_non_power_of_two" );
+        OE_INFO << LC << "  NPOT textures = " << SAYBOOL(_supportsNonPowerOfTwoTextures) << std::endl;
+
+
+        // Writing to gl_FragDepth is not supported under GLES:
+#if (defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE))
+        _supportsFragDepthWrite = false;
+#else
+        _supportsFragDepthWrite = true;
+#endif
 
         //_supportsTexture2DLod = osg::isGLExtensionSupported( id, "GL_ARB_shader_texture_lod" );
         //OE_INFO << LC << "  texture2DLod = " << SAYBOOL(_supportsTexture2DLod) << std::endl;
diff --git a/src/osgEarth/ClampableNode b/src/osgEarth/ClampableNode
index 1e90f35..f093dbe 100644
--- a/src/osgEarth/ClampableNode
+++ b/src/osgEarth/ClampableNode
@@ -36,7 +36,7 @@ namespace osgEarth
      * Usage: Create this node and put it anywhere in the scene graph. The
      * subgraph of this node will be draped on the MapNode's terrain.
      */
-    class OSGEARTH_EXPORT ClampableNode : public OverlayNode
+    class OSGEARTH_EXPORT ClampableNode : public OverlayNode, public DepthOffsetInterface
     {
     public:
         /**
@@ -44,19 +44,14 @@ namespace osgEarth
          */
         ClampableNode( MapNode* mapNode, bool clamped =true );
 
-    public:
-        /** Depth Offsetting properties. Clamped geometry is automatically
-         *  subjected to depth offsetting to mitigate z-buffer conflicts */
-        DepthOffsetOptions& depthOffset() { dirtyDepthOffsetOptions(); return _do; }
-        const DepthOffsetOptions& depthOffset() const { return _do; }
-        
-        /** Automatically attempt to select a minimum bias based on an analysis
-         *  of the subgraph's geometry. */
-        void setAutoCalculateDepthOffset();
-
-        /** Backwards compatibility */
-        void setClamped( bool value ) { setActive(value); }
-        bool getClamped() const { return getActive(); }
+
+    public: // DepthOffsetInterface
+
+        /** Sets the depth offsetting options. See DepthOffset */
+        void setDepthOffsetOptions( const DepthOffsetOptions& options );
+
+        /** Gets the depth offsetting options. See DepthOffset */
+        const DepthOffsetOptions& getDepthOffsetOptions() const;
 
     public: // osg::Node
 
@@ -64,20 +59,22 @@ namespace osgEarth
 
         virtual void traverse(osg::NodeVisitor& nv);
 
-    protected:
-        /** dtor */
-        virtual ~ClampableNode() { }
+    public: // backwards-compatibility
+
+        /** @deprecated */
+        void setClamped( bool value ) { setActive(value); }
+        /** @deprecated */
+        bool getClamped() const { return getActive(); }
 
-        bool _autoBias;
-        mutable osg::ref_ptr<osg::Uniform> _biasUniform;
-        mutable osg::ref_ptr<osg::Uniform> _rangeUniform;
+    protected:
+        void setUniforms();
+        void dirty();
+        void scheduleUpdate();
 
-        mutable DepthOffsetOptions _do;
-        bool _doDirty;
+        DepthOffsetAdapter _adapter;
+        bool               _updatePending;
 
-        void init();
-        void dirtyDepthOffsetOptions();
-        void applyDepthOffsetOptions();
+        virtual ~ClampableNode() { }
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/ClampableNode.cpp b/src/osgEarth/ClampableNode.cpp
index eb208d1..406ac10 100644
--- a/src/osgEarth/ClampableNode.cpp
+++ b/src/osgEarth/ClampableNode.cpp
@@ -22,6 +22,7 @@
 #include <osgEarth/DepthOffset>
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/MapNode>
+#include <osgEarth/VirtualProgram>
 
 #define LC "[ClampableNode] "
 
@@ -40,84 +41,58 @@ namespace
 //------------------------------------------------------------------------
 
 ClampableNode::ClampableNode( MapNode* mapNode, bool active ) :
-OverlayNode( mapNode, active, &getTechniqueGroup )
+OverlayNode( mapNode, active, &getTechniqueGroup ),
+_updatePending( false )
 {
-    init();
+    _adapter.setGraph( this );
+
+    if ( _adapter.isDirty() )
+        _adapter.recalculate();
 }
 
 void
-ClampableNode::init()
+ClampableNode::setDepthOffsetOptions(const DepthOffsetOptions& options)
 {
-    // auto-bias starts out true, but if you set the depth offset options
-    // it will toggle to false.
-    _autoBias = true;
-
-    _doDirty  = false;
-
-    osg::StateSet* s = this->getOrCreateStateSet();
-
-    _biasUniform = s->getOrCreateUniform( "oe_clamp_bias", osg::Uniform::FLOAT_VEC2 );
-    _biasUniform->set( osg::Vec2f(*_do.minBias(), *_do.maxBias()) );
-
-    _rangeUniform = s->getOrCreateUniform( "oe_clamp_range", osg::Uniform::FLOAT_VEC2 );
-    _rangeUniform->set( osg::Vec2f(*_do.minRange(), *_do.maxRange()) );
+    _adapter.setDepthOffsetOptions(options);
+    if ( _adapter.isDirty() && !_updatePending )
+        scheduleUpdate();
 }
 
-void
-ClampableNode::dirtyDepthOffsetOptions()
+const DepthOffsetOptions&
+ClampableNode::getDepthOffsetOptions() const
 {
-    if ( !_doDirty )
-    {
-        _doDirty = true;
-        _autoBias = false;
-        ADJUST_UPDATE_TRAV_COUNT( this, 1 );
-    }
+    return _adapter.getDepthOffsetOptions();
 }
 
 void
-ClampableNode::traverse(osg::NodeVisitor& nv)
+ClampableNode::scheduleUpdate()
 {
-    if ( nv.getVisitorType() == nv.UPDATE_VISITOR )
+    if ( !_updatePending && getDepthOffsetOptions().enabled() == true )
     {
-        applyDepthOffsetOptions();
-        _doDirty = false;
-        ADJUST_UPDATE_TRAV_COUNT( this, -1 );
+        ADJUST_UPDATE_TRAV_COUNT(this, 1);
+        _updatePending = true;
     }
-    OverlayNode::traverse(nv);
 }
 
-void
-ClampableNode::applyDepthOffsetOptions()
+osg::BoundingSphere
+ClampableNode::computeBound() const
 {
-    if ( _do.enabled() == true )
+    static Threading::Mutex s_mutex;
     {
-        _biasUniform->set( osg::Vec2f(*_do.minBias(), *_do.maxBias()) );
-        _rangeUniform->set( osg::Vec2f(*_do.minRange(), *_do.maxRange()) );
-        dirtyBound();
-    }
-    else
-    {
-        _biasUniform->set( osg::Vec2f(0.0f, 0.0f) );
+        Threading::ScopedMutexLock lock(s_mutex);
+        const_cast<ClampableNode*>(this)->scheduleUpdate();
     }
+    return OverlayNode::computeBound();
 }
 
 void
-ClampableNode::setAutoCalculateDepthOffset()
-{
-    // prompts OSG to call computeBound() on the next pass which
-    // will recalculate the minimum bias.
-    _autoBias = true;
-    dirtyBound();
-}
-
-osg::BoundingSphere
-ClampableNode::computeBound() const
+ClampableNode::traverse(osg::NodeVisitor& nv)
 {
-    if ( _autoBias && _do.enabled() == true )
+    if ( _updatePending && nv.getVisitorType() == nv.UPDATE_VISITOR )
     {
-        _do.minBias() = DepthOffsetUtils::recalculate( this );
-        _biasUniform->set( osg::Vec2f(*_do.minBias(), *_do.maxBias()) );
+        _adapter.recalculate();
+        ADJUST_UPDATE_TRAV_COUNT( this, -1 );
+        _updatePending = false;
     }
-
-    return OverlayNode::computeBound();
+    OverlayNode::traverse( nv );
 }
diff --git a/src/osgEarth/ClampingTechnique.cpp b/src/osgEarth/ClampingTechnique.cpp
index 79cdb49..c742cb8 100644
--- a/src/osgEarth/ClampingTechnique.cpp
+++ b/src/osgEarth/ClampingTechnique.cpp
@@ -28,6 +28,10 @@
 #include <osg/PolygonMode>
 #include <osg/Texture2D>
 #include <osg/Uniform>
+#include <osg/ValueObject>
+#include <osg/Timer>
+
+#include <osgDB/WriteFile>
 
 #define LC "[ClampingTechnique] "
 
@@ -37,6 +41,11 @@
 //#define USE_RENDER_BIN 1
 #undef USE_RENDER_BIN
 
+//#define DUMP_RTT_IMAGE 1
+//#undef DUMP_RTT_IMAGE
+
+//#define TIME_RTT_CAMERA 1
+
 using namespace osgEarth;
 
 //---------------------------------------------------------------------------
@@ -47,6 +56,21 @@ namespace
     {
         return mapNode ? mapNode->getOverlayDecorator()->getGroup<ClampingTechnique>() : 0L;
     }
+
+#ifdef TIME_RTT_CAMERA
+    static osg::Timer_t t0, t1;
+    struct RttIn : public osg::Camera::DrawCallback {
+        void operator()(osg::RenderInfo& r) const {
+            t0 = osg::Timer::instance()->tick();
+        }
+    };
+    struct RttOut : public osg::Camera::DrawCallback {
+        void operator()(osg::RenderInfo& r) const {
+            t1 = osg::Timer::instance()->tick();
+            OE_NOTICE << "RTT = " << osg::Timer::instance()->delta_m(t0, t1) << "ms" << std::endl;
+        }
+    };
+#endif
 }
 
 ClampingTechnique::TechniqueProvider ClampingTechnique::Provider = s_providerImpl;
@@ -78,13 +102,6 @@ namespace
 #endif
 
          "uniform float oe_clamp_horizonDistance; \n"
-
-         // uniforms from ClampableNode:
-         "uniform vec2 oe_clamp_bias; \n"
-         "uniform vec2 oe_clamp_range; \n"
-
-         "varying vec4 oe_clamp_simvert; \n"
-         "varying float oe_clamp_simvertrange; \n"
          "varying float oe_clamp_alphaFactor; \n"
 
          "void oe_clamp_vertex(inout vec4 VertexVIEW) \n"
@@ -106,6 +123,9 @@ namespace
          //   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"
 
@@ -118,35 +138,11 @@ namespace
          "    p_depthView.z += gl_Vertex.z*gl_Vertex.w/p_depthView.w; \n"
 
               // then transform the vert back into camera view space.
-         "    vec4 v_view_clamped = oe_clamp_depthView2cameraView * p_depthView; \n"
+         "    VertexVIEW = oe_clamp_depthView2cameraView * p_depthView; \n"
 #else
               // transform the depth-clip point back into camera view coords.
-         "    vec4 v_view_clamped = oe_clamp_depthClip2cameraView * p_depthClip; \n"
+         "    VertexVIEW = oe_clamp_depthClip2cameraView * p_depthClip; \n"
 #endif
-
-
-         //   now simulate a "closer" vertex for depth offsetting.
-         //   remap depth offset based on camera distance to vertex. The farther you are away,
-         //   the more of an offset you need.
-
-         //   calculate the range to target:
-         "    vec3 v_view_clamped3 = v_view_clamped.xyz/v_view_clamped.w; \n"
-         "    float range = length(v_view_clamped3); \n"
-
-         //   calculate the depth offset bias for this range:
-         "    float ratio = (clamp(range, oe_clamp_range[0], oe_clamp_range[1])-oe_clamp_range[0])/(oe_clamp_range[1]-oe_clamp_range[0]);\n"
-         "    float bias = oe_clamp_bias[0] + ratio * (oe_clamp_bias[1]-oe_clamp_bias[0]);\n"
-
-         //   calculate the "simluated" vertex:
-         "    vec3 adj_vec = normalize(v_view_clamped3); \n"
-         "    vec3 v_view_offset3 = v_view_clamped3 - (adj_vec * bias); \n"
-
-         "    vec4 v_view_sim = vec4( v_view_offset3 * v_view_clamped.w, v_view_clamped.w ); \n"
-         "    oe_clamp_simvert = gl_ProjectionMatrix * v_view_sim;\n"
-         "    oe_clamp_simvertrange = range - bias; \n"
-
-         "    VertexVIEW = v_view_clamped; \n"
-         //"    gl_Position = gl_ProjectionMatrix * v_view_clamped; \n"
          "} \n";
 
 
@@ -155,21 +151,10 @@ namespace
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        "varying vec4 oe_clamp_simvert; \n"
-        "varying float oe_clamp_simvertrange; \n"
         "varying float oe_clamp_alphaFactor; \n"
 
         "void oe_clamp_fragment(inout vec4 color)\n"
         "{ \n"
-        "    float sim_depth = 0.5 * (1.0+(oe_clamp_simvert.z/oe_clamp_simvert.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_clamp_simvertrange < 0.0 ) { sim_depth = 0.0; } \n"
-        "    gl_FragDepth = max(0.0, sim_depth); \n"
-
              // adjust the alpha component to "hide" geometry beyond the visible horizon.
         "    color.a *= oe_clamp_alphaFactor; \n"
         "}\n";
@@ -194,7 +179,26 @@ namespace
         osg::ref_ptr<osg::Uniform>   _horizonDistanceUniform;
 
         unsigned _renderLeafCount;
+
+#ifdef DUMP_RTT_IMAGE
+        osg::ref_ptr<osg::Image> _rttDebugImage;
+#endif
     };
+
+#ifdef DUMP_RTT_IMAGE
+    struct DumpTex : public osg::Camera::DrawCallback
+    {
+        osg::ref_ptr<osg::Image> _tex;
+        DumpTex(osg::Image* tex) : _tex(tex) { }
+        void operator () (osg::RenderInfo& renderInfo) const
+        {
+            static int s_cc = 0;
+            if ( s_cc++ % 60 == 0 ) {
+                osgDB::writeImageFile(*_tex.get(), "rttimage.osgb");
+            }
+        }
+    };
+#endif
 }
 
 #ifdef USE_RENDER_BIN
@@ -301,7 +305,6 @@ ClampingTechnique::reestablish(TerrainEngineNode* engine)
     // nop.
 }
 
-
 void
 ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 {
@@ -327,19 +330,30 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     params._rttCamera = new osg::Camera();
     params._rttCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
     params._rttCamera->setClearColor( osg::Vec4f(0,0,0,0) );
-    params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+    params._rttCamera->setClearMask( GL_DEPTH_BUFFER_BIT );
     params._rttCamera->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
     params._rttCamera->setViewport( 0, 0, *_textureSize, *_textureSize );
     params._rttCamera->setRenderOrder( osg::Camera::PRE_RENDER );
     params._rttCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
+    params._rttCamera->setImplicitBufferAttachmentMask(0, 0);
     params._rttCamera->attach( osg::Camera::DEPTH_BUFFER, local->_rttTexture.get() );
 
+#ifdef DUMP_RTT_IMAGE
+    local->_rttDebugImage = new osg::Image();
+    local->_rttDebugImage->allocateImage(4096, 4096, 1, GL_RGB, GL_UNSIGNED_BYTE);
+    memset( (void*)local->_rttDebugImage->getDataPointer(), 0xff, local->_rttDebugImage->getTotalSizeInBytes() );
+    params._rttCamera->attach( osg::Camera::COLOR_BUFFER, local->_rttDebugImage.get() );
+    params._rttCamera->setFinalDrawCallback( new DumpTex(local->_rttDebugImage.get()) );
+#endif
+
+#ifdef TIME_RTT_CAMERA
+    params._rttCamera->setInitialDrawCallback( new RttIn() );
+    params._rttCamera->setFinalDrawCallback( new RttOut() );
+#endif
+
     // 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 );
-
     rttStateSet->setMode(
         GL_BLEND, 
         osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
@@ -348,40 +362,27 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     rttStateSet->setAttributeAndModes(
         new osg::PolygonMode( osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL ),
         osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-
-#if 0 //OOPS this kills things like a vertical scale shader!!
-    // installs a dirt-simple program for rendering the depth texture that
-    // skips all the normal terrain rendering stuff
-    osg::Program* depthProg = new osg::Program();
-    depthProg->addShader(new osg::Shader(
-        osg::Shader::VERTEX, 
-        "void main() { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; }\n"));
-    depthProg->addShader(new osg::Shader(
-        osg::Shader::FRAGMENT, 
-        "void main() { gl_FragColor = vec4(1,1,1,1); }\n"));
-    rttStateSet->setAttributeAndModes(
-        depthProg,
-        osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED );
-#endif
     
     // attach the terrain to the camera.
     // todo: should probably protect this with a mutex.....
-    params._rttCamera->addChild( _engine ); //params._terrainParent->getChild(0) ); // the terrain itself.
+    params._rttCamera->addChild( _engine ); // the terrain itself.
 
     // assemble the overlay graph stateset.
     local->_groupStateSet = new osg::StateSet();
 
+    // Required for now, otherwise GPU-clamped geometry will jitter sometimes.
+    // TODO: figure out why and fix it. This is a workaround for now.
+    local->_groupStateSet->setDataVariance( osg::Object::DYNAMIC );
+
     local->_groupStateSet->setTextureAttributeAndModes( 
         _textureUnit, 
         local->_rttTexture.get(), 
-        osg::StateAttribute::ON );
+        osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
 
-#if 1
     // set up depth test/write parameters for the overlay geometry:
     local->_groupStateSet->setAttributeAndModes(
         new osg::Depth( osg::Depth::LEQUAL, 0.0, 1.0, true ),
         osg::StateAttribute::ON );
-#endif
 
     local->_groupStateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
 
@@ -422,11 +423,10 @@ ClampingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
 #endif
 
     // make the shader that will do clamping and depth offsetting.
-    VirtualProgram* vp = new VirtualProgram();
+    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 );
-    local->_groupStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
 }
 
 
@@ -455,6 +455,9 @@ ClampingTechnique::preCullTerrain(OverlayDecorator::TechRTTParams& params,
 
 #endif
     }
+
+#ifdef TIME_RTT_CAMERA
+#endif
 }
 
 
@@ -470,12 +473,20 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
 
         LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
 
-        // prime our CPM with the current cull visitor:
-        //local._cpm->setup( cv );
+#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 );
 
+
         // construct a matrix that transforms from camera view coords to depth texture
         // clip coords directly. This will avoid precision loss in the 32-bit shader.
         static osg::Matrix s_scaleBiasMat = 
@@ -486,12 +497,13 @@ ClampingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
             osg::Matrix::translate(1.0,1.0,1.0) * 
             osg::Matrix::scale    (0.5,0.5,0.5) );
 
+        osg::Matrix vm;
+        vm.invert( *cv->getModelViewMatrix() );
         osg::Matrix cameraViewToDepthView =
-            cv->getCurrentCamera()->getInverseViewMatrix() * 
+            vm *
             params._rttViewMatrix;
 
         osg::Matrix depthViewToDepthClip = 
-            //local._cpm->_clampedDepthProjMatrix *
             params._rttProjMatrix *
             s_scaleBiasMat;
 
diff --git a/src/osgEarth/ColorFilter b/src/osgEarth/ColorFilter
index 458c35e..77f3fea 100644
--- a/src/osgEarth/ColorFilter
+++ b/src/osgEarth/ColorFilter
@@ -42,7 +42,7 @@ namespace osgEarth
          * The name of the function to call in the custom shader. This function
          * must have the signature:
          *
-         *    void function(in int slot, inout vec4 color)
+         *    void function(inout vec4 color)
          *
          * Failure to match this signature will result in a shader compilation error.
          *
diff --git a/src/osgEarth/CompositeTileSource.cpp b/src/osgEarth/CompositeTileSource.cpp
index 8bd01f5..f362510 100644
--- a/src/osgEarth/CompositeTileSource.cpp
+++ b/src/osgEarth/CompositeTileSource.cpp
@@ -20,6 +20,7 @@
 #include <osgEarth/ImageUtils>
 #include <osgEarth/StringUtils>
 #include <osgEarth/Registry>
+#include <osgEarth/Progress>
 #include <osgDB/FileNameUtils>
 
 #define LC "[CompositeTileSource] "
diff --git a/src/osgEarth/Config b/src/osgEarth/Config
index 4d7cb6d..f8fa86b 100644
--- a/src/osgEarth/Config
+++ b/src/osgEarth/Config
@@ -54,18 +54,18 @@ namespace osgEarth
     class OSGEARTH_EXPORT Config
     {
     public:
-        Config() : _emptyConfig(0L) { }
+        Config() { }
 
         Config( const std::string& key )
-            : _key(key), _emptyConfig(0L) { }
+            : _key(key) { }
 
         Config( const std::string& key, const std::string& value ) 
-            : _key( key ), _defaultValue( value ), _emptyConfig(0L) { }
+            : _key( key ), _defaultValue( value ) { }
 
         Config( const Config& rhs ) 
-            : _key(rhs._key), _defaultValue(rhs._defaultValue), _children(rhs._children), _referrer(rhs._referrer), _emptyConfig(0L), _refMap(rhs._refMap) { }
+            : _key(rhs._key), _defaultValue(rhs._defaultValue), _children(rhs._children), _referrer(rhs._referrer), _refMap(rhs._refMap) { }
 
-        virtual ~Config() { if ( _emptyConfig ) delete _emptyConfig; }
+        virtual ~Config();
 
         /** Context for resolving relative URIs that occur in this Config */
         void setReferrer( const std::string& value );
@@ -377,8 +377,7 @@ namespace osgEarth
         std::string _key;
         std::string _defaultValue;
         ConfigSet   _children;   
-        std::string _referrer;
-        Config*     _emptyConfig;
+        std::string _referrer;        
 
         RefMap _refMap;
     };
@@ -464,7 +463,7 @@ namespace osgEarth
     /**
      * Base class for all serializable options classes.
      */
-    class ConfigOptions // header-only (no export required)
+    class OSGEARTH_EXPORT ConfigOptions
     {
     public:
         ConfigOptions( const Config& conf =Config() )
@@ -472,7 +471,7 @@ namespace osgEarth
         ConfigOptions( const ConfigOptions& rhs )
             : _conf( rhs.getConfig() ) { }
 
-        virtual ~ConfigOptions() { }
+        virtual ~ConfigOptions();
         
         const std::string& referrer() const { return _conf.referrer(); }
 
@@ -506,14 +505,14 @@ namespace osgEarth
     /**
      * Base configoptions class for driver options.
      */
-    class DriverConfigOptions : public ConfigOptions // header-only (no export required)
+    class OSGEARTH_EXPORT DriverConfigOptions : public ConfigOptions
     {
     public:
         DriverConfigOptions( const ConfigOptions& rhs =ConfigOptions() )
             : ConfigOptions( rhs ) { fromConfig( _conf ); }
 
         /** dtor */
-        virtual ~DriverConfigOptions() { }
+        virtual ~DriverConfigOptions();
 
         /** Gets or sets the name of the driver to load */
         void setDriver( const std::string& value ) { _driver = value; }
diff --git a/src/osgEarth/Config.cpp b/src/osgEarth/Config.cpp
index 072c4e9..6be55d2 100644
--- a/src/osgEarth/Config.cpp
+++ b/src/osgEarth/Config.cpp
@@ -19,6 +19,7 @@
 #include <osgEarth/Config>
 #include <osgEarth/XmlUtils>
 #include <osgEarth/JsonUtils>
+#include <osgEarth/FileUtils>
 #include <osgDB/ReaderWriter>
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
@@ -28,6 +29,10 @@
 
 using namespace osgEarth;
 
+Config::~Config()
+{
+}
+
 void
 Config::setReferrer( const std::string& referrer )
 {
@@ -144,9 +149,22 @@ Config::find( const std::string& key, bool checkMe )
     return 0L;
 }
 
+/****************************************************************/
+ConfigOptions::~ConfigOptions()
+{
+}
+
+/****************************************************************/
+DriverConfigOptions::~DriverConfigOptions()
+{
+}
+
 namespace
 {
-    Json::Value conf2json( const Config& conf )
+    // Converts a Config to JSON. The "nicer" flag formats the data in a more 
+    // readable way than nicer=false. Nicer=true attempts to create JSON "objects",
+    // whereas nicer=false makes "$key" and "$children" members.
+    Json::Value conf2json( const Config& conf, bool nicer )
     {
         Json::Value value( Json::objectValue );
 
@@ -156,26 +174,95 @@ namespace
         }
         else
         {
-            if ( !conf.key().empty() )
-                value["$key"] = conf.key();
+            if ( !nicer )
+            {
+                if ( !conf.key().empty() )
+                {
+                    value["$key"] = conf.key();
+                }
+            }
 
             if ( !conf.value().empty() )
+            {
                 value["$value"] = conf.value();
+            }
 
             if ( conf.children().size() > 0 )
             {
-                Json::Value children( Json::arrayValue );
-                unsigned i = 0;
-                for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c )
+                if ( nicer )
                 {
-                    if ( c->isSimple() )
-                        value[c->key()] = c->value();
-                    else
-                        children[i++] = conf2json( *c );
+                    std::map< std::string, std::vector<Config> > sets;
+
+                    // sort into bins by name:
+                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c )
+                    {
+                        sets[c->key()].push_back( *c );
+                    }
+
+                    for( std::map<std::string,std::vector<Config> >::iterator i = sets.begin(); i != sets.end(); ++i )
+                    {
+                        if ( i->second.size() == 1 )
+                        {
+                            Config& c = i->second[0];
+                            if ( c.isSimple() )
+                                value[i->first] = c.value();
+                            else
+                                value[i->first] = conf2json(c, nicer);
+                        }
+                        else
+                        {
+                            std::string array_key = Stringify() << i->first << "_$set";
+                            Json::Value array_value( Json::arrayValue );
+                            for( std::vector<Config>::iterator j = i->second.begin(); j != i->second.end(); ++j )
+                            {
+                                array_value.append( conf2json(*j, nicer) );
+                            }
+                            value[array_key] = array_value;
+                        }
+                    }
+
+#if 0
+                    bool hasdupes = false;
+                    std::set<std::string> dupes;
+                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c ) {
+                        if ( dupes.find( c->key() ) != dupes.end() ) {
+                            hasdupes = true;
+                            break;
+                        }
+                        else {
+                            dupes.insert(c->key());
+                        }
+                    }
+
+                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c )
+                    {
+                        if ( hasdupes )
+                            children[i++] = conf2json(*c, nicer);
+                        else if ( c->isSimple() )
+                            value[c->key()] = c->value();
+                        else
+                            value[c->key()] = conf2json(*c, nicer);
+                    }
+#endif
+                }
+                else
+                {
+                    Json::Value children( Json::arrayValue );
+                    unsigned i = 0;
+
+                    for( ConfigSet::const_iterator c = conf.children().begin(); c != conf.children().end(); ++c )
+                    {
+                        if ( c->isSimple() )
+                            value[c->key()] = c->value();
+                        else
+                            children[i++] = conf2json(*c, nicer);
+                    }
+
+                    if ( !children.empty() )
+                    {
+                        value["$children"] = children;
+                    }
                 }
-
-                if ( !children.empty() )
-                    value["$children"] = children;
             }
         }
 
@@ -202,7 +289,24 @@ namespace
             for( Json::Value::Members::const_iterator i = members.begin(); i != members.end(); ++i )
             {
                 const Json::Value& value = json[*i];
-                if ( (*i) == "$key" )
+
+                if ( value.isObject() )
+                {
+                    Config element( *i );
+                    json2conf( value, element );
+                    conf.add( element );
+                }
+                else if ( value.isArray() && endsWith(*i, "_$set") )
+                {
+                    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 );
+                        conf.add( child );
+                    }
+                }
+                else if ( (*i) == "$key" )
                 {
                     conf.key() = value.asString();
                 }
@@ -214,6 +318,12 @@ namespace
                 {
                     json2conf( value, conf );
                 }
+                else if ( value.isArray() )
+                {
+                    Config element( *i );
+                    json2conf( value, element );
+                    conf.add( element );
+                }
                 else
                 {
                     conf.add( *i, value.asString() );
@@ -232,7 +342,7 @@ namespace
         }
         else if ( json.type() != Json::nullValue )
         {
-            //conf.value() = json.asString();
+            conf.value() = json.asString();
         }
     }
 }
@@ -240,7 +350,7 @@ namespace
 std::string
 Config::toJSON( bool pretty ) const
 {
-    Json::Value root = conf2json( *this );
+    Json::Value root = conf2json( *this, pretty );
     if ( pretty )
         return Json::StyledWriter().write( root );
     else
diff --git a/src/osgEarth/Containers b/src/osgEarth/Containers
index 8de45ae..e4bec1d 100644
--- a/src/osgEarth/Containers
+++ b/src/osgEarth/Containers
@@ -125,14 +125,10 @@ namespace osgEarth
             Record(const T& value) : _value(value), _valid(true) { }
             const bool valid() const { return _valid; }
             const T& value() const { return _value; }
-            //Record(const T* value) : _value(value) { }
-            //const bool valid() const { return _value != 0L; }
-            //const T& value() const { return *_value; }
         private:
             bool _valid;
             T    _value;
             friend class LRUCache;
-            //const T* _value;
         };
 
     protected:
diff --git a/src/osgEarth/Cube b/src/osgEarth/Cube
index 3622a2a..0d7aad0 100644
--- a/src/osgEarth/Cube
+++ b/src/osgEarth/Cube
@@ -88,13 +88,13 @@ namespace osgEarth
      * osgTerrain locator for positioning data on the terrain using a cube-face
      * coordinate system.
      */
-    class CubeFaceLocator : public GeoLocator
+    class OSGEARTH_EXPORT CubeFaceLocator : public GeoLocator
     {
     public:
         CubeFaceLocator(unsigned int face);
 
         /** dtor */
-        virtual ~CubeFaceLocator() { }
+        virtual ~CubeFaceLocator();
 
         // This method will generate geocentric vertex coordinates, given local tile
         // coordinates (0=>1).
@@ -113,13 +113,13 @@ namespace osgEarth
      * the cube as whole lays out all six faces side by side, resulting in a space 
      * measuring (0,0=>6,1). The face number corresponds to the x-axis ordinal.
      */
-    class CubeSpatialReference : public SpatialReference
+    class OSGEARTH_EXPORT CubeSpatialReference : public SpatialReference
     {
     public:
         CubeSpatialReference(void* handle);
 
         /** dtor */
-        virtual ~CubeSpatialReference() { }
+        virtual ~CubeSpatialReference();
 
         virtual GeoLocator* createLocator(
             double xmin, double ymin, double xmax, double ymax,
@@ -175,6 +175,8 @@ namespace osgEarth
     public:
         UnifiedCubeProfile();
 
+        virtual ~UnifiedCubeProfile();
+
     public: // utilities
 
         /**
diff --git a/src/osgEarth/Cube.cpp b/src/osgEarth/Cube.cpp
index fe34101..c0c449d 100644
--- a/src/osgEarth/Cube.cpp
+++ b/src/osgEarth/Cube.cpp
@@ -299,6 +299,10 @@ _face(face)
     //NOP
 }
 
+CubeFaceLocator::~CubeFaceLocator()
+{
+}
+
 
 bool
 CubeFaceLocator::convertLocalToModel( const osg::Vec3d& local, osg::Vec3d& world ) const
@@ -395,6 +399,10 @@ SpatialReference( handle, "OSGEARTH" )
     _name      = "Unified Cube";
 }
 
+CubeSpatialReference::~CubeSpatialReference()
+{
+}
+
 void
 CubeSpatialReference::_init()
 {
@@ -715,3 +723,7 @@ UnifiedCubeProfile::getIntersectingTiles(
         }
     }
 }
+
+UnifiedCubeProfile::~UnifiedCubeProfile()
+{
+}
diff --git a/src/osgEarth/CullingUtils b/src/osgEarth/CullingUtils
index 06432cf..a1b252c 100644
--- a/src/osgEarth/CullingUtils
+++ b/src/osgEarth/CullingUtils
@@ -22,6 +22,7 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/optional>
+#include <osgEarth/SpatialReference>
 #include <osg/NodeCallback>
 #include <osg/ClusterCullingCallback>
 #include <osg/CoordinateSystemNode>
@@ -137,21 +138,36 @@ namespace osgEarth
      * and a world point and doesn't draw if there are intersections with the node.
      */
     struct OSGEARTH_EXPORT OcclusionCullingCallback : public osg::NodeCallback {
-        OcclusionCullingCallback( const osg::Vec3d& world, osg::Node* node);
+        OcclusionCullingCallback( const SpatialReference* srs, const osg::Vec3d& world, osg::Node* node);
 
         const osg::Vec3d& getWorld() const;
         void setWorld( const osg::Vec3d& world);
-        double getMaxRange() const;
-        void setMaxRange( double maxRange);
+        double getMaxAltitude() const;
+        void setMaxAltitude( double value);
         void operator()(osg::Node* node, osg::NodeVisitor* nv);
 
+        /**
+        * Gets the maximum number of ms that the OcclusionCullingCallback can run on each frame.
+        */
+        static double getMaxFrameTime();
+
+        /**
+        * Sets the maximum number of ms that the OcclusionCullingCallback can run on each frame.
+        * @param ms
+        *     The maximum number of milliseconds to run the OcclusionCullingCallback on each frame. 
+        */
+        static void setMaxFrameTime( double ms );
+
+    private:
+
         osg::ref_ptr< osg::Node > _node;
+        osg::ref_ptr< const osgEarth::SpatialReference > _srs;
         osg::Vec3d _world;
         osg::Vec3d _prevWorld;
         osg::Vec3d _prevEye;
         bool _visible;
-        double _maxRange;
-        double _maxRange2;
+        double _maxAltitude;
+        static double _maxFrameTime;
     };
 
     /**
@@ -211,6 +227,42 @@ namespace osgEarth
         void apply(osg::Transform& node);
         void apply(osg::Geode& node);
     };
+
+
+    /**
+     * Horizon culling in a shader program.
+     */
+    class OSGEARTH_EXPORT HorizonCullingProgram
+    {
+    public:
+        static void install( osg::StateSet* stateset );
+        static void remove ( osg::StateSet* stateset );
+    };
+
+
+    /**
+     * Group that lets you adjust the LOD scale on its children.
+     */
+    class OSGEARTH_EXPORT LODScaleGroup : public osg::Group
+    {
+    public:
+        LODScaleGroup();
+
+        /**
+         * Factor by which to multiply the camera's LOD scale.
+         */
+        void setLODScaleFactor( float value ) { _scaleFactor = value; }
+        float getLODScaleFactor() const { return _scaleFactor; }
+
+    public: // osg::Group
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        virtual ~LODScaleGroup() { }
+
+    private:
+        float _scaleFactor;
+    };
 }
 
 #endif // OSGEARTH_CULLING_UTILS_H
diff --git a/src/osgEarth/CullingUtils.cpp b/src/osgEarth/CullingUtils.cpp
index c004581..dc6110f 100644
--- a/src/osgEarth/CullingUtils.cpp
+++ b/src/osgEarth/CullingUtils.cpp
@@ -18,10 +18,14 @@
  */
 #include <osgEarth/CullingUtils>
 #include <osgEarth/LineFunctor>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/DPLineSegmentIntersector>
+#include <osgEarth/GeoData>
 #include <osg/ClusterCullingCallback>
 #include <osg/PrimitiveSet>
 #include <osg/Geode>
 #include <osg/TemplatePrimitiveFunctor>
+#include <osgGA/GUIActionAdapter>
 #include <osgUtil/CullVisitor>
 #include <osgUtil/IntersectionVisitor>
 #include <osgUtil/LineSegmentIntersector>
@@ -597,13 +601,27 @@ DisableSubgraphCulling::operator()(osg::Node* n, osg::NodeVisitor* v)
 
 //------------------------------------------------------------------------
 
-OcclusionCullingCallback::OcclusionCullingCallback(const osg::Vec3d& world, osg::Node* node):
-_world(world),
-_node( node ),
-_visible( true ),
-_maxRange(200000),
-_maxRange2(_maxRange * _maxRange)
+// The max frame time in ms
+double OcclusionCullingCallback::_maxFrameTime = 10.0;
+
+OcclusionCullingCallback::OcclusionCullingCallback(const osgEarth::SpatialReference *srs, const osg::Vec3d& world, osg::Node* node):
+_srs        ( srs ),
+_world      ( world ),
+_node       ( node ),
+_visible    ( true ),
+_maxAltitude( 200000 )
+{
+    //nop
+}
+
+double OcclusionCullingCallback::getMaxFrameTime()
+{
+    return _maxFrameTime;
+}
+
+void OcclusionCullingCallback::setMaxFrameTime( double ms )
 {
+    _maxFrameTime = ms;
 }
 
 const osg::Vec3d& OcclusionCullingCallback::getWorld() const
@@ -616,50 +634,100 @@ void OcclusionCullingCallback::setWorld( const osg::Vec3d& world)
     _world = world;
 }
 
-double OcclusionCullingCallback::getMaxRange() const
+double OcclusionCullingCallback::getMaxAltitude() const
 {
-    return _maxRange;
+    return _maxAltitude;
 }
 
-void OcclusionCullingCallback::setMaxRange( double maxRange)
+void OcclusionCullingCallback::setMaxAltitude( double maxAltitude )
 {
-    _maxRange = maxRange;
-    _maxRange2 = maxRange * maxRange;
+    _maxAltitude = maxAltitude;    
 }
 
 void OcclusionCullingCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
 {
     if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
-    {
-        osg::Vec3d eye, center, up;
+    {        
         osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
 
-        cv->getCurrentCamera()->getViewMatrixAsLookAt(eye,center,up);
+        static int frameNumber = -1;
+        static double remainingTime = OcclusionCullingCallback::_maxFrameTime;
+        static int numCompleted = 0;
+        static int numSkipped = 0;
+
+        if (nv->getFrameStamp()->getFrameNumber() != frameNumber)
+        {
+            if (numCompleted > 0 || numSkipped > 0)
+            {
+                OE_DEBUG << "OcclusionCullingCallback frame=" << frameNumber << " completed=" << numCompleted << " skipped=" << numSkipped << std::endl;
+            }
+            frameNumber = nv->getFrameStamp()->getFrameNumber();
+            numCompleted = 0;
+            numSkipped = 0;
+            remainingTime = OcclusionCullingCallback::_maxFrameTime;
+        }
+
+        osg::Vec3d eye = cv->getViewPoint();
 
         if (_prevEye != eye || _prevWorld != _world)
         {
-            double range = (eye-_world).length2();
-            //Only do the intersection if we are close enough for it to matter
-            if (range <= _maxRange2 && _node.valid())
+            if (remainingTime > 0.0)
             {
-                //Compute the intersection from the eye to the world point
-                osg::Vec3d start = eye;
-                osg::Vec3d end = _world;
-                osgUtil::LineSegmentIntersector* i = new osgUtil::LineSegmentIntersector( start, end );
-                i->setIntersectionLimit( osgUtil::Intersector::LIMIT_ONE );
-                osgUtil::IntersectionVisitor iv;
-                iv.setIntersector( i );
-                _node->accept( iv );
-                osgUtil::LineSegmentIntersector::Intersections& results = i->getIntersections();
-                _visible = results.empty();
+                double alt = 0.0;
+
+                if ( _srs && !_srs->isProjected() )
+                {
+                    osgEarth::GeoPoint mapPoint;
+                    mapPoint.fromWorld( _srs.get(), eye );
+                    alt = mapPoint.z();
+                }
+                else
+                {
+                    alt = eye.z();
+                }
+
+
+                //Only do the intersection if we are close enough for it to matter
+                if (alt <= _maxAltitude && _node.valid())
+                {
+                    //Compute the intersection from the eye to the world point
+                    osg::Timer_t startTick = osg::Timer::instance()->tick();
+                    osg::Vec3d start = eye;
+                    osg::Vec3d end = _world;
+                    DPLineSegmentIntersector* i = new DPLineSegmentIntersector( start, end );
+                    i->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
+                    osgUtil::IntersectionVisitor iv;
+                    iv.setIntersector( i );
+                    _node->accept( iv );
+                    osgUtil::LineSegmentIntersector::Intersections& results = i->getIntersections();
+                    _visible = results.empty();
+                    osg::Timer_t endTick = osg::Timer::instance()->tick();
+                    double elapsed = osg::Timer::instance()->delta_m( startTick, endTick );
+                    remainingTime -= elapsed;
+                }
+                else
+                {
+                    _visible = true;
+                }
+
+                numCompleted++;
+
+                _prevEye = eye;
+                _prevWorld = _world;
             }
             else
             {
-                _visible = true;
+                numSkipped++;
+                // if we skipped some we need to request a redraw so the remianing ones get processed on the next frame.
+                if ( cv->getCurrentCamera() && cv->getCurrentCamera()->getView() )
+                {
+                    osgGA::GUIActionAdapter* aa = dynamic_cast<osgGA::GUIActionAdapter*>(cv->getCurrentCamera()->getView());
+                    if ( aa )
+                    {
+                        aa->requestRedraw();
+                    }
+                }
             }
-
-            _prevEye = eye;
-            _prevWorld = _world;
         }
 
         if (_visible)
@@ -696,6 +764,10 @@ _cv             ( cv )
     this->setImageRequestHandler( _cv->getImageRequestHandler() );
     this->setUserData( _cv->getUserData() );
     this->setComputeNearFarMode( _cv->getComputeNearFarMode() );
+
+    this->pushViewport( _cv->getViewport() );
+    this->pushProjectionMatrix( _cv->getProjectionMatrix() );
+    this->pushModelViewMatrix( _cv->getModelViewMatrix(), osg::Transform::ABSOLUTE_RF );
 }
 
 osg::Vec3 
@@ -902,3 +974,95 @@ ProxyCullVisitor::apply(osg::Geode& node)
 
     _cv->popFromNodePath();
 }
+
+//-------------------------------------------------------------------------
+
+namespace
+{
+    const char* horizon_vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "uniform mat4 osg_ViewMatrix; \n"
+        "varying float oe_horizon_alpha; \n"
+        "void oe_horizon_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    const float scale     = 0.001; \n"                 // scale factor keeps dots&crosses in SP range
+        "    const float radiusMax = 6371000.0 * scale; \n"
+        "    vec3  originVIEW = (osg_ViewMatrix * vec4(0,0,0,1)).xyz * scale; \n"
+        "    vec3  x1 = vec3(0,0,0) - originVIEW; \n"              // vector from origin -> camera
+        "    vec3  x2 = (VertexVIEW.xyz * scale) - originVIEW; \n" // vector from origin -> vertex
+        "    vec3  v  = x2-x1; \n"
+        "    float vlen = length(v); \n"
+        "    float t = -dot(x1,v)/(vlen*vlen); \n"
+        "    bool visible = false; \n"
+        "    if ( t > 1.0 || t < 0.0 ) { \n"
+        "        oe_horizon_alpha = 1.0; \n"
+        "    } \n"
+        "    else { \n"
+        "        float d = length(cross(x1,x2)) / vlen; \n"
+        "        oe_horizon_alpha = d >= radiusMax ? 1.0 : 0.0; \n"
+        "    } \n"
+        "} \n";
+
+    const char* horizon_fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "varying float oe_horizon_alpha; \n"
+        "void oe_horizon_fragment(inout vec4 color) \n"
+        "{ \n"
+        "    color.a *= oe_horizon_alpha; \n"
+        "} \n";
+}
+
+
+void
+HorizonCullingProgram::install(osg::StateSet* stateset)
+{
+    if ( stateset )
+    {
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setFunction( "oe_horizon_vertex",   horizon_vs, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_horizon_fragment", horizon_fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    }
+}
+
+void
+HorizonCullingProgram::remove(osg::StateSet* stateset)
+{
+    if ( stateset )
+    {
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
+        {
+            vp->removeShader( "oe_horizon_vertex" );
+            vp->removeShader( "oe_horizon_fragment" );
+        }
+    }
+}
+
+//----------------------------------------------------------------
+
+LODScaleGroup::LODScaleGroup() :
+_scaleFactor( 1.0f )
+{
+    //nop
+}
+
+void
+LODScaleGroup::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        osg::CullStack* cs = dynamic_cast<osg::CullStack*>( &nv );
+        if ( cs )
+        {
+            float lodscale = cs->getLODScale();
+            cs->setLODScale( lodscale * _scaleFactor );
+            std::for_each( _children.begin(), _children.end(), osg::NodeAcceptOp(nv));
+            cs->setLODScale( lodscale );
+            return;
+        }
+    }
+
+    osg::Group::traverse( nv );
+}
diff --git a/src/osgEarth/DateTime b/src/osgEarth/DateTime
new file mode 100644
index 0000000..32b42a5
--- /dev/null
+++ b/src/osgEarth/DateTime
@@ -0,0 +1,74 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_DATE_TIME_H
+#define OSGEARTH_DATE_TIME_H
+
+#include <osgEarth/Common>
+#include <ctime>
+#include <cstring>
+
+namespace osgEarth
+{
+    /** Basic timestamp (seconds from the epoch) */
+    typedef ::time_t TimeStamp;
+
+    /** Time span (in seconds) */
+    typedef long TimeSpan;
+
+    /**
+     * General-purpose UTC date/time object.
+     */
+    class OSGEARTH_EXPORT DateTime
+    {
+    public:
+        /** DateTime representing "now" */
+        DateTime();
+
+        /** DateTime copy */
+        DateTime(const DateTime& rhs);
+
+        /** DateTime from a tm */
+        DateTime(const ::tm& tm);
+
+        /** DateTime from UTC seconds since the epoch */
+        DateTime(TimeStamp utc);
+
+        /** DateTime from year, month, date, hours */
+        DateTime(int year, int month, int day, double hours);
+
+        /** As a date/time string in RFC 1123 format */
+        const std::string asRFC1123() const;
+
+    public:
+        int    year()  const;
+        int    month() const;
+        int    day()   const;
+        double hours() const;
+
+        TimeStamp   asTimeStamp() const { return _time_t; }
+        const ::tm& as_tm()       const { return _tm; }
+
+    protected:
+        ::tm     _tm;
+        ::time_t _time_t;
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_DATE_TIME_H
diff --git a/src/osgEarth/DateTime.cpp b/src/osgEarth/DateTime.cpp
new file mode 100644
index 0000000..40f7c40
--- /dev/null
+++ b/src/osgEarth/DateTime.cpp
@@ -0,0 +1,134 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarth/DateTime>
+#include <osgEarth/StringUtils>
+#include <math.h>
+#include <iomanip>
+
+using namespace osgEarth;
+
+namespace
+{
+    // from RFC 1123, RFC 850
+
+    const char* rfc_wkday[7] = {
+        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+    };
+
+    const char* rfc_weekday[7] = {
+        "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
+    };
+
+    const char* rfc_month[12] = {
+        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+    };
+}
+
+//------------------------------------------------------------------------
+
+DateTime::DateTime()
+{
+    ::time( &_time_t );
+    tm* temp = ::gmtime( &_time_t );
+    if ( temp ) _tm = *temp;
+    else memset( &_tm, 0, sizeof(tm) );
+}
+
+DateTime::DateTime(TimeStamp utc)
+{
+    _time_t = utc;
+    tm* temp = ::gmtime( &_time_t );
+    if ( temp ) _tm = *temp;
+    else memset( &_tm, 0, sizeof(tm) );
+}
+
+DateTime::DateTime(const ::tm& in_tm)
+{
+    tm temptm = in_tm;
+    _time_t = ::mktime( &temptm );
+    tm* temp = ::gmtime( &_time_t );
+    if ( temp ) _tm = *temp;
+    else memset( &_tm, 0, sizeof(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);
+
+    // now go to time_t, and back to tm, to populate the rest of the fields.
+    _time_t =  ::mktime( &_tm );
+    tm* temp = ::gmtime( &_time_t );
+    if ( temp ) _tm = *temp;
+    else memset( &_tm, 0, sizeof(tm) );
+}
+
+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.;
+}
+
+const std::string
+DateTime::asRFC1123() const
+{
+    return Stringify()
+        << rfc_wkday[_tm.tm_wday] << ", "
+        << std::setfill('0') << std::setw(2) << _tm.tm_mday << ' '
+        << rfc_month[_tm.tm_mon] << ' '
+        << std::setw(4) << (1900 + _tm.tm_year) << ' '
+        << std::setw(2) << _tm.tm_hour << ':'
+        << std::setw(2) << _tm.tm_min << ':'
+        << std::setw(2) << _tm.tm_sec << ' '
+        << "GMT";
+}
diff --git a/src/osgEarthAnnotation/Decluttering b/src/osgEarth/Decluttering
similarity index 87%
rename from src/osgEarthAnnotation/Decluttering
rename to src/osgEarth/Decluttering
index 686c39c..a59a744 100644
--- a/src/osgEarthAnnotation/Decluttering
+++ b/src/osgEarth/Decluttering
@@ -16,10 +16,10 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ANNOTATION_DECLUTTER_RENDER_BIN_H
-#define OSGEARTH_ANNOTATION_DECLUTTER_RENDER_BIN_H 1
+#ifndef OSGEARTH_DECLUTTER_RENDER_BIN_H
+#define OSGEARTH_DECLUTTER_RENDER_BIN_H 1
 
-#include <osgEarthAnnotation/Common>
+#include <osgEarth/Common>
 #include <osgEarth/Config>
 #include <osg/Drawable>
 #include <osgUtil/RenderLeaf>
@@ -31,9 +31,9 @@
  * Decluttering::setEnabled( node->getOrCreateStateSet(), true );
  */
 
-#define OSGEARTH_DECLUTTER_BIN "declutter"
+#define OSGEARTH_DECLUTTER_BIN "osgearth_declutter"
 
-namespace osgEarth { namespace Annotation 
+namespace osgEarth
 {
     /**
      * Marker class hinting that an implementation supports the decluttering
@@ -45,6 +45,19 @@ namespace osgEarth { namespace Annotation
     };
 
     /**
+     * Interface that exposes set/getPriority for priority sorting
+     */
+    class PriorityProvider
+    {
+    public:
+        virtual void setPriority(float value) =0;
+        virtual float getPriority() const =0;
+
+    protected:
+        virtual ~PriorityProvider() { }
+    };
+
+    /**
      * Custom functor that compares two RenderLeaf's and returns TRUE if the left-hand one
      * is higher priority, otherwise FALSE. You can call setDeclutterPriorityFunctor()
      * to set a custom priority-sorting functor.
@@ -59,7 +72,7 @@ namespace osgEarth { namespace Annotation
      * A decluttering functor that sorts by the priority field in AnnotationData.
      * AnnotationData should be attached to each Drawable's user data.
      */
-    struct OSGEARTHANNO_EXPORT DeclutterByPriority : public DeclutterSortFunctor
+    struct OSGEARTH_EXPORT DeclutterByPriority : public DeclutterSortFunctor
     {
         virtual bool operator()(const osgUtil::RenderLeaf* lhs, const osgUtil::RenderLeaf* rhs ) const;
         virtual ~DeclutterByPriority() { }
@@ -68,7 +81,7 @@ namespace osgEarth { namespace Annotation
     /**
      * Options to control the annotation decluttering engine.
      */
-    class OSGEARTHANNO_EXPORT DeclutteringOptions
+    class OSGEARTH_EXPORT DeclutteringOptions
     {
     public:
         DeclutteringOptions( const Config& conf =Config() )
@@ -123,12 +136,12 @@ namespace osgEarth { namespace Annotation
         void fromConfig( const Config& conf );
     };
 
-    struct OSGEARTHANNO_EXPORT Decluttering
+    struct OSGEARTH_EXPORT Decluttering
     {
         /**
          * Enables or disables decluttering on a stateset.
          */
-        static void setEnabled( osg::StateSet* stateSet, bool enabled, int binNum =INT_MAX );
+        static void setEnabled( osg::StateSet* stateSet, bool enabled, int binNum =13 );
 
         /**
          * Enables or disables decluttering globally.
@@ -157,6 +170,6 @@ namespace osgEarth { namespace Annotation
         static const DeclutteringOptions& getOptions();
     };
 
-} } // namespace osgEarth::Annotation
+} // namespace osgEarth
 
-#endif //OSGEARTH_ANNOTATION_DECLUTTER_RENDER_BIN_H
+#endif //OSGEARTH_DECLUTTER_RENDER_BIN_H
diff --git a/src/osgEarthAnnotation/Decluttering.cpp b/src/osgEarth/Decluttering.cpp
similarity index 96%
rename from src/osgEarthAnnotation/Decluttering.cpp
rename to src/osgEarth/Decluttering.cpp
index 791379d..7a5f13c 100644
--- a/src/osgEarthAnnotation/Decluttering.cpp
+++ b/src/osgEarth/Decluttering.cpp
@@ -16,9 +16,8 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#include <osgEarthAnnotation/Decluttering>
-#include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarthAnnotation/AnnotationData>
+#include <osgEarth/Decluttering>
+//#include <osgEarthAnnotation/AnnotationData>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/Utils>
 #include <osgEarth/VirtualProgram>
@@ -34,7 +33,6 @@
 #define FADE_UNIFORM_NAME "oe_declutter_fade"
 
 using namespace osgEarth;
-using namespace osgEarth::Annotation;
 
 //----------------------------------------------------------------------------
 
@@ -566,10 +564,10 @@ struct DeclutterDraw : public osgUtil::RenderBin::DrawCallback
  * This wants to be in the global scope for the dynamic registration to work,
  * hence the annoyinging long class name
  */
-class osgEarthAnnotationDeclutterRenderBin : public osgUtil::RenderBin
+class osgEarthDeclutterRenderBin : public osgUtil::RenderBin
 {
 public:
-    osgEarthAnnotationDeclutterRenderBin()
+    osgEarthDeclutterRenderBin()
     {
         this->setName( OSGEARTH_DECLUTTER_BIN );
         _context = new DeclutterContext();
@@ -581,9 +579,9 @@ public:
         this->setStateSet( stateSet );
 
         // set up a VP to do fading.
-        VirtualProgram* vp = new VirtualProgram();
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateSet);
         vp->setFunction( "oe_declutter_apply_fade", s_faderFS, ShaderComp::LOCATION_FRAGMENT_COLORING );
-        stateSet->setAttributeAndModes(vp, 1);
+        //stateSet->setAttributeAndModes(vp, 1);
     }
 
     void setSortingFunctor( DeclutterSortFunctor* f )
@@ -668,7 +666,7 @@ void
 Decluttering::setSortFunctor( DeclutterSortFunctor* functor )
 {
     // pull our prototype
-    osgEarthAnnotationDeclutterRenderBin* bin = dynamic_cast<osgEarthAnnotationDeclutterRenderBin*>(
+    osgEarthDeclutterRenderBin* bin = dynamic_cast<osgEarthDeclutterRenderBin*>(
         osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_DECLUTTER_BIN ) );
 
     if ( bin )
@@ -681,7 +679,7 @@ void
 Decluttering::clearSortFunctor()
 {
     // pull our prototype
-    osgEarthAnnotationDeclutterRenderBin* bin = dynamic_cast<osgEarthAnnotationDeclutterRenderBin*>(
+    osgEarthDeclutterRenderBin* bin = dynamic_cast<osgEarthDeclutterRenderBin*>(
         osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_DECLUTTER_BIN ) );
 
     if ( bin )
@@ -694,7 +692,7 @@ void
 Decluttering::setOptions( const DeclutteringOptions& options )
 {
     // pull our prototype
-    osgEarthAnnotationDeclutterRenderBin* bin = dynamic_cast<osgEarthAnnotationDeclutterRenderBin*>(
+    osgEarthDeclutterRenderBin* bin = dynamic_cast<osgEarthDeclutterRenderBin*>(
         osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_DECLUTTER_BIN ) );
 
     if ( bin )
@@ -717,7 +715,7 @@ Decluttering::getOptions()
     static DeclutteringOptions s_defaultOptions;
 
     // pull our prototype
-    osgEarthAnnotationDeclutterRenderBin* bin = dynamic_cast<osgEarthAnnotationDeclutterRenderBin*>(
+    osgEarthDeclutterRenderBin* bin = dynamic_cast<osgEarthDeclutterRenderBin*>(
         osgUtil::RenderBin::getRenderBinPrototype( OSGEARTH_DECLUTTER_BIN ) );
 
     if ( bin )
@@ -736,10 +734,10 @@ bool
 DeclutterByPriority::operator()(const osgUtil::RenderLeaf* lhs, const osgUtil::RenderLeaf* rhs ) const
 {
     float diff = 0.0f;
-    const AnnotationData* lhsData = dynamic_cast<const AnnotationData*>(lhs->getDrawable()->getUserData());
+    const PriorityProvider* lhsData = dynamic_cast<const PriorityProvider*>(lhs->getDrawable()->getUserData());
     if ( lhsData )
     {
-        const AnnotationData* rhsData = dynamic_cast<const AnnotationData*>(rhs->getDrawable()->getUserData());
+        const PriorityProvider* rhsData = dynamic_cast<const PriorityProvider*>(rhs->getDrawable()->getUserData());
         if ( rhsData )
         {
             diff = lhsData->getPriority() - rhsData->getPriority();
@@ -763,4 +761,4 @@ DeclutterByPriority::operator()(const osgUtil::RenderLeaf* lhs, const osgUtil::R
 
 /** the actual registration. */
 extern "C" void osgEarth_declutter(void) {}
-static osgEarthRegisterRenderBinProxy<osgEarthAnnotationDeclutterRenderBin> s_regbin(OSGEARTH_DECLUTTER_BIN);
+static osgEarthRegisterRenderBinProxy<osgEarthDeclutterRenderBin> s_regbin(OSGEARTH_DECLUTTER_BIN);
diff --git a/src/osgEarth/DepthOffset b/src/osgEarth/DepthOffset
index aa432e7..b91b01c 100644
--- a/src/osgEarth/DepthOffset
+++ b/src/osgEarth/DepthOffset
@@ -79,6 +79,10 @@ namespace osgEarth
         optional<float>& maxRange() { return _maxRange; }
         const optional<float>& maxRange() const { return _maxRange; }
 
+        /** automatic calculation of the minRange based on geometry analysis */
+        optional<bool>& automatic() { return _auto; }
+        const optional<bool>& automatic() const { return _auto; }
+
     public:
         Config getConfig() const;
 
@@ -88,98 +92,59 @@ namespace osgEarth
         optional<float> _maxBias;
         optional<float> _minRange;
         optional<float> _maxRange;
+        optional<bool>  _auto;
     };
 
 
     /**
-     * Controller that affects a stateset with depth offset settings.
-     * It does NOT install any shaders.
+     * Interface for adding depth offset methods to anothe class without
+     * disturbing the class hierarchy. Use this in conjuction with the
+     * Adapter.
      */
-    class OSGEARTH_EXPORT DepthOffsetOptionsAdapter
+    class DepthOffsetInterface
     {
     public:
-        DepthOffsetOptionsAdapter(osg::StateSet* stateSet);
-
-        void setOptions(const DepthOffsetOptions& options);
-        const DepthOffsetOptions& getOptions() const { return _options; }
-
-    private:
-        osg::ref_ptr<osg::StateSet> _stateSet;
-        osg::ref_ptr<osg::Uniform>  _biasUniform;
-        osg::ref_ptr<osg::Uniform>  _rangeUniform;
-        DepthOffsetOptions          _options;
+        virtual void setDepthOffsetOptions( const DepthOffsetOptions& options ) =0;
+        virtual const DepthOffsetOptions& getDepthOffsetOptions() const =0;
     };
 
 
     /**
-     * Utilities to manage depth testing for feature data. Handy especially
-     * for terrain-conforming lines.
+     * Adapter that affects depth offset functionality on a graph.
      */
-    class OSGEARTH_EXPORT DepthOffsetUtils
+    class OSGEARTH_EXPORT DepthOffsetAdapter : public DepthOffsetInterface
     {
     public:
-        /**
-         * Creates a uniform that will configure the depth adjustment program.
-         * The value of the uniform is the minimum depth offset applied to 
-         * geometry under the program's stateset. If you pass in a graph, it
-         * will analyze it and attempt to come up with a reasonable default
-         * minimum offset.
-         */
-        static osg::Uniform* createMinOffsetUniform( osg::Node* graphToAdjust =0L );
+        DepthOffsetAdapter();
+        DepthOffsetAdapter(osg::Node* graph);
 
-        /**
-         * Analyses a graph, calculates a suitable minimum depth offset, and
-         * returns it. Also may install support uniforms within the graph as
-         * necessary to support depth offsetting.
-         */
-        static float recalculate( const osg::Node* graph );
+        void setGraph(osg::Node* graph );
+        void recalculate();
 
-        /**
-         * Traverses a graph and applies the necessary uniforms to statesets
-         * so they'll work with depth offsetting.
-         */
-        static void prepareGraph( osg::Node* graph );
-
-        /**
-         * Creates a complete shader program that you can use to implement vertex
-         * depth adjustment. Use createUniform() to make a uniform for tweaking
-         * the depth offset value.
-         */
-        static osg::Program* getOrCreateProgram();
+        bool isDirty() const { return _dirty; }
 
-        /**
-         * Returns a uniform that, when used with the Program, can inform the program
-         * whether the underlying drawables are osgText drawables.
-         */
-        static osg::Uniform* getIsTextUniform();
-
-        /**
-         * Returns a uniform that, when used with the Program, can inform the program
-         * whether the underlying drawables are NOT osgText drawables.
-         */
-        static osg::Uniform* getIsNotTextUniform();
+    public: // DepthOffsetInterface
 
-        /**
-         * Creates the source for a depth adjustment vertex shader. Use this instead
-         * of createProgram() if you want you are using the shader composition framework.
-         * You can install this in any FunctionLocation.
-         */
-        static std::string createVertexFunction(
-            const std::string& funcName ="osgearth_depth_adjustment_vertex" );
+        void setDepthOffsetOptions( const DepthOffsetOptions& options );
+        const DepthOffsetOptions& getDepthOffsetOptions() const { return _options; }
 
-        /**
-         * Creates the source for a depth adjustment fragment shader. Use this instead
-         * of createProgram() if you want you are using the shader composition framework.
-         * You can install this in any FunctionLocation.
-         */
-        static std::string createFragmentFunction(
-            const std::string& funcName ="osgearth_depth_adjustment_fragment" );
+    private:
+        void init();
+        void updateUniforms();
+
+        bool                         _supported;
+        bool                         _dirty;
+        osg::observer_ptr<osg::Node> _graph;
+        osg::ref_ptr<osg::Uniform>   _biasUniform;
+        osg::ref_ptr<osg::Uniform>   _rangeUniform;
+        DepthOffsetOptions           _options;
     };
 
+
     /**
      * Group that applies the depth offset technique to its children.
      */
-    class OSGEARTH_EXPORT DepthOffsetGroup : public osg::Group
+    class OSGEARTH_EXPORT DepthOffsetGroup : public osg::Group, public DepthOffsetInterface
     {
     public:
         /**
@@ -187,22 +152,13 @@ namespace osgEarth
          */
         DepthOffsetGroup();
 
-        /** dtor */
-        virtual ~DepthOffsetGroup() { }
 
-        /**
-         * Sets a minimum depth offset range (in scene units, e.g. meters)
-         * This is the minimim simulated depth offset that will be applied to 
-         * geometry under this group.
-         */
-        void setMinimumOffset( float value );
+    public: // DepthOffsetInterface
+
+        void setDepthOffsetOptions( const DepthOffsetOptions& options );
+
+        const DepthOffsetOptions& getDepthOffsetOptions() const;
 
-        /**
-         * Sets the group to automatically calculate an "appropriate" minimum
-         * depth offset based on the child geometry. Whenever the child graph
-         * changes, it will attempt to recalculate the best offset to use.
-         */
-        void setAutoMinimumOffset();
 
     public: // osg::Node
 
@@ -211,11 +167,14 @@ namespace osgEarth
         virtual void traverse(osg::NodeVisitor& );
 
     protected:
-        bool _auto;
-        bool _dirty;
-        osg::Uniform* _minOffsetUniform;
-        void update();
+        void setUniforms();
         void dirty();
+        void scheduleUpdate();
+
+        DepthOffsetAdapter _adapter;
+        bool               _updatePending;
+
+        virtual ~DepthOffsetGroup() { }
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/DepthOffset.cpp b/src/osgEarth/DepthOffset.cpp
index ba7995f..ca21d9b 100644
--- a/src/osgEarth/DepthOffset.cpp
+++ b/src/osgEarth/DepthOffset.cpp
@@ -23,85 +23,22 @@
 #include <osgEarth/Registry>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
 
 #include <osg/Geode>
 #include <osg/Geometry>
-#include <osgText/Text>
-#include <sstream>
 
 #define LC "[DepthOffset] "
 
-using namespace osgEarth;
-
-#define MIN_OFFSET_UNIFORM "osgearth_depthoffset_minoffset"
-#define IS_TEXT_UNIFORM    "osgearth_depthoffset_istext"
+// development testing; set to OE_NULL for production
+#define OE_TEST OE_NULL
 
-#define MAX_DEPTH_OFFSET 10000.0
+using namespace osgEarth;
 
 // undef this if you want to adjust in the normal direction (of a geocentric point) instead
 #define ADJUST_TOWARDS_EYE 1
 
 
-//------------------------------------------------------------------------
-
-
-DepthOffsetOptions::DepthOffsetOptions(const Config& conf) :
-_enabled ( true ),
-_minBias (      100.0f ),
-_maxBias (    10000.0f ),
-_minRange(     1000.0f ),
-_maxRange( 10000000.0f )
-{
-    conf.getIfSet( "enabled",   _enabled );
-    conf.getIfSet( "min_bias",  _minBias );
-    conf.getIfSet( "max_bias",  _maxBias );
-    conf.getIfSet( "min_range", _minRange );
-    conf.getIfSet( "max_range", _maxRange );
-}
-
-
-Config
-DepthOffsetOptions::getConfig() const
-{
-    Config conf("depth_offset");
-    conf.addIfSet( "enabled",   _enabled );
-    conf.addIfSet( "min_bias",  _minBias );
-    conf.addIfSet( "max_bias",  _maxBias );
-    conf.addIfSet( "min_range", _minRange );
-    conf.addIfSet( "max_range", _maxRange );
-    return conf;
-}
-
-
-//------------------------------------------------------------------------
-
-
-DepthOffsetOptionsAdapter::DepthOffsetOptionsAdapter(osg::StateSet* stateSet) :
-_stateSet( stateSet )
-{
-    if ( _stateSet.valid() )
-    {
-        _biasUniform = _stateSet->getOrCreateUniform( "oe_clamp_bias", osg::Uniform::FLOAT_VEC2 );
-        _biasUniform->set( osg::Vec2f(*_options.minBias(), *_options.maxBias()) );
-
-        _rangeUniform = _stateSet->getOrCreateUniform( "oe_clamp_range", osg::Uniform::FLOAT_VEC2 );
-        _rangeUniform->set( osg::Vec2f(*_options.minRange(), *_options.maxRange()) );
-    }
-}
-
-
-void 
-DepthOffsetOptionsAdapter::setOptions(const DepthOffsetOptions& options)
-{
-    _options = options;
-
-    if ( _stateSet.valid() )
-    {
-        _biasUniform->set( osg::Vec2f(*_options.minBias(), *_options.maxBias()) );
-        _rangeUniform->set( osg::Vec2f(*_options.minRange(), *_options.maxRange()) );
-    }
-}
-
 
 //------------------------------------------------------------------------
 
@@ -111,21 +48,20 @@ namespace
     struct SegmentAnalyzer
     {
         SegmentAnalyzer() : _maxLen2(0), _segmentsAnalyzed(0) { }
+
         void operator()( const osg::Vec3& v0, const osg::Vec3& v1, bool ) {
-            double len2 = (v1-v0).length2();
+            float len2 = (v1-v0).length2();
             if ( len2 > _maxLen2 ) _maxLen2 = len2;
             _segmentsAnalyzed++;
         }
-        double _maxLen2;
-        int    _segmentsAnalyzed;
+        float _maxLen2;
+        int   _segmentsAnalyzed;
     };
 
     struct GeometryAnalysisVisitor : public osg::NodeVisitor
     {
         GeometryAnalysisVisitor()
             : osg::NodeVisitor     (osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-              _analyzeSegments     (true),
-              _applyTextUniforms   (false),
               _maxSegmentsToAnalyze(250) { }
 
         void apply( osg::Node& node )
@@ -142,370 +78,296 @@ namespace
             {
                 osg::Drawable* d = geode.getDrawable(i);
 
-                if ( _analyzeSegments && d->asGeometry() )
+                if ( d->asGeometry() )
                 {
                     d->accept( _segmentAnalyzer );
                 }
-                else if ( _applyTextUniforms && dynamic_cast<osgText::Text*>(d) )
-                {
-                    d->getOrCreateStateSet()->addUniform( DepthOffsetUtils::getIsTextUniform() );
-                }
             }
             traverse((osg::Node&)geode);
         }
 
         LineFunctor<SegmentAnalyzer> _segmentAnalyzer;
         int                          _maxSegmentsToAnalyze;
-        bool                         _analyzeSegments;
-        bool                         _applyTextUniforms;
     };
 
 
     //...............................
     // Shader code:
 
+    const char* s_vertex =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-    static char remapFunction[] =
-        "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";
+        // uniforms from ClampableNode:
+        "uniform vec2 oe_doff_bias; \n"
+        "uniform vec2 oe_doff_range; \n"
 
-    static char castMat4ToMat3Function[] = 
-        "mat3 normalMatrix(in mat4 m) { \n"
-        "    return mat3( m[0].xyz, m[1].xyz, m[2].xyz ); \n"
-        "} \n";
+        // values to pass to fragment shader:
+        "varying vec4 oe_doff_vert; \n"
+        "varying float oe_doff_vertRange; \n"
 
-    static std::string createVertexShader( const std::string& shaderCompName )
-    {
+        "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_range[0], oe_doff_range[1])-oe_doff_range[0])/(oe_doff_range[1]-oe_doff_range[0]);\n"
+        "    float bias = oe_doff_bias[0] + ratio * (oe_doff_bias[1]-oe_doff_bias[0]);\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";
 
-        float glslVersion = Registry::instance()->getCapabilities().getGLSLVersion();
+    const char* s_fragment =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
 
-        std::string versionString = glslVersion < 1.2f ? "#version 110 \n" : "#version 120 \n";
+        // values to pass to fragment shader:
+        "varying vec4 oe_doff_vert; \n"
+        "varying float oe_doff_vertRange; \n"
 
-        std::string castMat4ToMat3 = glslVersion < 1.2f ? "castMat4ToMat3Function" : "mat3";
-        
+        "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";
+}
 
-        std::stringstream buf;
-        buf
-            << versionString
-            << remapFunction;
+//------------------------------------------------------------------------
 
-        if ( glslVersion < 1.2f )
-            buf << castMat4ToMat3Function;
 
-        buf
-            << "uniform mat4 osg_ViewMatrix; \n"
-            << "uniform mat4 osg_ViewMatrixInverse; \n"
-            << "uniform float " << MIN_OFFSET_UNIFORM << "; \n"
-            << "uniform bool " << IS_TEXT_UNIFORM << "; \n"
-            << "varying vec4 adjV; \n"
-            << "varying float simRange; \n";
-        
+DepthOffsetOptions::DepthOffsetOptions(const Config& conf) :
+_enabled ( true ),
+_minBias (      100.0f ),
+_maxBias (    10000.0f ),
+_minRange(     1000.0f ),
+_maxRange( 10000000.0f ),
+_auto    ( true )
+{
+    conf.getIfSet( "enabled",   _enabled );
+    conf.getIfSet( "min_bias",  _minBias );
+    conf.getIfSet( "max_bias",  _maxBias );
+    conf.getIfSet( "min_range", _minRange );
+    conf.getIfSet( "max_range", _maxRange );
+    conf.getIfSet( "auto",      _auto );
+}
 
-        if ( !shaderCompName.empty() )
-            buf << "void " << shaderCompName << "() { \n";
-        else
-            buf << "void main(void) { \n";
 
-        buf <<
-            // transform the vertex into eye space:
-            "vec4 vertEye  = gl_ModelViewMatrix * gl_Vertex; \n"
-            "vec3 vertEye3 = vertEye.xyz/vertEye.w; \n"
-            "float range = length(vertEye3); \n"
+Config
+DepthOffsetOptions::getConfig() const
+{
+    Config conf("depth_offset");
+    conf.addIfSet( "enabled",   _enabled );
+    conf.addIfSet( "min_bias",  _minBias );
+    conf.addIfSet( "max_bias",  _maxBias );
+    conf.addIfSet( "min_range", _minRange );
+    conf.addIfSet( "max_range", _maxRange );
+    conf.addIfSet( "auto",      _auto );
+    return conf;
+}
 
-#ifdef ADJUST_TOWARDS_EYE
 
-            "vec3 adjVecEye3 = normalize(vertEye3); \n"
+//------------------------------------------------------------------------
 
-#else // ADJUST_ALONG_UP_VECTOR
+DepthOffsetAdapter::DepthOffsetAdapter() :
+_dirty( false )
+{
+    init();
+}
 
-            // calculate the "up" vector, that will be our adjustment vector:
-            "vec4 vertWorld = osg_ViewMatrixInverse * vertEye; \n"
-            "vec3 adjVecWorld3 = -normalize(vertWorld.xyz/vertWorld.w); \n"
-            "vec3 adjVecEye3 = " << castMat4ToMat3 << "(osg_ViewMatrix) * adjVecWorld3; \n"
+DepthOffsetAdapter::DepthOffsetAdapter(osg::Node* graph) :
+_dirty( false )
+{
+    init();
+    setGraph( graph );
+}
 
-#endif
+void
+DepthOffsetAdapter::init()
+{
+    _supported = Registry::capabilities().supportsFragDepthWrite();
+    if ( _supported )
+    {
+        _biasUniform  = new osg::Uniform(osg::Uniform::FLOAT_VEC2, "oe_doff_bias");
+        _rangeUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC2, "oe_doff_range");
+        updateUniforms();
+    }
+}
 
-            // remap depth offset based on camera distance to vertex. The farther you are away,
-            // the more of an offset you need.        
-            "float offset = remap( range, 1000.0, 10000000.0, " << MIN_OFFSET_UNIFORM << ", 10000.0); \n"
+void
+DepthOffsetAdapter::setGraph(osg::Node* graph)
+{
+    if ( !_supported ) return;
 
-            // adjust the Z (distance from the eye) by our offset value:
-            "vertEye3 -= adjVecEye3 * offset; \n"
-            "vertEye.xyz = vertEye3 * vertEye.w; \n"
+    bool graphChanging =
+        _graph.get() != graph;
 
-            // Transform the new adjusted vertex into clip space and pass it to the fragment shader.
-            "adjV = gl_ProjectionMatrix * vertEye; \n"
+    bool uninstall =
+        (_graph.valid() && _graph->getStateSet()) &&
+        (graphChanging || (_options.enabled() == false));
 
-            // Also pass along the simulated range (eye=>vertex distance). We will need this
-            // to detect when the depth offset has pushed the Z value "behind" the camera.
-            "simRange = range - offset; \n"
-            ;
+    bool install =
+        (graph && graphChanging ) || 
+        (graph && (_options.enabled() == true));
 
-        if ( shaderCompName.empty() )
+    if ( uninstall )
+    {
+        OE_TEST << LC << "Removing depth offset shaders" << std::endl;
+
+        // uninstall uniforms and shaders.
+        osg::StateSet* s = _graph->getStateSet();
+        s->removeUniform( _biasUniform.get() );
+        s->removeUniform( _rangeUniform.get() );
+        VirtualProgram* vp = VirtualProgram::get( s );
+        if ( vp )
         {
-            buf << 
-                "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-                //"gl_Position = adjV; \n" // <-- uncomment for debugging
-                "gl_FrontColor = gl_Color; \n"
-                "gl_TexCoord[0] = gl_MultiTexCoord0; \n";
-            
+            vp->removeShader( "oe_doff_vertex" );
+            vp->removeShader( "oe_doff_fragment" );
         }
-
-        buf << "} \n";
-
-        std::string result;
-        result = buf.str();
-        return result;
     }
 
-    static std::string createFragmentShader( const std::string& shaderCompName )
+    if ( install )
     {
-        if ( shaderCompName.empty() )
-        {
-            // Transform out adjusted Z value (from the vertex shader) from clip space [-1..1]
-            // into depth buffer space [0..1] and write it to the z-buffer. Yes, this will
-            // deactivate early-Z optimizations; so be it!!
-            return 
-                "#version 110 \n"
-
-                "uniform bool " IS_TEXT_UNIFORM "; \n"
-                "uniform sampler2D tex0; \n"
-                "varying vec4 adjV; \n"
-                "varying float simRange; \n"
-
-                "void main(void) \n"
-                "{ \n"
-                "    if (" IS_TEXT_UNIFORM ") \n"
-                "    { \n"
-                "        float alpha = texture2D(tex0,gl_TexCoord[0].st).a; \n"
-                "        gl_FragColor = vec4( gl_Color.rgb, gl_Color.a * alpha); \n"
-                "    } \n"
-                "    else \n"
-                "    { \n"
-                "        gl_FragColor = gl_Color; \n"
-                "    } \n"
-
-                // transform clipspace depth into [0..1] for FragDepth:
-                "    float z = 0.5 * (1.0+(adjV.z/adjV.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 ( z > 1.0 && simRange < 0.0 ) { z = 0.0; } \n"
-
-                "    gl_FragDepth = max(0.0, z); \n"
-                "} \n";
-        }
-        else
-        {
-            return Stringify() <<
-                "#version 110 \n"
-                "varying vec4 adjV; \n"
-                "varying float simRange; \n"
-                "void " << shaderCompName << "(inout vec4 color) \n"
-                "{ \n"
-                "    float z = 0.5 * (1.0+(adjV.z/adjV.w)); \n"
-                "    if ( z > 1.0 && simRange < 0.0 ) { z = 0.0; } \n"
-                "    gl_FragDepth = max(0.0,z); \n"
-                "} \n";
-        }
-    }
-}
+        OE_TEST << LC << "Installing depth offset shaders" << std::endl;
 
-//---------------------------------------------------------------------------
+        // install uniforms and shaders.
+        osg::StateSet* s = graph->getOrCreateStateSet();
+        s->addUniform( _biasUniform.get() );
+        s->addUniform( _rangeUniform.get() );
 
-osg::Uniform*
-DepthOffsetUtils::createMinOffsetUniform( osg::Node* graph )
-{
-    osg::Uniform* u = new osg::Uniform(osg::Uniform::FLOAT, MIN_OFFSET_UNIFORM);
-    u->set( graph ? recalculate(graph) : 0.0f );
-    return u;
-}
+        VirtualProgram* vp = VirtualProgram::getOrCreate( s );
+        vp->setFunction( "oe_doff_vertex", s_vertex, ShaderComp::LOCATION_VERTEX_VIEW );
+        vp->setFunction( "oe_doff_fragment", s_fragment, ShaderComp::LOCATION_FRAGMENT_COLORING );
+        s->setAttributeAndModes( vp, osg::StateAttribute::ON );
+    }
 
-float
-DepthOffsetUtils::recalculate( const osg::Node* graph )
-{
-    double minDepthOffset = 0.0;
-    if ( graph )
+    if ( graphChanging )
     {
-        GeometryAnalysisVisitor v;
-        v._analyzeSegments = true;
-        const_cast<osg::Node*>(graph)->accept( v );
-        double maxLen = std::max(1.0, sqrt(v._segmentAnalyzer._maxLen2));
-        minDepthOffset = sqrt(maxLen)*19.0;
-
-        OE_DEBUG << LC << std::fixed << std::setprecision(2)
-            << "max res = " << maxLen << ", min offset = " << minDepthOffset << std::endl;
+        _graph = graph;
     }
-    return float(minDepthOffset);
+
+    // always set Dirty when setGraph is called sine it may be called anytime
+    // the subgraph changes (as can be detected by a computeBound)
+    _dirty = (_options.automatic() == true);
 }
 
 void
-DepthOffsetUtils::prepareGraph( osg::Node* graph )
+DepthOffsetAdapter::updateUniforms()
 {
-    if ( graph )
+    if ( !_supported ) return;
+
+    _biasUniform->set( osg::Vec2f(*_options.minBias(), *_options.maxBias()) );
+    _rangeUniform->set( osg::Vec2f(*_options.minRange(), *_options.maxRange()) );
+
+    if ( _options.enabled() == true )
     {
-        GeometryAnalysisVisitor v;
-        v._analyzeSegments = false;
-        graph->accept( v );
+        OE_TEST << LC << "bias=[" << *_options.minBias() << ", " << *_options.maxBias() << "] ... "
+                << "range=[" << *_options.minRange() << ", " << *_options.maxRange() << "]" << std::endl;
     }
 }
 
-namespace
+void 
+DepthOffsetAdapter::setDepthOffsetOptions(const DepthOffsetOptions& options)
 {
-    // dubious. refactor static-init stuff away please.
-    static osg::ref_ptr<osg::Uniform> s_isTextUniform;
-    static Threading::Mutex           s_isTextUniformMutex;
-}
+    if ( !_supported ) return;
 
-osg::Uniform*
-DepthOffsetUtils::getIsTextUniform()
-{
-    if ( !s_isTextUniform.valid() )
-    {
-        Threading::ScopedMutexLock exclusive(s_isTextUniformMutex);
-        if ( !s_isTextUniform.valid() )
-        {
-            s_isTextUniform = new osg::Uniform(osg::Uniform::BOOL, IS_TEXT_UNIFORM);
-            s_isTextUniform->set( true );
-        }
-    }
-    return s_isTextUniform.get();
-}
+    // if "enabled" changed, reset the graph.
+    bool reinitGraph = ( options.enabled() != _options.enabled() );
 
-namespace
-{    
-    // please refactor away the static-init stuff.
-    static osg::ref_ptr<osg::Uniform> s_isNotTextUniform;
-    static Threading::Mutex           s_isNotTextUniformMutex;
-}
+    _options = options;
 
-osg::Uniform*
-DepthOffsetUtils::getIsNotTextUniform()
-{
-    if ( !s_isNotTextUniform.valid() )
+    if ( reinitGraph )
     {
-        Threading::ScopedMutexLock exclusive(s_isNotTextUniformMutex);
-        if ( !s_isNotTextUniform.valid() )
-        {
-            s_isNotTextUniform = new osg::Uniform(osg::Uniform::BOOL, IS_TEXT_UNIFORM);
-            s_isNotTextUniform->set( false );
-        }
+        setGraph( _graph.get() );
     }
-    return s_isNotTextUniform.get();
-}
- 
-namespace
-{
-    // todo: refactor away the static init stuff
-    static osg::ref_ptr<osg::Program> s_depthOffsetProgram;
-    static Threading::Mutex           s_depthOffsetProgramMutex;
+
+    updateUniforms();
+
+    _dirty = (options.automatic() == true);
 }
 
-osg::Program*
-DepthOffsetUtils::getOrCreateProgram()
+void
+DepthOffsetAdapter::recalculate()
 {
-    if ( !s_depthOffsetProgram.valid() )
+    if ( _supported && _graph.valid() )
     {
-        Threading::ScopedMutexLock exclusive(s_depthOffsetProgramMutex);
-        if ( !s_depthOffsetProgram.valid() )
+        if ( _options.automatic() == true )
         {
-            s_depthOffsetProgram = new osg::Program();
-            s_depthOffsetProgram->setName( "osgEarth::DepthOffset" );
-            s_depthOffsetProgram->addShader( new osg::Shader(osg::Shader::VERTEX, createVertexShader("")) );
-            s_depthOffsetProgram->addShader( new osg::Shader(osg::Shader::FRAGMENT, createFragmentShader("")) );
+            GeometryAnalysisVisitor v;
+            _graph->accept( v );
+            float maxLen = std::max(1.0f, sqrtf(v._segmentAnalyzer._maxLen2));
+            _options.minRange() = sqrtf(maxLen) * 19.0f;
+            _dirty = false;
+            OE_TEST << LC << "Recalcluated." << std::endl;
         }
+        updateUniforms();
     }
-
-    return s_depthOffsetProgram.get();
-}
-
-std::string
-DepthOffsetUtils::createVertexFunction( const std::string& funcName )
-{
-    return createVertexShader( funcName );
-}
-
-std::string
-DepthOffsetUtils::createFragmentFunction( const std::string& funcName )
-{
-    return createFragmentShader( funcName );
 }
 
 //------------------------------------------------------------------------
 
 DepthOffsetGroup::DepthOffsetGroup() :
-_auto ( true ),
-_dirty( false )
+_updatePending( false )
 {
-    osg::StateSet* s = this->getOrCreateStateSet();
-
-    osg::Program* program = DepthOffsetUtils::getOrCreateProgram();
-    s->setAttributeAndModes( program, 1 );
-
-    _minOffsetUniform = DepthOffsetUtils::createMinOffsetUniform();
-    s->addUniform( _minOffsetUniform );
+    _adapter.setGraph( this );
 
-    s->addUniform( DepthOffsetUtils::getIsNotTextUniform() );
+    if ( _adapter.isDirty() )
+        _adapter.recalculate();
 }
 
 void
-DepthOffsetGroup::setMinimumOffset( float value )
+DepthOffsetGroup::setDepthOffsetOptions(const DepthOffsetOptions& options)
 {
-    _auto = false;
-    _minOffsetUniform->set( std::max(0.0f, value) );
+    _adapter.setDepthOffsetOptions(options);
+    if ( _adapter.isDirty() && !_updatePending )
+        scheduleUpdate();
 }
 
-void
-DepthOffsetGroup::setAutoMinimumOffset()
+const DepthOffsetOptions&
+DepthOffsetGroup::getDepthOffsetOptions() const
 {
-    _auto = true;
-    dirty();
+    return _adapter.getDepthOffsetOptions();
 }
 
 void
-DepthOffsetGroup::dirty()
+DepthOffsetGroup::scheduleUpdate()
 {
-    if ( !_dirty )
-    {
-        _dirty = true;
-        ADJUST_UPDATE_TRAV_COUNT(this, 1);
-    }
+    ADJUST_UPDATE_TRAV_COUNT(this, 1);
+    _updatePending = true;
 }
 
 osg::BoundingSphere
 DepthOffsetGroup::computeBound() const
 {
-    const_cast<DepthOffsetGroup*>(this)->dirty();
+    static Threading::Mutex s_mutex;
+    {
+        Threading::ScopedMutexLock lock(s_mutex);
+        const_cast<DepthOffsetGroup*>(this)->scheduleUpdate();
+    }
     return osg::Group::computeBound();
 }
 
 void
 DepthOffsetGroup::traverse(osg::NodeVisitor& nv)
 {
-    if ( _dirty && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR )
+    if ( _updatePending && nv.getVisitorType() == nv.UPDATE_VISITOR )
     {
-        update();
+        _adapter.recalculate();
         ADJUST_UPDATE_TRAV_COUNT( this, -1 );
-        _dirty = false;
+        _updatePending = false;
     }
     osg::Group::traverse( nv );
 }
-
-void
-DepthOffsetGroup::update()
-{
-    GeometryAnalysisVisitor v;
-    v._analyzeSegments = _auto;
-    this->accept( v );
-
-    if ( _auto )
-    {
-        double maxLen = sqrt(v._segmentAnalyzer._maxLen2);
-        _minOffsetUniform->set( float(sqrt(maxLen)*19.0) );
-    }
-}
diff --git a/src/osgEarth/Draggers.cpp b/src/osgEarth/Draggers.cpp
index ff19665..eaa4862 100644
--- a/src/osgEarth/Draggers.cpp
+++ b/src/osgEarth/Draggers.cpp
@@ -41,7 +41,7 @@ _dragger( dragger )
 }
 
 void onTileAdded( const TileKey& key, osg::Node* tile, TerrainCallbackContext& context )
-{
+{    
     _dragger->reclamp( key, tile, context.getTerrain() );
 }
 
@@ -50,7 +50,6 @@ Dragger* _dragger;
 
 /**********************************************************/
 Dragger::Dragger( MapNode* mapNode, int modKeyMask, const DragMode& defaultMode ):
-_mapNode( mapNode ),
 _position( mapNode->getMapSRS(), 0,0,0, ALTMODE_RELATIVE),
 _dragging(false),
 _hovered(false),
@@ -58,7 +57,7 @@ _modKeyMask(modKeyMask),
 _defaultMode(defaultMode),
 _elevationDragging(false),
 _verticalMinimum(0.0)
-{
+{    
     setNumChildrenRequiringEventTraversal( 1 );
 
     _autoClampCallback = new ClampDraggerCallback( this );
@@ -87,7 +86,7 @@ Dragger::setMapNode( MapNode* mapNode )
         _mapNode = mapNode;
 
         if ( _mapNode.valid() && _autoClampCallback.valid() )
-        {
+        {            
             _mapNode->getTerrain()->addTerrainCallback( _autoClampCallback.get() );
         }
     }
@@ -133,9 +132,14 @@ void Dragger::updateTransform(osg::Node* patch)
     if ( getMapNode() )
     {
         osg::Matrixd matrix;
-
+        
         GeoPoint mapPoint( _position );
-        mapPoint.makeAbsolute( getMapNode()->getTerrain() );
+        mapPoint = mapPoint.transform( _mapNode->getMapSRS() );
+        if (!mapPoint.makeAbsolute( getMapNode()->getTerrain() ))
+        {
+            OE_WARN << "Failed to clamp dragger" << std::endl;
+            return;            
+        }
 
         mapPoint.createLocalToWorld( matrix );
         setMatrix( matrix );
@@ -370,7 +374,7 @@ void Dragger::setHover( bool hovered)
 }
 
 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:
diff --git a/src/osgEarth/DrapingTechnique b/src/osgEarth/DrapingTechnique
index 1634561..5d35162 100644
--- a/src/osgEarth/DrapingTechnique
+++ b/src/osgEarth/DrapingTechnique
@@ -83,6 +83,12 @@ namespace osgEarth
         void setAttachStencil( bool value );
         bool getAttachStencil() const;
 
+        /**
+         * Ratio of near resolution to far resolution when draping. [value >= 1.0]
+         */
+        void setResolutionRatio( float value );
+        float getResolutionRatio() const;
+
 
     public: // OverlayTechnique
 
@@ -112,15 +118,14 @@ namespace osgEarth
         optional<int>                 _explicitTextureUnit;
         optional<int>                 _textureUnit;
         optional<int>                 _textureSize;
-        bool                          _useShaders;
         bool                          _mipmapping;
         bool                          _rttBlending;
         bool                          _attachStencil;
+        double                        _maxFarNearRatio;
 
         struct TechData : public osg::Referenced
         {
-            osg::ref_ptr<osg::Uniform>    _texGenUniform;
-            osg::ref_ptr<osg::TexGen>     _texGen;
+            osg::ref_ptr<osg::Uniform> _texGenUniform;
         };
 
     private:
diff --git a/src/osgEarth/DrapingTechnique.cpp b/src/osgEarth/DrapingTechnique.cpp
index a5d2cb7..4a3d9cb 100644
--- a/src/osgEarth/DrapingTechnique.cpp
+++ b/src/osgEarth/DrapingTechnique.cpp
@@ -41,21 +41,262 @@ namespace
     struct LocalPerViewData : public osg::Referenced
     {
         osg::ref_ptr<osg::Uniform> _texGenUniform;  // when shady
-        osg::ref_ptr<osg::TexGen>  _texGen;         // when not shady
     };
 }
 
+namespace
+{
+    struct Line2d
+    {
+        bool intersectRaysXY(
+            const osg::Vec3d& p0, const osg::Vec3d& d0,
+            const osg::Vec3d& p1, const osg::Vec3d& d1,
+            osg::Vec3d& out_p,
+            double&     out_u,
+            double&     out_v) const
+        {
+            static const double epsilon = 0.001;
+
+            double det = d0.y()*d1.x() - d0.x()*d1.y();
+            if ( osg::equivalent(det, 0.0, epsilon) )
+                return false; // parallel
+
+            out_u = (d1.x()*(p1.y()-p0.y())+d1.y()*(p0.x()-p1.x()))/det;
+            out_v = (d0.x()*(p1.y()-p0.y())+d0.y()*(p0.x()-p1.x()))/det;
+            out_p = p0 + d0*out_u;
+            return true;
+        }
+
+        osg::Vec3d _a, _b;
+
+        Line2d(const osg::Vec3d& p0, const osg::Vec3d& p1) : _a(p0), _b(p1) { }
+
+        Line2d(const osg::Vec4d& p0, const osg::Vec4d& p1)
+            : _a(p0.x()/p0.w(), p0.y()/p0.w(), p0.x()/p0.w()), _b(p1.x()/p1.w(), p1.y()/p1.w(), p1.z()/p1.w()) { }
+
+        bool intersect(const Line2d& rhs, osg::Vec4d& out) const {
+            double u, v;
+            osg::Vec3d temp;
+            bool ok = intersectRaysXY(_a, (_b-_a), rhs._a, (rhs._b-rhs._a), temp, u, v);
+            out.set( temp.x(), temp.y(), temp.z(), 1.0 );
+            return ok;
+        }
+        bool intersect(const Line2d& rhs, osg::Vec3d& out) const {
+            double u, v;
+            return intersectRaysXY(_a, (_b-_a), rhs._a, (rhs._b-rhs._a), out, u, v);
+        }
+    };
+
+    // Experimental.
+    void optimizeProjectionMatrix(OverlayDecorator::TechRTTParams& params, double maxFarNearRatio)
+    {
+        LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
+
+        //TODO: add this to the local
+        //local._rttLimitZ->set( 0.0f );
+
+        // t0,t1,t2,t3 will form a polygon that tightly fits the
+        // main camera's frustum. Texture near the camera will get
+        // more resolution then texture far away.
+        //
+        // The line segment t0t1 represents the near clip plane and is
+        // along the y=-1 line of the clip volume. t2t3 represents the
+        // far plane and lies long y=+1. This code calculates the optimal
+        // width of those 2 line segments off-center. Since the view
+        // frustum is symmertical (as calculated in OverlayDecorator)
+        // we only need find the half-width of each.
+        //
+        // NOTE: this algorithm only works with the top-down RTT camera 
+        // created by the OverlayDecorator, AND assumes a "level" view
+        // camera (no roll) with respect to the RTT camera.
+        osg::Vec4d t0, t1, t2, t3;
+        {
+            // cap the width of the far line w.r.t to y-axis of the clip
+            // space. (derived empirically)
+            const double maxApsectRatio   = 1.0; 
+
+            // this matrix xforms the verts from model to clip space.
+            osg::Matrix rttMVP = params._rttViewMatrix * params._rttProjMatrix;
+
+            // if the eyepoint lies within the RTT clip space, don't bother to
+            // optimize because the camera is looking downish and the existing
+            // rectangular volume is sufficient.
+            osg::Vec3d eyeClip = params._eyeWorld * rttMVP;
+            if ( eyeClip.y() >= -1.0 && eyeClip.y() <= 1.0 )
+                return;
+
+            // discover the max near-plane width.
+            double halfWidthNear = 0.0;
+            osgShadow::ConvexPolyhedron::Faces::iterator f = params._frustumPH._faces.begin();
+            f++; f++; f++; f++; // the near plane Face
+            // f->vertices.size() should always be 4, I would think.. but it's not..
+            for(unsigned i=0; i<f->vertices.size(); ++i)
+            {
+                osg::Vec3d p = f->vertices[i] * rttMVP;
+                if ( fabs(p.x()) > halfWidthNear )
+                    halfWidthNear = fabs(p.x());
+            }
+
+            double aspectRatio  = DBL_MAX;
+            double farNearRatio = DBL_MAX;
+            double halfWidthFar = DBL_MAX;
+
+            // Next, find the point in the camera frustum that forms the largest angle
+            // with the center line (line of sight). This is simply the minimum dot
+            // product of LOS vector and the vector from (0,-1,0) to the point.
+            osg::Vec3d look(0,1,0);
+            double     min_dp   = 1.0;
+            osg::Vec3d rightmost_p;
+            f++; // the Far plane face
+            for(unsigned i=0; i<f->vertices.size(); ++i)
+            {
+                osg::Vec3d p = f->vertices[i] * rttMVP;
+                // only check points on the right (since it's symmetrical)
+                if ( p.x() > 0 ) 
+                {
+                    osg::Vec3d pv(p.x(), p.y()+1.0, 0); pv.normalize();
+                    double dp = look * pv;
+                    if ( dp < min_dp )
+                    {
+                        min_dp = dp;
+                        rightmost_p = p;
+                    }
+                }
+            }
+
+            // Now calculate the far extent. This is an iterative process;
+            // If either the aspectRatio or far/near-ratio limits are exceeded
+            // by the value we calculate, reset the near width to accomodate
+            // and try again. Worst case this should be no more than 3 iterations.
+            double minHalfWidthNear = halfWidthNear;
+
+            Line2d farLine( osg::Vec3d(-1,1,0), osg::Vec3d(1,1,0) );
+
+            int iterations = 0;
+            while(
+                (aspectRatio > maxApsectRatio || farNearRatio > maxFarNearRatio) &&
+                (halfWidthFar > halfWidthNear) &&
+                (iterations++ < 10) )
+            {
+                // make sure all the far-clip verts are inside our polygon.
+                // stretch out the far line to accomodate them.
+                osg::Vec3d NR( halfWidthNear, -1, 0);
+
+                osg::Vec3d i;
+                Line2d( NR, rightmost_p ).intersect( farLine, i );
+                halfWidthFar = i.x();
+
+                aspectRatio  = (halfWidthFar-halfWidthNear)/2.0;
+                if ( aspectRatio > maxApsectRatio )
+                {
+                    halfWidthNear = halfWidthFar - 2.0*maxApsectRatio;
+                }
+
+                farNearRatio = halfWidthFar/halfWidthNear;
+                if ( farNearRatio > maxFarNearRatio )
+                {
+                    halfWidthNear = halfWidthFar / maxFarNearRatio;
+                    //break;
+                }
+
+                halfWidthNear = std::max(halfWidthNear, minHalfWidthNear);
+            }
+
+            // if the far plane is narrower than the near plane, bail out and 
+            // fall back on a simple rectangular clip camera.
+            if ( halfWidthFar <= halfWidthNear )
+                return;
+
+            //OE_NOTICE  << "\n"
+            //    << "HN = " << halfWidthNear << "\n"
+            //    << "HF = " << halfWidthFar << "\n"
+            //    << "AR = " << aspectRatio << "\n"
+            //    << "FNR= " << farNearRatio << "\n"
+            //    << std::endl;
+
+            // construct the polygon.
+            t0.set(  halfWidthFar,   1.0, 0.0, 1.0 );
+            t1.set( -halfWidthFar,   1.0, 0.0, 1.0 );
+            t2.set( -halfWidthNear, -1.0, 0.0, 1.0 );
+            t3.set(  halfWidthNear, -1.0, 0.0, 1.0 );
+        }
+
+        // OK now warp our polygon t0,t1,t2,t3 into a clip-space square
+        // through a series of matrix operations.
+        osg::Vec4d  u, v;
+        osg::Matrix M;
+        
+        // translate the center of the near plane to the origin
+        u = (t2 + t3) / 2.0;
+        osg::Matrix T1;
+        T1.makeTranslate(-u.x(), -u.y(), 0.0);
+        M = T1;
+
+        // find the intersection of the side lines t0,t3 and t1,t2
+        // and translate that point is at the origin:
+        osg::Vec4d i;
+        Line2d(t0, t3).intersect( Line2d(t1, t2), i );
+        u = i*M;
+        osg::Matrix T2;
+        T2.makeTranslate( -u.x(), -u.y(), 0.0 );
+        M = T2*M;
+
+        // scale the near corners to [-1,1] and [1,1] respectively:
+        u = t3*M; // ...not t2.
+        osg::Matrix S1;
+        S1.makeScale( 1/u.x(), 1/u.y(), 1.0 );
+        M = M*S1;
+
+        // project onto the Y plane and translate the whole thing
+        // back down to the origin at the same time.
+        osg::Matrix N(
+            1,  0, 0, 0,
+            0,  1, 0, 1,
+            0,  0, 1, 0,
+            0, -1, 0, 0);
+        M = M*N;
+
+        // scale it back to unit size:
+        u = t0*M;
+        v = t3*M;
+        osg::Matrix S3;
+        S3.makeScale( 1.0, 2.0/(u.y()/u.w() - v.y()/v.w()), 1.0 );
+        M = M*S3;
+
+        // finally, translate it to it lines up with the clip space boundaries.
+        osg::Matrix T4;
+        T4.makeTranslate( 0.0, -1.0, 0.0 );
+        M = M*T4;
+
+        // apply the result to the projection matrix.
+        params._rttProjMatrix.postMult( M );
+
+        // btw, this new clip matrix distorts the Z coordinate as
+        // y approaches +1. That can cause bleed-through in a geocentric
+        // terrain from the other side of the globe. To prevent that, sample a 
+        // point at the near plane and record that as the Maximum allowable
+        // Z coordinate; a vertex shader in the RTT camera will enforce this.
+        osg::Vec4d sampleFar = osg::Vec4d(0,1,1,1) * M;
+
+        //TODO: add this to the shader.
+        //local._rttLimitZ->set( (float)sampleFar.z() );
+    }
+}
+
 //---------------------------------------------------------------------------
 
 DrapingTechnique::DrapingTechnique() :
 _textureUnit     ( 1 ),
 _textureSize     ( 1024 ),
-_useShaders      ( false ),
 _mipmapping      ( false ),
 _rttBlending     ( true ),
-_attachStencil   ( true )
+_attachStencil   ( false ),
+_maxFarNearRatio ( 3.0 )
 {
-    // nop
+    // try newer version
+    const char* nfr2 = ::getenv("OSGEARTH_OVERLAY_RESOLUTION_RATIO");
+    if ( nfr2 )
+        _maxFarNearRatio = as<double>(nfr2, 0.0);
 }
 
 
@@ -117,7 +358,6 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     // set up the RTT camera:
     params._rttCamera = new osg::Camera();
     params._rttCamera->setClearColor( osg::Vec4f(0,0,0,0) );
-
     // this ref frame causes the RTT to inherit its viewpoint from above (in order to properly
     // process PagedLOD's etc. -- it doesn't affect the perspective of the RTT camera though)
     params._rttCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT );
@@ -125,10 +365,13 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     params._rttCamera->setComputeNearFarMode( osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR );
     params._rttCamera->setRenderOrder( osg::Camera::PRE_RENDER );
     params._rttCamera->setRenderTargetImplementation( osg::Camera::FRAME_BUFFER_OBJECT );
-    params._rttCamera->attach( osg::Camera::COLOR_BUFFER, projTexture, 0, 0, _mipmapping );
+    params._rttCamera->setImplicitBufferAttachmentMask(0, 0);
+    params._rttCamera->attach( osg::Camera::COLOR_BUFFER0, projTexture, 0, 0, _mipmapping );
 
     if ( _attachStencil )
     {
+        OE_INFO << LC << "Attaching a stencil buffer to the RTT camera" << std::endl;
+
         // try a depth-packed buffer. failing that, try a normal one.. if the FBO doesn't support
         // that (which is doesn't on some GPUs like Intel), it will automatically fall back on 
         // a PBUFFER_RTT impl
@@ -146,11 +389,11 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
         }
 
         params._rttCamera->setClearStencil( 0 );
-        params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
+        params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); //GL_DEPTH_BUFFER_BIT |  );
     }
     else
     {
-        params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+        params._rttCamera->setClearMask( GL_COLOR_BUFFER_BIT ); //| GL_DEPTH_BUFFER_BIT );
     }
 
     // set up a StateSet for the RTT camera.
@@ -160,15 +403,11 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     rttStateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
 
     // install a new default shader program that replaces anything from above.
-    if ( _useShaders )
-    {
-        VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "DrapingTechnique RTT" );
-        vp->setInheritShaders( false );
-        rttStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
-    }
+    VirtualProgram* rtt_vp = VirtualProgram::getOrCreate(rttStateSet);
+    rtt_vp->setName( "DrapingTechnique RTT" );
+    rtt_vp->setInheritShaders( false );
     
-    // active blending within the RTT camera's FBO
+    // activate blending within the RTT camera's FBO
     if ( _rttBlending )
     {
         //Setup a separate blend function for the alpha components and the RGB components.  
@@ -210,62 +449,47 @@ DrapingTechnique::setUpCamera(OverlayDecorator::TechRTTParams& params)
     LocalPerViewData* local = new LocalPerViewData();
     params._techniqueData = local;
     
-    if ( _useShaders )
-    {            
-        // GPU path
-
-        VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "DrapingTechnique terrain shaders");
-        params._terrainStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
-
-        // sampler for projected texture:
-        params._terrainStateSet->getOrCreateUniform(
-            "oe_overlay_tex", osg::Uniform::SAMPLER_2D )->set( *_textureUnit );
-
-        // the texture projection matrix uniform.
-        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";
-
-        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";
-
-        vp->setFunction( "oe_overlay_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
-    }
-    else
-    {
-        // FFP path
-        local->_texGen = new osg::TexGen();
-        local->_texGen->setMode( osg::TexGen::EYE_LINEAR );
-        params._terrainStateSet->setTextureAttributeAndModes( *_textureUnit, local->_texGen.get(), 1 );
-
-        osg::TexEnv* env = new osg::TexEnv();
-        env->setMode( osg::TexEnv::DECAL );
-        params._terrainStateSet->setTextureAttributeAndModes( *_textureUnit, env, 1 );
-    }
+
+    // Assemble the terrain shaders that will apply projective texturing.
+    VirtualProgram* terrain_vp = VirtualProgram::getOrCreate(params._terrainStateSet);
+    terrain_vp->setName( "DrapingTechnique terrain shaders");
+
+    // sampler for projected texture:
+    params._terrainStateSet->getOrCreateUniform(
+        "oe_overlay_tex", osg::Uniform::SAMPLER_2D )->set( *_textureUnit );
+
+    // the texture projection matrix uniform.
+    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 );
 }
 
 
@@ -277,17 +501,6 @@ DrapingTechnique::preCullTerrain(OverlayDecorator::TechRTTParams& params,
     {
         setUpCamera( params );
     }
-
-    if ( params._rttCamera.valid() )
-    {
-        LocalPerViewData& local = *static_cast<LocalPerViewData*>(params._techniqueData.get());
-        if ( local._texGen.valid() )
-        {
-            // FFP path only
-            cv->getCurrentRenderBin()->getStage()->addPositionedTextureAttribute(
-                *_textureUnit, cv->getModelViewMatrix(), local._texGen.get() );
-        }
-    }
 }
 
 
@@ -302,6 +515,12 @@ DrapingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
             osg::Matrix::translate(1.0,1.0,1.0) * 
             osg::Matrix::scale(0.5,0.5,0.5);
 
+        // resolution weighting based on camera distance.
+        if ( _maxFarNearRatio > 1.0 )
+        {
+            optimizeProjectionMatrix( params, _maxFarNearRatio );
+        }
+
         params._rttCamera->setViewMatrix      ( params._rttViewMatrix );
         params._rttCamera->setProjectionMatrix( params._rttProjMatrix );
 
@@ -311,14 +530,21 @@ DrapingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
 
         if ( local._texGenUniform.valid() )
         {
-            // premultiply the inv view matrix so we don't have
-            // precision problems in the shader (and it's faster too)
-            local._texGenUniform->set( cv->getCurrentCamera()->getInverseViewMatrix() * VPT );
-        }
-        else
-        {
-            // FFP path
-            local._texGen->setPlanesFromMatrix( VPT );
+            // premultiply the inv view matrix so we don't have precision problems in the shader 
+            // (and it's faster too)
+
+            // TODO:
+            // This only works properly if the terrain tiles have a DYNAMIC data variance.
+            // That is because we are setting a Uniform value during the CULL traversal, and
+            // it's possible that the stateset from the previous frame has not yet been
+            // dispatched to render. So we need to come up with a way to address this.
+            // In the meantime, I patched the MP engine to set a DYNAMIC data variance on
+            // terrain tiles to work around the problem.
+            //
+            // Note that we require the InverseViewMatrix, but it is OK to invert the ModelView matrix as the model matrix is identity here.
+            osg::Matrix vm;
+            vm.invert( *cv->getModelViewMatrix() );
+            local._texGenUniform->set( vm * VPT );
         }
 
         // traverse the overlay group (via the RTT camera).
@@ -330,10 +556,7 @@ DrapingTechnique::cullOverlayGroup(OverlayDecorator::TechRTTParams& params,
 void
 DrapingTechnique::setTextureSize( int texSize )
 {
-    if ( texSize != _textureSize.value() )
-    {
-        _textureSize = texSize;
-    }
+    _textureSize = texSize;
 }
 
 void
@@ -382,23 +605,30 @@ DrapingTechnique::setAttachStencil( bool value )
 }
 
 void
-DrapingTechnique::onInstall( TerrainEngineNode* engine )
+DrapingTechnique::setResolutionRatio(float value)
 {
-    // see whether we want shader support:
-    // TODO: this is not stricty correct; you might still want to use shader overlays
-    // in multipass mode, AND you might want FFP overlays in multitexture-FFP mode.
-    _useShaders = 
-        Registry::capabilities().supportsGLSL() && (
-            !engine->getTextureCompositor() ||
-            engine->getTextureCompositor()->usesShaderComposition() );
+    // not a typo. "near/far resolution" is equivalent to "far/near clip plane extent"
+    // with respect to the overlay projection frustum.
+    _maxFarNearRatio = (double)osg::clampAbove(value, 1.0f);
+}
+
+float
+DrapingTechnique::getResolutionRatio() const
+{
+    // not a typo. "near/far resolution" is equivalent to "far/near clip plane extent"
+    // with respect to the overlay projection frustum.
+    return (float)_maxFarNearRatio;
+}
 
+void
+DrapingTechnique::onInstall( TerrainEngineNode* engine )
+{
     if ( !_textureSize.isSet() )
     {
         unsigned maxSize = Registry::capabilities().getMaxFastTextureSize();
         _textureSize.init( osg::minimum( 4096u, maxSize ) );
-
-        OE_INFO << LC << "Using texture size = " << *_textureSize << std::endl;
     }
+    OE_INFO << LC << "Using texture size = " << *_textureSize << std::endl;
 }
 
 void
diff --git a/src/osgEarth/DrawInstanced b/src/osgEarth/DrawInstanced
index 6623f0f..39e28ec 100644
--- a/src/osgEarth/DrawInstanced
+++ b/src/osgEarth/DrawInstanced
@@ -64,7 +64,8 @@ namespace osgEarth
          * visitor first.
          * Called by convertGraphToUseDrawInstanced().
          */
-        extern OSGEARTH_EXPORT VirtualProgram* createDrawInstancedProgram();
+        extern OSGEARTH_EXPORT void install(osg::StateSet* stateset);
+        extern OSGEARTH_EXPORT void remove (osg::StateSet* stateset);
 
 
         /**
diff --git a/src/osgEarth/DrawInstanced.cpp b/src/osgEarth/DrawInstanced.cpp
index c230f90..cc0b212 100644
--- a/src/osgEarth/DrawInstanced.cpp
+++ b/src/osgEarth/DrawInstanced.cpp
@@ -18,21 +18,25 @@
  */
 #include <osgEarth/DrawInstanced>
 #include <osgEarth/CullingUtils>
-#include <osgEarth/ShaderUtils>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/StateSetCache>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/ImageUtils>
 
 #include <osg/ComputeBoundsVisitor>
 #include <osg/MatrixTransform>
-#include <osg/BufferIndexBinding>
 #include <osgUtil/MeshOptimizers>
 
-#define MAX_COUNT_UBO   (Registry::capabilities().getMaxUniformBlockSize()/64)
-#define MAX_COUNT_ARRAY 128 // max size of a mat4 uniform array...how to query?
+#define LC "[DrawInstanced] "
 
 using namespace osgEarth;
 using namespace osgEarth::DrawInstanced;
 
+// Ref: http://sol.gfxile.net/instancing.html
+
+#define POSTEX_TEXTURE_UNIT 5
+#define POSTEX_MAX_TEXTURE_SIZE 256
 
 //----------------------------------------------------------------------
 
@@ -49,6 +53,48 @@ namespace
         StaticBoundingBox( const osg::BoundingBox& bbox ) : _bbox(bbox) { }
         osg::BoundingBox computeBound(const osg::Drawable&) const { return _bbox; }
     };
+
+    // assume x is positive
+    int nextPowerOf2(int x)
+    {
+        --x;
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        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);
+        }
+    }
 }
 
 //----------------------------------------------------------------------
@@ -57,10 +103,12 @@ namespace
 ConvertToDrawInstanced::ConvertToDrawInstanced(unsigned                numInstances,
                                                const osg::BoundingBox& bbox,
                                                bool                    optimize ) :
-osg::NodeVisitor ( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ),
 _numInstances    ( numInstances ),
 _optimize        ( optimize )
 {
+    setTraversalMode( TRAVERSE_ALL_CHILDREN );
+    setNodeMaskOverride( ~0 );
+
     _staticBBoxCallback = new StaticBoundingBox(bbox);
 }
 
@@ -75,7 +123,6 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
         {
             if ( _optimize )
             {
-                // convert to triangles
                 osgUtil::IndexMeshVisitor imv;
                 imv.makeMesh( *geom );
 
@@ -99,46 +146,58 @@ ConvertToDrawInstanced::apply( osg::Geode& geode )
 }
 
 
-VirtualProgram*
-DrawInstanced::createDrawInstancedProgram()
+void
+DrawInstanced::install(osg::StateSet* stateset)
 {
-    VirtualProgram* vp = new VirtualProgram();
-    vp->setName( "DrawInstanced" );
-
-    std::stringstream buf;
+    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 float oe_di_postex_size; \n"
+        << "void oe_di_setInstancePosition(inout vec4 VertexMODEL) \n"
+        << "{ \n"
+        << "    float index = float(4 * gl_InstanceID) / oe_di_postex_size; \n"
+        << "    float s = fract(index); \n"
+        << "    float t = floor(index)/oe_di_postex_size; \n"
+        << "    float step = 1.0 / oe_di_postex_size; \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);
 
-    buf << "#version 120 \n"
-        << "#extension GL_EXT_gpu_shader4 : enable \n";
-        //<< "#extension GL_EXT_draw_instanced : enable\n";
+    vp->setFunction(
+        "oe_di_setInstancePosition",
+        src_vert,
+        ShaderComp::LOCATION_VERTEX_MODEL );
 
-    if ( Registry::capabilities().supportsUniformBufferObjects() )
-    {
-        // note: no newlines in the layout() line, please
-        buf << "#extension GL_ARB_uniform_buffer_object : enable\n"
-            << "layout(std140) uniform oe_di_modelData { "
-            <<     "mat4 oe_di_modelMatrix[" << MAX_COUNT_UBO << "]; } ;\n";
+    stateset->getOrCreateUniform("oe_di_postex", osg::Uniform::SAMPLER_2D)->set(POSTEX_TEXTURE_UNIT);
+}
 
-        vp->getTemplate()->addBindUniformBlock( "oe_di_modelData", 0 );
-    }
-    else
-    {
-        buf << "uniform mat4 oe_di_modelMatrix[" << MAX_COUNT_ARRAY << "];\n";
-    }
 
-    buf << "void oe_di_setPosition(inout vec4 VertexModel)\n"
-        << "{\n"
-        << "    VertexModel = oe_di_modelMatrix[gl_InstanceID] * VertexModel; \n"
-        << "}\n";
+void
+DrawInstanced::remove(osg::StateSet* stateset)
+{
+    if ( !stateset )
+        return;
 
-    std::string src;
-    src = buf.str();
+    VirtualProgram* vp = VirtualProgram::get(stateset);
+    if ( !vp )
+        return;
 
-    vp->setFunction(
-        "oe_di_setPosition",
-        src,
-        ShaderComp::LOCATION_VERTEX_MODEL );
+    vp->removeShader( "oe_di_setInstancePosition" );
 
-    return vp;
+    stateset->removeUniform("oe_di_postex");
+    stateset->removeUniform("oe_di_postex_size");
 }
 
 
@@ -169,13 +228,9 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
     // get rid of the old matrix transforms.
     parent->removeChildren(0, parent->getNumChildren());
 
-    // whether to use UBOs.
-    bool useUBO = Registry::capabilities().supportsUniformBufferObjects();
-
     // maximum size of a slice.
-    // for UBOs, assume 64K / sizeof(mat4) = 1024.
-    // for uniform array, assume 8K / sizeof(mat4) = 128.
-    unsigned maxSliceSize = useUBO ? MAX_COUNT_UBO : MAX_COUNT_ARRAY;
+    unsigned maxTexSize   = POSTEX_MAX_TEXTURE_SIZE;
+    unsigned maxSliceSize = (maxTexSize*maxTexSize)/4; // 4 vec4s per matrix.
 
     // For each model:
     for( ModelNodeMatrices::iterator i = models.begin(); i != models.end(); ++i )
@@ -217,12 +272,13 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
         ConvertToDrawInstanced cdi(sliceSize, bbox, true);
         node->accept( cdi );
 
-        // If we don't have an even number of instance groups, make a smaller last one.
+        // 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( 
+            lastNode = osg::clone(
                 node, 
                 osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_PRIMITIVES );
 
@@ -242,32 +298,36 @@ DrawInstanced::convertGraphToUseDrawInstanced( osg::Group* parent )
             // this group is simply a container for the uniform:
             osg::Group* sliceGroup = new osg::Group();
 
-            if ( useUBO ) // uniform buffer object:
-            {
-                osg::MatrixfArray* mats = new osg::MatrixfArray();
-                mats->setBufferObject( new osg::UniformBufferObject() );
-                // 64 = sizeof(mat4)
-                osg::UniformBufferBinding* ubb = new osg::UniformBufferBinding( 0, mats->getBufferObject(), 0, currentSize * 64 );
-                sliceGroup->getOrCreateStateSet()->setAttribute( ubb, osg::StateAttribute::ON );
-                for( unsigned m=0; m < currentSize; ++m )
-                {
-                    mats->push_back( matrices[offset + m] );
-                }
-                ubb->setDataVariance( osg::Object::DYNAMIC );
-            }
-            else // just use a uniform array
+            // 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->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 );
+
+            osg::StateSet* stateset = sliceGroup->getOrCreateStateSet();
+            stateset->setTextureAttributeAndModes(POSTEX_TEXTURE_UNIT, postex, 1);
+            stateset->getOrCreateUniform("oe_di_postex_size", osg::Uniform::FLOAT)->set((float)texSize.x());
+
+            // could use PixelWriter but we know the format.
+            GLfloat* ptr = reinterpret_cast<GLfloat*>( image->data() );
+            for(unsigned m=0; m<currentSize; ++m)
             {
-                // assign the matrices to the uniform array:
-                ArrayUniform uniform(
-                    "oe_di_modelMatrix", 
-                    osg::Uniform::FLOAT_MAT4,
-                    sliceGroup->getOrCreateStateSet(),
-                    currentSize );
-
-                for( unsigned m=0; m < currentSize; ++m )
-                {
-                    uniform.setElement( m, matrices[offset + 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);
             }
 
             // add the node as a child:
diff --git a/src/osgEarth/ElevationLayer b/src/osgEarth/ElevationLayer
index 3793996..3aa04e2 100644
--- a/src/osgEarth/ElevationLayer
+++ b/src/osgEarth/ElevationLayer
@@ -40,6 +40,9 @@ namespace osgEarth
         /** dtor */
         virtual ~ElevationLayerOptions() { }
 
+        optional<bool>& offset() { return _offset; }
+        const optional<bool>& offset() const { return _offset; }  
+
     public:
         virtual Config getConfig() const { return getConfig(false); }
         virtual Config getConfig( bool isolate ) const;
@@ -48,6 +51,8 @@ namespace osgEarth
     private:
         void fromConfig( const Config& conf );
         void setDefaults();
+
+        optional< bool > _offset;
     };
 
     //--------------------------------------------------------------------
@@ -115,6 +120,11 @@ namespace osgEarth
             const TileKey&    key,
             ProgressCallback* progress =0L );
 
+        /**
+         * Whether the given key is valid for this layer
+         */
+        virtual bool isKeyValid(const TileKey& key) const;
+
     protected:
         
         // creates a geoHF directly from the tile source
@@ -179,7 +189,7 @@ namespace osgEarth
         void setExpressTileSize( unsigned tileSize );
 
     private:
-        optional<unsigned> _expressTileSize;
+        optional<unsigned> _expressTileSize;        
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/ElevationLayer.cpp b/src/osgEarth/ElevationLayer.cpp
index e5b14b3..c33c774 100644
--- a/src/osgEarth/ElevationLayer.cpp
+++ b/src/osgEarth/ElevationLayer.cpp
@@ -19,6 +19,7 @@
 #include <osgEarth/ElevationLayer>
 #include <osgEarth/VerticalDatum>
 #include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Progress>
 #include <osg/Version>
 
 using namespace osgEarth;
@@ -45,19 +46,21 @@ TerrainLayerOptions( name, driverOptions )
 void
 ElevationLayerOptions::setDefaults()
 {
-    //NOP
+    _offset = false;
 }
 
 Config
 ElevationLayerOptions::getConfig( bool isolate ) const
 {
-    return TerrainLayerOptions::getConfig( isolate );
+    Config conf = TerrainLayerOptions::getConfig( isolate );
+    conf.updateIfSet("offset", _offset);
+    return conf;
 }
 
 void
 ElevationLayerOptions::fromConfig( const Config& conf )
 {
-    //NOP
+    conf.getIfSet( "offset", _offset );
 }
 
 void
@@ -91,6 +94,23 @@ namespace
             op( hf.get() );
         }
     };
+
+    // perform very basic sanity-check validation on a heightfield.
+    bool validateHeightField(osg::HeightField* hf)
+    {
+        if (!hf) 
+            return false;
+        if (hf->getNumRows() < 2 || hf->getNumRows() > 1024)
+            return false;
+        if (hf->getNumColumns() < 2 || hf->getNumColumns() > 1024)
+            return false;
+        if (hf->getHeightList().size() != hf->getNumColumns() * hf->getNumRows())
+            return false;
+        if (hf->getXInterval() < 1e-5 || hf->getYInterval() < 1e-5)
+            return false;
+        
+        return true;
+    }
 }
 
 //------------------------------------------------------------------------
@@ -119,7 +139,8 @@ _runtimeOptions( options )
 void
 ElevationLayer::init()
 {
-    _tileSize = 32;
+    _tileSize = 15;
+    //_tileSize = 32;
 }
 
 std::string
@@ -238,28 +259,6 @@ ElevationLayer::createHeightFieldFromTileSource(const TileKey&    key,
         result = assembleHeightFieldFromTileSource( key, progress );
     }
 
-#if 0
-    // If the profiles don't match, use a more complicated technique to assemble the tile:
-    if ( !key.getProfile()->isEquivalentTo( getProfile() ) )
-    {
-        result = assembleHeightFieldFromTileSource( key, progress );
-    }
-    else
-    {
-        // Only try to get data if the source actually has data
-        if ( !source->hasData( key ) )
-        {
-            OE_DEBUG << LC << "Source for layer has no data at " << key.str() << std::endl;
-            return 0L;
-        }
-
-        // Make it from the source:
-        result = source->createHeightField( key, _preCacheOp.get(), progress );
-    }
-#endif
-
-
-
     return result;
 }
 
@@ -388,7 +387,7 @@ GeoHeightField
 ElevationLayer::createHeightField(const TileKey&    key, 
                                   ProgressCallback* progress )
 {
-    osg::HeightField* result = 0L;
+    osg::ref_ptr<osg::HeightField> result;
 
     // If the layer is disabled, bail out.
     if ( _runtimeOptions.enabled().isSetTo( false ) )
@@ -396,6 +395,12 @@ ElevationLayer::createHeightField(const TileKey&    key,
         return GeoHeightField::INVALID;
     }
 
+    // Check the max data level, which limits the LOD of available data.
+    if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() )
+    {
+        return GeoHeightField::INVALID;
+    }
+
     CacheBin* cacheBin = getCacheBin( key.getProfile() );
 
     // validate that we have either a valid tile source, or we're cache-only.
@@ -419,22 +424,25 @@ ElevationLayer::createHeightField(const TileKey&    key,
     bool fromCache = false;
     if ( cacheBin && getCachePolicy().isCacheReadable() )
     {
-        ReadResult r = cacheBin->readObject( key.str() );
+        ReadResult r = cacheBin->readObject( key.str(), getCachePolicy().getMinAcceptTime() );
         if ( r.succeeded() )
         {
-            result = r.release<osg::HeightField>();
-            if ( result )
+            osg::HeightField* cachedHF = r.get<osg::HeightField>();
+            if ( cachedHF && validateHeightField(cachedHF) )
+            {
+                result = cachedHF;
                 fromCache = true;
+            }
         }
     }
 
     // if we're cache-only, but didn't get data from the cache, fail silently.
-    if ( !result && isCacheOnly() )
+    if ( !result.valid() && isCacheOnly() )
     {
         return GeoHeightField::INVALID;
     }
 
-    if ( !result )
+    if ( !result.valid() )
     {
         // bad tilesource? fail
         if ( !getTileSource() || !getTileSource()->isOK() )
@@ -445,6 +453,13 @@ ElevationLayer::createHeightField(const TileKey&    key,
 
         // build a HF from the TileSource.
         result = createHeightFieldFromTileSource( key, progress );
+
+        // validate it to make sure it's legal.
+        if ( result.valid() && !validateHeightField(result.get()) )
+        {
+            OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl;
+            result = 0L;
+        }
     }
 
     // cache if necessary
@@ -475,6 +490,21 @@ ElevationLayer::createHeightField(const TileKey&    key,
 }
 
 
+bool
+ElevationLayer::isKeyValid(const TileKey& key) const
+{
+    if (!key.valid())
+        return false;
+
+    if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() ) 
+    {
+        return false;
+    }
+
+    return TerrainLayer::isKeyValid(key);
+}
+
+
 //------------------------------------------------------------------------
 
 #undef  LC
@@ -517,6 +547,8 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
     //Get a HeightField for each of the enabled layers
     GeoHeightFieldVector heightFields;
 
+    GeoHeightFieldVector offsetHeightFields;
+
     //The number of fallback heightfields we have
     int numFallbacks = 0;
 
@@ -541,9 +573,14 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
     for( ElevationLayerVector::const_iterator i = this->begin(); i != this->end(); i++ )
     {
         ElevationLayer* layer = i->get();
-        if ( layer->getVisible() && layer->isKeyValid( keyToUse ) )
+
+        if ( layer->getEnabled() && layer->getVisible() )
         {
-            GeoHeightField geoHF = layer->createHeightField( keyToUse, progress );
+            GeoHeightField geoHF;
+            if ( layer->isKeyValid(keyToUse) )
+            {
+                geoHF = layer->createHeightField( keyToUse, progress );
+            }
 
             // if "fallback" is set, try to fall back on lower LODs.
             if ( !geoHF.valid() && fallback )
@@ -571,7 +608,16 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
 
             if ( geoHF.valid() )
             {
-                heightFields.push_back( geoHF );
+                //If the layer is offset, add it to the list of offset heightfields
+                if (*layer->getElevationLayerOptions().offset())
+                {                    
+                    offsetHeightFields.push_back( geoHF );
+                }
+                //Otherwise add it to the list of regular heightfields
+                else
+                {
+                    heightFields.push_back( geoHF );
+                }
             }
         }
     }
@@ -584,17 +630,18 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
     }   
 
     if ( heightFields.size() == 0 )
-    {            
+    {
         //If we got no heightfields but were requested to fallback, create an empty heightfield.
         if ( fallback )
         {
-            unsigned defaultSize = _expressTileSize.getOrUse( 8 );
+            unsigned defaultSize = _expressTileSize.getOrUse( 7 );
 
             out_result = HeightFieldUtils::createReferenceHeightField( 
                 keyToUse.getExtent(), 
                 defaultSize, 
                 defaultSize );
 
+            if ( offsetHeightFields.size() == 0 )
             return true;
         }
         else
@@ -623,6 +670,7 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
         {
             out_result = HeightFieldUtils::resampleHeightField(
                 out_result.get(),
+                key.getExtent(),
                 *_expressTileSize,
                 *_expressTileSize,
                 interpolation );
@@ -763,5 +811,36 @@ ElevationLayerVector::createHeightField(const TileKey&                  key,
         out_result->setBorderWidth( 0 );
     }
 
+    // Add any "offset" elevation layers to the resulting heightfield
+    if (out_result.valid() && offsetHeightFields.size() )
+    {        
+        // calculate the post spacings.
+        double minx, miny, maxx, maxy;
+        key.getExtent().getBounds(minx, miny, maxx, maxy);
+        double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1);
+        double dy = (maxy - miny)/(double)(out_result->getNumRows()-1);
+
+        const SpatialReference* keySRS = keyToUse.getProfile()->getSRS();
+
+        for( GeoHeightFieldVector::iterator itr = offsetHeightFields.begin(); itr != offsetHeightFields.end(); ++itr )
+        {
+            for (unsigned int c = 0; c < out_result->getNumColumns(); c++)
+            {
+                double x = minx + (dx * (double)c);
+                for (unsigned int r = 0; r < out_result->getNumRows(); r++)
+                {                         
+                    double y = miny + (dy * (double)r);
+                    float elevation = 0.0;                    
+                    if (itr->getElevation(keySRS, x, y, interpolation, keySRS, elevation))
+                    {                    
+                        double h = out_result->getHeight( c, r );                        
+                        h += elevation;                                     
+                        out_result->setHeight( c, r, h );
+                    }                                
+                }
+            }
+        }
+    }
+
     return out_result.valid();
 }
diff --git a/src/osgEarth/ElevationQuery.cpp b/src/osgEarth/ElevationQuery.cpp
index e21dea5..0fcb470 100644
--- a/src/osgEarth/ElevationQuery.cpp
+++ b/src/osgEarth/ElevationQuery.cpp
@@ -57,70 +57,105 @@ ElevationQuery::getMaxLevel( double x, double y, const SpatialReference* srs, co
     unsigned int maxLevel = 0;
     for( ElevationLayerVector::const_iterator i = _mapf.elevationLayers().begin(); i != _mapf.elevationLayers().end(); ++i )
     {
+        // skip disabled layers
+        if ( !i->get()->getEnabled() )
+            continue;
+
         unsigned int layerMax = 0;
+
         osgEarth::TileSource* ts = i->get()->getTileSource();
-        if ( ts && ts->getDataExtents().size() > 0 )
+        if ( ts )
         {
-            osg::Vec3d tsCoord(x, y, 0);
-
-            const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
-            if ( srs && tsSRS )
-                srs->transform(tsCoord, tsSRS, tsCoord);
-            else
-                tsSRS = srs;
-            
-            for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
+            // TileSource is good; check for optional data extents:
+            if ( ts->getDataExtents().size() > 0 )
             {
-                if (j->maxLevel().isSet() && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                osg::Vec3d tsCoord(x, y, 0);
+
+                const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
+                if ( srs && tsSRS )
+                    srs->transform(tsCoord, tsSRS, tsCoord);
+                else
+                    tsSRS = srs;
+                
+                for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
                 {
-                    layerMax = j->maxLevel().value();
+                    if (j->maxLevel().isSet() && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                    {
+                        layerMax = j->maxLevel().value();
+                    }
                 }
+
+                //Need to convert the layer max of this TileSource to that of the actual profile
+                layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
             }
 
-            //Need to convert the layer max of this TileSource to that of the actual profile
-            layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
+            // cap the max to the layer's express max level (if set).
+            if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
+            {
+                layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
+            }
+        }
+        else
+        {
+            // no TileSource? probably in cache-only mode. Use the layer max (or its default).
+            layerMax = i->get()->getTerrainLayerRuntimeOptions().maxLevel().value();
         }
-
-        if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
-            layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
 
         if (layerMax > maxLevel) maxLevel = layerMax;
     }    
 
-    // need to check the image layers too, because if image layers do deeper than elevation layers,
+    // need to check the image layers too, because if image layers go deeper than elevation layers,
     // upsampling occurs that can change the formation of the terrain skin.
-    // NOTE: this probably doesn't happen in "triangulation" interpolation mode.. -gw
-    for( ImageLayerVector::const_iterator i = _mapf.imageLayers().begin(); i != _mapf.imageLayers().end(); ++i )
+    // NOTE: this doesn't happen in "triangulation" interpolation mode.
+    if ( _mapf.getMapInfo().getElevationInterpolation() != osgEarth::INTERP_TRIANGULATE )
     {
-        unsigned int layerMax = 0;
-        osgEarth::TileSource* ts = i->get()->getTileSource();
-        if ( ts && ts->getDataExtents().size() > 0 )
+        for( ImageLayerVector::const_iterator i = _mapf.imageLayers().begin(); i != _mapf.imageLayers().end(); ++i )
         {
-            osg::Vec3d tsCoord(x, y, 0);
-            const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
-            if ( srs && tsSRS )
-                srs->transform(tsCoord, tsSRS, tsCoord);
-            else
-                tsSRS = srs;
-            
-            for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
+            // skip disabled layers
+            if ( !i->get()->getEnabled() )
+                continue;
+
+            unsigned int layerMax = 0;
+            osgEarth::TileSource* ts = i->get()->getTileSource();
+            if ( ts )
             {
-                if (j->maxLevel().isSet()  && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                // TileSource is good; check for optional data extents:
+                if ( ts->getDataExtents().size() > 0 )
                 {
-                    layerMax = j->maxLevel().value();
+                    osg::Vec3d tsCoord(x, y, 0);
+                    const SpatialReference* tsSRS = ts->getProfile() ? ts->getProfile()->getSRS() : 0L;
+                    if ( srs && tsSRS )
+                        srs->transform(tsCoord, tsSRS, tsCoord);
+                    else
+                        tsSRS = srs;
+                    
+                    for (osgEarth::DataExtentList::iterator j = ts->getDataExtents().begin(); j != ts->getDataExtents().end(); j++)
+                    {
+                        if (j->maxLevel().isSet()  && j->maxLevel() > layerMax && j->contains( tsCoord.x(), tsCoord.y(), tsSRS ))
+                        {
+                            layerMax = j->maxLevel().value();
+                        }
+                    }
+
+                    // Need to convert the layer max of this TileSource to that of the actual profile
+                    layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
+                }        
+                
+                if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
+                {
+                    layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
                 }
             }
+            else
+            {
+                // no TileSource? probably in cache-only mode. Use the layer max (or its default).
+                layerMax = i->get()->getTerrainLayerRuntimeOptions().maxLevel().value();
+            }
 
-            //Need to convert the layer max of this TileSource to that of the actual profile
-            layerMax = profile->getEquivalentLOD( ts->getProfile(), layerMax );            
-        }        
-        
-        if ( i->get()->getTerrainLayerRuntimeOptions().maxLevel().isSet() )
-            layerMax = std::min( layerMax, *i->get()->getTerrainLayerRuntimeOptions().maxLevel() );
-
-        if (layerMax > maxLevel)
-            maxLevel = layerMax;
-    } 
+            if (layerMax > maxLevel)
+                maxLevel = layerMax;
+        }
+    }
 
     if (maxLevel == 0) 
     {
@@ -257,11 +292,10 @@ ElevationQuery::getElevationImpl(const GeoPoint& point,
     }
 
     // Check the tile cache. Note that the TileSource already likely has a MemCache
-    // attached to it. We employ a secondary cache here for a couple reasons. One, this
-    // cache will store not only the heightfield, but also the tesselated tile in the event
-    // that we're using GEOMETRIC mode. Second, since the call the getHeightField can 
-    // fallback on a lower resolution, this cache will hold the final resolution heightfield
-    // instead of trying to fetch the higher resolution one each item.
+    // attached to it. We employ a secondary cache here because: since the call to
+    // getHeightField can fallback on a lower resolution, this cache will hold the
+    // final resolution heightfield instead of trying to fetch the higher resolution
+    // one each item.
 
     TileCache::Record record;
     if ( _tileCache.get(key, record) )
diff --git a/src/osgEarth/FadeEffect.cpp b/src/osgEarth/FadeEffect.cpp
index 9e179d0..b5902af 100644
--- a/src/osgEarth/FadeEffect.cpp
+++ b/src/osgEarth/FadeEffect.cpp
@@ -50,7 +50,7 @@ FadeOptions::getConfig() const
 
 namespace
 {
-    char* FadeEffectVertexShader =
+    const char* FadeEffectVertexShader =
         "#version " GLSL_VERSION_STR "\n"
 #ifdef OSG_GLES2_AVAILABLE
         "precision mediump float; \n"
@@ -70,7 +70,7 @@ namespace
         "    oe_fadeeffect_opacity = clamp(t, 0.0, 1.0) * clamp(r, 0.0, 1.0); \n"
         "} \n";
 
-    char* FadeEffectFragmentShader = 
+    const char* FadeEffectFragmentShader = 
         "#version " GLSL_VERSION_STR "\n"
 #ifdef OSG_GLES2_AVAILABLE
         "precision mediump float; \n"
@@ -163,7 +163,7 @@ FadeEffect::getAttenuationDistance() const
 
 namespace
 {
-    char* FadeLODFragmentShader = 
+    const char* FadeLODFragmentShader = 
         "#version " GLSL_VERSION_STR "\n"
 #ifdef OSG_GLES_AVAILABLE
         "precision mediump float; \n"
@@ -245,73 +245,3 @@ FadeLOD::traverse( osg::NodeVisitor& nv )
     }
 }
 
-
-#if 0
-void 
-FadeLOD::setMinPixelExtent( float value )
-{
-    osg::Vec4f value;
-    _params->get( value );
-    value[0] = value;
-    _params->set( value );
-}
-
-float 
-FadeLOD::getMinPixelExtent() const
-{
-    osg::Vec4f value;
-    _params->get( value );
-    return value[0];
-}
-
-void 
-FadeLOD::setMaxPixelExtent( float value )
-{
-    osg::Vec4f value;
-    _params->get( value );
-    value[1] = value;
-    _params->set( value );
-}
-
-float 
-FadeLOD::getMaxPixelExtent() const
-{
-    osg::Vec4f value;
-    _params->get( value );
-    return value[1];
-}
-
-void
-FadeLOD::setMinFadeExtent( float value )
-{
-    osg::Vec4f value;
-    _params->get( value );
-    value[2] = value;
-    _params->set( value );
-}
-
-float
-FadeLOD::getMinFadeExtent() const
-{
-    osg::Vec4f value;
-    _params->get( value );
-    return value[2];
-}
-
-void
-FadeLOD::setMaxFadeExtent( float value )
-{
-    osg::Vec4f value;
-    _params->get( value );
-    value[3] = value;
-    _params->set( value );
-}
-
-float
-FadeLOD::getMaxFadeExtent() const
-{
-    osg::Vec4f value;
-    _params->get( value );
-    return value[3];
-}
-#endif
diff --git a/src/osgEarth/FileUtils b/src/osgEarth/FileUtils
index 60a1231..9f872be 100644
--- a/src/osgEarth/FileUtils
+++ b/src/osgEarth/FileUtils
@@ -21,6 +21,8 @@
 #define OSGEARTH_FILEUTILS_H
 
 #include <osgEarth/Common>
+#include <osgEarth/DateTime>
+#include <vector>
 
 namespace osgEarth
 {
@@ -61,6 +63,16 @@ namespace osgEarth
     extern OSGEARTH_EXPORT std::string getTempPath();
 
     /**
+     * Sets the "Last Modified" timestamp of a file to Now.
+     */
+    extern OSGEARTH_EXPORT bool touchFile(const std::string& path);
+
+    /**
+     * Gets the "Last Modified" timestamp of a file.
+     */
+    extern OSGEARTH_EXPORT TimeStamp getLastModifiedTime(const std::string& path);
+
+    /**
      * Gets a temporary filename
      * @param prefix
      *        The prefix of the temporary filename
@@ -69,6 +81,52 @@ namespace osgEarth
      */
      extern OSGEARTH_EXPORT std::string getTempName(const std::string& prefix="", const std::string& suffix="");
 
+     /**
+      * Utility class that processes files and directories recursively.
+      */
+     class OSGEARTH_EXPORT DirectoryVisitor
+     {
+     public:
+         /**
+          * Create a new DirectoryVisitor
+          */
+         DirectoryVisitor();
+
+         /**
+          * Processes a file.  Override this in your subclass.
+          */
+         virtual void handleFile( const std::string& filename );
+
+         /**
+          * Processes a directory.
+          * @returns
+          *     true if the directory should be traversed, false otherwise.
+          */
+         virtual bool handleDir( const std::string& path );
+
+         /**
+          * Traverse into a directory
+          * @param path
+          *     The path to traverse
+          */
+         virtual void traverse(const std::string& path );
+     };
+
+     /**
+      * Utility class that recursively collects all files within a directory.
+      */
+     class OSGEARTH_EXPORT CollectFilesVisitor : public DirectoryVisitor
+     {
+     public:
+         CollectFilesVisitor();
+
+         virtual void handleFile( const std::string& filename );      
+
+         /**
+          * The collected filenames.
+          */
+         std::vector< std::string > filenames;    
+     };
 
 }
 
diff --git a/src/osgEarth/FileUtils.cpp b/src/osgEarth/FileUtils.cpp
index 396a5bc..85967ea 100644
--- a/src/osgEarth/FileUtils.cpp
+++ b/src/osgEarth/FileUtils.cpp
@@ -19,17 +19,34 @@
 
 #include <osgEarth/FileUtils>
 #include <osgEarth/StringUtils>
+#include <osgEarth/DateTime>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/Registry>
 #include <osg/Notify>
-#include <list>
-#include <sstream>
 
 #ifdef WIN32
-#include <windows.h>
+#  include <windows.h>
+#else
+#  include <stdio.h>
+#  include <stdlib.h>
 #endif
 
+#include <sys/types.h>
+
+#ifdef WIN32
+#  include <sys/utime.h>
+#else
+#  include <utime.h>
+#endif
+
+#include <sys/stat.h>
+#include <time.h>
+
+#include <list>
+#include <sstream>
+
+
 using namespace osgEarth;
 
 bool osgEarth::isRelativePath(const std::string& fileName)
@@ -167,3 +184,73 @@ std::string osgEarth::getTempName(const std::string& prefix, const std::string&
     }
     return "";
 }
+
+
+bool
+osgEarth::touchFile(const std::string& path)
+{
+    DateTime now;
+    struct ::utimbuf ut;
+    ut.actime = now.asTimeStamp();
+    ut.modtime = now.asTimeStamp();
+    return 0 == ::utime( path.c_str(), &ut );
+}
+
+
+TimeStamp
+osgEarth::getLastModifiedTime(const std::string& path)
+{
+    struct stat buf;
+    if ( stat(path.c_str(), &buf) == 0 )
+        return buf.st_mtime;
+    else
+        return 0;
+}
+
+
+/**************************************************/
+DirectoryVisitor::DirectoryVisitor()
+{
+}    
+
+void DirectoryVisitor::handleFile( const std::string& filename )
+{
+}
+
+bool DirectoryVisitor::handleDir( const std::string& path )
+{
+	return true;
+}
+
+void DirectoryVisitor::traverse(const std::string& path )
+{
+	if ( osgDB::fileType(path) == osgDB::DIRECTORY )
+	{            
+		if (handleDir( path ))
+		{
+			osgDB::DirectoryContents files = osgDB::getDirectoryContents(path);
+			for( osgDB::DirectoryContents::const_iterator f = files.begin(); f != files.end(); ++f )
+			{
+				if ( f->compare(".") == 0 || f->compare("..") == 0 )
+					continue;
+
+				std::string filepath = osgDB::concatPaths( path, *f );
+				traverse( filepath );                
+			}
+		}
+	}
+	else if ( osgDB::fileType(path) == osgDB::REGULAR_FILE )
+	{
+		handleFile( path );            
+	}
+}
+
+/**************************************************/
+CollectFilesVisitor::CollectFilesVisitor()  
+{
+}
+
+void CollectFilesVisitor::handleFile( const std::string& filename )
+{
+	filenames.push_back( filename );        
+}        
\ No newline at end of file
diff --git a/src/osgEarth/GeoData b/src/osgEarth/GeoData
index c452ac3..cb05a9f 100644
--- a/src/osgEarth/GeoData
+++ b/src/osgEarth/GeoData
@@ -198,6 +198,17 @@ namespace osgEarth
          */
         bool createWorldToLocal( osg::Matrixd& out_world2local ) const;
 
+        /**
+         * Outputs an "up" vector corresponding to the given point. The up vector
+         * is orthogonal to a local tangent plane at that point on the map.
+         */
+        bool createWorldUpVector( osg::Vec3d& out_up ) const;
+
+        /**
+         * Calculates the distance in meters from this geopoint to another.
+         */
+        double distanceTo(const GeoPoint& rhs) const;
+
 
         bool operator == (const GeoPoint& rhs) const;
         bool operator != (const GeoPoint& rhs) const { return !operator==(rhs); }
@@ -325,6 +336,7 @@ namespace osgEarth
          */
         bool getCentroid( double& out_x, double& out_y ) const;
         osg::Vec3d getCentroid() const { osg::Vec3d r; getCentroid(r.x(), r.y()); return r; }
+        bool getCentroid( GeoPoint& output ) const;
 
         /**
          * Returns true is that extent is in a Geographic (lat/long) SRS that spans
diff --git a/src/osgEarth/GeoData.cpp b/src/osgEarth/GeoData.cpp
index d35454b..fed0880 100644
--- a/src/osgEarth/GeoData.cpp
+++ b/src/osgEarth/GeoData.cpp
@@ -314,7 +314,8 @@ GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* t
 bool
 GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* terrain, double& out_z ) const
 {
-    if ( !isValid() ) return false;
+    if ( !isValid() )
+        return false;
     
     // already in the target mode? just return z.
     if ( _altMode == altMode ) 
@@ -323,7 +324,8 @@ GeoPoint::transformZ(const AltitudeMode& altMode, const TerrainHeightProvider* t
         return true;
     }
 
-    if ( !terrain ) return false;
+    if ( !terrain )
+        return false;
 
     // convert to geographic if necessary and sample the MSL height under the point.
     double out_hamsl;
@@ -383,7 +385,10 @@ GeoPoint::toWorld( osg::Vec3d& out_world, const TerrainHeightProvider* terrain )
     else if ( terrain != 0L )
     {
         GeoPoint absPoint = *this;
-        absPoint.makeAbsolute( terrain );
+        if (!absPoint.makeAbsolute( terrain ))
+        {
+            return false;            
+        }
         return absPoint.toWorld( out_world );
     }
     else
@@ -433,6 +438,66 @@ GeoPoint::createWorldToLocal( osg::Matrixd& out_w2l ) const
     return _srs->createWorldToLocal( _p, out_w2l );
 }
 
+bool
+GeoPoint::createWorldUpVector( osg::Vec3d& out_up ) const
+{
+    if ( !isValid() ) return false;
+
+    if ( _srs->isProjected() )
+    {
+        out_up.set(0, 0, 1);
+        return true;
+    }
+    else if ( _srs->isGeographic() )
+    {
+        double coslon = cos( osg::DegreesToRadians(x()) );
+        double coslat = cos( osg::DegreesToRadians(y()) );
+        double sinlon = sin( osg::DegreesToRadians(x()) );
+        double sinlat = sin( osg::DegreesToRadians(y()) );
+
+        out_up.set( coslon*coslat, sinlon*coslat, sinlat );
+        return true;
+    }
+    else
+    {
+        osg::Vec3d ecef;
+        if ( this->toWorld( ecef ) )
+        {
+            out_up = _srs->getEllipsoid()->computeLocalUpVector( ecef.x(), ecef.y(), ecef.z() );
+            return true;
+        }
+    }
+    return false;
+}
+
+double
+GeoPoint::distanceTo(const GeoPoint& rhs) const
+{
+    if ( getSRS()->isProjected() && rhs.getSRS()->isProjected() )
+    {
+        if ( getSRS()->isEquivalentTo(rhs.getSRS()) )
+        {
+            return (vec3d() - rhs.vec3d()).length();
+        }
+        else
+        {
+            GeoPoint rhsT = transform(rhs.getSRS());
+            return (vec3d() - rhsT.vec3d()).length();
+        }
+    }
+    else
+    {
+        GeoPoint p1 = transform( getSRS()->getGeographicSRS() );
+        GeoPoint p2 = rhs.transform( getSRS()->getGeodeticSRS() );
+
+        return GeoMath::distance(
+            osg::DegreesToRadians(p1.y()), osg::DegreesToRadians(p1.x()),
+            osg::DegreesToRadians(p2.y()), osg::DegreesToRadians(p2.x()),
+            getSRS()->getGeographicSRS()->getEllipsoid()->getRadiusEquator() );
+    }
+}
+
+
 //------------------------------------------------------------------------
 
 #undef  LC
@@ -580,6 +645,13 @@ _circle( rhs._circle )
 }
 
 bool
+GeoExtent::getCentroid(GeoPoint& out) const
+{
+    out = GeoPoint(_srs, getCentroid(), ALTMODE_ABSOLUTE);
+    return true;
+}
+
+bool
 GeoExtent::operator == ( const GeoExtent& rhs ) const
 {
     if ( !isValid() && !rhs.isValid() )
@@ -1187,9 +1259,9 @@ GeoImage::getExtent() const {
 
 double
 GeoImage::getUnitsPerPixel() const {
-	double uppw = _extent.width() / (double)_image->s();
-	double upph = _extent.height() / (double)_image->t();
-	return (uppw + upph) / 2.0;
+    double uppw = _extent.width() / (double)_image->s();
+    double upph = _extent.height() / (double)_image->t();
+    return (uppw + upph) / 2.0;
 }
 
 GeoImage
@@ -1281,161 +1353,163 @@ GeoImage::addTransparentBorder(bool leftBorder, bool rightBorder, bool bottomBor
     return GeoImage(newImage, GeoExtent(getSRS(), xmin, ymin, xmax, ymax));
 }
 
-static osg::Image*
-createImageFromDataset(GDALDataset* ds)
+namespace
 {
-    // called internally -- GDAL lock not required
+    osg::Image*
+    createImageFromDataset(GDALDataset* ds)
+    {
+        // called internally -- GDAL lock not required
 
-    //Allocate the image
-    osg::Image *image = new osg::Image;
-    image->allocateImage(ds->GetRasterXSize(), ds->GetRasterYSize(), 1, GL_RGBA, GL_UNSIGNED_BYTE);
+        //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);
-    ds->FlushCache();
+        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();
+        image->flipVertical();
 
-    return image;
-}
+        return image;
+    }
 
-static GDALDataset*
-createMemDS(int width, int height, double minX, double minY, double maxX, double maxY, const std::string &projection)
-{
-    //Get the MEM driver
-    GDALDriver* memDriver = (GDALDriver*)GDALGetDriverByName("MEM");
-    if (!memDriver)
+    GDALDataset*
+    createMemDS(int width, int height, double minX, double minY, double maxX, double maxY, const std::string &projection)
     {
-        OE_NOTICE << "[osgEarth::GeoData] Could not get MEM driver" << std::endl;
-    }
+        //Get the MEM driver
+        GDALDriver* memDriver = (GDALDriver*)GDALGetDriverByName("MEM");
+        if (!memDriver)
+        {
+            OE_NOTICE << "[osgEarth::GeoData] Could not get MEM driver" << std::endl;
+        }
 
-    //Create the in memory dataset.
-    GDALDataset* ds = memDriver->Create("", width, height, 4, GDT_Byte, 0);
+        //Create the in memory dataset.
+        GDALDataset* ds = memDriver->Create("", width, height, 4, GDT_Byte, 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);
+        //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);
 
-    //Initialize the geotransform
-    double geotransform[6];
-    double x_units_per_pixel = (maxX - minX) / (double)width;
-    double y_units_per_pixel = (maxY - minY) / (double)height;
-    geotransform[0] = minX;
-    geotransform[1] = x_units_per_pixel;
-    geotransform[2] = 0;
-    geotransform[3] = maxY;
-    geotransform[4] = 0;
-    geotransform[5] = -y_units_per_pixel;
-    ds->SetGeoTransform(geotransform);
-    ds->SetProjection(projection.c_str());
+        //Initialize the geotransform
+        double geotransform[6];
+        double x_units_per_pixel = (maxX - minX) / (double)width;
+        double y_units_per_pixel = (maxY - minY) / (double)height;
+        geotransform[0] = minX;
+        geotransform[1] = x_units_per_pixel;
+        geotransform[2] = 0;
+        geotransform[3] = maxY;
+        geotransform[4] = 0;
+        geotransform[5] = -y_units_per_pixel;
+        ds->SetGeoTransform(geotransform);
+        ds->SetProjection(projection.c_str());
 
-    return ds;
-}
+        return ds;
+    }
 
-static GDALDataset*
-createDataSetFromImage(const osg::Image* image, double minX, double minY, double maxX, double maxY, const std::string &projection)
-{
-    //Clone the incoming image
-    osg::ref_ptr<osg::Image> clonedImage = new osg::Image(*image);
+    GDALDataset*
+    createDataSetFromImage(const osg::Image* image, double minX, double minY, double maxX, double maxY, const std::string &projection)
+    {
+        //Clone the incoming image
+        osg::ref_ptr<osg::Image> clonedImage = new osg::Image(*image);
 
-    //Flip the image
-    clonedImage->flipVertical();  
+        //Flip the image
+        clonedImage->flipVertical();  
 
-    GDALDataset* srcDS = createMemDS(image->s(), image->t(), minX, minY, maxX, maxY, projection);
+        GDALDataset* srcDS = createMemDS(image->s(), image->t(), minX, minY, maxX, maxY, projection);
 
-    //Write the image data into the memory dataset
-    //If the image is already RGBA, just read all 4 bands in one call
-    if (image->getPixelFormat() == GL_RGBA)
-    {
-        srcDS->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), (void*)clonedImage->data(), clonedImage->s(), clonedImage->t(), GDT_Byte, 4, NULL, 4, 4 * image->s(), 1);
-    }
-    else if (image->getPixelFormat() == GL_RGB)
-    {    
-        //OE_NOTICE << "[osgEarth::GeoData] Reprojecting RGB " << std::endl;
-        //Read the read, green and blue bands
-        srcDS->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), (void*)clonedImage->data(), clonedImage->s(), clonedImage->t(), GDT_Byte, 3, NULL, 3, 3 * image->s(), 1);
+        //Write the image data into the memory dataset
+        //If the image is already RGBA, just read all 4 bands in one call
+        if (image->getPixelFormat() == GL_RGBA)
+        {
+            srcDS->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), (void*)clonedImage->data(), clonedImage->s(), clonedImage->t(), GDT_Byte, 4, NULL, 4, 4 * image->s(), 1);
+        }
+        else if (image->getPixelFormat() == GL_RGB)
+        {    
+            //OE_NOTICE << "[osgEarth::GeoData] Reprojecting RGB " << std::endl;
+            //Read the read, green and blue bands
+            srcDS->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), (void*)clonedImage->data(), clonedImage->s(), clonedImage->t(), GDT_Byte, 3, NULL, 3, 3 * image->s(), 1);
 
-        //Initialize the alpha values to 255.
-        unsigned char *alpha = new unsigned char[clonedImage->s() * clonedImage->t()];
-        memset(alpha, 255, clonedImage->s() * clonedImage->t());
+            //Initialize the alpha values to 255.
+            unsigned char *alpha = new unsigned char[clonedImage->s() * clonedImage->t()];
+            memset(alpha, 255, clonedImage->s() * clonedImage->t());
 
-        GDALRasterBand* alphaBand = srcDS->GetRasterBand(4);
-        alphaBand->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), alpha, clonedImage->s(),clonedImage->t(), GDT_Byte, 0, 0);
+            GDALRasterBand* alphaBand = srcDS->GetRasterBand(4);
+            alphaBand->RasterIO(GF_Write, 0, 0, clonedImage->s(), clonedImage->t(), alpha, clonedImage->s(),clonedImage->t(), GDT_Byte, 0, 0);
 
-        delete[] alpha;
-    }
-    else
-    {
-        OE_WARN << LC << "createDataSetFromImage: unsupported pixel format " << std::hex << image->getPixelFormat() << std::endl;
+            delete[] alpha;
+        }
+        else
+        {
+            OE_WARN << LC << "createDataSetFromImage: unsupported pixel format " << std::hex << image->getPixelFormat() << std::endl;
+        }
+        srcDS->FlushCache();
+
+        return srcDS;
     }
-    srcDS->FlushCache();
 
-    return srcDS;
-}
+    osg::Image*
+    reprojectImage(osg::Image* srcImage, const std::string srcWKT, double srcMinX, double srcMinY, double srcMaxX, double srcMaxY,
+                   const std::string destWKT, double destMinX, double destMinY, double destMaxX, double destMaxY,
+                   int width = 0, int height = 0, bool useBilinearInterpolation = true)
+    {
+        GDAL_SCOPED_LOCK;
+        osg::Timer_t start = osg::Timer::instance()->tick();
 
-static osg::Image*
-reprojectImage(osg::Image* srcImage, const std::string srcWKT, double srcMinX, double srcMinY, double srcMaxX, double srcMaxY,
-               const std::string destWKT, double destMinX, double destMinY, double destMaxX, double destMaxY,
-               int width = 0, int height = 0, bool useBilinearInterpolation = true)
-{
-    GDAL_SCOPED_LOCK;
-	osg::Timer_t start = osg::Timer::instance()->tick();
+        //Create a dataset from the source image
+        GDALDataset* srcDS = createDataSetFromImage(srcImage, srcMinX, srcMinY, srcMaxX, srcMaxY, srcWKT);
 
-    //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 << "Source image is " << srcImage->s() << "x" << srcImage->t() << std::endl;
 
+        if (width == 0 || height == 0)
+        {
+            double outgeotransform[6];
+            double extents[4];
+            void* transformer = GDALCreateGenImgProjTransformer(srcDS, srcWKT.c_str(), NULL, destWKT.c_str(), 1, 0, 0);
+            GDALSuggestedWarpOutput2(srcDS,
+                GDALGenImgProjTransform, transformer,
+                outgeotransform,
+                &width,
+                &height,
+                extents,
+                0);
+            GDALDestroyGenImgProjTransformer(transformer);
+        }
+	    OE_DEBUG << "Creating warped output of " << width <<"x" << height << std::endl;
+       
+        GDALDataset* destDS = createMemDS(width, height, destMinX, destMinY, destMaxX, destMaxY, destWKT);
 
-    if (width == 0 || height == 0)
-    {
-        double outgeotransform[6];
-        double extents[4];
-        void* transformer = GDALCreateGenImgProjTransformer(srcDS, srcWKT.c_str(), NULL, destWKT.c_str(), 1, 0, 0);
-        GDALSuggestedWarpOutput2(srcDS,
-            GDALGenImgProjTransform, transformer,
-            outgeotransform,
-            &width,
-            &height,
-            extents,
-            0);
-        GDALDestroyGenImgProjTransformer(transformer);
-    }
-	OE_DEBUG << "Creating warped output of " << width <<"x" << height << std::endl;
-   
-    GDALDataset* destDS = createMemDS(width, height, destMinX, destMinY, destMaxX, destMaxY, destWKT);
+        if (useBilinearInterpolation == true)
+        {
+            GDALReprojectImage(srcDS, NULL,
+                               destDS, NULL,
+                               GRA_Bilinear,
+                               0,0,0,0,0);
+        }
+        else
+        {
+            GDALReprojectImage(srcDS, NULL,
+                               destDS, NULL,
+                               GRA_NearestNeighbour,
+                               0,0,0,0,0);
+        }
 
-    if (useBilinearInterpolation == true)
-    {
-        GDALReprojectImage(srcDS, NULL,
-                           destDS, NULL,
-                           GRA_Bilinear,
-                           0,0,0,0,0);
-    }
-    else
-    {
-        GDALReprojectImage(srcDS, NULL,
-                           destDS, NULL,
-                           GRA_NearestNeighbour,
-                           0,0,0,0,0);
-    }
+        osg::Image* result = createImageFromDataset(destDS);
+        
+        delete srcDS;
+        delete destDS;  
 
-    osg::Image* result = createImageFromDataset(destDS);
-    
-    delete srcDS;
-    delete destDS;  
+        osg::Timer_t end = osg::Timer::instance()->tick();
 
-	osg::Timer_t end = osg::Timer::instance()->tick();
+        OE_DEBUG << "Reprojected image in " << osg::Timer::instance()->delta_m(start,end) << std::endl;
 
-	OE_DEBUG << "Reprojected image in " << osg::Timer::instance()->delta_m(start,end) << std::endl;
+        return result;
+    }    
 
-    return result;
-}    
 
-namespace
-{
-    osg::Image* manualReproject(
+    osg::Image*
+    manualReproject(
         const osg::Image* image, 
         const GeoExtent&  src_extent, 
         const GeoExtent&  dest_extent,
@@ -1603,17 +1677,7 @@ namespace
                 }
 
                 writer(color, c, r);
-
-#if 0
-                unsigned char* rgba = const_cast<unsigned char*>(ra.data(c,r,0));
-
-                rgba[0] = (unsigned char)(color.r() * 255);
-                rgba[1] = (unsigned char)(color.g() * 255);
-                rgba[2] = (unsigned char)(color.b() * 255);
-                rgba[3] = (unsigned char)(color.a() * 255);
-#endif
-
-                pixel++;            
+                pixel++;
             }
         }
 
diff --git a/src/osgEarth/HTTPClient b/src/osgEarth/HTTPClient
index 42ed686..a194f68 100644
--- a/src/osgEarth/HTTPClient
+++ b/src/osgEarth/HTTPClient
@@ -21,7 +21,6 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/IOTypes>
-#include <osgEarth/Progress>
 #include <osg/ref_ptr>
 #include <osg/Referenced>
 #include <osgDB/ReaderWriter>
@@ -33,6 +32,8 @@
 
 namespace osgEarth
 {
+    class ProgressCallback;
+
     /**
      * Proxy server configuration.
      */
@@ -114,7 +115,9 @@ namespace osgEarth
         enum Code {
             NONE         = 0,
             OK           = 200,
+            BAD_REQUEST  = 400,
             NOT_FOUND    = 404,
+            CONFLICT     = 409,
             SERVER_ERROR = 500
         };
 
@@ -176,6 +179,14 @@ namespace osgEarth
     };
 
     /**
+     * Object that lets you modify and incoming URL before it's passed to the server
+     */
+    struct OSGEARTH_EXPORT URLRewriter : public osg::Referenced
+    {    
+        virtual std::string rewrite( const std::string& url ) = 0;
+    };
+
+    /**
      * Utility class for making HTTP requests.
      *
      * TODO: This class will actually read data from disk as well, and therefore should
@@ -217,7 +228,26 @@ namespace osgEarth
         /**
            Sets the timeout in seconds to use for HTTP requests.
            Setting to 0 (default) is infinite timeout */
-        void setTimeout( long timeout );
+        static void setTimeout( long timeout );
+
+        /**
+           Gets the timeout in seconds to use for HTTP connect requests.*/
+        static long getConnectTimeout();
+
+        /**
+           Sets the timeout in seconds to use for HTTP connect requests.
+           Setting to 0 (default) is infinite timeout */
+        static void setConnectTimeout( long timeout );
+
+        /**
+         * Gets the URLRewriter that is used to modify urls before sending them to the server
+         */
+        static URLRewriter* getURLRewriter();
+
+        /**
+         * Sets the URLRewriter that is used to modify urls before sending them to the server         
+         */
+        static void setURLRewriter( URLRewriter* rewriter );
 
         /**
          * One time thread safe initialization. In osgEarth, you don't need
diff --git a/src/osgEarth/HTTPClient.cpp b/src/osgEarth/HTTPClient.cpp
index 0905834..22897b7 100644
--- a/src/osgEarth/HTTPClient.cpp
+++ b/src/osgEarth/HTTPClient.cpp
@@ -19,6 +19,7 @@
 #include <osgEarth/HTTPClient>
 #include <osgEarth/Registry>
 #include <osgEarth/Version>
+#include <osgEarth/Progress>
 #include <osgDB/ReadFile>
 #include <osgDB/Registry>
 #include <osgDB/FileNameUtils>
@@ -305,9 +306,12 @@ namespace
     static std::string                 s_userAgent = USER_AGENT;
 
     static long                        s_timeout = 0;
+    static long                        s_connectTimeout = 0;
 
     // HTTP debugging.
     static bool                        s_HTTP_DEBUG = false;
+
+    static osg::ref_ptr< URLRewriter > s_rewriter;
 }
 
 HTTPClient&
@@ -370,15 +374,25 @@ HTTPClient::initializeImpl()
     curl_easy_setopt( _curl_handle, CURLOPT_FOLLOWLOCATION, (void*)1 );
     curl_easy_setopt( _curl_handle, CURLOPT_MAXREDIRS, (void*)5 );
     curl_easy_setopt( _curl_handle, CURLOPT_PROGRESSFUNCTION, &CurlProgressCallback);
-    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //FALSE);    
+    curl_easy_setopt( _curl_handle, CURLOPT_NOPROGRESS, (void*)0 ); //FALSE);
+    curl_easy_setopt( _curl_handle, CURLOPT_FILETIME, true );
+
     long timeout = s_timeout;
     const char* timeoutEnv = getenv("OSGEARTH_HTTP_TIMEOUT");
     if (timeoutEnv)
-    {        
+    {
         timeout = osgEarth::as<long>(std::string(timeoutEnv), 0);
     }
     OE_DEBUG << LC << "Setting timeout to " << timeout << std::endl;
     curl_easy_setopt( _curl_handle, CURLOPT_TIMEOUT, timeout );
+    long connectTimeout = s_connectTimeout;
+    const char* connectTimeoutEnv = getenv("OSGEARTH_HTTP_CONNECTTIMEOUT");
+    if (connectTimeoutEnv)
+    {
+        connectTimeout = osgEarth::as<long>(std::string(connectTimeoutEnv), 0);
+    }
+    OE_DEBUG << LC << "Setting connect timeout to " << connectTimeout << std::endl;
+    curl_easy_setopt( _curl_handle, CURLOPT_CONNECTTIMEOUT, connectTimeout );
 
     _initialized = true;
 }
@@ -415,6 +429,25 @@ void HTTPClient::setTimeout( long timeout )
     s_timeout = timeout;
 }
 
+long HTTPClient::getConnectTimeout()
+{
+    return s_connectTimeout;
+}
+
+void HTTPClient::setConnectTimeout( long timeout )
+{
+    s_connectTimeout = timeout;
+}
+URLRewriter* HTTPClient::getURLRewriter()
+{
+    return s_rewriter.get();
+}
+
+void HTTPClient::setURLRewriter( URLRewriter* rewriter )
+{
+    s_rewriter = rewriter;
+}
+
 void
 HTTPClient::globalInit()
 {
@@ -671,7 +704,7 @@ HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, Pr
         }
     }
 
-    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");	
+    const char* proxyEnvAuth = getenv("OSGEARTH_CURL_PROXYAUTH");
     if (proxyEnvAuth)
     {
         proxy_auth = std::string(proxyEnvAuth);
@@ -708,9 +741,18 @@ HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, Pr
         curl_easy_setopt( _curl_handle, CURLOPT_PROXY, 0 );
     }
 
+    std::string url = request.getURL();
+    // Rewrite the url if the url rewriter is available  
+    osg::ref_ptr< URLRewriter > rewriter = getURLRewriter();
+    if ( rewriter.valid() )
+    {
+        std::string oldURL = url;
+        url = rewriter->rewrite( oldURL );
+        OE_INFO << "Rewrote URL " << oldURL << " to " << url << std::endl;
+    }
 
     const osgDB::AuthenticationDetails* details = authenticationMap ?
-        authenticationMap->getAuthenticationDetails(request.getURL()) :
+        authenticationMap->getAuthenticationDetails( url ) :
         0;
 
     if (details)
@@ -754,7 +796,7 @@ HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, Pr
 
     //Take a temporary ref to the callback
     osg::ref_ptr<ProgressCallback> progressCallback = callback;
-    curl_easy_setopt( _curl_handle, CURLOPT_URL, request.getURL().c_str() );
+    curl_easy_setopt( _curl_handle, CURLOPT_URL, url.c_str() );
     if (callback)
     {
         curl_easy_setopt(_curl_handle, CURLOPT_PROGRESSDATA, progressCallback.get());
@@ -787,46 +829,45 @@ HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, Pr
                 return HTTPResponse(0);
             }
         }
-        
-        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );
+
+        curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &response_code );        
     }
     else
     {
         // simulate failure with a custom response code
         response_code = _simResponseCode;
         res = response_code == 408 ? CURLE_OPERATION_TIMEDOUT : CURLE_COULDNT_CONNECT;
-    }
-
-    if ( s_HTTP_DEBUG )
-    {
-        OE_NOTICE << LC << "GET(" << response_code << "): \"" << request.getURL() << "\"" << std::endl;
-    }
+    }    
 
     HTTPResponse response( response_code );
-   
-    if ( response_code == 200L && res != CURLE_ABORTED_BY_CALLBACK && res != CURLE_OPERATION_TIMEDOUT )
-    {
-        // check for multipart content:
-        char* content_type_cp;
-        curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );
-        if ( content_type_cp == NULL )
-        {
-            OE_WARN << LC
-                << "NULL Content-Type (protocol violation) " 
-                << "URL=" << request.getURL() << std::endl;
-            return HTTPResponse(0L);
-        }
+    
+    // read the response content type:
+    char* content_type_cp;
+
+    curl_easy_getinfo( _curl_handle, CURLINFO_CONTENT_TYPE, &content_type_cp );    
 
-        // NOTE:
-        //   WCS 1.1 specified a "multipart/mixed" response, but ArcGIS Server gives a "multipart/related"
-        //   content type ...
+    if ( content_type_cp != NULL )
+    {
+        response._mimeType = content_type_cp;    
+    }            
 
-        std::string content_type( content_type_cp );
+    if ( s_HTTP_DEBUG )
+    {
+        TimeStamp filetime = 0;
+        if (CURLE_OK != curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime))
+            filetime = 0;
 
-        //OE_DEBUG << LC << "content-type = \"" << content_type << "\"" << std::endl;
+        OE_NOTICE << LC 
+            << "GET(" << response_code << ", " << response._mimeType << ") : \"" 
+            << url << "\" (" << DateTime(filetime).asRFC1123() << ")"<< std::endl;
+    }
 
-        if ( content_type.length() > 9 && ::strstr( content_type.c_str(), "multipart" ) == content_type.c_str() )
-        //if ( content_type == "multipart/mixed; boundary=wcs" ) //todo: parse this.
+    // upon success, parse the data:
+    if ( res != CURLE_ABORTED_BY_CALLBACK && res != CURLE_OPERATION_TIMEDOUT )
+    {        
+        // check for multipart content
+        if (response._mimeType.length() > 9 && 
+            ::strstr( response._mimeType.c_str(), "multipart" ) == response._mimeType.c_str() )
         {
             OE_DEBUG << LC << "detected multipart data; decoding..." << std::endl;
 
@@ -836,25 +877,16 @@ HTTPClient::doGet( const HTTPRequest& request, const osgDB::Options* options, Pr
         else
         {
             // store headers that we care about
-            part->_headers[IOMetadata::CONTENT_TYPE] = content_type;
-
+            part->_headers[IOMetadata::CONTENT_TYPE] = response._mimeType;
             response._parts.push_back( part.get() );
         }
     }
-    else if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT)
+    else  /*if (res == CURLE_ABORTED_BY_CALLBACK || res == CURLE_OPERATION_TIMEDOUT) */
     {        
         //If we were aborted by a callback, then it was cancelled by a user
         response._cancelled = true;
     }
 
-    // Store the mime-type, if any. (Note: CURL manages the buffer returned by
-    // this call.)
-    char* ctbuf = NULL;
-    if ( curl_easy_getinfo(_curl_handle, CURLINFO_CONTENT_TYPE, &ctbuf) == 0 && ctbuf )
-    {
-        response._mimeType = ctbuf;
-    }
-
     return response;
 }
 
@@ -924,6 +956,13 @@ namespace
             }
         }
 
+        if ( !reader )
+        {
+            OE_WARN << LC << "Cannot find an OSG plugin to read response data (ext="
+                << ext << "; mime-type=" << response.getMimeType()
+                << ")" << std::endl;
+        }
+
         return reader;
     }
 }
@@ -943,8 +982,7 @@ HTTPClient::doReadImage(const std::string&    location,
     {
         osgDB::ReaderWriter* reader = getReader(location, response);
         if (!reader)
-        {
-            OE_WARN << LC << "Can't find an OSG plugin to read "<<location<<std::endl;
+        {            
             result = ReadResult(ReadResult::RESULT_NO_READER);
         }
 
@@ -965,6 +1003,13 @@ HTTPClient::doReadImage(const std::string&    location,
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
             }
         }
+        
+        // last-modified (file time)
+        TimeStamp filetime = 0;
+        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
+        {
+            result.setLastModifiedTime( filetime );
+        }
     }
     else
     {
@@ -982,7 +1027,7 @@ HTTPClient::doReadImage(const std::string&    location,
                 OE_DEBUG << "Error in HTTPClient for " << location << " but it's recoverable" << std::endl;
                 callback->setNeedsRetry( true );
             }
-        }
+        }        
     }
 
     // set the source name
@@ -1008,7 +1053,6 @@ HTTPClient::doReadNode(const std::string&    location,
         osgDB::ReaderWriter* reader = getReader(location, response);
         if (!reader)
         {
-            OE_WARN << LC << "Can't find an OSG plugin to read "<<location<<std::endl;
             result = ReadResult(ReadResult::RESULT_NO_READER);
         }
 
@@ -1029,6 +1073,13 @@ HTTPClient::doReadNode(const std::string&    location,
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
             }
         }
+        
+        // last-modified (file time)
+        TimeStamp filetime = 0;
+        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
+        {
+            result.setLastModifiedTime( filetime );
+        }
     }
     else
     {
@@ -1068,7 +1119,6 @@ HTTPClient::doReadObject(const std::string&    location,
         osgDB::ReaderWriter* reader = getReader(location, response);
         if (!reader)
         {
-            OE_WARN << LC << "Can't find an OSG plugin to read "<<location<<std::endl;
             result = ReadResult(ReadResult::RESULT_NO_READER);
         }
 
@@ -1089,6 +1139,13 @@ HTTPClient::doReadObject(const std::string&    location,
                 result = ReadResult(ReadResult::RESULT_READER_ERROR);
             }
         }
+        
+        // last-modified (file time)
+        TimeStamp filetime = 0;
+        if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
+        {
+            result.setLastModifiedTime( filetime );
+        }
     }
     else
     {
@@ -1127,6 +1184,17 @@ HTTPClient::doReadString(const std::string&    location,
     {
         result = ReadResult( new StringObject(response.getPartAsString(0)), response.getHeadersAsConfig());
     }
+
+    else if ( response.getCode() >= 400 && response.getCode() < 500 && response.getCode() != 404 )
+    {
+        // for request errors, return an error result with the part data intact
+        // so the user can parse it as needed. We only do this for readString.
+        result = ReadResult( 
+            ReadResult::RESULT_SERVER_ERROR,
+            new StringObject(response.getPartAsString(0)), 
+            response.getHeadersAsConfig() );
+    }
+
     else
     {
         result = ReadResult(
@@ -1146,5 +1214,12 @@ HTTPClient::doReadString(const std::string&    location,
         }
     }
 
+    // last-modified (file time)
+    TimeStamp filetime = 0;
+    if ( CURLE_OK == curl_easy_getinfo(_curl_handle, CURLINFO_FILETIME, &filetime) )
+    {
+        result.setLastModifiedTime( filetime );
+    }
+
     return result;
 }
diff --git a/src/osgEarth/HeightFieldUtils b/src/osgEarth/HeightFieldUtils
index bd1b22d..a752744 100644
--- a/src/osgEarth/HeightFieldUtils
+++ b/src/osgEarth/HeightFieldUtils
@@ -28,6 +28,42 @@
 
 namespace osgEarth
 {
+    struct HeightFieldNeighborhood
+    {
+        osg::ref_ptr<osg::HeightField> _center;
+        osg::ref_ptr<osg::HeightField> _neighbors[8];
+
+        osg::HeightField* getNeighbor(int xoffset, int yoffset) const {
+            if ( xoffset == 0 && yoffset == 0 ) return _center.get();
+            int index = 3*(yoffset+1)+(xoffset+1);
+            if (index > 4) index--;
+            return _neighbors[index].get();
+        }
+
+        void setNeighbor(int xoffset, int yoffset, osg::HeightField* hf) {
+            if ( xoffset == 0 && yoffset == 0 ) {
+                _center = hf;
+            }
+            else {
+                int index = 3*(yoffset+1)+(xoffset+1);
+                if (index > 4) index--;
+                _neighbors[index] = hf;
+            }
+        }
+
+        void getNeighborForNormalizedLocation(double nx, double ny, osg::ref_ptr<osg::HeightField>& hf, 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);
+            else
+                hf = _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;
+        }
+    };
+
+
     class OSGEARTH_EXPORT HeightFieldUtils
     {
     public:
@@ -40,6 +76,16 @@ namespace osgEarth
             ElevationInterpolation interpoltion = INTERP_BILINEAR);
         
         /**
+         * Gets the height value at the specified column and row, but instead of reading
+         * the actual height, interpolates a height based on the neighbors.
+         */
+        static bool getInterpolatedHeight(
+            const osg::HeightField* hf, 
+            unsigned c, unsigned r, 
+            float& out_height,
+            ElevationInterpolation interpoltion = INTERP_BILINEAR);
+        
+        /**
          * Gets the interpolated height value at the specific geolocation.
          */
         static float getHeightAtLocation(
@@ -60,6 +106,27 @@ namespace osgEarth
             ElevationInterpolation interp = INTERP_BILINEAR);
 
         /**
+         * Gets the interpolated elevation at the specified "normalized unit location".
+         * i.e., nx => [-1.0...2.0], ny => [-1.0...2.0] since it can query neighbors
+         * as well.
+         */
+        static float getHeightAtNormalizedLocation(
+            const HeightFieldNeighborhood& hood,
+            double nx, double ny,
+            ElevationInterpolation interp = INTERP_BILINEAR);
+
+        /**
+         * Gets the normal vector at the specified "normalized unit location".
+         * i.e., nx => [0.0, 1.0], ny => [0.0, 1.0] where 0.0 and 1.0 are the opposing
+         * endposts of the heightfield.
+         */
+        static bool getNormalAtNormalizedLocation(
+            const osg::HeightField* hf,
+            double nx, double ny,
+            osg::Vec3& output,
+            ElevationInterpolation interp = INTERP_BILINEAR);
+
+        /**
          * Scales all the height values in a heightfield from scalar units to "linear degrees".
          * The only purpose of this is to show reasonable height values in a projected
          * Plate Carre map (in which vertical units are not well defined).
@@ -92,6 +159,7 @@ namespace osgEarth
          */
         static osg::HeightField* resampleHeightField(
             osg::HeightField* input,
+            const GeoExtent& inputEx,
             int newX,
             int newY,
             ElevationInterpolation interp = INTERP_BILINEAR );
@@ -112,8 +180,8 @@ namespace osgEarth
          * the ellipsoid model.
          */
         static osg::NodeCallback* createClusterCullingCallback(
-            osg::HeightField*    grid, 
-            osg::EllipsoidModel* em, 
+            osg::HeightField*          grid, 
+            const osg::EllipsoidModel* em, 
             float verticalScale =1.0f );
     };
 
diff --git a/src/osgEarth/HeightFieldUtils.cpp b/src/osgEarth/HeightFieldUtils.cpp
index 4cd59e8..df590a8 100644
--- a/src/osgEarth/HeightFieldUtils.cpp
+++ b/src/osgEarth/HeightFieldUtils.cpp
@@ -44,11 +44,11 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
 
         if (rowMin == rowMax)
         {
-            if (rowMin < (int)hf->getNumRows()-2)
+            if (rowMin < (int)hf->getNumRows()-1)
             {
                 rowMax = rowMin + 1;
             }
-            else
+            else if ( rowMax > 0 )
             {
                 rowMin = rowMax - 1;
             }
@@ -56,11 +56,11 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
 
          if (colMin == colMax)
          {
-            if (colMin < (int)hf->getNumColumns()-2)
+            if (colMin < (int)hf->getNumColumns()-1)
             {
                 colMax = colMin + 1;
             }
-            else
+            else if ( colMax > 0 )
             {
                colMin = colMax - 1;
             }
@@ -80,8 +80,6 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
             return NO_DATA_VALUE;
         }
 
-        double dx = c - (double)colMin;
-        double dy = r - (double)rowMin;
 
         //The quad consisting of the 4 corner points can be made into two triangles.
         //The "left" triangle is ll, ur, ul
@@ -89,6 +87,55 @@ 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;
+
         if (dx > dy)
         {
             //The point lies in the right triangle
@@ -103,6 +150,7 @@ 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);
@@ -182,6 +230,39 @@ HeightFieldUtils::getHeightAtPixel(const osg::HeightField* hf, double c, double
     return result;
 }
 
+bool
+HeightFieldUtils::getInterpolatedHeight(const osg::HeightField* hf, 
+                                        unsigned c, unsigned r, 
+                                        float& out_height,
+                                        ElevationInterpolation interpolation)
+{
+    int count = 0;
+    float total = 0.0f;
+    if ( c > 0 ) {
+        total += hf->getHeight(c-1, r);
+        count++;
+    }
+    if ( c < hf->getNumColumns()-1 ) {
+        total += hf->getHeight(c+1, r);
+        count++;
+    }
+    if ( r > 0 ) {
+        total += hf->getHeight(c, r-1);
+        count++;
+    }
+    if ( r < hf->getNumRows()-1 ) {
+        total += hf->getHeight(c, r+1);
+        count++;
+    }
+    if ( count > 0 )
+        total /= (float)count;
+    else
+        return false;
+
+    out_height = total;
+    return true;
+}
+
 float
 HeightFieldUtils::getHeightAtLocation(const osg::HeightField* hf, double x, double y, double llx, double lly, double dx, double dy, ElevationInterpolation interpolation)
 {
@@ -196,11 +277,53 @@ HeightFieldUtils::getHeightAtNormalizedLocation(const osg::HeightField* input,
                                                 double nx, double ny,
                                                 ElevationInterpolation interp)
 {
-    double px = nx * (double)(input->getNumColumns() - 1);
-    double py = ny * (double)(input->getNumRows() - 1);
+    double px = osg::clampBetween(nx, 0.0, 1.0) * (double)(input->getNumColumns() - 1);
+    double py = osg::clampBetween(ny, 0.0, 1.0) * (double)(input->getNumRows() - 1);
     return getHeightAtPixel( input, px, py, interp );
 }
 
+float
+HeightFieldUtils::getHeightAtNormalizedLocation(const HeightFieldNeighborhood& hood,
+                                                double nx, double ny,
+                                                ElevationInterpolation interp)
+{
+    osg::ref_ptr<osg::HeightField> hf;
+    double nx2, ny2;
+    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);
+    return getHeightAtPixel( hf.get(), px, py, interp );
+}
+
+bool
+HeightFieldUtils::getNormalAtNormalizedLocation(const osg::HeightField* input,
+                                                double nx, double ny,
+                                                osg::Vec3& output,
+                                                ElevationInterpolation interp)
+{
+    double xcells = (double)(input->getNumColumns()-1);
+    double ycells = (double)(input->getNumRows()-1);
+
+    double w = input->getXInterval() * xcells * 111000.0;
+    double h = input->getYInterval() * ycells * 111000.0;
+
+    double ndx = 1.0/xcells;
+    double ndy = 1.0/ycells;
+
+    double xmin = osg::clampAbove( nx-ndx, 0.0 );
+    double xmax = osg::clampBelow( nx+ndx, 1.0 );
+    double ymin = osg::clampAbove( ny-ndy, 0.0 );
+    double ymax = osg::clampBelow( ny+ndy, 1.0 );
+
+    osg::Vec3 west (xmin*w, ny*h, getHeightAtNormalizedLocation(input, xmin, ny, interp));
+    osg::Vec3 east (xmax*w, ny*h, getHeightAtNormalizedLocation(input, xmax, ny, interp));
+    osg::Vec3 south(nx*w, ymin*h, getHeightAtNormalizedLocation(input, nx, ymin, interp));
+    osg::Vec3 north(nx*w, ymax*h, getHeightAtNormalizedLocation(input, nx, ymax, interp));
+
+    output = (west-east) ^ (north-south);
+    output.normalize();
+    return true;
+}
 
 void
 HeightFieldUtils::scaleHeightFieldToDegrees( osg::HeightField* hf )
@@ -271,6 +394,7 @@ HeightFieldUtils::createSubSample(osg::HeightField* input, const GeoExtent& inpu
 
 osg::HeightField*
 HeightFieldUtils::resampleHeightField(osg::HeightField*      input,
+                                      const GeoExtent&       extent,
                                       int                    newColumns, 
                                       int                    newRows,
                                       ElevationInterpolation interp)
@@ -282,8 +406,8 @@ HeightFieldUtils::resampleHeightField(osg::HeightField*      input,
         return input;
         //return new osg::HeightField( *input, osg::CopyOp::DEEP_COPY_ALL );
 
-    double spanX = (input->getNumColumns()-1) * input->getXInterval();
-    double spanY = (input->getNumRows()-1) * input->getYInterval();
+    double spanX = extent.width(); //(input->getNumColumns()-1) * input->getXInterval();
+    double spanY = extent.height(); //(input->getNumRows()-1) * input->getYInterval();
     const osg::Vec3& origin = input->getOrigin();
 
     double stepX = spanX/(double)(newColumns-1);
@@ -301,7 +425,7 @@ HeightFieldUtils::resampleHeightField(osg::HeightField*      input,
         {
             double nx = (double)x / (double)(newColumns-1);
             double ny = (double)y / (double)(newRows-1);
-            float h = getHeightAtNormalizedLocation( input, nx, ny );
+            float h = getHeightAtNormalizedLocation( input, nx, ny, interp );
             output->setHeight( x, y, h );
         }
     }
@@ -394,7 +518,9 @@ HeightFieldUtils::resolveInvalidHeights(osg::HeightField* grid,
 }
 
 osg::NodeCallback*
-HeightFieldUtils::createClusterCullingCallback( osg::HeightField* grid, osg::EllipsoidModel* et, float verticalScale )
+HeightFieldUtils::createClusterCullingCallback(osg::HeightField*          grid, 
+                                               const osg::EllipsoidModel* et, 
+                                               float                      verticalScale )
 {
     //This code is a very slightly modified version of the DestinationTile::createClusterCullingCallback in VirtualPlanetBuilder.
     if ( !grid || !et )
diff --git a/src/osgEarth/IOTypes b/src/osgEarth/IOTypes
index e4f6764..801a663 100644
--- a/src/osgEarth/IOTypes
+++ b/src/osgEarth/IOTypes
@@ -21,6 +21,7 @@
 #define OSGEARTH_IOTYPES_H 1
 
 #include <osgEarth/Config>
+#include <osgEarth/DateTime>
 
 /**
  * A collectin of types used by the various I/O systems in osgEarth. These
@@ -28,9 +29,6 @@
  */
 namespace osgEarth
 {
-
-//--------------------------------------------------------------------
-
     /**
      * String wrapped in an osg::Object (for I/O purposes)
      */
@@ -42,11 +40,11 @@ namespace osgEarth
         StringObject( const std::string& in ) : osg::Object(), _str(in) { }
 
         /** dtor */
-        virtual ~StringObject() { }
+        virtual ~StringObject();
         META_Object( osgEarth, StringObject );
 
-        void setString( const std::string& value ) { _str = value; }
-        const std::string& getString() const { return _str; }
+        void setString( const std::string& value );
+        const std::string& getString() const;
     private:
         std::string _str;
     };
@@ -67,7 +65,7 @@ namespace osgEarth
     /**
      * Return value from a read* method
      */
-    struct /*no-export*/ ReadResult
+    struct OSGEARTH_EXPORT ReadResult
     {
         /** Read result codes. */
         enum Code
@@ -75,6 +73,7 @@ namespace osgEarth
             RESULT_OK,
             RESULT_CANCELED,
             RESULT_NOT_FOUND,
+            RESULT_EXPIRED,
             RESULT_SERVER_ERROR,
             RESULT_TIMEOUT,
             RESULT_NO_READER,
@@ -85,19 +84,23 @@ namespace osgEarth
 
         /** Construct a result with no object */
         ReadResult( Code code =RESULT_NOT_FOUND )
-            : _code(code), _fromCache(false) { }
+            : _code(code), _fromCache(false), _lmt(0) { }
 
         /** Construct a successful result */
         ReadResult( osg::Object* result )
-            : _code(RESULT_OK), _result(result), _fromCache(false) { }
+            : _code(RESULT_OK), _result(result), _fromCache(false), _lmt(0) { }
 
         /** Construct a successful result with metadata */
         ReadResult( osg::Object* result, const Config& meta )
-            : _code(RESULT_OK), _result(result), _meta(meta), _fromCache(false) { }
+            : _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) { }
+            : _code(rhs._code), _result(rhs._result.get()), _meta(rhs._meta), _fromCache(rhs._fromCache), _lmt(rhs._lmt) { }
 
         /** dtor */
         virtual ~ReadResult() { }
@@ -114,6 +117,9 @@ namespace osgEarth
         /** The result code */
         const Code& code() const { return _code; }
 
+        /** Last modified timestamp */
+        TimeStamp lastModifiedTime() const { return _lmt; }
+
         /** True if the object came from the cache */
         bool isFromCache() const { return _fromCache; }
 
@@ -148,7 +154,7 @@ namespace osgEarth
                 code == RESULT_OK              ? "OK" :
                 code == RESULT_CANCELED        ? "Read canceled" :
                 code == RESULT_NOT_FOUND       ? "Target not found" :
-                code == RESULT_SERVER_ERROR    ? "Server error" :
+                code == RESULT_SERVER_ERROR    ? "Server reported error" :
                 code == RESULT_TIMEOUT         ? "Read timed out" :
                 code == RESULT_NO_READER       ? "No suitable ReaderWriter found" :
                 code == RESULT_READER_ERROR    ? "ReaderWriter error" :
@@ -164,6 +170,8 @@ namespace osgEarth
     public:
         void setIsFromCache(bool value) { _fromCache = value; }
 
+        void setLastModifiedTime(TimeStamp t) { _lmt = t; }
+
     protected:
         Code                      _code;
         osg::ref_ptr<osg::Object> _result;
@@ -171,6 +179,7 @@ namespace osgEarth
         std::string               _emptyString;
         Config                    _emptyConfig;
         bool                      _fromCache;
+        TimeStamp                 _lmt;
     };
 
 //--------------------------------------------------------------------
@@ -229,7 +238,7 @@ namespace osgEarth
         URIReadCallback();
 
         /** dtor */
-        virtual ~URIReadCallback() { }
+        virtual ~URIReadCallback();
     };
 
 }
diff --git a/src/osgEarth/IOTypes.cpp b/src/osgEarth/IOTypes.cpp
index bb85ba7..5f03c8d 100644
--- a/src/osgEarth/IOTypes.cpp
+++ b/src/osgEarth/IOTypes.cpp
@@ -37,6 +37,20 @@ osg::Object()
     //nop
 }
 
+StringObject::~StringObject()
+{
+}
+
+const std::string& StringObject::getString() const
+{
+    return _str;
+}
+
+void StringObject::setString( const std::string& value )
+{
+    _str = value;
+}
+
 //------------------------------------------------------------------------
 
 URIReadCallback::URIReadCallback()
@@ -44,6 +58,10 @@ URIReadCallback::URIReadCallback()
     //nop
 }
 
+URIReadCallback::~URIReadCallback()
+{
+}
+
 //------------------------------------------------------------------------
 
 /**
diff --git a/src/osgEarth/ImageLayer b/src/osgEarth/ImageLayer
index 0b40f72..7795107 100644
--- a/src/osgEarth/ImageLayer
+++ b/src/osgEarth/ImageLayer
@@ -93,6 +93,34 @@ namespace osgEarth
         ColorFilterChain& colorFilters() { return _colorFilters; }
         const ColorFilterChain& colorFilters() const { return _colorFilters; }
 
+        /**
+         * A shared image layer is bound to its own texture image units at render
+         * so that all layers have access to its sampler.
+         */
+        optional<bool>& shared() { return _shared; }
+        const optional<bool>& shared() const { return _shared; }
+
+        /**
+         * 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.
+         */
+        optional<bool>& featherPixels() { return _featherPixels; }
+        const optional<bool>& featherPixels() const { return _featherPixels; }
+
+        /**
+         * The minification filter to be applied to textures. This is the interpolation
+         * mechanism to use when the texture uses fewer screen pixels than are available.
+         */
+        optional<osg::Texture::FilterMode>& minFilter() { return _minFilter; }
+        const optional<osg::Texture::FilterMode>& minFilter() const { return _minFilter; }
+
+        /**
+         * The magnification filter to be applied to textures. This is the interpolation
+         * mechanism to use when the texture uses more screen pixels than are available.
+         */
+        optional<osg::Texture::FilterMode>& magFilter() { return _magFilter; }
+        const optional<osg::Texture::FilterMode>& magFilter() const { return _magFilter; }
+
     public:
 
         virtual Config getConfig() const { return getConfig(false); }
@@ -110,6 +138,10 @@ namespace osgEarth
         optional<URI>         _noDataImageFilename;
         optional<bool>        _lodBlending;
         ColorFilterChain      _colorFilters;
+        optional<bool>        _shared;
+        optional<bool>        _featherPixels;
+        optional<osg::Texture::FilterMode> _minFilter;
+        optional<osg::Texture::FilterMode> _magFilter;
     };
 
     //--------------------------------------------------------------------
@@ -196,7 +228,6 @@ namespace osgEarth
         /** Override: see TerrainLayer */
         virtual void setTargetProfileHint( const Profile* profile );
 
-
         /**
          * Add a color filter (to the end of the chain)
          */
@@ -232,6 +263,14 @@ namespace osgEarth
         float getMaxVisibleRange() const { return *_runtimeOptions.maxVisibleRange();}
         void setMaxVisibleRange( float maxVisibleRange );
 
+        // 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.
+        optional<int>& shareImageUnit() { return _shareImageUnit; }
+        const optional<int>& shareImageUnit() const { return _shareImageUnit; }
+
 
     public: // methods
 
@@ -274,6 +313,7 @@ namespace osgEarth
         osg::ref_ptr<TileSource::ImageOperation> _preCacheOp;
         osg::ref_ptr<osg::Image>                 _emptyImage;
         ImageLayerCallbackList                   _callbacks;
+        optional<int>                            _shareImageUnit;
 
         virtual void fireCallback( TerrainLayerCallbackMethodPtr method );
         virtual void fireCallback( ImageLayerCallbackMethodPtr method );
diff --git a/src/osgEarth/ImageLayer.cpp b/src/osgEarth/ImageLayer.cpp
index 4e99558..1ef102c 100644
--- a/src/osgEarth/ImageLayer.cpp
+++ b/src/osgEarth/ImageLayer.cpp
@@ -23,6 +23,7 @@
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/StringUtils>
+#include <osgEarth/Progress>
 #include <osgEarth/URI>
 #include <osg/Version>
 #include <osgDB/WriteFile>
@@ -62,6 +63,9 @@ ImageLayerOptions::setDefaults()
     _minRange.init( -FLT_MAX );
     _maxRange.init( FLT_MAX );
     _lodBlending.init( false );
+    _featherPixels.init( false );
+    _minFilter.init( osg::Texture::LINEAR );
+    _magFilter.init( osg::Texture::LINEAR );
 }
 
 void
@@ -74,11 +78,13 @@ ImageLayerOptions::mergeConfig( const Config& conf )
 void
 ImageLayerOptions::fromConfig( const Config& conf )
 {
-    conf.getIfSet( "nodata_image", _noDataImageFilename );
-    conf.getIfSet( "opacity", _opacity );
-    conf.getIfSet( "min_range", _minRange );
-    conf.getIfSet( "max_range", _maxRange );
-    conf.getIfSet( "lod_blending", _lodBlending );
+    conf.getIfSet( "nodata_image",   _noDataImageFilename );
+    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( "feather_pixels", _featherPixels);
 
     if ( conf.hasValue( "transparent_color" ) )
         _transparentColor = stringToColor( conf.value( "transparent_color" ), osg::Vec4ub(0,0,0,0));
@@ -88,6 +94,19 @@ ImageLayerOptions::fromConfig( const Config& conf )
         _colorFilters.clear();
         ColorFilterRegistry::instance()->readChain( conf.child("color_filters"), _colorFilters );
     }
+
+    conf.getIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
+    conf.getIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.getIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.getIfSet("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
+    conf.getIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.getIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+    conf.getIfSet("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
+    conf.getIfSet("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.getIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.getIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
+    conf.getIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.getIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
 }
 
 Config
@@ -95,11 +114,13 @@ ImageLayerOptions::getConfig( bool isolate ) const
 {
     Config conf = TerrainLayerOptions::getConfig( isolate );
 
-    conf.updateIfSet( "nodata_image", _noDataImageFilename );
-    conf.updateIfSet( "opacity", _opacity );
-    conf.updateIfSet( "min_range", _minRange );
-    conf.updateIfSet( "max_range", _maxRange );
-    conf.updateIfSet( "lod_blending", _lodBlending );
+    conf.updateIfSet( "nodata_image",   _noDataImageFilename );
+    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( "feather_pixels", _featherPixels );
 
     if (_transparentColor.isSet())
         conf.update("transparent_color", colorToString( _transparentColor.value()));
@@ -112,6 +133,19 @@ ImageLayerOptions::getConfig( bool isolate ) const
             conf.add( filtersConf );
         }
     }
+
+    conf.updateIfSet("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
+    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.updateIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.updateIfSet("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
+    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.updateIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
+    conf.updateIfSet("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
+    conf.updateIfSet("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
+    conf.updateIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
+    conf.updateIfSet("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
+    conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
+    conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
     
     return conf;
 }
@@ -190,7 +224,7 @@ ImageLayerTileProcessor::process( osg::ref_ptr<osg::Image>& image ) const
     }
 
     // If this is a compressed image, uncompress it IF the image is not already in the
-    // target profile...becuase if it's not in the target profile, we will have to do
+    // target profile...because if it's not in the target profile, we will have to do
     // some mosaicing...and we can't mosaic a compressed image.
     if (!_layerInTargetProfile &&
         ImageUtils::isCompressed(image.get()) &&
@@ -406,6 +440,11 @@ ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* pr
         // find the intersection of keys.
         std::vector<TileKey> nativeKeys;
         nativeProfile->getIntersectingTiles(key.getExtent(), nativeKeys);
+
+
+        //OE_INFO << "KEY = " << key.str() << ":" << std::endl;
+        //for(int i=0; i<nativeKeys.size(); ++i)
+        //    OE_INFO << "    " << nativeKeys[i].str() << std::endl;
         
         // build a mosaic of the images from the native profile keys:
         bool foundAtLeastOneRealTile = false;
@@ -445,11 +484,18 @@ ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* pr
                 mosaic.createImage(), 
                 GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) );
 
+#if 1
+            return result;
+
+#else // let's try this. why crop? Just leave it. Faster and more compatible with NPOT
+      // systems (like iOS)
+
             // calculate a tigher extent that matches the original input key:
             GeoExtent tightExtent = nativeProfile->clampAndTransformExtent( key.getExtent() );
 
             // a non-exact crop is critical here to avoid resampling the data
             return result.crop( tightExtent, false, 0, 0, *_runtimeOptions.driver()->bilinearReprojection() );
+#endif
         }
 
         else // all fallback data
@@ -489,6 +535,12 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
         return GeoImage::INVALID;
     }
 
+    // Check the max data level, which limits the LOD of available data.
+    if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() )
+    {
+        return GeoImage::INVALID;
+    }
+
     // Check for a "Minumum level" setting on this layer. If we are before the
     // min level, just return the empty image. Do not cache empties
     if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() )
@@ -537,16 +589,16 @@ ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progr
     // map profile, we can try this first.
     if ( cacheBin && getCachePolicy().isCacheReadable() )
     {
-        ReadResult r = cacheBin->readImage( key.str() );
+        ReadResult r = cacheBin->readImage( key.str(), getCachePolicy().getMinAcceptTime() );
         if ( r.succeeded() )
-        {            
+        {
             ImageUtils::normalizeImage( r.getImage() );
             return GeoImage( r.releaseImage(), key.getExtent() );
         }
-        else
-        {
-            //OE_INFO << LC << getName() << " : " << key.str() << " cache miss" << std::endl;
-        }
+        //else if ( r.code() == ReadResult::RESULT_EXPIRED )
+        //{
+        //    OE_INFO << LC << getName() << " : " << key.str() << " record expired!" << std::endl;
+        //}
     }
     
     // The data was not in the cache. If we are cache-only, fail sliently
@@ -625,39 +677,26 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
         return assembleImageFromTileSource( key, progress, out_isFallback );
     }
 
-    // Fail is the image is blacklisted.
-    // ..unless there will be a fallback attempt.
-    if ( source->getBlacklist()->contains( key.getTileId() ) && !forceFallback )
-    {
-        OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
-        return GeoImage::INVALID;
-    }
-
-    // Fail if no data is available for this key.
-    if ( !source->hasDataAtLOD( key.getLevelOfDetail() ) && !forceFallback )
-    {
-        OE_DEBUG << LC << "createImageFromTileSource: hasDataAtLOD(" << key.str() << ") == false" << std::endl;
-        return GeoImage::INVALID;
-    }
-
-    if ( !source->hasDataInExtent( key.getExtent() ) )
-    {
-        OE_DEBUG << LC << "createImageFromTileSource: hasDataInExtent(" << key.str() << ") == false" << std::endl;
-        return GeoImage::INVALID;
-    }
-
     // Good to go, ask the tile source for an image:
     osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp;
 
     osg::ref_ptr<osg::Image> result;
-    TileKey finalKey = key;
-    bool fellBack = false;
 
     if ( forceFallback )
-    {        
+    {
+        // check if the tile source has any data coverage for the requested key.
+        // the LOD is ignore here and checked later
+        if ( !source->hasDataInExtent( key.getExtent() ) )
+        {
+            OE_DEBUG << LC << "createImageFromTileSource: hasDataInExtent(" << key.str() << ") == false" << std::endl;
+            return GeoImage::INVALID;
+        }
+
+        TileKey finalKey = key;
         while( !result.valid() && finalKey.valid() )
         {
-            if ( !source->getBlacklist()->contains( finalKey.getTileId() ) )
+            if ( !source->getBlacklist()->contains( finalKey.getTileId() ) &&
+                source->hasDataForFallback(finalKey))
             {
                 result = source->createImage( finalKey, op.get(), progress );
                 if ( result.valid() )
@@ -670,7 +709,6 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
                         GeoImage raw( result.get(), finalKey.getExtent() );
                         GeoImage cropped = raw.crop( key.getExtent(), true, raw.getImage()->s(), raw.getImage()->t(), *_runtimeOptions.driver()->bilinearReprojection() );
                         result = cropped.takeImage();
-                        fellBack = true;
                     }
                 }
             }
@@ -688,17 +726,28 @@ ImageLayer::createImageFromTileSource(const TileKey&    key,
             finalKey = key;
         }
     }
-
     else
     {
+        // Fail is the image is blacklisted.
+        if ( source->getBlacklist()->contains( key.getTileId() ) )
+        {
+            OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
+            return GeoImage::INVALID;
+        }
+    
+        if ( !source->hasData( key ) )
+        {
+            OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
+            return GeoImage::INVALID;
+        }
         result = source->createImage( key, op.get(), progress );
     }
 
-    // Process images with full alpha to properly support MP blending.
-    if ( result != 0L )
+    // Process images with full alpha to properly support MP blending.    
+    if ( result != 0L && *_runtimeOptions.featherPixels())
     {
         ImageUtils::featherAlphaRegions( result.get() );
-    }
+    }    
     
     // If image creation failed (but was not intentionally canceled),
     // blacklist this tile for future requests.
@@ -818,7 +867,7 @@ ImageLayer::assembleImageFromTileSource(const TileKey&    key,
     }
 
     // Process images with full alpha to properly support MP blending.
-    if ( result.valid() )
+    if ( result.valid() && *_runtimeOptions.featherPixels() )
     {
         ImageUtils::featherAlphaRegions( result.getImage() );
     }
diff --git a/src/osgEarth/ImageUtils b/src/osgEarth/ImageUtils
index c5d68a1..113d23c 100644
--- a/src/osgEarth/ImageUtils
+++ b/src/osgEarth/ImageUtils
@@ -138,7 +138,7 @@ namespace osgEarth
          * Creates and returns a copy of the input image after applying a
          * sharpening filter. Returns a new image, leaving the input image unaltered.
          */
-        static osg::Image* sharpenImage( const osg::Image* image );
+        static osg::Image* createSharpenedImage( const osg::Image* image );
 
         /**
          * Gets whether the input image's dimensions are powers of 2.
@@ -151,6 +151,11 @@ namespace osgEarth
         static osg::Image* createEmptyImage();
 
         /**
+         * Gets a transparent image used for a placeholder with the specified dimensions
+         */
+        static osg::Image* createEmptyImage(unsigned int s, unsigned int t);
+
+        /**
          * Creates a one-pixel image.
          */
         static osg::Image* createOnePixelImage(const osg::Vec4& color);
@@ -220,13 +225,17 @@ namespace osgEarth
          * to match that or neighboring non-alpha pixels. This facilitates multipass
          * blending or abutting tiles by overlapping them slightly. Specify "maxAlpha"
          * as the maximum value to consider when searching for fully-transparent pixels.
+         *
+         * Returns false if there is no reader or writer for the image's format.
          */
-        static void featherAlphaRegions(osg::Image* image, float maxAlpha =0.0f);
+        static bool featherAlphaRegions(osg::Image* image, float maxAlpha =0.0f);
 
         /**
          * Converts an image (in place) to premultiplied-alpha format.
+         * Returns False is the conversion fails, e.g., if there is no reader
+         * or writer for the image format.
          */
-        static void convertToPremultipliedAlpha(osg::Image* image);
+        static bool convertToPremultipliedAlpha(osg::Image* image);
 
         /**
          * Checks whether the given image is compressed
@@ -239,6 +248,11 @@ namespace osgEarth
         static osg::Image* createBumpMap( const osg::Image* input );
 
         /**
+         * Is it a floating-point texture format?
+         */
+        static bool isFloatingPointInternalFormat( GLint internalFormat );
+
+        /**
          * Reads color data out of an image, regardles of its internal pixel format.
          */
         class OSGEARTH_EXPORT PixelReader
diff --git a/src/osgEarth/ImageUtils.cpp b/src/osgEarth/ImageUtils.cpp
index 106e23c..766280a 100644
--- a/src/osgEarth/ImageUtils.cpp
+++ b/src/osgEarth/ImageUtils.cpp
@@ -98,6 +98,9 @@ ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start
     // otherwise loop through an convert pixel-by-pixel.
     else
     {
+        if ( !PixelReader::supports(src) || !PixelWriter::supports(dst) )
+            return false;
+
         PixelReader read(src);
         PixelWriter write(dst);
 
@@ -116,6 +119,9 @@ ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start
 osg::Image*
 ImageUtils::createBumpMap(const osg::Image* input)
 {
+    if ( !PixelReader::supports(input) || !PixelWriter::supports(input) )
+        return 0L;
+
     osg::Image* output = osg::clone(input, osg::CopyOp::DEEP_COPY_ALL);
 
     static const float kernel[] = {
@@ -214,7 +220,7 @@ ImageUtils::resizeImage(const osg::Image* input,
         memcpy( output->data(), input->data(), input->getTotalSizeInBytes() );
     }
     else
-    {       
+    {
         PixelReader read( input );
         PixelWriter write( output.get() );
 
@@ -326,13 +332,17 @@ namespace
 bool
 ImageUtils::mix(osg::Image* dest, const osg::Image* src, float a)
 {
-    if (!dest || !src || dest->s() != src->s() || dest->t() != src->t() )
+    if (!dest || !src || dest->s() != src->s() || dest->t() != src->t() ||
+        !PixelReader::supports(src) ||
+        !PixelWriter::supports(dest) )
+    {
         return false;
+    }
     
     PixelVisitor<MixImage> mixer;
     mixer._a = osg::clampBetween( a, 0.0f, 1.0f );
     mixer._srcHasAlpha = src->getPixelSizeInBits() == 32;
-    mixer._destHasAlpha = src->getPixelSizeInBits() == 32;    
+    mixer._destHasAlpha = src->getPixelSizeInBits() == 32;
 
     mixer.accept( src, dest );  
 
@@ -405,7 +415,7 @@ ImageUtils::isPowerOfTwo(const osg::Image* image)
 
 
 osg::Image*
-ImageUtils::sharpenImage( const osg::Image* input )
+ImageUtils::createSharpenedImage( const osg::Image* input )
 {
     int filter[9] = { 0, -1, 0, -1, 5, -1, 0, -1, 0 };
     osg::Image* output = ImageUtils::cloneImage(input);
@@ -449,21 +459,28 @@ ImageUtils::createEmptyImage()
     {
         Threading::ScopedMutexLock exclusive( s_emptyImageMutex );
         if (!s_emptyImage.valid())
-        {
-            s_emptyImage = new osg::Image;
-            s_emptyImage->allocateImage(1,1,1, GL_RGBA, GL_UNSIGNED_BYTE);
-            s_emptyImage->setInternalTextureFormat( GL_RGB8A_INTERNAL );
-            unsigned char *data = s_emptyImage->data(0,0);
-            memset(data, 0, 4);
+        {            
+            s_emptyImage = createEmptyImage( 1, 1 );
         }     
     }
     return s_emptyImage.get();
 }
 
+osg::Image*
+ImageUtils::createEmptyImage(unsigned int s, unsigned int t)
+{
+    osg::Image* empty = new osg::Image;
+    empty->allocateImage(s,t,1, GL_RGBA, GL_UNSIGNED_BYTE);
+    empty->setInternalTextureFormat( GL_RGB8A_INTERNAL );
+    unsigned char *data = empty->data(0,0);
+    memset(data, 0, 4 * s * t);
+    return empty;
+}
+
 bool
 ImageUtils::isEmptyImage(const osg::Image* image, float alphaThreshold)
 {
-    if ( !hasAlphaChannel(image) )
+    if ( !hasAlphaChannel(image) || !PixelReader::supports(image) )
         return false;
 
     PixelReader read(image);
@@ -476,7 +493,7 @@ ImageUtils::isEmptyImage(const osg::Image* image, float alphaThreshold)
                 return false;
         }
     }
-    return true;    
+    return true;
 }
 
 
@@ -494,6 +511,9 @@ ImageUtils::createOnePixelImage(const osg::Vec4& color)
 bool
 ImageUtils::isSingleColorImage(const osg::Image* image, float threshold)
 {
+    if ( !PixelReader::supports(image) )
+        return false;
+
     PixelReader read(image);
 
     osg::Vec4 referenceColor = read(0, 0);
@@ -516,7 +536,8 @@ ImageUtils::isSingleColorImage(const osg::Image* image, float threshold)
             }
         }
     }
-    return true;    
+
+    return true;
 }
 
 bool
@@ -604,26 +625,37 @@ ImageUtils::hasAlphaChannel(const osg::Image* image)
     return image && (
         image->getPixelFormat() == GL_RGBA ||
         image->getPixelFormat() == GL_BGRA ||
-        image->getPixelFormat() == GL_LUMINANCE_ALPHA );
+        image->getPixelFormat() == GL_LUMINANCE_ALPHA ||
+        image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
+        image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ||
+        image->getPixelFormat() == GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ||
+        image->getPixelFormat() == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG );
 }
 
 
 bool
 ImageUtils::hasTransparency(const osg::Image* image, float threshold)
 {
-    if ( !image ) return false;
+    if ( !image || !PixelReader::supports(image) )
+        return false;
+
     PixelReader read(image);
     for( int t=0; t<image->t(); ++t )
         for( int s=0; s<image->s(); ++s )
             if ( read(s, t).a() < threshold )
                 return true;
+
     return false;
 }
 
 
-void
+bool
 ImageUtils::featherAlphaRegions(osg::Image* image, float maxAlpha)
 {
+    if ( !PixelReader::supports(image) || !PixelWriter::supports(image) )
+        return false;
+
     PixelReader read (image);
     PixelWriter write(image);
 
@@ -685,12 +717,17 @@ ImageUtils::featherAlphaRegions(osg::Image* image, float maxAlpha)
             }
         }
     }
+
+    return true;
 }
 
 
-void
+bool
 ImageUtils::convertToPremultipliedAlpha(osg::Image* image)
 {
+    if ( !PixelReader::supports(image) || !PixelWriter::supports(image) )
+        return false;
+
     PixelReader read(image);
     PixelWriter write(image);
     for(int s=0; s<image->s(); ++s) {
@@ -699,6 +736,7 @@ ImageUtils::convertToPremultipliedAlpha(osg::Image* image)
             write( osg::Vec4f(c.r()*c.a(), c.g()*c.a(), c.b()*c.a(), c.a()), s, t);
         }
     }
+    return true;
 }
 
 
@@ -733,6 +771,15 @@ ImageUtils::isCompressed(const osg::Image *image)
     }
 }
 
+
+bool
+ImageUtils::isFloatingPointInternalFormat(GLint i)
+{
+    return 
+        (i >= 0x8C10 && i <= 0x8C17) || // GL_TEXTURE_RED_TYPE_ARB, et al
+        (i >= 0x8814 && i <= 0x881F);   // GL_RGBA32F_ARB, et al
+}
+
 //------------------------------------------------------------------------
 
 namespace
@@ -807,7 +854,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) = (GLubyte)(c.r() / GLTypeTraits<T>::scale());
+            (*ptr) = (T)(c.r() / GLTypeTraits<T>::scale());
         }
     };
 
@@ -828,7 +875,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) = (GLubyte)(c.r() / GLTypeTraits<T>::scale());
+            (*ptr) = (T)(c.r() / GLTypeTraits<T>::scale());
         }
     };
 
@@ -849,7 +896,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) = (GLubyte)(c.a() / GLTypeTraits<T>::scale());
+            (*ptr) = (T)(c.a() / GLTypeTraits<T>::scale());
         }
     };
 
@@ -871,8 +918,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++ = (GLubyte)( c.r() / GLTypeTraits<T>::scale() );
-            *ptr   = (GLubyte)( c.a() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale() );
+            *ptr   = (T)( c.a() / GLTypeTraits<T>::scale() );
         }
     };
 
@@ -895,9 +942,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++ = (GLubyte)( c.r() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.b() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale() );
         }
     };
 
@@ -921,10 +968,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++ = (GLubyte)( c.r() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.a() / GLTypeTraits<T>::scale() );
+            *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() );
         }
     };
 
@@ -947,9 +994,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++ = (GLubyte)( c.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.r() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.b() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.g() / GLTypeTraits<T>::scale() );
+            *ptr++ = (T)( c.r() / GLTypeTraits<T>::scale() );
         }
     };
 
@@ -973,10 +1020,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++ = (GLubyte)( c.b() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.g() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.r() / GLTypeTraits<T>::scale() );
-            *ptr++ = (GLubyte)( c.a() / GLTypeTraits<T>::scale() );
+            *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() );
         }
     };
 
diff --git a/src/osgEarth/Locators b/src/osgEarth/Locators
index 38e2c1c..226e343 100644
--- a/src/osgEarth/Locators
+++ b/src/osgEarth/Locators
@@ -49,6 +49,9 @@ namespace osgEarth
         static GeoLocator* createForKey( const class TileKey& key, const class MapInfo& mapInfo );
         static GeoLocator* createForExtent( const GeoExtent& extent, const class MapInfo& mapInfo);
 
+        GeoLocator* createSameTypeForKey(const class TileKey& key, const class MapInfo& mapInfo );
+        virtual GeoLocator* createSameTypeForExtent(const GeoExtent& extent, const class MapInfo& mapInfo);
+
         void setDataExtent( const GeoExtent& extent );
         const GeoExtent& getDataExtent() const;
 
@@ -56,6 +59,11 @@ namespace osgEarth
 
         virtual bool isEquivalentTo( const GeoLocator& rhs ) const;
 
+        // generates linear (evenly-spaced) coordinates
+        virtual bool isLinear() const { return true; }
+
+        virtual bool createScaleBiasMatrix(const GeoExtent& window, osg::Matrixd& out_m) const;
+
     public: // better-sounding functions.
 
         bool modelToUnit(const osg::Vec3d& model, osg::Vec3d& unit) const {
@@ -95,12 +103,15 @@ namespace osgEarth
 
         //virtual bool convertLocalToModel(const osg::Vec3d& local, osg::Vec3d& model) const;
         virtual bool convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const;
-        
-        /** Clones the current locator, applying a new display (i.e. crop) extent. */
-        //virtual GeoLocator* cloneAndCrop( const osgTerrain::Locator& prototype, const GeoExtent& displayExtent );
 
         virtual GeoLocator* getGeographicFromGeocentric() const;
 
+        // does NOT generate linear coordinates
+        virtual bool isLinear() const { return false; }
+
+        // override.
+        virtual GeoLocator* createSameTypeForExtent(const GeoExtent& extent, const class MapInfo& mapInfo);
+
     private:
         GeoExtent _geoDataExtent;
 
diff --git a/src/osgEarth/Locators.cpp b/src/osgEarth/Locators.cpp
index 81221c4..4ef0649 100644
--- a/src/osgEarth/Locators.cpp
+++ b/src/osgEarth/Locators.cpp
@@ -96,6 +96,18 @@ GeoLocator::createForExtent( const GeoExtent& extent, const class MapInfo& map)
     return locator;
 }
 
+GeoLocator*
+GeoLocator::createSameTypeForKey(const TileKey& key, const MapInfo& map)
+{
+    return createSameTypeForExtent( key.getExtent(), map );
+}
+
+GeoLocator*
+GeoLocator::createSameTypeForExtent(const GeoExtent& extent, const MapInfo& map)
+{
+    return createForExtent( extent, map );
+}
+
 void
 GeoLocator::setDataExtent( const GeoExtent& value ) {
     _dataExtent = value;
@@ -106,14 +118,6 @@ GeoLocator::getDataExtent() const {
     return _dataExtent;
 }
 
-#if 0
-GeoLocator*
-GeoLocator::cloneAndCrop( const osgTerrain::Locator& prototype, const GeoExtent& displayExtent ) const
-{
-    return new GeoLocator( prototype, _dataExtent, displayExtent );
-}
-#endif
-
 bool
 GeoLocator::convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const
 {
@@ -154,6 +158,22 @@ GeoLocator::getGeographicFromGeocentric( ) const
     return NULL;
 }
 
+bool
+GeoLocator::createScaleBiasMatrix(const GeoExtent& window, osg::Matrixd& out) const
+{
+    double scalex = window.width() / _dataExtent.width();
+    double scaley = window.height() / _dataExtent.height();
+    double biasx  = (window.xMin()-_dataExtent.xMin()) / _dataExtent.width();
+    double 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;
+}
+
 /****************************************************************************/
 
 
@@ -225,13 +245,11 @@ MercatorLocator::postInit()
 }
 
 
-#if 0
 GeoLocator*
-MercatorLocator::cloneAndCrop( const osgTerrain::Locator& prototype, const GeoExtent& displayExtent )
+MercatorLocator::createSameTypeForExtent(const GeoExtent& extent, const MapInfo& map)
 {
-    return new MercatorLocator( prototype, getDataExtent() );
+    return new MercatorLocator(extent);
 }
-#endif
 
 bool
 MercatorLocator::convertModelToLocal(const osg::Vec3d& world, osg::Vec3d& local) const
diff --git a/src/osgEarth/Map b/src/osgEarth/Map
index 4a30f54..b10915c 100644
--- a/src/osgEarth/Map
+++ b/src/osgEarth/Map
@@ -31,7 +31,7 @@
 #include <osgEarth/MaskLayer>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
-#include <osgDB/ReaderWriter>
+#include <osgDB/Options>
 
 namespace osgEarth
 {
@@ -73,6 +73,11 @@ namespace osgEarth
         const SpatialReference* getSRS() const { return _profile.valid() ? _profile->getSRS() : 0L; }
 
         /**
+         * Gets the SRS of the world (scene)
+         */
+        const SpatialReference* getWorldSRS() const;
+
+        /**
          * Copies references of the map image layers into the output list.
          * This method is thread safe. It returns the map revision that was
          * in effect when the data was copied.
@@ -266,8 +271,8 @@ namespace osgEarth
         /**
          * Gets the user-provided options structure stored in this map.
          */
-        const osgDB::ReaderWriter::Options* getGlobalOptions() const;
-        void setGlobalOptions( const osgDB::ReaderWriter::Options* options );
+        const osgDB::Options* getGlobalOptions() const;
+        void setGlobalOptions( const osgDB::Options* options );
 
         /**
          * Sets the readable name of this map.
@@ -345,7 +350,7 @@ namespace osgEarth
          * Returns true if new Map model data was available and a sync occurred;
          * returns false if nothing changed.
          */    
-        bool sync( class MapFrame& frame ) const;    
+        bool sync( class MapFrame& frame ) const;
 
         enum ModelParts {
             IMAGE_LAYERS     = 1 << 0,
@@ -378,7 +383,7 @@ namespace osgEarth
         ModelLayerVector _modelLayers;
         MaskLayerVector _terrainMaskLayers;
         MapCallbackList _mapCallbacks;
-        osg::ref_ptr<const osgDB::ReaderWriter::Options> _globalOptions;
+        osg::ref_ptr<const osgDB::Options> _globalOptions;
         Threading::ReadWriteMutex _mapDataMutex;
         osg::ref_ptr<const Profile> _profile;
         osg::ref_ptr<const Profile> _profileNoVDatum;
@@ -386,6 +391,16 @@ namespace osgEarth
         Revision _dataModelRevision;
         osg::ref_ptr<osgDB::Options> _dbOptions;
 
+        struct ElevationLayerCB : public ElevationLayerCallback {
+            osg::observer_ptr<Map> _map;
+            ElevationLayerCB(Map*);
+            void onVisibleChanged(TerrainLayer* layer);
+        };
+        osg::ref_ptr<ElevationLayerCB> _elevationLayerCB;
+        friend struct ElevationLayerCB;
+
+        void notifyElevationLayerVisibleChanged(TerrainLayer*);
+
     private:
         void calculateProfile();
 
diff --git a/src/osgEarth/Map.cpp b/src/osgEarth/Map.cpp
index b1dac48..0f91988 100644
--- a/src/osgEarth/Map.cpp
+++ b/src/osgEarth/Map.cpp
@@ -31,6 +31,24 @@ using namespace osgEarth;
 
 //------------------------------------------------------------------------
 
+Map::ElevationLayerCB::ElevationLayerCB(Map* map) :
+_map(map)
+{
+    //nop
+}
+
+void
+Map::ElevationLayerCB::onVisibleChanged(TerrainLayer* layer)
+{
+    osg::ref_ptr<Map> map;
+    if ( _map.lock(map) )
+    {
+        _map->notifyElevationLayerVisibleChanged(layer);
+    }
+}
+
+//------------------------------------------------------------------------
+
 Map::Map( const MapOptions& options ) :
 osg::Referenced      ( true ),
 _mapOptions          ( options ),
@@ -89,6 +107,10 @@ _dataModelRevision   ( 0 )
     {
         _elevationLayers.setExpressTileSize( *_mapOptions.elevationTileSize() );
     }
+
+    // set up a callback that the Map will use to detect Elevation Layer
+    // visibility changes
+    _elevationLayerCB = new ElevationLayerCB(this);
 }
 
 Map::~Map()
@@ -96,6 +118,24 @@ Map::~Map()
     OE_DEBUG << "~Map" << std::endl;
 }
 
+void
+Map::notifyElevationLayerVisibleChanged(TerrainLayer* layer)
+{
+    // bump the revision safely:
+    Revision newRevision;
+    {
+        Threading::ScopedWriteLock lock( const_cast<Map*>(this)->_mapDataMutex );
+        newRevision = ++_dataModelRevision;
+    }
+
+    // a separate block b/c we don't need the mutex   
+    for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
+    {
+        i->get()->onMapModelChanged( MapModelChange(
+            MapModelChange::TOGGLE_ELEVATION_LAYER, newRevision, layer) );
+    }
+}
+
 bool
 Map::isGeocentric() const
 {
@@ -422,8 +462,8 @@ Map::addImageLayer( ImageLayer* layer )
         {
             i->get()->onMapModelChanged( MapModelChange(
                 MapModelChange::ADD_IMAGE_LAYER, newRevision, layer, index) );
-        }   
-    }   
+        }
+    }
 }
 
 
@@ -494,13 +534,16 @@ Map::addElevationLayer( ElevationLayer* layer )
             newRevision = ++_dataModelRevision;
         }
 
+        // listen for changes in the layer.
+        layer->addCallback( _elevationLayerCB.get() );
+
         // a separate block b/c we don't need the mutex   
         for( MapCallbackList::iterator i = _mapCallbacks.begin(); i != _mapCallbacks.end(); i++ )
         {
             i->get()->onMapModelChanged( MapModelChange(
                 MapModelChange::ADD_ELEVATION_LAYER, newRevision, layer, index) );
-        }   
-    }   
+        }
+    }
 }
 
 void 
@@ -561,6 +604,8 @@ Map::removeElevationLayer( ElevationLayer* layer )
                 break;
             }
         }
+
+        layerToRemove->removeCallback( _elevationLayerCB.get() );
     }
 
     // a separate block b/c we don't need the mutex
@@ -866,9 +911,6 @@ Map::clear()
         elevLayersRemoved.swap ( _elevationLayers );
         modelLayersRemoved.swap( _modelLayers );
 
-        // Because you cannot remove a mask layer once it's in place
-        //maskLayersRemoved.swap ( _terrainMaskLayers );
-
         // calculate a new revision.
         newRevision = ++_dataModelRevision;
     }
@@ -882,8 +924,6 @@ Map::clear()
             i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_ELEVATION_LAYER, newRevision, k->get()) );
         for( ModelLayerVector::iterator k = modelLayersRemoved.begin(); k != modelLayersRemoved.end(); ++k )
             i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_MODEL_LAYER, newRevision, k->get()) );
-        //for( MaskLayerVector::iterator k = maskLayersRemoved.begin(); k != maskLayersRemoved.end(); ++k )
-        //    i->get()->onMapModelChanged( MapModelChange(MapModelChange::REMOVE_MASK_LAYER, newRevision, k->get()) );
     }
 }
 
@@ -1070,6 +1110,12 @@ Map::getHeightField(const TileKey&                  key,
         progress );
 }
 
+const SpatialReference*
+Map::getWorldSRS() const
+{
+    return isGeocentric() ? getSRS()->getECEF() : getSRS();
+}
+
 bool
 Map::sync( MapFrame& frame ) const
 {
@@ -1091,10 +1137,6 @@ Map::sync( MapFrame& frame ) const
         if ( frame._parts & ELEVATION_LAYERS )
         {
             frame._elevationLayers = _elevationLayers;
-            //if ( !frame._initialized )
-            //    frame._elevationLayers.reserve( _elevationLayers.size() );
-            //frame._elevationLayers.clear();
-            //std::copy( _elevationLayers.begin(), _elevationLayers.end(), std::back_inserter(frame._elevationLayers) );
             if ( _mapOptions.elevationTileSize().isSet() )
                 frame._elevationLayers.setExpressTileSize( *_mapOptions.elevationTileSize() );
         }
diff --git a/src/osgEarth/MapModelChange b/src/osgEarth/MapModelChange
index 622267b..93141f7 100644
--- a/src/osgEarth/MapModelChange
+++ b/src/osgEarth/MapModelChange
@@ -44,6 +44,7 @@ namespace osgEarth
             ADD_ELEVATION_LAYER,
             REMOVE_ELEVATION_LAYER,
             MOVE_ELEVATION_LAYER,
+            TOGGLE_ELEVATION_LAYER,     // visibilty toggle on elevation is a model change.
             ADD_MODEL_LAYER,
             REMOVE_MODEL_LAYER,
             MOVE_MODEL_LAYER,
diff --git a/src/osgEarth/MapNode b/src/osgEarth/MapNode
index 87344ed..878189f 100644
--- a/src/osgEarth/MapNode
+++ b/src/osgEarth/MapNode
@@ -25,6 +25,7 @@
 #include <osgEarth/MapNodeOptions>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/TerrainEngineNode>
+#include <osgEarth/ShaderUtils>
 
 namespace osgEarth
 {
@@ -209,14 +210,14 @@ namespace osgEarth
         bool                     _terrainEngineInitialized;
         osg::Group*              _terrainEngineContainer;
 
+        UpdateLightingUniformsHelper _updateLightingUniformsHelper;
+
+
     public: // MapCallback proxy
 
         void onModelLayerAdded( ModelLayer*, unsigned int );
         void onModelLayerRemoved( ModelLayer* );
-		void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex );
-
-    public:
-        void onModelLayerOverlayChanged( ModelLayer* layer );
+        void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex );
 
     public:
         struct TileRangeData : public osg::Referenced {
@@ -227,7 +228,6 @@ namespace osgEarth
 
     private:
 
-        osg::ref_ptr< ModelLayerCallback > _modelLayerCallback;
         osg::ref_ptr< MapCallback >        _mapCallback;
     
         void init();
diff --git a/src/osgEarth/MapNode.cpp b/src/osgEarth/MapNode.cpp
index 4ddebe1..72438cc 100644
--- a/src/osgEarth/MapNode.cpp
+++ b/src/osgEarth/MapNode.cpp
@@ -59,27 +59,6 @@ namespace
         void onModelLayerMoved( ModelLayer* layer, unsigned int oldIndex, unsigned int newIndex ) {
             _node->onModelLayerMoved( layer, oldIndex, newIndex);
         }
-#if 0
-        void onMaskLayerAdded( MaskLayer* layer ) {
-            _node->onMaskLayerAdded( layer );
-        }
-        void onMaskLayerRemoved( MaskLayer* layer ) {
-            _node->onMaskLayerRemoved( layer );
-        }
-#endif
-
-        osg::observer_ptr<MapNode> _node;
-    };
-
-    // converys overlay property changes to the OverlayDecorator in MapNode.
-    struct MapModelLayerCallback : public ModelLayerCallback
-    {
-        MapModelLayerCallback(MapNode* mapNode) : _node(mapNode) { }
-
-        virtual void onOverlayChanged(ModelLayer* layer)
-        {
-            _node->onModelLayerOverlayChanged( layer );
-        }
 
         osg::observer_ptr<MapNode> _node;
     };
@@ -236,13 +215,6 @@ MapNode::init()
 
     setName( "osgEarth::MapNode" );
 
-    // Since we have global uniforms in the stateset, mark it dynamic so it is immune to
-    // multi-threaded overlap
-    // TODO: do we need this anymore? there are no more global uniforms in here.. gw
-    //getOrCreateStateSet()->setDataVariance(osg::Object::DYNAMIC);
-
-    _modelLayerCallback = new MapModelLayerCallback(this);
-
     _maskLayerNode = 0L;
     _lastNumBlacklistedFilenames = 0;
 
@@ -288,9 +260,12 @@ MapNode::init()
     // initialize terrain-level lighting:
     if ( terrainOptions.enableLighting().isSet() )
     {
-        _terrainEngineContainer->getOrCreateStateSet()->setMode( 
-            GL_LIGHTING, 
-            terrainOptions.enableLighting().value() ? 1 : 0 );
+        //_terrainEngineContainer->getOrCreateStateSet()->setMode( 
+        //    GL_LIGHTING, 
+        //    terrainOptions.enableLighting().value() ? 1 : 0 );
+
+        _terrainEngineContainer->getOrCreateStateSet()->addUniform(
+            Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, *terrainOptions.enableLighting()) );
     }
 
     if ( _terrainEngine )
@@ -323,14 +298,20 @@ MapNode::init()
     {
         DrapingTechnique* draping = new DrapingTechnique();
 
+        const char* envOverlayTextureSize = ::getenv("OSGEARTH_OVERLAY_TEXTURE_SIZE");
+
         if ( _mapNodeOptions.overlayBlending().isSet() )
             draping->setOverlayBlending( *_mapNodeOptions.overlayBlending() );
-        if ( _mapNodeOptions.overlayTextureSize().isSet() )
+        if ( envOverlayTextureSize )
+            draping->setTextureSize( as<int>(envOverlayTextureSize, 1024) );
+        else if ( _mapNodeOptions.overlayTextureSize().isSet() )
             draping->setTextureSize( *_mapNodeOptions.overlayTextureSize() );
         if ( _mapNodeOptions.overlayMipMapping().isSet() )
             draping->setMipMapping( *_mapNodeOptions.overlayMipMapping() );
         if ( _mapNodeOptions.overlayAttachStencil().isSet() )
             draping->setAttachStencil( *_mapNodeOptions.overlayAttachStencil() );
+        if ( _mapNodeOptions.overlayResolutionRatio().isSet() )
+            draping->setResolutionRatio( *_mapNodeOptions.overlayResolutionRatio() );
 
         _overlayDecorator->addTechnique( draping );
     }
@@ -356,24 +337,23 @@ MapNode::init()
     // install a layer callback for processing further map actions:
     _map->addMapCallback( _mapCallback.get()  );
 
-    osg::StateSet* ss = getOrCreateStateSet();
-
+    osg::StateSet* stateset = getOrCreateStateSet();
     if ( _mapNodeOptions.enableLighting().isSet() )
     {
-        ss->setMode( 
+        stateset->setMode( 
             GL_LIGHTING, 
             _mapNodeOptions.enableLighting().value() ? 1 : 0 );
     }
 
     dirtyBound();
 
-    // Install top-level shader programs:
+    // Install a default lighting shader program.
     if ( Registry::capabilities().supportsGLSL() )
     {
-        VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "MapNode" );
-        Registry::instance()->getShaderFactory()->installLightingShaders( vp );
-        ss->setAttributeAndModes( vp, osg::StateAttribute::ON );
+        VirtualProgram* vp = VirtualProgram::getOrCreate( stateset );
+        vp->setName( "osgEarth::MapNode" );
+
+        Registry::shaderFactory()->installLightingShaders( vp );
     }
 
     // register for event traversals so we can deal with blacklisted filenames
@@ -511,8 +491,6 @@ MapNode::onModelLayerAdded( ModelLayer* layer, unsigned int index )
     // create the scene graph:
     osg::Node* node = layer->createSceneGraph( _map.get(), _map->getDBOptions(), 0L );
 
-    layer->addCallback(_modelLayerCallback.get() );
-
     if ( node )
     {
         if ( _modelLayerNodes.find( layer ) != _modelLayerNodes.end() )
@@ -531,13 +509,6 @@ MapNode::onModelLayerAdded( ModelLayer* layer, unsigned int index )
             }
             else
             {
-                if ( layer->getOverlay() )
-                {
-                    DrapeableNode* draper = new DrapeableNode( this );
-                    draper->addChild( node );
-                    node = draper;
-                }
-
                 _models->insertChild( index, node );
             }
 
@@ -565,8 +536,6 @@ MapNode::onModelLayerRemoved( ModelLayer* layer )
 {
     if ( layer )
     {
-        layer->removeCallback( _modelLayerCallback.get() );
-
         // look up the node associated with this model layer.
         ModelLayerNodeMap::iterator i = _modelLayerNodes.find( layer );
         if ( i != _modelLayerNodes.end() )
@@ -682,6 +651,9 @@ MapNode::traverse( osg::NodeVisitor& nv )
 
     else if ( nv.getVisitorType() == nv.CULL_VISITOR )
     {
+        // update the light model uniforms.
+        _updateLightingUniformsHelper.cullTraverse( this, &nv );
+
         osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
         if ( cv )
         {
@@ -711,7 +683,7 @@ MapNode::traverse( osg::NodeVisitor& nv )
             osg::Matrix3 m3( m4(0,0), m4(1,0), m4(2,0),
                              m4(0,1), m4(1,1), m4(2,1),
                              m4(0,2), m4(1,2), m4(2,2) );
-            cullData->_windowScaleMatrix->set( m3 );
+            cullData->_windowScaleMatrixUniform->set( m3 );
 
             // traverse:
             cv->pushStateSet( cullData->_stateSet.get() );
@@ -729,26 +701,6 @@ MapNode::traverse( osg::NodeVisitor& nv )
     }
 }
 
-void
-MapNode::onModelLayerOverlayChanged( ModelLayer* layer )
-{
-    osg::ref_ptr<osg::Node> node = _modelLayerNodes[ layer ];
-    if ( node.get() )
-    {
-        OverlayNode* overlay = dynamic_cast<OverlayNode*>(node.get());
-        if ( !overlay && layer->getOverlay() )
-        {
-            overlay = new DrapeableNode(this);
-            overlay->addChild( node.get() );
-            _models->replaceChild( node.get(), overlay );
-        }
-        else if ( overlay )
-        {
-            overlay->setActive( layer->getOverlay() );
-        }
-    }
-}
-
 MapNodeCullData*
 MapNode::getCullData(osg::Camera* key) const
 {
diff --git a/src/osgEarth/MapNodeOptions b/src/osgEarth/MapNodeOptions
index d0b7979..0ae8253 100644
--- a/src/osgEarth/MapNodeOptions
+++ b/src/osgEarth/MapNodeOptions
@@ -83,6 +83,19 @@ namespace osgEarth
         const optional<bool>& overlayAttachStencil() const { return _overlayAttachStencil; }
 
         /**
+         * Ratio of resolution of near geometry to far geometry in a draped overlay view.
+         * E.g.: a ratio of 3 means that the draping mechanism will weight the resolution
+         * of closer geometry over farther geometry by a ratio of 3:1. This is desirable
+         * since viewing draped geometry from an oblique angle will otherwise result in
+         * substantial aliasing near the camera.
+         *
+         * Default value = 5.0
+         * Minimum value = 1.0 (no warping occurs).
+         */
+        optional<float>& overlayResolutionRatio() { return _overlayResolutionRatio; }
+        const optional<float>& overlayResolutionRatio() const { return _overlayResolutionRatio; }
+
+        /**
          * Options to conigure the terrain engine (the component that renders the
          * terrain surface).
          */
@@ -105,6 +118,7 @@ namespace osgEarth
         optional<unsigned> _overlayTextureSize;
         optional<bool>     _overlayMipMapping;
         optional<bool>     _overlayAttachStencil;
+        optional<float>    _overlayResolutionRatio;
 
         optional<Config> _terrainOptionsConf;
         TerrainOptions* _terrainOptions;
diff --git a/src/osgEarth/MapNodeOptions.cpp b/src/osgEarth/MapNodeOptions.cpp
index fc83ecf..4e52180 100644
--- a/src/osgEarth/MapNodeOptions.cpp
+++ b/src/osgEarth/MapNodeOptions.cpp
@@ -31,44 +31,44 @@ static TerrainOptions s_defaultTerrainOptions;
 //----------------------------------------------------------------------------
 
 MapNodeOptions::MapNodeOptions( const Config& conf ) :
-ConfigOptions        ( conf ),
-_proxySettings       ( ProxySettings() ),
-_cacheOnly           ( false ),
-_enableLighting      ( true ),
-_overlayVertexWarping( false ),
-_overlayBlending     ( true ),
-_overlayMipMapping   ( false ),
-_overlayTextureSize  ( 4096 ),
-_terrainOptions      ( 0L ),
-_overlayAttachStencil( true )
+ConfigOptions          ( conf ),
+_proxySettings         ( ProxySettings() ),
+_cacheOnly             ( false ),
+_enableLighting        ( true ),
+_overlayBlending       ( true ),
+_overlayMipMapping     ( false ),
+_overlayTextureSize    ( 4096 ),
+_terrainOptions        ( 0L ),
+_overlayAttachStencil  ( false ),
+_overlayResolutionRatio( 3.0f )
 {
     mergeConfig( conf );
 }
 
 MapNodeOptions::MapNodeOptions( const TerrainOptions& to ) :
-_proxySettings       ( ProxySettings() ),
-_cacheOnly           ( false ),
-_enableLighting      ( true ),
-_overlayVertexWarping( false ),
-_overlayBlending     ( true ),
-_overlayTextureSize  ( 4096 ),
-_overlayMipMapping   ( false ),
-_overlayAttachStencil( true ),
-_terrainOptions      ( 0L )
+_proxySettings         ( ProxySettings() ),
+_cacheOnly             ( false ),
+_enableLighting        ( true ),
+_overlayBlending       ( true ),
+_overlayTextureSize    ( 4096 ),
+_overlayMipMapping     ( false ),
+_overlayAttachStencil  ( false ),
+_overlayResolutionRatio( 3.0f ),
+_terrainOptions        ( 0L )
 {
     setTerrainOptions( to );
 }
 
 MapNodeOptions::MapNodeOptions( const MapNodeOptions& rhs ) :
-_proxySettings       ( ProxySettings() ),
-_cacheOnly           ( false ),
-_enableLighting      ( true ),
-_overlayVertexWarping( false ),
-_overlayBlending     ( true ),
-_overlayTextureSize  ( 4096 ),
-_overlayMipMapping   ( false ),
-_terrainOptions      ( 0L ),
-_overlayAttachStencil( true )
+_proxySettings         ( ProxySettings() ),
+_cacheOnly             ( false ),
+_enableLighting        ( true ),
+_overlayBlending       ( true ),
+_overlayTextureSize    ( 4096 ),
+_overlayMipMapping     ( false ),
+_overlayAttachStencil  ( false ),
+_overlayResolutionRatio( 3.0f ),
+_terrainOptions        ( 0L )
 {
     mergeConfig( rhs.getConfig() );
 }
@@ -89,15 +89,16 @@ MapNodeOptions::getConfig() const
     Config conf; // start with a fresh one since this is a FINAL object  // = ConfigOptions::getConfig();
     conf.key() = "options";
 
-    conf.updateObjIfSet( "proxy",                  _proxySettings );
-    conf.updateIfSet   ( "cache_only",             _cacheOnly );
-    conf.updateIfSet   ( "lighting",               _enableLighting );
-    conf.updateIfSet   ( "terrain",                _terrainOptionsConf );
-    conf.updateIfSet   ( "overlay_warping",        _overlayVertexWarping );
-    conf.updateIfSet   ( "overlay_blending",       _overlayBlending );
-    conf.updateIfSet   ( "overlay_texture_size",   _overlayTextureSize );
-    conf.updateIfSet   ( "overlay_mipmapping",     _overlayMipMapping );
-    conf.updateIfSet   ( "overlay_attach_stencil", _overlayAttachStencil );
+    conf.updateObjIfSet( "proxy",                    _proxySettings );
+    conf.updateIfSet   ( "cache_only",               _cacheOnly );
+    conf.updateIfSet   ( "lighting",                 _enableLighting );
+    conf.updateIfSet   ( "terrain",                  _terrainOptionsConf );
+    conf.updateIfSet   ( "overlay_warping",          _overlayVertexWarping );
+    conf.updateIfSet   ( "overlay_blending",         _overlayBlending );
+    conf.updateIfSet   ( "overlay_texture_size",     _overlayTextureSize );
+    conf.updateIfSet   ( "overlay_mipmapping",       _overlayMipMapping );
+    conf.updateIfSet   ( "overlay_attach_stencil",   _overlayAttachStencil );
+    conf.updateIfSet   ( "overlay_resolution_ratio", _overlayResolutionRatio );
 
     return conf;
 }
@@ -107,14 +108,15 @@ MapNodeOptions::mergeConfig( const Config& conf )
 {
     ConfigOptions::mergeConfig( conf );
 
-    conf.getObjIfSet( "proxy",                  _proxySettings );
-    conf.getIfSet   ( "cache_only",             _cacheOnly );
-    conf.getIfSet   ( "lighting",               _enableLighting );
-    conf.getIfSet   ( "overlay_warping",        _overlayVertexWarping );
-    conf.getIfSet   ( "overlay_blending",       _overlayBlending );
-    conf.getIfSet   ( "overlay_texture_size",   _overlayTextureSize );
-    conf.getIfSet   ( "overlay_mipmapping",     _overlayMipMapping );
-    conf.getIfSet   ( "overlay_attach_stencil", _overlayAttachStencil );
+    conf.getObjIfSet( "proxy",                    _proxySettings );
+    conf.getIfSet   ( "cache_only",               _cacheOnly );
+    conf.getIfSet   ( "lighting",                 _enableLighting );
+    conf.getIfSet   ( "overlay_warping",          _overlayVertexWarping );
+    conf.getIfSet   ( "overlay_blending",         _overlayBlending );
+    conf.getIfSet   ( "overlay_texture_size",     _overlayTextureSize );
+    conf.getIfSet   ( "overlay_mipmapping",       _overlayMipMapping );
+    conf.getIfSet   ( "overlay_attach_stencil",   _overlayAttachStencil );
+    conf.getIfSet   ( "overlay_resolution_ratio", _overlayResolutionRatio );
 
     if ( conf.hasChild( "terrain" ) )
     {
diff --git a/src/osgEarth/MapOptions b/src/osgEarth/MapOptions
index 43be06d..7b9ee42 100644
--- a/src/osgEarth/MapOptions
+++ b/src/osgEarth/MapOptions
@@ -48,8 +48,8 @@ namespace osgEarth
               _cachePolicy           ( ),
               _cstype                ( CSTYPE_GEOCENTRIC ),
               _referenceURI          ( "" ),
-              _elevationInterpolation( INTERP_BILINEAR ),
-              _elevTileSize          ( 8 )
+              _elevationInterpolation( INTERP_TRIANGULATE ), //INTERP_BILINEAR ),
+              _elevTileSize          ( 15 )
         {
             fromConfig(_conf);
         }
diff --git a/src/osgEarth/MaskNode b/src/osgEarth/MaskNode
index 18d0e25..5c27927 100644
--- a/src/osgEarth/MaskNode
+++ b/src/osgEarth/MaskNode
@@ -28,12 +28,14 @@ namespace osgEarth
     {
     public:
         MaskNode();
-        MaskNode( const MaskNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL );
+        MaskNode( const MaskNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL );        
 
         META_Node(osgEarth, MaskNode);
 
+    protected:
+
         /** dtor */
-        virtual ~MaskNode() { }
+        virtual ~MaskNode();
     };
 }
 
diff --git a/src/osgEarth/MaskNode.cpp b/src/osgEarth/MaskNode.cpp
index f67b644..3687afd 100644
--- a/src/osgEarth/MaskNode.cpp
+++ b/src/osgEarth/MaskNode.cpp
@@ -31,6 +31,10 @@ MaskNode::MaskNode( const MaskNode& rhs, const osg::CopyOp& op )
     //nop
 }
 
+MaskNode::~MaskNode()
+{
+}
+
 
 
 
diff --git a/src/osgEarth/MaskSource b/src/osgEarth/MaskSource
index 60dfeee..75bcab7 100644
--- a/src/osgEarth/MaskSource
+++ b/src/osgEarth/MaskSource
@@ -22,7 +22,6 @@
 
 #include <osgEarth/Common>
 #include <osgEarth/Config>
-#include <osgEarth/Progress>
 #include <osgEarth/Revisioning>
 #include <osgEarth/SpatialReference>
 #include <osg/Referenced>
@@ -30,6 +29,7 @@
 namespace osgEarth
 {   
     class Map;
+    class ProgressCallback;
     
     /**
      * Configuration options for a masking source.
@@ -41,7 +41,7 @@ namespace osgEarth
               DriverConfigOptions( options ) { fromConfig(_conf); }
 
         /** dtor */
-        virtual ~MaskSourceOptions() { }
+        virtual ~MaskSourceOptions();
 
     public: // properties
 
@@ -64,7 +64,7 @@ namespace osgEarth
         MaskSource( const MaskSourceOptions& options =MaskSourceOptions() );
 
         /** dtor */
-        virtual ~MaskSource() { }
+        virtual ~MaskSource();
 
         /**
          * Subclass implements this method to create the boundary geometry.
@@ -95,8 +95,9 @@ namespace osgEarth
 
     class OSGEARTH_EXPORT MaskSourceDriver : public osgDB::ReaderWriter
     {
-    protected:
+    protected:        
         const MaskSourceOptions& getMaskSourceOptions( const osgDB::ReaderWriter::Options* rwOpt ) const;
+        virtual ~MaskSourceDriver();
     };
 
     //--------------------------------------------------------------------
@@ -105,6 +106,7 @@ namespace osgEarth
     {   
 	public:
         static MaskSource* create( const MaskSourceOptions& options );
+        virtual ~MaskSourceFactory();
     };
 }
 
diff --git a/src/osgEarth/MaskSource.cpp b/src/osgEarth/MaskSource.cpp
index 403711b..cdf404c 100644
--- a/src/osgEarth/MaskSource.cpp
+++ b/src/osgEarth/MaskSource.cpp
@@ -26,6 +26,10 @@ using namespace OpenThreads;
 
 /****************************************************************/
 
+MaskSourceOptions::~MaskSourceOptions()
+{
+}
+
 void
 MaskSourceOptions::fromConfig( const Config& conf )
 {
@@ -55,12 +59,26 @@ _options( options )
     this->setThreadSafeRefUnref( true );
 }
 
+MaskSource::~MaskSource()
+{
+}
+
+//------------------------------------------------------------------------
+
+MaskSourceDriver::~MaskSourceDriver()
+{
+}
+
 //------------------------------------------------------------------------
 
 #undef  LC
 #define LC "[MaskSourceFactory] "
 #define MASK_SOURCE_OPTIONS_TAG "__osgEarth::MaskSourceOptions"
 
+MaskSourceFactory::~MaskSourceFactory()
+{
+}
+
 MaskSource*
 MaskSourceFactory::create( const MaskSourceOptions& options )
 {
diff --git a/src/osgEarth/MemCache.cpp b/src/osgEarth/MemCache.cpp
index 4b7551d..aded09b 100644
--- a/src/osgEarth/MemCache.cpp
+++ b/src/osgEarth/MemCache.cpp
@@ -23,6 +23,8 @@
 
 using namespace osgEarth;
 
+#define LC "[MemCacheBin] "
+
 //------------------------------------------------------------------------
 
 namespace
@@ -34,13 +36,12 @@ namespace
     {
         MemCacheBin( const std::string& id, unsigned maxSize )
             : CacheBin( id ),
-              _lru    ( true, maxSize )
+              _lru    ( true /* MT-safe */, maxSize )
         {
             //nop
         }
 
-        ReadResult readObject(const std::string& key,
-                              double             maxAge )
+        ReadResult readObject(const std::string& key, TimeStamp minTime)
         {
             MemCacheLRU::Record rec;
             _lru.get(key, rec);
@@ -49,30 +50,27 @@ namespace
 
             if ( rec.valid() )
             {
+                //OE_INFO << LC << "hits: " << _lru.getStats()._hitRatio*100.0f << "%" << std::endl;
+
                 return ReadResult( 
                    osg::clone(rec.value().first.get(), osg::CopyOp::DEEP_COPY_ALL),
                    rec.value().second );
             }
             else
+            {
+                //OE_INFO << LC << "hits: " << _lru.getStats()._hitRatio*100.0f << "%" << std::endl;
                 return ReadResult();
+            }
         }
 
-        ReadResult readImage(const std::string& key,
-                             double             maxAge )
-        {
-            return readObject( key, maxAge );
-        }
-
-        ReadResult readString(const std::string& key,
-                              double             maxAge )
+        ReadResult readImage(const std::string& key, TimeStamp minTime)
         {
-            return readObject( key, maxAge );
+            return readObject( key, minTime );
         }
 
-        ReadResult readConfig(const std::string& key,
-                              double             maxAge )
+        ReadResult readString(const std::string& key, TimeStamp minTime)
         {
-            return readObject( key, maxAge );
+            return readObject( key, minTime );
         }
 
         bool write( const std::string& key, const osg::Object* object, const Config& meta )
@@ -86,9 +84,23 @@ namespace
                 return false;
         }
 
-        bool isCached( const std::string& key, double maxAge ) 
+        bool remove(const std::string& key)
+        {
+            _lru.erase(key);
+            return true;
+        }
+
+        bool touch(const std::string& key)
+        {
+            // just doing a get will put it at the front of the LRU list
+            MemCacheLRU::Record dummy;
+            return _lru.get(key, dummy);
+        }
+
+        RecordStatus getRecordStatus( const std::string& key, TimeStamp minTime )
         {
-            return _lru.has(key);
+            // ignore minTime; MemCache does not support expiration
+            return _lru.has(key) ? STATUS_OK : STATUS_NOT_FOUND;
         }
 
         bool purge()
diff --git a/src/osgEarth/ModelLayer b/src/osgEarth/ModelLayer
index eb0801d..7cf08fd 100644
--- a/src/osgEarth/ModelLayer
+++ b/src/osgEarth/ModelLayer
@@ -21,6 +21,7 @@
 #define OSGEARTH_MODEL_LAYER_H 1
 
 #include <osgEarth/Common>
+#include <osgEarth/AlphaEffect>
 #include <osgEarth/Layer>
 #include <osgEarth/Config>
 #include <osgEarth/ModelSource>
@@ -76,11 +77,10 @@ namespace osgEarth
         const optional<bool>& visible() const { return _visible; }
 
         /**
-         * Whether to drape the model geometry over the terrain as a projected overlay.
-         * Defaults to false
+         * The opacity of this layer
          */
-        optional<bool>& overlay() { return _overlay; }
-        const optional<bool>& overlay() const { return _overlay; }
+        optional<float>& opacity() { return _opacity; }
+        const optional<float>& opacity() const { return _opacity; }
 
     public:
         virtual Config getConfig() const;
@@ -91,10 +91,10 @@ namespace osgEarth
         void setDefaults();
 
         optional<std::string>        _name;
-        optional<bool>               _overlay;
         optional<ModelSourceOptions> _driver;
         optional<bool>               _enabled;
         optional<bool>               _visible;
+        optional<float>              _opacity;
         optional<bool>               _lighting;
     };
 
@@ -104,7 +104,7 @@ namespace osgEarth
     struct ModelLayerCallback : public osg::Referenced
     {
         virtual void onVisibleChanged( class ModelLayer* layer ) { }
-        virtual void onOverlayChanged( class ModelLayer* layer ) { }
+        virtual void onOpacityChanged( class ModelLayer* layer ) { }
         virtual ~ModelLayerCallback() { }
     };
 
@@ -180,14 +180,17 @@ namespace osgEarth
         /** Whether this layer is used at all. */
         bool getEnabled() const;
 
-        /** Whether this layer is drawn as normal geometry or as a draped overlay. */
-        bool getOverlay() const;
-        void setOverlay( bool overlay );
-
         /** whether to apply lighting to the model layer's root node */
         void setLightingEnabled( bool value );
         bool isLightingEnabled() const;
 
+        /**
+         * Sets the opacity of this image layer.
+         * @param opacity Opacity [0..1] -> [transparent..opaque]
+         */
+        void setOpacity( float opacity );
+        float getOpacity() const;
+
     public:
 
         /** Adds a property notification callback to this layer */
@@ -202,7 +205,8 @@ namespace osgEarth
         ModelLayerOptions            _runtimeOptions;
         Revision                     _modelSourceRev;
         ModelLayerCallbackList       _callbacks;
-        UpdateLightingUniformsHelper _updateLightingUniformsHelper;
+        osg::ref_ptr<AlphaEffect>    _alphaEffect;
+        //UpdateLightingUniformsHelper _updateLightingUniformsHelper;
 
         typedef std::set< osg::observer_ptr<osg::Node> > NodeObserverSet;
         NodeObserverSet _nodeSet;
diff --git a/src/osgEarth/ModelLayer.cpp b/src/osgEarth/ModelLayer.cpp
index b7d8d0a..2c72d62 100644
--- a/src/osgEarth/ModelLayer.cpp
+++ b/src/osgEarth/ModelLayer.cpp
@@ -20,6 +20,7 @@
 #include <osgEarth/Map>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/ShaderFactory>
 #include <osg/Depth>
 
 #define LC "[ModelLayer] "
@@ -66,10 +67,10 @@ ConfigOptions()
 void
 ModelLayerOptions::setDefaults()
 {
-    _overlay.init     ( false );
     _enabled.init     ( true );
     _visible.init     ( true );
     _lighting.init    ( true );
+    _opacity.init     ( 1.0f );
 }
 
 Config
@@ -79,10 +80,10 @@ ModelLayerOptions::getConfig() const
     Config conf = ConfigOptions::newConfig();
 
     conf.updateIfSet( "name", _name );
-    conf.updateIfSet( "overlay", _overlay );
     conf.updateIfSet( "enabled", _enabled );
     conf.updateIfSet( "visible", _visible );
     conf.updateIfSet( "lighting", _lighting );
+    conf.updateIfSet( "opacity",  _opacity );
 
     // Merge the ModelSource options
     if ( driver().isSet() )
@@ -95,10 +96,10 @@ void
 ModelLayerOptions::fromConfig( const Config& conf )
 {
     conf.getIfSet( "name", _name );
-    conf.getIfSet( "overlay", _overlay );
     conf.getIfSet( "enabled", _enabled );
     conf.getIfSet( "visible", _visible );
     conf.getIfSet( "lighting", _lighting );
+    conf.getIfSet( "opacity",        _opacity );
 
     if ( conf.hasValue("driver") )
         driver() = ModelSourceOptions(conf);
@@ -148,6 +149,9 @@ void
 ModelLayer::copyOptions()
 {
     _runtimeOptions = _initOptions;
+
+    _alphaEffect = new AlphaEffect();
+    _alphaEffect->setAlpha( *_initOptions.opacity() );
 }
 
 void
@@ -194,11 +198,13 @@ ModelLayer::createSceneGraph(const Map*            map,
                 ss->setRenderBinDetails( 99999, "RenderBin" ); //TODO: configure this bin ...
             }
 
+#if 0 // moved the MapNode level.
             if ( Registry::capabilities().supportsGLSL() )
             {
                 // install a callback that keeps the shader uniforms up to date
                 node->addCullCallback( new UpdateLightingUniformsHelper() );
             }
+#endif
 
             _modelSource->sync( _modelSourceRev );
 
@@ -207,7 +213,15 @@ ModelLayer::createSceneGraph(const Map*            map,
         }
     }
 
-    return node;
+    // add a parent group for shaders/effects to attach to without overwriting any model programs directly
+    osg::Group* group = 0L;
+    if ( node ) {
+      group = new osg::Group();
+      group->addChild(node);
+      _alphaEffect->attach( group->getOrCreateStateSet() );
+    }
+
+    return group;
 }
 
 bool
@@ -237,13 +251,29 @@ ModelLayer::setVisible(bool value)
             }
         }
 
-        //if ( _node.valid() )
-        //    _node->setNodeMask( value ? ~0 : 0 );
-
         fireCallback( &ModelLayerCallback::onVisibleChanged );
     }
 }
 
+float
+ModelLayer::getOpacity() const
+{
+    return *_runtimeOptions.opacity();
+}
+
+void
+ModelLayer::setOpacity(float opacity)
+{
+    if ( _runtimeOptions.opacity() != opacity )
+    {
+        _runtimeOptions.opacity() = opacity;
+
+        _alphaEffect->setAlpha(opacity);
+
+        fireCallback( &ModelLayerCallback::onOpacityChanged );
+    }
+}
+
 void
 ModelLayer::setLightingEnabled( bool value )
 {
@@ -253,9 +283,17 @@ ModelLayer::setLightingEnabled( bool value )
     {
         if ( i->valid() )
         {
-            i->get()->getOrCreateStateSet()->setMode( 
+            osg::StateSet* stateset = i->get()->getOrCreateStateSet();
+
+            stateset->setMode( 
                 GL_LIGHTING, value ? osg::StateAttribute::ON : 
                 (osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED) );
+
+            if ( Registry::capabilities().supportsGLSL() )
+            {
+                stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(
+                    GL_LIGHTING, value ) );
+            }
         }
     }
 }
@@ -266,22 +304,6 @@ ModelLayer::isLightingEnabled() const
     return *_runtimeOptions.lightingEnabled();
 }
 
-bool
-ModelLayer::getOverlay() const
-{
-    return *_runtimeOptions.overlay();
-}
-
-void
-ModelLayer::setOverlay(bool overlay)
-{
-    if ( _runtimeOptions.overlay() != overlay )
-    {
-        _runtimeOptions.overlay() = overlay;
-        fireCallback( &ModelLayerCallback::onOverlayChanged );
-    }
-}
-
 void
 ModelLayer::addCallback( ModelLayerCallback* cb )
 {
diff --git a/src/osgEarth/ModelSource b/src/osgEarth/ModelSource
index 3b2d0f5..a9cf8aa 100644
--- a/src/osgEarth/ModelSource
+++ b/src/osgEarth/ModelSource
@@ -23,7 +23,6 @@
 #include <osgEarth/Common>
 #include <osgEarth/Config>
 #include <osgEarth/NodeUtils>
-#include <osgEarth/Progress>
 #include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <osg/Referenced>
@@ -31,6 +30,7 @@
 namespace osgEarth
 {   
     class Map;
+    class ProgressCallback;
     
     /**
      * Configuration options for a models source.
@@ -41,7 +41,7 @@ namespace osgEarth
         ModelSourceOptions( const ConfigOptions& options =ConfigOptions() );
 
         /** dtor */
-        virtual ~ModelSourceOptions() { }
+        virtual ~ModelSourceOptions();
 
     public: // properties
 
@@ -86,7 +86,7 @@ namespace osgEarth
         ModelSource( const ModelSourceOptions& options =ModelSourceOptions() );
 
         /** dtor */
-        virtual ~ModelSource() { }
+        virtual ~ModelSource();
 
         osg::Node* createNode(
             const Map*            map,
@@ -162,6 +162,8 @@ namespace osgEarth
     {
     protected:
         const ModelSourceOptions& getModelSourceOptions( const osgDB::ReaderWriter::Options* rwOpt ) const;
+
+        virtual ~ModelSourceDriver();
     };
 
     //--------------------------------------------------------------------
@@ -174,6 +176,8 @@ namespace osgEarth
     {   
     public:
         static ModelSource* create( const ModelSourceOptions& options );
+
+        virtual ~ModelSourceFactory();
     };
 }
 
diff --git a/src/osgEarth/ModelSource.cpp b/src/osgEarth/ModelSource.cpp
index 7b35ef1..fd8deda 100644
--- a/src/osgEarth/ModelSource.cpp
+++ b/src/osgEarth/ModelSource.cpp
@@ -38,6 +38,10 @@ _depthTestEnabled  ( true )
     fromConfig(_conf);
 }
 
+ModelSourceOptions::~ModelSourceOptions()
+{
+}
+
 void
 ModelSourceOptions::fromConfig( const Config& conf )
 {
@@ -70,8 +74,10 @@ ModelSourceOptions::getConfig() const
 ModelSource::ModelSource( const ModelSourceOptions& options ) :
 _options( options )
 {
-    //TODO: is this really necessary?
-    this->setThreadSafeRefUnref( true );
+}
+
+ModelSource::~ModelSource()
+{
 }
 
 
@@ -132,6 +138,10 @@ ModelSource::firePostProcessors( osg::Node* node )
 #define LC "[ModelSourceFactory] "
 #define MODEL_SOURCE_OPTIONS_TAG "__osgEarth::ModelSourceOptions"
 
+ModelSourceFactory::~ModelSourceFactory()
+{
+}
+
 ModelSource*
 ModelSourceFactory::create( const ModelSourceOptions& options )
 {
@@ -170,3 +180,7 @@ ModelSourceDriver::getModelSourceOptions( const osgDB::ReaderWriter::Options* op
 {
     return *static_cast<const ModelSourceOptions*>( options->getPluginData( MODEL_SOURCE_OPTIONS_TAG ) );
 }
+
+ModelSourceDriver::~ModelSourceDriver()
+{
+}
diff --git a/src/osgEarth/OverlayDecorator b/src/osgEarth/OverlayDecorator
index 006a10d..5d38f17 100644
--- a/src/osgEarth/OverlayDecorator
+++ b/src/osgEarth/OverlayDecorator
@@ -27,6 +27,7 @@
 #include <osg/TexGenNode>
 #include <osg/Uniform>
 #include <osgUtil/CullVisitor>
+#include <osgShadow/ConvexPolyhedron>
 
 namespace osgEarth
 {
@@ -82,16 +83,6 @@ namespace osgEarth
     protected:
         virtual ~OverlayDecorator() { }
 
-        //// Root group for an overlay technique
-        //struct OverlayRootGroup : public osg::Group
-        //{
-        //    OverlayRootGroup();
-        //    osg::Polytope& cullingFrustum() { return _cb->_cullingFrustum; }
-        //    osg::ref_ptr<osg::NodeCallback> _cb;
-        //    mutable Threading::Mutex        _mutex;
-        //    osg::BoundingSphere computeBound() const; // override
-        //};
-
     public:
 
         // RTT camera parameters for an overlay technique. There will be
@@ -107,6 +98,8 @@ namespace osgEarth
             osg::ref_ptr<osg::Referenced> _techniqueData;    // technique sets this if needed
             const double*                 _horizonDistance;  // points to the PVD horizon distance.
             osg::Group*                   _terrainParent;    // the terrain is in getChild(0).
+            osg::Vec3d                    _eyeWorld;         // eyepoint in world coords
+            osgShadow::ConvexPolyhedron   _frustumPH;        // polyhedron representing the frustum
         };
 
         // One of these per view (camera). The terrain state set
@@ -126,7 +119,6 @@ namespace osgEarth
         optional<int>                 _textureSize;
         bool                          _useShaders;
         bool                          _isGeocentric;
-        double                        _maxProjectedMapExtent;
         bool                          _mipmapping;
         bool                          _rttBlending;
         bool                          _updatePending;
diff --git a/src/osgEarth/OverlayDecorator.cpp b/src/osgEarth/OverlayDecorator.cpp
index f86b408..45a0a11 100755
--- a/src/osgEarth/OverlayDecorator.cpp
+++ b/src/osgEarth/OverlayDecorator.cpp
@@ -45,6 +45,52 @@ using namespace osgEarth;
 
 namespace
 {
+    struct ComputeVisibleBounds : public osg::NodeVisitor
+    {
+        ComputeVisibleBounds(osg::Polytope& tope, osg::Matrix& local2world) 
+            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN)
+        {
+            _matrixStack.push(local2world);
+            _topeStack.push(tope);
+        }
+
+        void apply(osg::Geode& node)
+        {
+            const osg::BoundingSphere& bs = node.getBound();
+            osg::Vec3 p = bs.center() * _matrixStack.top();
+            if ( _topeStack.top().contains(p) )
+            {
+                _bs.expandBy( osg::BoundingSphere(p, bs.radius()) );
+            }
+            //if ( _topeStack.top().contains(bs) )
+            //{
+            //    osg::Vec3 p = _matrixStack.top() * bs.center();
+            //    _bs.expandBy( osg::BoundingSphere(p, bs.radius()) );
+            //}
+        }
+
+        void apply(osg::Transform& xform)
+        {
+            osg::Matrix m;
+            xform.computeLocalToWorldMatrix(m, this);
+
+            _matrixStack.push( _matrixStack.top() );
+            _matrixStack.top().preMult( m );
+
+            //_topeStack.push( _topeStack.top() );
+            //_topeStack.top().transformProvidingInverse(m);
+
+            traverse(xform);
+
+            _matrixStack.pop();
+            //_topeStack.pop();
+        }
+
+        std::stack<osg::Matrix>   _matrixStack;
+        std::stack<osg::Polytope> _topeStack;
+        osg::BoundingSphere       _bs;
+    };
+
     void setFar(osg::Matrix& m, double newFar)
     {
         if ( osg::equivalent(m(0,3),0.0) && osg::equivalent(m(1,3),0.0) && osg::equivalent(m(2,3),0.0) )
@@ -306,13 +352,6 @@ OverlayDecorator::onInstall( TerrainEngineNode* engine )
     _srs = info.getProfile()->getSRS();
     _ellipsoid = info.getProfile()->getSRS()->getEllipsoid();
 
-    // the maximum extent (for projected maps only)
-    if ( !_isGeocentric )
-    {
-        const GeoExtent& extent = info.getProfile()->getExtent();
-        _maxProjectedMapExtent = osg::maximum( extent.width(), extent.height() );
-    }
-
     //todo: need this? ... probably not anymore
     _useShaders = 
         Registry::capabilities().supportsGLSL() && (
@@ -343,11 +382,8 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
                                                    PerViewData&          pvd)
 {
     static int s_frame = 1;
-    static osg::Vec3d zero(0.0, 0.0, 0.0);
 
-    osg::Matrixd invViewMatrix = cv->getCurrentCamera()->getInverseViewMatrix();
-    osg::Vec3d eye = zero * invViewMatrix;
-    //osg::Vec3 eye = cv->getEyePoint();
+    osg::Vec3d eye = cv->getViewPoint();
 
     double eyeLen;
     osg::Vec3d worldUp;
@@ -370,22 +406,24 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
     {
         eyeLen = eye.length();
 
-        double lat, lon;
-        _ellipsoid->convertXYZToLatLongHeight( eye.x(), eye.y(), eye.z(), lat, lon, hasl );
+        const SpatialReference* geoSRS = _engine->getTerrain()->getSRS();
+        osg::Vec3d geodetic;
+        geoSRS->transformFromWorld(eye, geodetic);
 
+        hasl = geodetic.z();
         R = eyeLen - hasl;
         
         //Actually sample the terrain to get the height and adjust the eye position so it's a tighter fit to the real data.
         double height;
-        if (_engine->getTerrain()->getHeight( SpatialReference::create("epsg:4326"), osg::RadiansToDegrees( lon ), osg::RadiansToDegrees( lat ), &height))
+        if (_engine->getTerrain()->getHeight(geoSRS, geodetic.x(), geodetic.y(), &height)) // SpatialReference::create("epsg:4326"), osg::RadiansToDegrees( lon ), osg::RadiansToDegrees( lat ), &height))
         {
-            hasl -= height;
+            geodetic.z() -= height;
         }
         hasl = osg::maximum( hasl, 100.0 );
 
+        // up vector tangent to the ellipsoid under the eye.
         worldUp = _ellipsoid->computeLocalUpVector(eye.x(), eye.y(), eye.z());
 
-
         // radius of the earth under the eyepoint
         // gw: wrong. use R instead.
         double radius = eyeLen - hasl; 
@@ -411,11 +449,11 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
     haslWeight = osg::absolute(worldUp * lookVector);
 
     // unit look-vector of the eye:
-    osg::Vec3d from, to, up;
+    osg::Vec3d camEye, camTo, camUp;
     const osg::Matrix& mvMatrix = *cv->getModelViewMatrix();
-    mvMatrix.getLookAt( from, to, up, eyeLen);
-    osg::Vec3 camLookVec = to-from;
-    camLookVec.normalize();
+    mvMatrix.getLookAt( camEye, camTo, camUp, 1.0); //eyeLen);
+    osg::Vec3 camLook = camTo-camEye;
+    camLook.normalize();
 
     // Save and reset the current near/far planes before traversing the subgraph.
     // We do this because we want a projection matrix that includes ONLY the clip
@@ -490,22 +528,11 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
         inverseMVP.invert(MVP);
     }
 
-    // Build a polyhedron for the new frustum so we can slice it.
-    // TODO: do we really even need to slice it anymore? consider
-    osgShadow::ConvexPolyhedron frustumPH;
-    frustumPH.setToUnitFrustum(true, true);
-    frustumPH.transform( inverseMVP, MVP );
-
-    // extract the verts associated with the frustum's PH:
-    std::vector<osg::Vec3d> verts;
-    frustumPH.getPoints( verts );
-
     // calculate the new RTT matrices. All techniques will share the 
     // same set. We could probably put these in the "shared" category
     // and use pointers..todo.
     osg::Matrix rttViewMatrix, rttProjMatrix;
 
-
     // for a camera that cares about geometry (like the draping technique) it's important
     // to include the geometry in the ortho-camera's Z range. But for a camera that just
     // cares about the terrain depth (like the clamping technique) we want to constrain 
@@ -515,23 +542,33 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
 
     // For now: our RTT camera z range will be based on this equation:
     double zspan = std::max(50000.0, hasl+25000.0);
-
+    osg::Vec3d up = camLook;
     if ( _isGeocentric )
     {
-        rttViewMatrix.makeLookAt( eye+worldUp*zspan, osg::Vec3d(0,0,0), osg::Vec3d(0,0,1) );
+        osg::Vec3d rttEye = eye+worldUp*zspan;
+        //establish a valid up vector
+        osg::Vec3d rttLook = -rttEye;
+        rttLook.normalize();
+        if ( fabs(rttLook * camLook) > 0.9999 )
+            up.set( camUp );
+
+        // do NOT look at (0,0,0); must look down the ellipsoid up vector.
+        rttViewMatrix.makeLookAt( rttEye, rttEye-worldUp*zspan, up );
     }
     else
     {
-        rttViewMatrix.makeLookAt( eye+worldUp*zspan, eye-worldUp*zspan, osg::Vec3d(0,1,0) );
-    }
+        osg::Vec3d rttLook(0, 0, -1);
+        if ( fabs(rttLook * camLook) > 0.9999 )
+            up.set( camUp );
 
-    // calculate an orthographic RTT projection matrix based on the view-space
-    // bounds of the vertex list (i.e. the extents surrounding the RTT camera 
-    // that bounds all the polyherdron verts in its XY plane)
-    double xmin, ymin, xmax, ymax, maxDist;
-    getExtentInSilhouette(rttViewMatrix, eye, verts, xmin, ymin, xmax, ymax, maxDist);
-    rttProjMatrix.makeOrtho(xmin, xmax, ymin, ymax, 0.0, std::min(maxDist,eyeLen)+zspan);
+        rttViewMatrix.makeLookAt( camEye + worldUp*zspan, camEye - worldUp*zspan, up );
+    }
 
+    // Build a polyhedron for the new frustum so we can slice it.
+    // TODO: do we really even need to slice it anymore? consider
+    osgShadow::ConvexPolyhedron frustumPH;
+    frustumPH.setToUnitFrustum(true, true);
+    frustumPH.transform( inverseMVP, MVP );
 
     // now copy the RTT matrixes over to the techniques.
     for( unsigned t=0; t<pvd._techParams.size(); ++t )
@@ -542,8 +579,63 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
         if ( !_techniques[t]->hasData(params) )
             continue;
 
-        params._rttViewMatrix.set( rttViewMatrix );
-        params._rttProjMatrix.set( rttProjMatrix );
+        // slice it to fit the overlay geometry. (this says 'visible' but it's just everything..
+        // perhaps we can truly make it visible)
+        osgShadow::ConvexPolyhedron visiblePH( frustumPH );
+
+#if 0
+        osg::Polytope frustumPT;
+        frustumPH.getPolytope(frustumPT);
+        ComputeVisibleBounds cvb(frustumPT, MVP);
+        params._group->accept(cvb);
+        const osg::BoundingSphere& visibleOverlayBS = cvb._bs;
+        OE_WARN << "VBS radius = " << visibleOverlayBS.radius() << std::endl;
+#else
+        const osg::BoundingSphere& visibleOverlayBS = params._group->getBound();
+#endif
+        if ( visibleOverlayBS.valid() )
+        {
+            osg::BoundingBox visibleOverlayBB;
+            visibleOverlayBB.expandBy( visibleOverlayBS );
+            osg::Polytope visibleOverlayPT;
+            visibleOverlayPT.setToBoundingBox( visibleOverlayBB );
+            visiblePH.cut( visibleOverlayPT );
+        }
+
+        // extract the verts associated with the frustum's PH:
+        std::vector<osg::Vec3d> verts;
+        visiblePH.getPoints( verts );
+
+        // zero verts means the visible PH does not intersect the frustum.
+        // TODO: when verts = 0 should we do something different? or use the previous
+        // frame's view matrix?
+        if ( verts.size() > 0 )
+        {
+            // calculate an orthographic RTT projection matrix based on the view-space
+            // bounds of the vertex list (i.e. the extents surrounding the RTT camera 
+            // that bounds all the polyherdron verts in its XY plane)
+            double xmin, ymin, xmax, ymax, maxDist;
+            getExtentInSilhouette(rttViewMatrix, eye, verts, xmin, ymin, xmax, ymax, maxDist);
+
+            // make sure the ortho camera penetrates the terrain. This is a must for depth buffer sampling
+            double dist = std::max(hasl*1.5, std::min(maxDist, eyeLen));
+
+            // in ecef it can't go past the horizon though, or you get bleed thru
+            if ( _isGeocentric )
+                dist = std::min(dist, eyeLen);
+
+            rttProjMatrix.makeOrtho(xmin, xmax, ymin, ymax, 0.0, dist+zspan);
+
+            //OE_WARN << LC << "verts size = " << verts.size()
+            //    << "xmin=" << xmin << ", xmax=" << xmax
+            //    << ", ymin=" << ymin << ", ymax=" << ymax
+            //    << std::endl;
+
+            params._rttViewMatrix.set( rttViewMatrix );
+            params._rttProjMatrix.set( rttProjMatrix );
+            params._eyeWorld = eye;
+            params._frustumPH = frustumPH;
+        }
 
         // service a "dump" of the polyhedrons for dubugging purposes
         // (see osgearth_overlayviewer)
@@ -558,17 +650,10 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             osg::Node* camNode = osgDB::readNodeFile(fn);
             camNode->setName("camera");
 
-            //// visible PH or overlay:
-            //visiblePHBeforeCut.dumpGeometry(0,0,0,fn,osg::Vec4(0,1,1,1),osg::Vec4(0,1,1,.25));
-            //osg::Node* overlay = osgDB::readNodeFile(fn);
-            //overlay->setName("overlay");
-
-#if 0
-            // visible overlay Polyherdron AFTER frustum intersection:
+            // visible overlay Polyherdron AFTER cuting:
             visiblePH.dumpGeometry(0,0,0,fn,osg::Vec4(1,.5,1,1),osg::Vec4(1,.5,0,.25));
             osg::Node* intersection = osgDB::readNodeFile(fn);
             intersection->setName("intersection");
-#endif
 
             // RTT frustum:
             {
@@ -594,8 +679,7 @@ OverlayDecorator::cullTerrainAndCalculateRTTParams(osgUtil::CullVisitor* cv,
             osg::Group* g = new osg::Group();
             g->getOrCreateStateSet()->setAttribute(new osg::Program(), 0);
             g->addChild(camNode);
-            //g->addChild(overlay);
-            //g->addChild(intersection);
+            g->addChild(intersection);
             g->addChild(rttNode);
             g->addChild(dsgmt);
 
@@ -677,27 +761,6 @@ OverlayDecorator::traverse( osg::NodeVisitor& nv )
                     TechRTTParams& params = pvd._techParams[i];
                     _techniques[i]->cullOverlayGroup( params, cv );
                 }
-
-#if 0
-                // new CullVisitor to traverse the subgroups:
-                MyCullVisitor* mcv = static_cast<MyCullVisitor*>(cv);
-
-                // prep and traverse the RTT camera(s):
-                for(unsigned i=0; i<_techniques.size(); ++i)
-                {
-                    TechRTTParams& params = pvd._techParams[i];
-
-                    //mcv->_mvp = params._rttViewMatrix * params._rttProjMatrix;
-                    mcv->_cullingFrustum->setToUnitFrustum( true, true );
-                    mcv->_cullingFrustum->transformProvidingInverse( params._rttProjMatrix );
-                    //mcv->_cullingFrustum->transformProvidingInverse( params._rttViewMatrix * params._rttProjMatrix );
-
-                    _techniques[i]->cullOverlayGroup( params, cv );
-                    //_techniques[i]->cullOverlayGroup( params, cullVisitor.get() );
-                }
-                
-                static_cast<MyCullVisitor*>(cv)->_cullingFrustum.unset();
-#endif
             }
             else
             {
diff --git a/src/osgEarth/OverlayNode b/src/osgEarth/OverlayNode
index 04102f0..da137e7 100644
--- a/src/osgEarth/OverlayNode
+++ b/src/osgEarth/OverlayNode
@@ -76,7 +76,7 @@ namespace osgEarth
 
     protected:
         /** dtor */
-        virtual ~OverlayNode() { }
+        virtual ~OverlayNode();
 
     private:
         bool                          _active;
diff --git a/src/osgEarth/OverlayNode.cpp b/src/osgEarth/OverlayNode.cpp
index 56b8a90..2da2216 100644
--- a/src/osgEarth/OverlayNode.cpp
+++ b/src/osgEarth/OverlayNode.cpp
@@ -22,6 +22,8 @@
 #include <osgEarth/OverlayDecorator>
 #include <osgEarth/MapNode>
 #include <osgEarth/NodeUtils>
+#include <osgEarth/PrimitiveIntersector>
+#include <osgEarth/DPLineSegmentIntersector>
 #include <osgUtil/IntersectionVisitor>
 
 #define LC "[OverlayNode] "
@@ -156,16 +158,23 @@ _getGroup ( provider )
     }
 }
 
+OverlayNode::~OverlayNode()
+{
+    // cleans up the overlayProxyContainer if necessary.
+    setMapNode(0L);
+}
+
 void
 OverlayNode::setMapNode( MapNode* mapNode )
 {
-    MapNode* oldMapNode = getMapNode();
+    osg::ref_ptr<MapNode> oldMapNode;
+    _mapNode.lock(oldMapNode);
 
-    if ( oldMapNode != mapNode )
+    if ( oldMapNode.get() != mapNode )
     {
-        if ( oldMapNode && _getGroup && _active && _overlayProxyContainer->getNumParents() > 0 )
+        if ( oldMapNode.valid() && _getGroup && _active && _overlayProxyContainer->getNumParents() > 0 )
         {
-            osg::Group* group = _getGroup( oldMapNode );
+            osg::Group* group = _getGroup( oldMapNode.get() );
             if ( group )
                 group->removeChild( _overlayProxyContainer.get() );
         }
@@ -194,17 +203,18 @@ OverlayNode::applyChanges()
 {
     _active = _newActive;
 
-    if ( getMapNode() && _getGroup )
+    osg::ref_ptr<MapNode> mapNode;
+    if ( _mapNode.lock(mapNode) && _getGroup )
     {
         if ( _active && _overlayProxyContainer->getNumParents() == 0 )
         {
-            osg::Group* group = _getGroup( getMapNode() );
+            osg::Group* group = _getGroup( mapNode.get() );
             if ( group )
                 group->addChild( _overlayProxyContainer.get() );
         }
         else if ( !_active && _overlayProxyContainer->getNumParents() > 0 )
         {
-            osg::Group* group = _getGroup( getMapNode() );
+            osg::Group* group = _getGroup( mapNode.get() );
             if ( group )
                 group->removeChild( _overlayProxyContainer.get() );
         }
@@ -298,6 +308,75 @@ OverlayNode::traverse( osg::NodeVisitor& nv )
             osg::Group::traverse( nv );
         }
 
+        else if (dynamic_cast<osgUtil::IntersectionVisitor*>(&nv))
+        {
+            /*
+               In order to properly intersect with overlay geometries, attempt to find the point on the terrain where the pick occurred
+               cast a second intersector vertically at that point.
+
+               Currently this is only imlpemented for our custom PrimitiveIntersector.
+            */
+            osgUtil::IntersectionVisitor* iv = dynamic_cast<osgUtil::IntersectionVisitor*>(&nv);
+            osgEarth::PrimitiveIntersector* pi = dynamic_cast<osgEarth::PrimitiveIntersector *>(iv->getIntersector());
+
+            osg::ref_ptr<MapNode> mapNode;
+            if (pi && !pi->getOverlayIgnore() && _mapNode.lock(mapNode))
+            { 
+                osg::NodePath path = iv->getNodePath();
+                osg::NodePath prunedNodePath( path.begin(), path.end()-1 );
+                osg::Matrix modelToWorld = osg::computeLocalToWorld(prunedNodePath);
+                osg::Vec3d worldStart = pi->getStart() * modelToWorld;
+                osg::Vec3d worldEnd = pi->getEnd() * modelToWorld;
+
+                osg::ref_ptr<DPLineSegmentIntersector> lsi = new DPLineSegmentIntersector(worldStart, worldEnd);
+                osgUtil::IntersectionVisitor ivTerrain(lsi.get());
+                mapNode->getTerrainEngine()->accept(ivTerrain);
+
+                if (lsi->containsIntersections())
+                {
+                  osg::Vec3d worldIntersect = lsi->getFirstIntersection().getWorldIntersectPoint();
+                  
+                  GeoPoint mapIntersect;
+                  mapIntersect.fromWorld(mapNode->getMapSRS(), worldIntersect);
+
+                  osg::Vec3d newMapStart(mapIntersect.x(), mapIntersect.y(), 25000.0);
+                  osg::Vec3d newMapEnd(mapIntersect.x(), mapIntersect.y(), -25000.0);
+
+                  osg::Vec3d newWorldStart;
+                  mapNode->getMapSRS()->transformToWorld(newMapStart, newWorldStart);
+
+                  osg::Vec3d newWorldEnd;
+                  mapNode->getMapSRS()->transformToWorld(newMapEnd, newWorldEnd);
+
+                  osg::Matrix worldToModel;
+                  worldToModel.invert(modelToWorld);
+
+                  osg::Vec3d newModelStart = newWorldStart * worldToModel;
+                  osg::Vec3d newModelEnd = newWorldEnd * worldToModel;
+
+                  osg::ref_ptr<osgEarth::PrimitiveIntersector> pi2 = new osgEarth::PrimitiveIntersector(osgUtil::Intersector::MODEL, newModelStart, newModelEnd, pi->getThickness(), true);
+                  osgUtil::IntersectionVisitor iv2(pi2);
+                  iv2.setTraversalMask(iv->getTraversalMask());
+                  path[0]->accept(iv2);
+
+                  if (pi2->containsIntersections())
+                  {
+                    // Insert newlly found intersections into the original intersector.
+                    for (PrimitiveIntersector::Intersections::iterator it = pi2->getIntersections().begin(); it != pi2->getIntersections().end(); ++it)
+                      pi->insertIntersection(*it);
+                  }
+                }
+                else
+                {
+                  //OE_WARN << LC << "No hits on terrain!" << std::endl;
+                }
+            }
+            else
+            {
+              osg::Group::traverse( nv );
+            }
+        }
+
         // handle other visitor types (like intersections, etc) by simply
         // traversing the child graph.
         else // if ( nv.getNodeVisitor() == osg::NodeVisitor::NODE_VISITOR )
diff --git a/src/osgEarth/Pickers b/src/osgEarth/Pickers
index 6adbda6..3657ad2 100644
--- a/src/osgEarth/Pickers
+++ b/src/osgEarth/Pickers
@@ -20,8 +20,8 @@
 #define OSGEARTH_PICKING_UTILS_H
 
 #include <osgEarth/Common>
+#include <osgEarth/PrimitiveIntersector>
 #include <osgViewer/View>
-#include <osgUtil/PolytopeIntersector>
 
 namespace osgEarth
 {
@@ -32,8 +32,8 @@ namespace osgEarth
     class OSGEARTH_EXPORT Picker
     {
     public:
-        typedef osgUtil::PolytopeIntersector::Intersection  Hit;
-        typedef osgUtil::PolytopeIntersector::Intersections Hits;
+        typedef osgEarth::PrimitiveIntersector::Intersection Hit;
+        typedef osgEarth::PrimitiveIntersector::Intersections Hits;
 
         enum Limit {
             NO_LIMIT,
@@ -57,12 +57,28 @@ namespace osgEarth
             osg::Node*       graph         =0L, 
             unsigned         traversalMask =~0,
             float            buffer        =5.0f,
-            Limit            limit         =LIMIT_ONE);
+            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().
diff --git a/src/osgEarth/Pickers.cpp b/src/osgEarth/Pickers.cpp
index 8385733..a8fdee5 100644
--- a/src/osgEarth/Pickers.cpp
+++ b/src/osgEarth/Pickers.cpp
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarth/Pickers>
-#include <osgUtil/PolytopeIntersector>
-#include <osg/Polytope>
+#include <osgEarth/PrimitiveIntersector>
 
 #define LC "[Picker] "
 
@@ -35,6 +34,24 @@ _limit   ( limit )
         _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
 {
@@ -43,7 +60,7 @@ Picker::pick( float x, float y, Hits& results ) const
     if ( !camera )
         camera = _view->getCamera();
 
-    osg::ref_ptr<osgUtil::PolytopeIntersector> picker;
+    osg::ref_ptr<osgEarth::PrimitiveIntersector> picker;
 
     double buffer_x = _buffer, buffer_y = _buffer;
     if ( camera->getViewport() )
@@ -52,52 +69,52 @@ Picker::pick( float x, float y, Hits& results ) const
         buffer_x *= aspectRatio;
         buffer_y /= aspectRatio;
     }
-    
-    double zNear = 0.00001;
-    double zFar  = 1.0;
-
-    double xMin = local_x - buffer_x;
-    double xMax = local_x + buffer_x;
-    double yMin = local_y - buffer_y;
-    double yMax = local_y + buffer_y;
-
-    osg::Polytope winPT;
-    winPT.add(osg::Plane( 1.0, 0.0, 0.0, -xMin));
-    winPT.add(osg::Plane(-1.0, 0.0 ,0.0,  xMax));
-    winPT.add(osg::Plane( 0.0, 1.0, 0.0, -yMin));
-    winPT.add(osg::Plane( 0.0,-1.0, 0.0,  yMax));
-    winPT.add(osg::Plane( 0.0, 0.0, 1.0, zNear));
 
     osg::Matrix windowMatrix;
 
     if ( _root.valid() )
     {
-        osg::Matrix matrix;
+        osg::Matrix modelMatrix;
 
         if (camera->getViewport())
         {
             windowMatrix = camera->getViewport()->computeWindowMatrix();
-            matrix.preMult( windowMatrix );
-            zNear = 0.0;
-            zFar = 1.0;
+            modelMatrix.preMult( windowMatrix );
         }
 
-        matrix.preMult( camera->getProjectionMatrix() );
-        matrix.preMult( camera->getViewMatrix() );
+        modelMatrix.preMult( camera->getProjectionMatrix() );
+        modelMatrix.preMult( camera->getViewMatrix() );
 
         osg::NodePath prunedNodePath( _path.begin(), _path.end()-1 );
-        matrix.preMult( osg::computeWorldToLocal(prunedNodePath) );
+        modelMatrix.preMult( osg::computeWorldToLocal(prunedNodePath) );
 
-        osg::Polytope transformedPT;
-        transformedPT.setAndTransformProvidingInverse( winPT, matrix );
-        
-        picker = new osgUtil::PolytopeIntersector(osgUtil::Intersector::MODEL, transformedPT);
-    }
+        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
     {
-        osgUtil::Intersector::CoordinateFrame cf = camera->getViewport() ? osgUtil::Intersector::WINDOW : osgUtil::Intersector::PROJECTION;
-        picker = new osgUtil::PolytopeIntersector(cf, winPT);
+        picker = new osgEarth::PrimitiveIntersector(camera->getViewport() ? osgUtil::Intersector::WINDOW : osgUtil::Intersector::PROJECTION, local_x, local_y, _buffer);
     }
 
     //picker->setIntersectionLimit( (osgUtil::Intersector::IntersectionLimit)_limit );
diff --git a/src/osgEarth/PrimitiveIntersector b/src/osgEarth/PrimitiveIntersector
new file mode 100644
index 0000000..71b9e2c
--- /dev/null
+++ b/src/osgEarth/PrimitiveIntersector
@@ -0,0 +1,127 @@
+/* -*-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_PRIMITIVE_INTERSECTOR_H
+#define OSGEARTH_PRIMITIVE_INTERSECTOR_H 1
+
+#include <osgUtil/IntersectionVisitor>
+#include <osgEarth/Common>
+
+namespace osgEarth
+{
+
+/** Concrete class for implementing line intersections with the scene graph.
+  * To be used in conjunction with IntersectionVisitor. */
+class OSGEARTH_EXPORT PrimitiveIntersector : public osgUtil::Intersector
+{
+public:
+
+    /** Convenience constructor for supporting picking in WINDOW, or PROJECTION coordinates
+      * In WINDOW coordinates creates a start value of (x,y,0) and end value of (x,y,1).
+      * In PROJECTION coordinates (clip space cube) creates a start value of (x,y,-1) and end value of (x,y,1).*/
+    PrimitiveIntersector(CoordinateFrame cf, double x, double y, double thickness);
+
+    /** Constructor for initializing with full start and end vectors. */
+    PrimitiveIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end, double thickness, bool overlayIgnore=false);
+
+    struct Intersection
+    {
+        Intersection():
+            ratio(-1.0),
+            primitiveIndex(0) {}
+
+        bool operator < (const Intersection& rhs) const { return ratio < rhs.ratio; }
+
+        typedef std::vector<unsigned int>   IndexList;
+        typedef std::vector<double>         RatioList;
+
+        double                          ratio;
+        osg::NodePath                   nodePath;
+        osg::ref_ptr<osg::Drawable>     drawable;
+        osg::ref_ptr<osg::RefMatrix>    matrix;
+        osg::Vec3d                      localIntersectionPoint;
+        osg::Vec3d                       localIntersectionNormal;
+        IndexList                       indexList;
+        RatioList                       ratioList;
+        unsigned int                    primitiveIndex;
+
+        const osg::Vec3d& getLocalIntersectPoint() const { return localIntersectionPoint; }
+        osg::Vec3d getWorldIntersectPoint() const { return matrix.valid() ? localIntersectionPoint * (*matrix) : localIntersectionPoint; }
+
+        const osg::Vec3d& getLocalIntersectNormal() const { return localIntersectionNormal; }
+        osg::Vec3d getWorldIntersectNormal() const { return matrix.valid() ? osg::Matrix::transform3x3(osg::Matrix::inverse(*matrix),localIntersectionNormal) : localIntersectionNormal; }
+    };
+
+    typedef std::multiset<Intersection> Intersections;
+
+    inline void insertIntersection(const Intersection& intersection) { getIntersections().insert(intersection); }
+
+    inline Intersections& getIntersections() { return _parent ? _parent->_intersections : _intersections; }
+
+    inline Intersection getFirstIntersection() { Intersections& intersections = getIntersections(); return intersections.empty() ? Intersection() : *(intersections.begin()); }
+
+    inline void setStart(const osg::Vec3d& start) { _start = start; }
+    inline const osg::Vec3d& getStart() const { return _start; }
+
+    inline void setEnd(const osg::Vec3d& end) { _end = end; }
+    inline const osg::Vec3d& getEnd() const { return _end; }
+
+    void setThickness(double thickness);
+    inline double getThickness() const { return _thicknessVal; }
+
+    inline bool getOverlayIgnore() const { return _overlayIgnore; }
+
+public:
+
+    virtual Intersector* clone(osgUtil::IntersectionVisitor& iv);
+
+    virtual bool enter(const osg::Node& node);
+
+    virtual void leave();
+
+    virtual void intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable);
+
+    virtual void reset();
+
+    virtual bool containsIntersections() { return !getIntersections().empty(); }
+
+protected:
+
+    // Internal constructor used for clone request
+    PrimitiveIntersector();
+
+    bool intersects(const osg::BoundingSphere& bs);
+    bool intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const osg::BoundingBox& bb);
+
+    PrimitiveIntersector* _parent;
+
+    osg::Vec3d  _start;
+    osg::Vec3d  _end;
+    osg::Vec3d  _thickness;
+    double _thicknessVal;
+    bool _overlayIgnore;
+
+    Intersections _intersections;
+
+};
+
+} // namespace osgEarth
+
+#endif //OSGEARTH_PRIMITIVE_INTERSECTOR_H
+
diff --git a/src/osgEarth/PrimitiveIntersector.cpp b/src/osgEarth/PrimitiveIntersector.cpp
new file mode 100644
index 0000000..3232c0f
--- /dev/null
+++ b/src/osgEarth/PrimitiveIntersector.cpp
@@ -0,0 +1,681 @@
+/* -*-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/PrimitiveIntersector>
+#include <osgEarth/StringUtils>
+
+#include <osg/Geode>
+#include <osg/KdTree>
+#include <osg/Notify>
+#include <osg/TemplatePrimitiveFunctor>
+
+#define LC "[PrmitiveIntersector] "
+
+using namespace osgEarth;
+
+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):
+        _index(index),
+        _normal(normal),
+        _r1(r1),
+        _v1(v1),
+        _r2(r2),
+        _v2(v2),
+        _r3(r3),
+        _v3(v3) {}
+
+    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;
+
+protected:
+
+    PrimitiveIntersection& operator = (const PrimitiveIntersection&) { return *this; }
+};
+
+typedef std::multimap<float,PrimitiveIntersection> PrimitiveIntersections;
+
+struct PrimitiveIntersectorFunctor
+{
+    osg::Vec3d   _s;
+    osg::Vec3d   _d;
+    osg::Vec3d    _thickness;
+
+    float       _length;
+
+    int         _index;
+    float       _ratio;
+    bool        _hit;
+    bool        _limitOneIntersection;
+
+    PrimitiveIntersections _intersections;
+
+    PrimitiveIntersectorFunctor()
+    {
+        _length = 0.0f;
+        _index = 0;
+        _ratio = 0.0f;
+        _hit = false;
+        _limitOneIntersection = false;
+    }
+
+    void set(const osg::Vec3d& start, osg::Vec3d& end, const osg::Vec3d& thickness, float ratio=FLT_MAX)
+    {
+        _hit=false;
+        _thickness = thickness;
+        _index = 0;
+        _ratio = ratio;
+
+        _s = start;
+        _d = end - start;
+        _length = _d.length();
+        _d /= _length;
+    }
+
+    //POINT
+    inline void operator () (const osg::Vec3d& p, bool treatVertexDataAsTemporary)
+    {
+        if (_limitOneIntersection && _hit) return;
+
+        osg::Vec3d n = _d ^ _thickness;
+
+        osg::Vec3d v1 = p + _thickness;
+        osg::Vec3d v2 = p - n;
+        osg::Vec3d v3 = p - _thickness;
+        osg::Vec3d v4 = p + n;
+
+        //this->operator()(v1, v2, v3, v4, treatVertexDataAsTemporary);
+        this->triNoBuffer(v1, v2, v3, treatVertexDataAsTemporary);
+        --_index;
+        this->triNoBuffer(v1, v3, v4, treatVertexDataAsTemporary);
+    }
+
+    //LINE
+    inline void operator () (const osg::Vec3d& v1, const osg::Vec3d& v2, bool treatVertexDataAsTemporary)
+     {
+        if (_limitOneIntersection && _hit) return;
+
+        float thickness =  _thickness.length();
+        osg::Vec3d l12 = v2 - v1;
+        osg::Vec3d ln = _d ^ l12;
+        ln.normalize();
+
+        osg::Vec3d vq1 = v1 + ln*thickness;
+        osg::Vec3d vq2 = v2 + ln*thickness;
+        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;
+
+        --_index;
+        this->triNoBuffer(vq1, vq3, vq4, treatVertexDataAsTemporary);
+    }
+
+    //QUAD
+    inline void operator () (
+        const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, const osg::Vec3d& v4, bool treatVertexDataAsTemporary
+        )
+    {
+        if (_limitOneIntersection && _hit) return;
+
+        this->operator()(v1, v2, v3, treatVertexDataAsTemporary);
+        if (_limitOneIntersection && _hit) return;
+
+        --_index;
+        this->operator()(v1, v3, v4, treatVertexDataAsTemporary);
+    }
+
+    //TRIANGLE (buffered)
+    inline void operator () (
+        const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, bool treatVertexDataAsTemporary
+        )
+    {
+        if (_limitOneIntersection && _hit) return;
+
+        // first do a simple test against the unbuffered triangle:
+        this->triNoBuffer(v1, v2, v3, treatVertexDataAsTemporary);
+        if (_limitOneIntersection && _hit) return;
+
+        // 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);
+        if (_limitOneIntersection && _hit) return;
+
+        --_index;
+        this->triNoBuffer(v1+buf, v3-buf, v1-buf, 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 );
+        if (_limitOneIntersection && _hit) return;
+
+        --_index;
+        this->triNoBuffer(v2+buf, v3-buf, v2-buf, 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);
+        if (_limitOneIntersection && _hit) return;
+
+        --_index;
+        this->triNoBuffer(v3+buf, v1-buf, v3-buf, treatVertexDataAsTemporary);
+    }
+
+    //TRIANGLE (no buffer applied)
+    inline void triNoBuffer(const osg::Vec3d& v1, const osg::Vec3d& v2, const osg::Vec3d& v3, bool treatVertexDataAsTemporary)
+    {
+        ++_index;
+
+        if (_limitOneIntersection && _hit) return;
+
+        if (v1==v2 || v2==v3 || v1==v3) return;
+
+        osg::Vec3d v12 = v2-v1;
+        osg::Vec3d n12 = v12^_d;
+        float ds12 = (_s-v1)*n12;
+        float d312 = (v3-v1)*n12;
+        if (d312>=0.0f)
+        {
+            if (ds12<0.0f) return;
+            if (ds12>d312) return;
+        }
+        else                     // d312 < 0
+        {
+            if (ds12>0.0f) return;
+            if (ds12<d312) return;
+        }
+
+        osg::Vec3d v23 = v3-v2;
+        osg::Vec3d n23 = v23^_d;
+        float ds23 = (_s-v2)*n23;
+        float d123 = (v1-v2)*n23;
+        if (d123>=0.0f)
+        {
+            if (ds23<0.0f) return;
+            if (ds23>d123) return;
+        }
+        else                     // d123 < 0
+        {
+            if (ds23>0.0f) return;
+            if (ds23<d123) return;
+        }
+
+        osg::Vec3d v31 = v1-v3;
+        osg::Vec3d n31 = v31^_d;
+        float ds31 = (_s-v3)*n31;
+        float d231 = (v2-v3)*n31;
+        if (d231>=0.0f)
+        {
+            if (ds31<0.0f) return;
+            if (ds31>d231) return;
+        }
+        else                     // d231 < 0
+        {
+            if (ds31>0.0f) return;
+            if (ds31<d231) return;
+        }
+
+
+        float r3;
+        if (ds12==0.0f) r3=0.0f;
+        else if (d312!=0.0f) r3 = ds12/d312;
+        else return; // the triangle and the line must be parallel intersection.
+
+        float r1;
+        if (ds23==0.0f) r1=0.0f;
+        else if (d123!=0.0f) r1 = ds23/d123;
+        else return; // the triangle and the line must be parallel intersection.
+
+        float r2;
+        if (ds31==0.0f) r2=0.0f;
+        else if (d231!=0.0f) r2 = ds31/d231;
+        else return; // the triangle and the line must be parallel intersection.
+
+        float total_r = (r1+r2+r3);
+        if (total_r!=1.0f)
+        {
+            if (total_r==0.0f) return; // the triangle and the line must be parallel intersection.
+            float inv_total_r = 1.0f/total_r;
+            r1 *= inv_total_r;
+            r2 *= inv_total_r;
+            r3 *= inv_total_r;
+        }
+
+        osg::Vec3d in = v1*r1+v2*r2+v3*r3;
+        if (!in.valid())
+        {
+            //OE_WARN << LC << "Picked up error in TriangleIntersect" << std::endl;;
+            return;
+        }
+
+        float d = (in-_s)*_d;
+
+        if (d<0.0f) return;
+        if (d>_length) return;
+
+        osg::Vec3d normal = v12^v23;
+        normal.normalize();
+
+        float r = d/_length;
+
+
+        if (treatVertexDataAsTemporary)
+        {
+            _intersections.insert(std::pair<const float,PrimitiveIntersection>(r,PrimitiveIntersection(_index-1,normal,r1,0,r2,0,r3,0)));
+        }
+        else
+        {
+            _intersections.insert(std::pair<const float,PrimitiveIntersection>(r,PrimitiveIntersection(_index-1,normal,r1,&v1,r2,&v2,r3,&v3)));
+        }
+        _hit = true;
+    }
+
+
+};
+
+} //namespace
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+//  PrimitiveIntersector
+//
+PrimitiveIntersector::PrimitiveIntersector()
+{
+}
+
+PrimitiveIntersector::PrimitiveIntersector(CoordinateFrame cf, double x, double y, double thickness):
+    Intersector(cf),
+    _parent(0),
+    _overlayIgnore(false)
+{
+    switch(cf)
+    {
+        case WINDOW: _start.set(x,y,0.0); _end.set(x,y,1.0); break;
+        case PROJECTION : _start.set(x,y,-1.0); _end.set(x,y,1.0); break;
+        case VIEW : _start.set(x,y,0.0); _end.set(x,y,1.0); break;
+        case MODEL : _start.set(x,y,0.0); _end.set(x,y,1.0); break;
+    }
+
+    setThickness(thickness);
+}
+
+PrimitiveIntersector::PrimitiveIntersector(CoordinateFrame cf, const osg::Vec3d& start, const osg::Vec3d& end, double thickness, bool overlayIgnore):
+    Intersector(cf),
+    _parent(0),
+    _overlayIgnore(overlayIgnore)
+{
+  _start.set(start);
+  _end.set(end);
+
+  setThickness(thickness);
+}
+
+void PrimitiveIntersector::setThickness(double thickness)
+{
+  _thicknessVal = thickness;
+  double halfThickness = 0.5 * thickness;
+  _thickness.set(_start.x()+halfThickness, _start.y()+halfThickness, _start.z()+halfThickness);
+}
+
+osgUtil::Intersector* PrimitiveIntersector::clone(osgUtil::IntersectionVisitor& iv)
+{
+    if (_coordinateFrame==MODEL && iv.getModelMatrix()==0)
+    {
+        osg::ref_ptr<PrimitiveIntersector> lsi = new PrimitiveIntersector;
+        
+        lsi->_start = _start;
+        lsi->_end = _end;
+        lsi->_thickness = _thickness;
+        lsi->_thicknessVal = _thicknessVal;
+        lsi->_parent = this;
+        lsi->_intersectionLimit = _intersectionLimit;
+
+        return lsi.release();
+    }
+
+    // compute the matrix that takes this Intersector from its CoordinateFrame into the local MODEL coordinate frame
+    // that geometry in the scene graph will always be in.
+    osg::Matrix matrix;
+    osg::Matrix inverse;
+
+    switch (_coordinateFrame)
+    {
+        case(WINDOW):
+            if (iv.getWindowMatrix()) matrix.preMult( *iv.getWindowMatrix() );
+            if (iv.getProjectionMatrix()) matrix.preMult( *iv.getProjectionMatrix() );
+            if (iv.getViewMatrix()) matrix.preMult( *iv.getViewMatrix() );
+            if (iv.getModelMatrix()) matrix.preMult( *iv.getModelMatrix() );
+            break;
+        case(PROJECTION):
+            if (iv.getProjectionMatrix()) matrix.preMult( *iv.getProjectionMatrix() );
+            if (iv.getViewMatrix()) matrix.preMult( *iv.getViewMatrix() );
+            if (iv.getModelMatrix()) matrix.preMult( *iv.getModelMatrix() );
+            break;
+        case(VIEW):
+            if (iv.getViewMatrix()) matrix.preMult( *iv.getViewMatrix() );
+            if (iv.getModelMatrix()) matrix.preMult( *iv.getModelMatrix() );
+            break;
+        case(MODEL):
+            if (iv.getModelMatrix()) matrix = *iv.getModelMatrix();
+            break;
+    }
+
+    inverse.invert(matrix);
+
+    osg::ref_ptr<PrimitiveIntersector> lsi = new PrimitiveIntersector;
+    lsi->_start = _start*inverse;
+    lsi->_end = _end*inverse;
+    lsi->_thickness = _thickness*inverse;
+    lsi->_thicknessVal = _thicknessVal;
+    lsi->_parent = this;
+    lsi->_intersectionLimit = _intersectionLimit;
+    
+    return lsi.release();
+}
+
+bool PrimitiveIntersector::enter(const osg::Node& node)
+{
+    if (reachedLimit()) return false;
+
+    osg::BoundingSphere bs = node.getBound();
+    if (bs.valid())
+    {
+        bs.radius() += (_thickness - _start).length();
+    }
+
+    return !node.isCullingActive() || intersects(bs);
+}
+
+void PrimitiveIntersector::leave()
+{
+    // do nothing
+}
+
+void PrimitiveIntersector::intersect(osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable)
+{
+    if (reachedLimit()) return;
+
+    osg::BoundingBox bb = drawable->getBound();
+    if (bb.valid())
+        bb.expandBy(osg::BoundingSphere(bb.center(), (_thickness - _start).length()));
+
+    osg::Vec3d s(_start), e(_end);
+    if ( !intersectAndClip( s, e, bb ) ) return;
+
+    if (iv.getDoDummyTraversal()) return;
+
+
+    osg::TemplatePrimitiveFunctor<PrimitiveIntersectorFunctor> ti;
+
+    ti.set(s,e,_thickness-_start);
+    ti._limitOneIntersection = (_intersectionLimit == LIMIT_ONE_PER_DRAWABLE || _intersectionLimit == LIMIT_ONE);
+    drawable->accept(ti);
+
+    if (ti._hit)
+    {
+        osg::Geometry* geometry = drawable->asGeometry();
+
+        for(PrimitiveIntersections::iterator thitr = ti._intersections.begin(); thitr != ti._intersections.end(); ++thitr)
+        {
+
+            // get ratio in s,e range
+            double ratio = thitr->first;
+
+            // remap ratio into _start, _end range
+            double remap_ratio = ((s-_start).length() + ratio * (e-s).length() )/(_end-_start).length();
+
+            if ( _intersectionLimit == LIMIT_NEAREST && !getIntersections().empty() )
+            {
+                if (remap_ratio >= getIntersections().begin()->ratio )
+                    break;
+                else
+                    getIntersections().clear();
+            }
+
+            PrimitiveIntersection& triHit = thitr->second;
+
+            Intersection hit;
+            hit.ratio = remap_ratio;
+            hit.matrix = iv.getModelMatrix();
+            hit.nodePath = iv.getNodePath();
+            hit.drawable = drawable;
+            hit.primitiveIndex = triHit._index;
+
+            hit.localIntersectionPoint = _start*(1.0-remap_ratio) + _end*remap_ratio;
+
+            hit.localIntersectionNormal = triHit._normal;
+
+            if (geometry)
+            {
+                osg::Vec3dArray* vertices = dynamic_cast<osg::Vec3dArray*>(geometry->getVertexArray());
+                if (vertices)
+                {
+                    osg::Vec3d* first = &(vertices->front());
+                    if (triHit._v1)
+                    {
+                        hit.indexList.push_back(triHit._v1-first);
+                        hit.ratioList.push_back(triHit._r1);
+                    }
+                    if (triHit._v2)
+                    {
+                        hit.indexList.push_back(triHit._v2-first);
+                        hit.ratioList.push_back(triHit._r2);
+                    }
+                    if (triHit._v3)
+                    {
+                        hit.indexList.push_back(triHit._v3-first);
+                        hit.ratioList.push_back(triHit._r3);
+                    }
+                }
+            }
+
+            insertIntersection(hit);
+        }
+    }
+}
+
+void PrimitiveIntersector::reset()
+{
+    Intersector::reset();
+
+    _intersections.clear();
+}
+
+bool PrimitiveIntersector::intersects(const osg::BoundingSphere& bs)
+{
+    // if bs not valid then return true based on the assumption that an invalid sphere is yet to be defined.
+    if (!bs.valid()) return true;
+
+    osg::Vec3d sm = _start - bs._center;
+    double c = sm.length2()-bs._radius*bs._radius;
+    if (c<0.0) return true;
+
+    osg::Vec3d se = _end-_start;
+    double a = se.length2();
+    double b = (sm*se)*2.0;
+    double d = b*b-4.0*a*c;
+
+    if (d<0.0) return false;
+
+    d = sqrt(d);
+
+    double div = 1.0/(2.0*a);
+
+    double r1 = (-b-d)*div;
+    double r2 = (-b+d)*div;
+
+    if (r1<=0.0 && r2<=0.0) return false;
+
+    if (r1>=1.0 && r2>=1.0) return false;
+
+    if (_intersectionLimit == LIMIT_NEAREST && !getIntersections().empty())
+    {
+        double ratio = (sm.length() - bs._radius) / sqrt(a);
+        if (ratio >= getIntersections().begin()->ratio) return false;
+    }
+
+    // passed all the rejection tests so line must intersect bounding sphere, return true.
+    return true;
+}
+
+bool PrimitiveIntersector::intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const osg::BoundingBox& bbInput)
+{
+    osg::Vec3d bb_min(bbInput._min);
+    osg::Vec3d bb_max(bbInput._max);
+
+    double epsilon = 1e-4;
+    bb_min.x() -= epsilon;
+    bb_min.y() -= epsilon;
+    bb_min.z() -= epsilon;
+    bb_max.x() += epsilon;
+    bb_max.y() += epsilon;
+    bb_max.z() += epsilon;
+
+    // compate s and e against the xMin to xMax range of bb.
+    if (s.x()<=e.x())
+    {
+
+        // trivial reject of segment wholely outside.
+        if (e.x()<bb_min.x()) return false;
+        if (s.x()>bb_max.x()) return false;
+
+        if (s.x()<bb_min.x())
+        {
+            // clip s to xMin.
+            s = s+(e-s)*(bb_min.x()-s.x())/(e.x()-s.x());
+        }
+
+        if (e.x()>bb_max.x())
+        {
+            // clip e to xMax.
+            e = s+(e-s)*(bb_max.x()-s.x())/(e.x()-s.x());
+        }
+    }
+    else
+    {
+        if (s.x()<bb_min.x()) return false;
+        if (e.x()>bb_max.x()) return false;
+
+        if (e.x()<bb_min.x())
+        {
+            // clip s to xMin.
+            e = s+(e-s)*(bb_min.x()-s.x())/(e.x()-s.x());
+        }
+
+        if (s.x()>bb_max.x())
+        {
+            // clip e to xMax.
+            s = s+(e-s)*(bb_max.x()-s.x())/(e.x()-s.x());
+        }
+    }
+
+    // compate s and e against the yMin to yMax range of bb.
+    if (s.y()<=e.y())
+    {
+
+        // trivial reject of segment wholely outside.
+        if (e.y()<bb_min.y()) return false;
+        if (s.y()>bb_max.y()) return false;
+
+        if (s.y()<bb_min.y())
+        {
+            // clip s to yMin.
+            s = s+(e-s)*(bb_min.y()-s.y())/(e.y()-s.y());
+        }
+
+        if (e.y()>bb_max.y())
+        {
+            // clip e to yMax.
+            e = s+(e-s)*(bb_max.y()-s.y())/(e.y()-s.y());
+        }
+    }
+    else
+    {
+        if (s.y()<bb_min.y()) return false;
+        if (e.y()>bb_max.y()) return false;
+
+        if (e.y()<bb_min.y())
+        {
+            // clip s to yMin.
+            e = s+(e-s)*(bb_min.y()-s.y())/(e.y()-s.y());
+        }
+
+        if (s.y()>bb_max.y())
+        {
+            // clip e to yMax.
+            s = s+(e-s)*(bb_max.y()-s.y())/(e.y()-s.y());
+        }
+    }
+
+    // compate s and e against the zMin to zMax range of bb.
+    if (s.z()<=e.z())
+    {
+
+        // trivial reject of segment wholely outside.
+        if (e.z()<bb_min.z()) return false;
+        if (s.z()>bb_max.z()) return false;
+
+        if (s.z()<bb_min.z())
+        {
+            // clip s to zMin.
+            s = s+(e-s)*(bb_min.z()-s.z())/(e.z()-s.z());
+        }
+
+        if (e.z()>bb_max.z())
+        {
+            // clip e to zMax.
+            e = s+(e-s)*(bb_max.z()-s.z())/(e.z()-s.z());
+        }
+    }
+    else
+    {
+        if (s.z()<bb_min.z()) return false;
+        if (e.z()>bb_max.z()) return false;
+
+        if (e.z()<bb_min.z())
+        {
+            // clip s to zMin.
+            e = s+(e-s)*(bb_min.z()-s.z())/(e.z()-s.z());
+        }
+
+        if (s.z()>bb_max.z())
+        {
+            // clip e to zMax.
+            s = s+(e-s)*(bb_max.z()-s.z())/(e.z()-s.z());
+        }
+    }
+
+    return true;
+}
diff --git a/src/osgEarth/Profile b/src/osgEarth/Profile
index a4bc7d6..97b3b97 100644
--- a/src/osgEarth/Profile
+++ b/src/osgEarth/Profile
@@ -241,7 +241,7 @@ namespace osgEarth
         /**
          * Creates a tile key for a tile that contains the input location at the specified LOD.
          * Express the coordinates in the profile's SRS.
-         * Returns NULL if the point lies outside the profile's extents.
+         * Returns TileKey::INVALID if the point lies outside the profile's extents.
          */
         TileKey createTileKey( double x, double y, unsigned int level ) const;
 
diff --git a/src/osgEarth/Profile.cpp b/src/osgEarth/Profile.cpp
index a14c3f4..ec6902a 100644
--- a/src/osgEarth/Profile.cpp
+++ b/src/osgEarth/Profile.cpp
@@ -573,6 +573,25 @@ Profile::clampAndTransformExtent( const GeoExtent& input, bool* out_clamped ) co
     return result;
 }
 
+namespace
+{
+    double round( double in, int places )
+    {
+        for(int i=0; i<places; ++i)
+            in *= 10.0;
+        in = ceil(in);
+        for(int i=0; i<places; ++i)
+            in *= 0.1;
+        return in;
+    }
+
+    int quantize( double in, double epsilon )
+    {
+        int floored = (int)in;
+        int floored2 = (int)(in + epsilon);
+        return floored == floored2 ? floored : floored2;
+    }
+}
 
 void
 Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& out_intersectingKeys) const
@@ -584,59 +603,77 @@ Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& ou
         return;
     }
 
-    double keyWidth = key_ext.width();
-    double keyHeight = key_ext.height();
-    double keyArea = keyWidth * keyHeight;
+    int tileMinX, tileMaxX;
+    int tileMinY, tileMaxY;
+    int destLOD;
 
-    // bail out if the key has a null extent. This might happen is the original key represents an
-    // area in one profile that is out of bounds in this profile.
-    if ( keyArea <= 0.0 )
-        return;
+    // Special path for mercator (does NOT work for cube, e.g.)
+    if ( key_ext.getSRS()->isMercator() )
+    {
+        int precision = 5;
+        double eps = 0.001;
 
-    int destLOD = 1;
-    double destTileWidth, destTileHeight;
+        double keyWidth = round(key_ext.width(), precision);
+        destLOD = 0;
+        double w, h;
+        getTileDimensions(0, w, h);
+        for(; (round(w,precision) - keyWidth) > eps; w*=0.5, h*=0.5, destLOD++ );
 
-    int currLOD = 0;
-    destLOD = currLOD;
-    getTileDimensions(destLOD, destTileWidth, destTileHeight);
+        double destTileWidth, destTileHeight;
+        getTileDimensions( destLOD, destTileWidth, destTileHeight );
+        destTileWidth = round(destTileWidth, precision);
+        destTileHeight = round(destTileHeight, precision);
 
-    //Find the LOD that most closely matches the area of the incoming key without going under.
-#if 0
-    while (true)
-    {
-        currLOD++;
-        double w, h;
-        getTileDimensions(currLOD, w,h);
-        //OE_INFO << std::fixed << "  " << currLOD << "(" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
-        double a = w * h;
-        if (a < keyArea) break;
-        destLOD = currLOD;
-        destTileWidth = w;
-        destTileHeight = h;
+        tileMinX = quantize( ((key_ext.xMin() - _extent.xMin()) / destTileWidth), eps );
+        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
+
+        tileMinY = quantize( ((_extent.yMax() - key_ext.yMax()) / destTileHeight), eps );
+        tileMaxY = (int) ((_extent.yMax() - key_ext.yMin()) / destTileHeight);
     }
-#else
-    while( true )
+
+    else
     {
-        currLOD++;
-        double w, h;
-        getTileDimensions(currLOD, w,h);
-        if ( w < keyWidth || h < keyHeight ) break;
-        //double a = w * h;
-        //if (a < keyArea) break;
+        double keyWidth = key_ext.width();
+        double keyHeight = key_ext.height();
+
+        // bail out if the key has a null extent. This might happen is the original key represents an
+        // area in one profile that is out of bounds in this profile.
+        if ( keyWidth <= 0.0 && keyHeight <= 0.0 )
+            return;
+
+        double keySpan = std::min( keyWidth, keyHeight );
+        double keyArea = keyWidth * keyHeight;
+        double keyAvg  = 0.5*(keyWidth+keyHeight);
+
+        destLOD = 1;
+        double destTileWidth, destTileHeight;
+
+        int currLOD = 0;
         destLOD = currLOD;
-        destTileWidth = w;
-        destTileHeight = h;
-    }
-#endif
+        getTileDimensions(destLOD, destTileWidth, destTileHeight);
+
+        while( true )
+        {
+            currLOD++;
+            double w, h;
+            getTileDimensions(currLOD, w, h);
+            
+            if ( w < keyAvg || h < keyAvg ) break;
+            destLOD = currLOD;
+            destTileWidth = w;
+            destTileHeight = h;
+        }
 
-    //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
-    OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
 
-    int tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
-    int tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
+        //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
+        //OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
 
-    int tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
-    int tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); 
+        tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
+        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
+
+        tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
+        tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); 
+    }
 
     unsigned int numWide, numHigh;
     getNumTiles(destLOD, numWide, numHigh);
@@ -664,7 +701,13 @@ Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& ou
         }
     }
 
-    OE_DEBUG << "    Found " << out_intersectingKeys.size() << " keys " << std::endl;
+    //if ( key_ext.getSRS()->isMercator() && tileMinX != tileMaxX )
+    //{
+    //    OE_WARN << LC << "MERC GIT got too many horizontal tiles (" << tileMaxX-tileMinX+1 << ", vert=(" <<
+    //        tileMaxY-tileMinY+1 << ")" << std::endl;
+    //}
+
+    //OE_INFO << "    Found " << out_intersectingKeys.size() << " keys " << std::endl;
 }
 
 
@@ -714,6 +757,7 @@ Profile::getIntersectingTiles(const GeoExtent& extent, std::vector<TileKey>& out
     }
 }
 
+
 unsigned int
 Profile::getEquivalentLOD( const Profile* profile, unsigned int lod ) const
 {    
diff --git a/src/osgEarth/Progress b/src/osgEarth/Progress
index dc2e98e..580193f 100644
--- a/src/osgEarth/Progress
+++ b/src/osgEarth/Progress
@@ -86,7 +86,7 @@ namespace osgEarth
         /**
          * Whether cancelation was requested
          */
-        bool isCanceled() const { return _canceled; }
+        virtual bool isCanceled() const { return _canceled; }
 
         std::string& message() { return _message; }
 
@@ -102,9 +102,9 @@ namespace osgEarth
 
     protected:
         volatile unsigned _numStages;
-        volatile bool     _canceled;
         std::string       _message;
-        volatile bool     _needsRetry;
+        mutable  bool     _needsRetry;
+        mutable  bool     _canceled;
     };
 
 
diff --git a/src/osgEarth/Registry.cpp b/src/osgEarth/Registry.cpp
index 4cc3293..0fa9fb5 100644
--- a/src/osgEarth/Registry.cpp
+++ b/src/osgEarth/Registry.cpp
@@ -87,6 +87,7 @@ _terrainEngineDriver( "mp" )
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "application/json",                     "osgb" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "text/json",                            "osgb" );
     osgDB::Registry::instance()->addMimeTypeExtensionMapping( "text/x-json",                          "osgb" );
+    osgDB::Registry::instance()->addMimeTypeExtensionMapping( "image/jpg",                            "jpg" );
     
     // pre-load OSG's ZIP plugin so that we can use it in URIs
     std::string zipLib = osgDB::Registry::instance()->createLibraryNameForExtension( "zip" );
@@ -121,17 +122,27 @@ _terrainEngineDriver( "mp" )
     // activate cache-only mode from the environment
     if ( ::getenv("OSGEARTH_CACHE_ONLY") )
     {
-        setOverrideCachePolicy( CachePolicy::CACHE_ONLY );
+        _overrideCachePolicy->usage() = CachePolicy::USAGE_CACHE_ONLY;
+        //setOverrideCachePolicy( CachePolicy::CACHE_ONLY );
         OE_INFO << LC << "CACHE-ONLY MODE set from environment variable" << std::endl;
     }
 
     // activate no-cache mode from the environment
     else if ( ::getenv("OSGEARTH_NO_CACHE") )
     {
-        setOverrideCachePolicy( CachePolicy::NO_CACHE );
+        _overrideCachePolicy->usage() = CachePolicy::USAGE_NO_CACHE;
+        //setOverrideCachePolicy( CachePolicy::NO_CACHE );
         OE_INFO << LC << "NO-CACHE MODE set from environment variable" << std::endl;
     }
 
+    // cache max age?
+    const char* cacheMaxAge = ::getenv("OSGEARTH_CACHE_MAX_AGE");
+    if ( cacheMaxAge )
+    {
+        TimeSpan maxAge = osgEarth::as<long>( std::string(cacheMaxAge), INT_MAX );
+        _overrideCachePolicy->maxAge() = maxAge;
+    }
+
     const char* teStr = ::getenv("OSGEARTH_TERRAIN_ENGINE");
     if ( teStr )
     {
@@ -214,26 +225,6 @@ const Profile*
 Registry::getGlobalMercatorProfile() const
 {
     return getSphericalMercatorProfile();
-#if 0
-    if ( !_global_mercator_profile.valid() )
-    {
-        GDAL_SCOPED_LOCK;
-
-        if ( !_global_mercator_profile.valid() ) // double-check pattern
-        {
-            // automatically figure out proper mercator extents:
-            const SpatialReference* srs = SpatialReference::create( "world-mercator" );
-
-            //double e, dummy;
-            //srs->getGeographicSRS()->transform2D( 180.0, 0.0, srs, e, dummy );
-            //const_cast<Registry*>(this)->_global_mercator_profile = Profile::create(
-            //    srs, -e, -e, e, e, 1, 1 );
-            const_cast<Registry*>(this)->_global_mercator_profile = Profile::create(
-                srs, MERC_MINX, MERC_MINY, MERC_MAXX, MERC_MAXY, 1, 1 );
-        }
-    }
-    return _global_mercator_profile.get();
-#endif
 }
 
 
diff --git a/src/osgEarth/ShaderFactory b/src/osgEarth/ShaderFactory
index 522df6f..8e14c58 100644
--- a/src/osgEarth/ShaderFactory
+++ b/src/osgEarth/ShaderFactory
@@ -25,39 +25,70 @@
 namespace osgEarth
 {
     /**
-     * A factory class that generates shader functions for the osgEarth engine.
-     * The default ShaderFactory is stored in the osgEarth registry. You can replace it
-     * if you want to replace osgEarth's default shader templates.
+     * A factory object that generates shader program "scaffolding" for VirtualProgram
+     * attributes. Ths factory generates main() functions for each stage in the shader
+     * pipline. You don't use it directly; instead you just create VirtualProgram
+     * attributes, and those VPs will automatically invoke this factory object to
+     * generate their main() functions.
+     *
+     * The default ShaderFactory is stored in the osgEarth registry. You can replace it,
+     * 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
     {
     public:
-        /** Creates a vertex shader main(). */
+        /**
+         * Creates a vertex shader main() function for use with VirtualPrograms.
+         * Do not call this function directly; VirtualProgram will call it to
+         * install its main() vertex function.
+         */
         virtual osg::Shader* createVertexShaderMain(
             const ShaderComp::FunctionLocationMap& functions) const;
-
-        /** Creates a fragment shader main(). */
+        
+        /**
+         * Creates a fragment shader main() function for use with VirtualPrograms.
+         * Do not call this function directly; VirtualProgram will call it to
+         * install its main() fragment function.
+         */
         virtual osg::Shader* createFragmentShaderMain(
             const ShaderComp::FunctionLocationMap& functions) const;
 
         /**
          * Gets the uniform/shader name of the sampler corresponding the the provider
          * texture image unit
+         *
+         * @deprecated Only supported by older texture compositors, which will likely
+         *             go away in a future version
          */
         virtual std::string getSamplerName( unsigned texImageUnit ) const;
 
         /**
          * Install lighting shaders in a VirtualProgram.
+         *
+         * Note: this will likely go away in a future version. At this point it makes
+         * more sense to decouple the default lighting shaders from the main shader
+         * factory and simply use a VirtualProgram rather than replacing the ShaderFactory.
          */
         virtual void installLightingShaders(VirtualProgram* vp) const;
 
         /**
-         * Builds a shader that executes an image filter chain.
+         * Builds a shader function that executes an image filter chain.
+         * @param functionName Name to give to the resulting shader function
+         * @param chain        Color filter chain to execute
          */
-        virtual osg::Shader* createColorFilterChainFragmentShader( const std::string& function, const ColorFilterChain& chain ) const;
+        virtual osg::Shader* createColorFilterChainFragmentShader(
+            const std::string&      functionName,
+            const ColorFilterChain& chain ) const;
 
         /**
-         * Gets a uniform corresponding to the given mode and value
+         * Gets a uniform corresponding to the given mode and value. These uniforms are 
+         * named in the form "oe_mode_MODE" (e.g., "oe_mode_GL_LIGHTING").
          */
         virtual osg::Uniform* createUniformForGLMode(
             osg::StateAttribute::GLMode      mode,
diff --git a/src/osgEarth/ShaderFactory.cpp b/src/osgEarth/ShaderFactory.cpp
index d2849ac..2de7ccd 100755
--- a/src/osgEarth/ShaderFactory.cpp
+++ b/src/osgEarth/ShaderFactory.cpp
@@ -94,6 +94,7 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
     // main:
     buf <<
         "varying vec4 osg_FrontColor; \n"
+        "varying vec3 oe_Normal; \n"
         "void main(void) \n"
         "{ \n"
         INDENT "osg_FrontColor = gl_Color; \n"
@@ -102,10 +103,18 @@ ShaderFactory::createVertexShaderMain(const FunctionLocationMap& functions) cons
     // call Model stage methods.
     if ( modelStage )
     {
+        buf << INDENT "oe_Normal = gl_Normal; \n";
+
         for( OrderedFunctionMap::const_iterator i = modelStage->begin(); i != modelStage->end(); ++i )
         {
             buf << INDENT << i->second << "(vertex); \n";
         }
+
+        buf << INDENT << "oe_Normal = normalize(gl_NormalMatrix * oe_Normal); \n";
+    }
+    else
+    {
+        buf << INDENT << "oe_Normal = normalize(gl_NormalMatrix * gl_Normal); \n";
     }
 
     // call View stage methods.
@@ -226,13 +235,14 @@ ShaderFactory::installLightingShaders(VirtualProgram* vp) const
         "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 oe_lighting_vertex(inout vec4 VertexMODEL) \n"
+        "void oe_lighting_vertex(inout vec4 VertexVIEW) \n"
         "{ \n"
         "    oe_lighting_adjustment = vec4(1.0); \n"
         "    if (oe_mode_GL_LIGHTING) \n"
         "    { \n"
-        "        vec3 N = normalize(gl_NormalMatrix * gl_Normal); \n"
+        "        vec3 N = oe_Normal; \n" //normalize(gl_NormalMatrix * gl_Normal); \n"
         "        float NdotL = dot( N, normalize(gl_LightSource[0].position.xyz) ); \n"
         "        NdotL = max( 0.0, NdotL ); \n"
 
@@ -272,7 +282,7 @@ ShaderFactory::installLightingShaders(VirtualProgram* vp) const
          "    } \n"
         "} \n";
 
-    vp->setFunction( "oe_lighting_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.0 );
+    vp->setFunction( "oe_lighting_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW, 0.0 );
     vp->setFunction( "oe_lighting_fragment", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING, 0.0 );
 }
 
@@ -290,18 +300,18 @@ ShaderFactory::createColorFilterChainFragmentShader(const std::string&      func
     for( ColorFilterChain::const_iterator i = chain.begin(); i != chain.end(); ++i )
     {
         ColorFilter* filter = i->get();
-        buf << "void " << filter->getEntryPointFunctionName() << "(in int slot, inout vec4 color);\n";
+        buf << "void " << filter->getEntryPointFunctionName() << "(inout vec4 color);\n";
     }
 
     // write out the main function:
-    buf << "void " << function << "(in int slot, inout vec4 color) \n"
+    buf << "void " << function << "(inout vec4 color) \n"
         << "{ \n";
 
     // write out the function calls. if there are none, it's a NOP.
     for( ColorFilterChain::const_iterator i = chain.begin(); i != chain.end(); ++i )
     {
         ColorFilter* filter = i->get();
-        buf << INDENT << filter->getEntryPointFunctionName() << "(slot, color);\n";
+        buf << INDENT << filter->getEntryPointFunctionName() << "(color);\n";
     }
         
     buf << "} \n";
diff --git a/src/osgEarth/ShaderGenerator b/src/osgEarth/ShaderGenerator
index 88d8b54..aa38a97 100644
--- a/src/osgEarth/ShaderGenerator
+++ b/src/osgEarth/ShaderGenerator
@@ -36,7 +36,10 @@ namespace osgEarth
      * Usage:
      *
      *   ShaderGenerator gen;
-     *   graph->accept( gen );
+     *   gen.run( graph );
+     *
+     * If osgEarth detects a lack of GLSL support, the ShaderGenerator
+     * will do nothing.
      */
     class OSGEARTH_EXPORT ShaderGenerator : public osg::NodeVisitor
     {
@@ -47,10 +50,34 @@ namespace osgEarth
         ShaderGenerator();
 
         /**
-         * Constructs a new shader generator that will use an external
-         * state set cache to optimize state changes.
+         * Sets the name to give any VP's we create
          */
-        ShaderGenerator( StateSetCache* cache );
+        void setProgramName(const std::string& name);
+
+        /**
+         * Runs the shader generator and then optimizes state sharing when it's done.
+         */
+        void run(osg::Node* graph, StateSetCache* cache =0L);
+
+    public:
+        /**
+         * User callback that lets you selectly reject shader generation for
+         * specific state attributes.
+         */
+        struct AcceptCallback : public osg::Referenced
+        {
+            /** Return true to generate shader code for the SA; false to ignore and skip it */
+            virtual bool accept(const osg::StateAttribute* sa) const =0;
+        };
+
+        /**
+         * Adds an acceptor callback that the generator will use to decide
+         * whether to ignore certain state attributes.
+         */
+        void addAcceptCallback(AcceptCallback* cb);
+
+
+    public:
 
         /** dtor. */
         virtual ~ShaderGenerator() { }
@@ -66,19 +93,26 @@ namespace osgEarth
 
         void apply( osg::ProxyNode& );
 
+        void apply( osg::ClipNode& );
+
     protected:
 
         void apply( osg::Drawable* );
 
-        bool processGeometry( osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement );
+        bool processGeometry(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
 
-        bool processText( osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement );
+        bool processText(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);
 
         osg::ref_ptr<osg::State> _state;
 
-        osg::ref_ptr<StateSetCache> _stateSetCache;
+        bool _active;
+
+        std::string _name;
+
+        typedef std::vector<osg::ref_ptr<AcceptCallback> > AcceptCallbackVector;
+        AcceptCallbackVector _acceptCallbacks;
 
-        osg::ref_ptr<VirtualProgram> _defaultVP;
+        bool accept(const osg::StateAttribute* sa) const;
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/ShaderGenerator.cpp b/src/osgEarth/ShaderGenerator.cpp
index 1a800ad..93616ff 100644
--- a/src/osgEarth/ShaderGenerator.cpp
+++ b/src/osgEarth/ShaderGenerator.cpp
@@ -18,6 +18,7 @@
  */
 
 #include <osgEarth/Capabilities>
+#include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/ShaderGenerator>
@@ -30,8 +31,10 @@
 #include <osg/Texture1D>
 #include <osg/Texture2D>
 #include <osg/Texture3D>
+#include <osg/TextureRectangle>
 #include <osg/TexEnv>
 #include <osg/TexGen>
+#include <osg/ClipNode>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/ReadFile>
@@ -109,8 +112,8 @@ struct OSGEarthShaderGenPseudoLoader : public osgDB::ReaderWriter
         osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(stripped, options);
         if ( node )
         {
-            ShaderGenerator gen( Registry::stateSetCache() );
-            node->accept( gen );
+            ShaderGenerator gen;
+            gen.run(node, Registry::stateSetCache());
         }
 
         return node.valid() ? ReadResult(node.release()) : ReadResult::ERROR_IN_READING_FILE;
@@ -188,89 +191,102 @@ namespace
 
 //------------------------------------------------------------------------
 
-ShaderGenerator::ShaderGenerator() :
-osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN )
+ShaderGenerator::ShaderGenerator()
 {
-    _state = new StateEx();
-    //_stateSetCache = new StateSetCache();
-    _stateSetCache = 0L;
-    _defaultVP = new VirtualProgram();
-    Registry::instance()->getShaderFactory()->installLightingShaders( _defaultVP.get() );
+    // find everything regardless of node masking
+    setTraversalMode( TRAVERSE_ALL_CHILDREN );
+    setNodeMaskOverride( ~0 );
+
+    // set a default program name:
+    setProgramName( "osgEarth.ShaderGenerator" );
+
+    // make sure we support shaders:
+    _active = Registry::capabilities().supportsGLSL();
+    if ( _active )
+    {
+        _state = new StateEx();
+    }
 }
 
+void
+ShaderGenerator::setProgramName(const std::string& name)
+{
+    _name = name;
+}
 
-ShaderGenerator::ShaderGenerator( StateSetCache* cache ) :
-osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN )
+void
+ShaderGenerator::addAcceptCallback(AcceptCallback* cb)
 {
-    _state = new StateEx();
-    _stateSetCache = cache; // ? cache : new StateSetCache();
-    _defaultVP = new VirtualProgram();
-    Registry::instance()->getShaderFactory()->installLightingShaders( _defaultVP.get() );
+    _acceptCallbacks.push_back( cb );
 }
 
+bool
+ShaderGenerator::accept(const osg::StateAttribute* sa) const
+{
+    if ( sa == 0L )
+        return false;
 
-void 
-ShaderGenerator::apply( osg::Node& node )
+    for(AcceptCallbackVector::const_iterator i = _acceptCallbacks.begin(); i != _acceptCallbacks.end(); ++i )
+    {
+        if ( !i->get()->accept(sa) )
+            return false;
+    }
+    return true;
+}
+
+void
+ShaderGenerator::run(osg::Node* graph, StateSetCache* cache)
 {
-    osg::ref_ptr<osg::StateSet> ss = node.getStateSet();
-    if ( ss.valid() )
+    if ( graph )
     {
-        _state->pushStateSet( ss.get() );
+        // generate shaders:
+        graph->accept( *this );
 
-        osg::ref_ptr<osg::StateSet> replacement;
-        if ( processGeometry(ss.get(), replacement) )
-        {
-            // optimize state set sharing
-            if ( _stateSetCache.valid() )
-                _stateSetCache->share(replacement, replacement);
+        // perform GL state sharing
+        if ( cache )
+            cache->optimize( graph );
 
-            _state->popStateSet();
-            node.setStateSet( replacement.get() );
-            _state->pushStateSet( replacement.get() );
+        // install a blank VP at the top as the default.
+        VirtualProgram* vp = VirtualProgram::get(graph->getStateSet());
+        if ( !vp )
+        {
+            vp = VirtualProgram::getOrCreate( graph->getOrCreateStateSet() );
+            vp->setInheritShaders( true );
+            vp->setName( _name );
         }
     }
+}
+
+void 
+ShaderGenerator::apply( osg::Node& node )
+{
+    if ( !_active ) return;
+
+    if ( node.getStateSet() )
+        _state->pushStateSet( node.getStateSet() );
 
     traverse(node);
 
-    if ( ss.get() )
-    {
+    if ( node.getStateSet() )
         _state->popStateSet();
-    }
 }
 
 
 void 
 ShaderGenerator::apply( osg::Geode& geode )
 {
-    osg::ref_ptr<osg::StateSet> ss = geode.getStateSet();
-    if ( ss.valid() )
-    {
-        _state->pushStateSet( ss.get() );
+    if ( !_active ) return;
 
-        osg::ref_ptr<osg::StateSet> replacement;
-        if ( processGeometry(ss.get(), replacement) )
-        {
-            _state->popStateSet();
-            
-            // optimize state set sharing
-            if ( _stateSetCache.valid() )
-                _stateSetCache->share(replacement, replacement);
-
-            geode.setStateSet( replacement.get() );
-
-            _state->pushStateSet( replacement.get() );
-        }
-    }
+    if ( geode.getStateSet() )
+        _state->pushStateSet( geode.getStateSet() );
 
     for( unsigned d = 0; d < geode.getNumDrawables(); ++d )
     {
         apply( geode.getDrawable(d) );
     }
 
-    if ( ss.valid() )
-    {
+    if ( geode.getStateSet() )
         _state->popStateSet();
-    }
 }
 
 
@@ -283,33 +299,46 @@ ShaderGenerator::apply( osg::Drawable* drawable )
         if ( ss.valid() )
         {
             _state->pushStateSet(ss.get());
+        }
 
-            osg::ref_ptr<osg::StateSet> replacement;
-
-            if ( dynamic_cast<osgText::Text*>(drawable) != 0L )
+        osg::ref_ptr<osg::StateSet> replacement;
+        if ( dynamic_cast<osgText::Text*>(drawable) != 0L )
+        {
+            if ( processText(ss.get(), replacement) )
             {
-                if ( processText(ss.get(), replacement) )
-                {
-                    drawable->setStateSet( replacement.get() );
-                }
+                drawable->setStateSet( replacement.get() );
             }
-            else
+        }
+        else
+        {
+            osg::Geometry* geom = drawable->asGeometry();
+            if ( geom )
             {
-                if ( processGeometry(ss.get(), replacement) )
-                {
-                    drawable->setStateSet(replacement.get());
-                }
+                geom->setUseVertexBufferObjects(true);
+                geom->setUseDisplayList(false);
             }
 
+            if ( processGeometry(ss.get(), replacement) )
+            {
+                drawable->setStateSet(replacement.get());
+            }
+        }
+
+        if ( ss.valid() )
+        {
             _state->popStateSet();
+        }
 
-            // optimize state set sharing
-            if ( _stateSetCache.valid() && replacement.valid() )
+#if 0
+        // optimize state set sharing
+        if ( _stateSetCache.valid() && replacement.valid() )
+        {
+            if ( _stateSetCache->share(replacement, replacement) )
             {
-                if ( _stateSetCache->share(replacement, replacement) )
-                    drawable->setStateSet( replacement.get() );
+                drawable->setStateSet( replacement.get() );
             }
         }
+#endif
     }
 }
 
@@ -317,6 +346,8 @@ ShaderGenerator::apply( osg::Drawable* drawable )
 void
 ShaderGenerator::apply(osg::PagedLOD& node)
 {
+    if ( !_active ) return;
+
     for( unsigned i=0; i<node.getNumFileNames(); ++i )
     {
         const std::string& filename = node.getFileName( i );
@@ -334,6 +365,8 @@ ShaderGenerator::apply(osg::PagedLOD& node)
 void
 ShaderGenerator::apply(osg::ProxyNode& node)
 {
+    if ( !_active ) return;
+
     if ( node.getLoadingExternalReferenceMode() != osg::ProxyNode::LOAD_IMMEDIATELY )
     {
         // rewrite the filenames to include the shadergen pseudo-loader extension so
@@ -353,11 +386,31 @@ ShaderGenerator::apply(osg::ProxyNode& node)
 }
 
 
+void
+ShaderGenerator::apply(osg::ClipNode& node)
+{
+    static const char* s_clip_source =
+        "#version " GLSL_VERSION_STR "\n"
+        "void sg_set_clipvertex(inout vec4 vertexVIEW)\n"
+        "{\n"
+        "    gl_ClipVertex = vertexVIEW; \n"
+        "}\n";
+
+    if ( !_active ) return;
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate(node.getOrCreateStateSet());
+    if ( vp->referenceCount() == 1 ) vp->setName( _name );
+    vp->setFunction( "sg_set_clipvertex", s_clip_source, ShaderComp::LOCATION_VERTEX_VIEW );
+
+    apply( static_cast<osg::Group&>(node) );
+}
+
+
 bool
-ShaderGenerator::processText( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement )
+ShaderGenerator::processText(const osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement)
 {
     // do nothing if there's no GLSL support
-    if ( !Registry::capabilities().supportsGLSL() )
+    if ( !_active )
         return false;
 
     // State object with extra accessors:
@@ -369,11 +422,18 @@ ShaderGenerator::processText( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& re
     if ( dynamic_cast<osg::Program*>(program) != 0L )
         return false;
 
-    // see if the current state set contains a VirtualProgram already. If so,
-    // we will add to it if necessary.
-    VirtualProgram* vp = dynamic_cast<VirtualProgram*>( ss->getAttribute(VirtualProgram::SA_TYPE) );
+    // new state set:
+    replacement = ss ? osg::clone(ss, osg::CopyOp::SHALLOW_COPY) : new osg::StateSet();
 
-    replacement = osg::clone(ss, osg::CopyOp::DEEP_COPY_ALL);
+    // new VP:
+    VirtualProgram* vp = 0L;
+    if ( VirtualProgram::get(replacement.get()) )
+        vp =  osg::clone(VirtualProgram::get(replacement.get()), osg::CopyOp::DEEP_COPY_ALL);
+    else
+        vp = VirtualProgram::getOrCreate(replacement.get());
+
+    if ( vp->referenceCount() == 1 )
+        vp->setName( _name );
 
     std::string vertSrc =
         "#version " GLSL_VERSION_STR "\n" GLSL_PRECISION "\n"
@@ -393,10 +453,6 @@ ShaderGenerator::processText( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& re
         INDENT "color.a *= texel.a; \n"
         "}\n";
 
-    if ( !vp )
-        vp = osg::clone( _defaultVP.get() );
-    replacement->setAttributeAndModes( vp, osg::StateAttribute::ON );
-
     vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
     vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING );
     replacement->getOrCreateUniform( SAMPLER_TEXT, osg::Uniform::SAMPLER_2D )->set( 0 );
@@ -406,10 +462,10 @@ ShaderGenerator::processText( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& re
 
 
 bool
-ShaderGenerator::processGeometry( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement )
+ShaderGenerator::processGeometry( const osg::StateSet* ss, osg::ref_ptr<osg::StateSet>& replacement )
 {
     // do nothing if there's no GLSL support
-    if ( !Registry::capabilities().supportsGLSL() )
+    if ( !_active )
         return false;
 
     // State object with extra accessors:
@@ -422,36 +478,30 @@ ShaderGenerator::processGeometry( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>
     if ( dynamic_cast<osg::Program*>(program) != 0L )
         return false;
 
-    // see if the current state set contains a VirtualProgram already. If so,
-    // we will add to it if necessary.
-    osg::ref_ptr<VirtualProgram> vp = 
-        dynamic_cast<VirtualProgram*>( ss->getAttribute(VirtualProgram::SA_TYPE) );
+    // prepare to generate:
+    osg::ref_ptr<osg::StateSet> new_stateset = ss ? osg::clone(ss, osg::CopyOp::SHALLOW_COPY) : new osg::StateSet();
+    VirtualProgram* vp = VirtualProgram::cloneOrCreate(ss, new_stateset);
+    bool need_new_stateset = false;
+    
+    if ( vp->referenceCount() == 1 )
+        vp->setName( _name );
 
     // Check whether the lighting state has changed and install a mode uniform.
-    if ( ss->getMode(GL_LIGHTING) != osg::StateAttribute::INHERIT )
+    // TODO: fix this
+    if ( ss && ss->getMode(GL_LIGHTING) != osg::StateAttribute::INHERIT )
     {
-        if ( !replacement.valid() ) 
-            replacement = osg::clone(ss, osg::CopyOp::DEEP_COPY_ALL);
+        need_new_stateset = true;
 
         osg::StateAttribute::GLModeValue value = state->getMode(GL_LIGHTING); // from the state, not the ss.
-        replacement->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
+        new_stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(GL_LIGHTING, value) );
     }
 
     // if the stateset changes any texture attributes, we need a new virtual program:
-    if (ss->getTextureAttributeList().size() > 0)
+    if (state->getNumTextureAttributes() > 0)
     {
-        if ( !replacement.valid() ) 
-            replacement = osg::clone(ss, osg::CopyOp::DEEP_COPY_ALL);
-
         // work off the state's accumulated texture attribute set:
         int texCount = state->getNumTextureAttributes();
 
-        if ( !vp )
-        {
-            vp = osg::clone( _defaultVP.get() );
-            replacement->setAttributeAndModes( vp, osg::StateAttribute::ON );
-        }
-
         // start generating the shader source.
         std::stringstream vertHead, vertBody, fragHead, fragBody;
 
@@ -464,31 +514,38 @@ ShaderGenerator::processGeometry( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>
 
         fragBody << "void " FRAGMENT_FUNCTION "(inout vec4 color)\n{\n";
 
+        bool wroteTexelDecl = false;
+
         for( int t = 0; t < texCount; ++t )
         {
-            if (t == 0)
+            if ( !wroteTexelDecl )
             {
                 fragBody << INDENT << MEDIUMP "vec4 texel; \n";
+                wroteTexelDecl = true;
             }
 
-            osg::StateAttribute* tex = state->getTextureAttribute( t, osg::StateAttribute::TEXTURE );
-            if ( tex )
+            osg::Texture* tex = dynamic_cast<osg::Texture*>(state->getTextureAttribute(t, osg::StateAttribute::TEXTURE));
+
+            if (accept(tex) && !ImageUtils::isFloatingPointInternalFormat(tex->getInternalFormat()))
             {
+                // made it this far, new stateset required.
+                need_new_stateset = true;
+
                 // see if we have a texenv; if so get its blending mode.
                 osg::TexEnv::Mode blendingMode = osg::TexEnv::MODULATE;
                 osg::TexEnv* env = dynamic_cast<osg::TexEnv*>(state->getTextureAttribute(t, osg::StateAttribute::TEXENV) );
-                if ( env )
+                if ( accept(env) )
                 {
                     blendingMode = env->getMode();
                     if ( blendingMode == osg::TexEnv::BLEND )
                     {
-                        replacement->getOrCreateUniform( Stringify() << TEXENV_COLOR << t, osg::Uniform::FLOAT_VEC4 )->set( env->getColor() );
+                        new_stateset->getOrCreateUniform( Stringify() << TEXENV_COLOR << t, osg::Uniform::FLOAT_VEC4 )->set( env->getColor() );
                     }
                 }
 
                 osg::TexGen::Mode texGenMode = osg::TexGen::OBJECT_LINEAR;
                 osg::TexGen* texGen = dynamic_cast<osg::TexGen*>(state->getTextureAttribute(t, osg::StateAttribute::TEXGEN));
-                if ( texGen )
+                if ( accept(texGen) )
                 {
                     texGenMode = texGen->getMode();
                 }
@@ -523,41 +580,42 @@ ShaderGenerator::processGeometry( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>
                 {
                     fragHead << "uniform sampler1D " SAMPLER << t << ";\n";
                     fragBody << INDENT "texel = texture1D(" SAMPLER << t << ", " TEX_COORD << t << ".x);\n";
-                    replacement->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_1D )->set( t );
+                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_1D )->set( t );
                 }
-#if 1
                 else if ( dynamic_cast<osg::Texture2D*>(tex) )
                 {
                     fragHead << "uniform sampler2D " SAMPLER << t << ";\n";
                     fragBody << INDENT "texel = texture2D(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
-                    replacement->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
+                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
                 }
-#else // embosser
-                else if ( dynamic_cast<osg::Texture2D*>(tex) )
-                {
-                    fragHead << "uniform sampler2D " SAMPLER << t << ";\n";
-                    fragBody 
-                        << INDENT "{\n"
-                        << INDENT "float bs = 1.0/256.0;\n"
-                        << INDENT "vec4 bm = vec4(0.0);\n"
-                        << INDENT "float u = " TEX_COORD << t << ".x;\n"
-                        << INDENT "float v = " TEX_COORD << t << ".y;\n"
-                        << INDENT "texel = texture2D(" SAMPLER << t << ", vec2(u, v)); \n"
-                        << INDENT "bm = texture2D(" SAMPLER << t << ", vec2(u-bs, v-bs)) + \n"
-                        << INDENT "     texture2D(" SAMPLER << t << ", vec2(u-bs, v-bs)) - \n"
-                        << INDENT "     texel                                            - \n"
-                        << INDENT "     texture2D(" SAMPLER << t << ", vec2(u+bs, v+bs));  \n"
-                        << INDENT "texel *= vec4( 2.0*(bm.rgb+vec3(0.5,0.5,0.5)),1.0 );\n"
-                        << INDENT "}\n";
 
-                    replacement->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
+#if 0 // works, but requires a higher version of GL?
+                else if ( dynamic_cast<osg::TextureRectangle*>(tex) )
+                {
+                    fragHead << "uniform sampler2Drect " SAMPLER << t << ";\n";
+                    fragBody << INDENT "texel = texture2Drect(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
+                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D_RECT )->set( t );
                 }
 #endif
+                // doesn't work. why?
+                else if ( dynamic_cast<osg::TextureRectangle*>(tex) )
+                {
+                    osg::Image* image = static_cast<osg::TextureRectangle*>(tex)->getImage();
+
+                    vertBody 
+                        << INDENT << TEX_COORD << t << ".x /= " << (image->s()-1) << ".0;\n"
+                        << INDENT << TEX_COORD << t << ".y /= " << (image->t()-1) << ".0;\n";
+
+                    fragHead << "uniform sampler2D " SAMPLER << t << ";\n";
+                    fragBody << INDENT "texel = texture2D(" SAMPLER << t << ", " TEX_COORD << t << ".xy);\n";
+                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_2D )->set( t );
+                }
+
                 else if ( dynamic_cast<osg::Texture3D*>(tex) )
                 {
                     fragHead << "uniform sampler3D " SAMPLER << t << ";\n";
                     fragBody << INDENT "texel = texture3D(" SAMPLER << t << ", " TEX_COORD << t << ".xyz);\n";
-                    replacement->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_3D )->set( t );
+                    new_stateset->getOrCreateUniform( Stringify() << SAMPLER << t, osg::Uniform::SAMPLER_3D )->set( t );
                 }
 
                 // See http://www.opengl.org/sdk/docs/man/xhtml/glTexEnv.xml
@@ -591,23 +649,30 @@ ShaderGenerator::processGeometry( osg::StateSet* ss, osg::ref_ptr<osg::StateSet>
             }
         }
 
-        // close out functions:
-        vertBody << "}\n";
-        fragBody << "}\n";
-
-        // Extract the shader source strings (win compat method)
-        std::string vertBodySrc, vertSrc, fragBodySrc, fragSrc;
-        vertBodySrc = vertBody.str();
-        vertHead << vertBodySrc;
-        vertSrc = vertHead.str();
-        fragBodySrc = fragBody.str();
-        fragHead << fragBodySrc;
-        fragSrc = fragHead.str();
-
-        // inject the shaders:
-        vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
-        vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING );
+        if ( need_new_stateset )
+        {
+            // close out functions:
+            vertBody << "}\n";
+            fragBody << "}\n";
+
+            // Extract the shader source strings (win compat method)
+            std::string vertBodySrc, vertSrc, fragBodySrc, fragSrc;
+            vertBodySrc = vertBody.str();
+            vertHead << vertBodySrc;
+            vertSrc = vertHead.str();
+            fragBodySrc = fragBody.str();
+            fragHead << fragBodySrc;
+            fragSrc = fragHead.str();
+
+            // inject the shaders:
+            vp->setFunction( VERTEX_FUNCTION,   vertSrc, ShaderComp::LOCATION_VERTEX_VIEW );
+            vp->setFunction( FRAGMENT_FUNCTION, fragSrc, ShaderComp::LOCATION_FRAGMENT_COLORING );
+        }
     }
 
+    if ( need_new_stateset )
+    {
+        replacement = new_stateset.get();
+    }
     return replacement.valid();
 }
diff --git a/src/osgEarth/ShaderUtils.cpp b/src/osgEarth/ShaderUtils.cpp
index 1320661..9f567ec 100644
--- a/src/osgEarth/ShaderUtils.cpp
+++ b/src/osgEarth/ShaderUtils.cpp
@@ -50,6 +50,7 @@ namespace
 
             if ( (val & osg::StateAttribute::INHERIT) == 0 )
             {
+
                 if ((val & osg::StateAttribute::PROTECTED)!=0 ||
                     (base_val & osg::StateAttribute::OVERRIDE)==0)
                 {
@@ -406,6 +407,9 @@ UpdateLightingUniformsHelper::cullTraverse( osg::Node* node, osg::NodeVisitor* n
     {
         StateSetStack stateSetStack;
 
+        if ( node->getStateSet() )
+            stateSetStack.push_front( node->getStateSet() );
+
         osgUtil::StateGraph* sg = cv->getCurrentStateGraph();
         while( sg )
         {
diff --git a/src/osgEarth/SpatialReference b/src/osgEarth/SpatialReference
index 9d74192..14bef5f 100644
--- a/src/osgEarth/SpatialReference
+++ b/src/osgEarth/SpatialReference
@@ -110,7 +110,8 @@ namespace osgEarth
          */
         double transformUnits(
             double                  distance,
-            const SpatialReference* outputSRS ) const;
+            const SpatialReference* outputSRS,
+            double                  latitude =0.0) const;
 
 
     public: // World transformations.
@@ -141,45 +142,6 @@ namespace osgEarth
             osg::Vec3d&       out_local,
             double*           out_geodeticZ =0L ) const;
 
-#if 0
-
-    public:  // ECEF transformations.
-
-        /**
-         * Transforms a point to geocentric/ECEF coordinates.
-         */
-        bool transformToECEF(
-            const osg::Vec3d& input,
-            osg::Vec3d&       output ) const;
-
-        /**
-         * Transforms an array of points in to geocentric/ECEF coordinates. The points
-         * are transformed in place.
-         */
-        bool transformToECEF( std::vector<osg::Vec3d>& points ) const;
-
-        /**
-         * Transforms a point from geocentric/ECEF coordinates into this SRS.
-         * @param input
-         *      ECEF point to transform
-         * @param output
-         *      Result placed in this output parameter upon success
-         * @param out_geodeticZ
-         *      (optional) Geodetic (height above ellipsoid) Z placed here; it will
-         *      only differ from output.z() if a vertidal datum exists in this SRS
-         */
-        bool transformFromECEF(
-            const osg::Vec3d& input,
-            osg::Vec3d&       output,
-            double*           out_geodeticZ =0L ) const;
-
-        /**
-         * Transforms an array of points from geocentric/ECEF coordinates into this SRS.
-         * The points are transformed in place.
-         */
-        bool transformFromECEF( std::vector<osg::Vec3d>& points ) const;
-#endif
-
     public: // extent transformations.
         
         /**
@@ -340,6 +302,12 @@ namespace osgEarth
             double xmin, double ymin, double xmax, double ymax,
             bool plate_carre =false ) const;
 
+		/**
+		 * Gets the underlying OGRLayerH that this SpatialReference owns.
+		 * Don't use this unless you know what you're doing.
+		 */
+		void* getHandle() const { return _handle;}
+
 
 
     protected:
diff --git a/src/osgEarth/SpatialReference.cpp b/src/osgEarth/SpatialReference.cpp
index 57831c7..9501584 100644
--- a/src/osgEarth/SpatialReference.cpp
+++ b/src/osgEarth/SpatialReference.cpp
@@ -1127,12 +1127,12 @@ SpatialReference::transformXYPointArrays(double*  x,
     TransformHandleCache::const_iterator itr = _transformHandleCache.find(out_srs->getWKT());
     if (itr != _transformHandleCache.end())
     {
-        OE_DEBUG << "using cached transform handle" << std::endl;
+        //OE_DEBUG << LC << "using cached transform handle" << std::endl;
         xform_handle = itr->second;
     }
     else
     {
-        OE_DEBUG << "allocating new OCT Transform" << std::endl;
+        OE_DEBUG << LC << "allocating new OCT Transform" << std::endl;
         xform_handle = OCTNewCoordinateTransformation( _handle, out_srs->_handle);
         const_cast<SpatialReference*>(this)->_transformHandleCache[out_srs->getWKT()] = xform_handle;
     }
@@ -1268,122 +1268,34 @@ SpatialReference::transformFromWorld(const osg::Vec3d& world,
     }
 }
 
-
-#if 0
-bool 
-SpatialReference::transformToECEF(const osg::Vec3d& input,
-                                  osg::Vec3d&       output ) const
-{
-    osg::Vec3d geo(input);
-    
-    // first convert to lat/long/hae:
-    if ( !isGeodetic() )
-    {
-        if ( !transform(input, getGeodeticSRS(), geo) )
-            return false;
-    }
-
-    // then convert to ECEF.
-    getEllipsoid()->convertLatLongHeightToXYZ(
-        osg::DegreesToRadians( geo.y() ), osg::DegreesToRadians( geo.x() ), geo.z(),
-        output.x(), output.y(), output.z() );
-
-    return true;
-}
-
-bool 
-SpatialReference::transformToECEF(std::vector<osg::Vec3d>& points) const
-{
-    if ( points.size() == 0 )
-        return false;
-
-    // transform the points to lat/long/hae first:
-    if ( !isGeodetic() )
-    {
-        if ( !transform(points, getGeodeticSRS()) )
-            return false;
-    }
-
-    // then convert to ECEF:
-    for( unsigned i=0; i<points.size(); ++i )
-    {
-        osg::Vec3d& p = points[i];
-
-        getEllipsoid()->convertLatLongHeightToXYZ(
-            osg::DegreesToRadians( p.y() ), osg::DegreesToRadians( p.x() ), p.z(),
-            p.x(), p.y(), p.z() );
-    }
-
-    return true;
-}
-
-bool 
-SpatialReference::transformFromECEF(const osg::Vec3d& ecef,
-                                    osg::Vec3d&       output,
-                                    double*           out_haeZ ) const
-{
-    // transform to lat/long/hae (geodetic):
-    osg::Vec3d geodetic;
-
-    getEllipsoid()->convertXYZToLatLongHeight(
-        ecef.x(),     ecef.y(),     ecef.z(),
-        geodetic.y(), geodetic.x(), geodetic.z() );
-
-    geodetic.y() = osg::RadiansToDegrees(geodetic.y());
-    geodetic.x() = osg::RadiansToDegrees(geodetic.x());
-
-    // if our SRS is geographic, save the HAE now:
-    if ( out_haeZ )
-        *out_haeZ = geodetic.z();
-
-    return getGeodeticSRS()->transform(geodetic, this, output);
-}
-
-bool 
-SpatialReference::transformFromECEF(std::vector<osg::Vec3d>& points) const
-{
-    bool ok = true;
-
-    // first convert all the points to lat/long/hae (geodetic) in place:
-    for( unsigned i=0; i<points.size(); ++i )
-    {
-        osg::Vec3d& p = points[i];
-        osg::Vec3d geodetic;
-        getEllipsoid()->convertXYZToLatLongHeight(
-            p.x(), p.y(), p.z(),
-            geodetic.y(), geodetic.x(), geodetic.z() );
-
-        geodetic.x() = osg::RadiansToDegrees( geodetic.x() );
-        geodetic.y() = osg::RadiansToDegrees( geodetic.y() );
-        p = geodetic;
-    }
-
-    // then convert them all to the local SRS if necessary.
-    if ( !isGeodetic() )
-    {
-        ok = getGeodeticSRS()->transform( points, this );
-    }
-
-    return ok;
-}
-#endif
-
 double
 SpatialReference::transformUnits(double                  input,
-                                 const SpatialReference* outSRS ) const
+                                 const SpatialReference* outSRS,
+                                 double                  latitude) const
 {
     if ( this->isProjected() && outSRS->isGeographic() )
     {
         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
-        double inputDegrees = getUnits().convertTo(Units::METERS, input) / metersPerEquatorialDegree;
+        double inputDegrees = getUnits().convertTo(Units::METERS, input) / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
+        return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
+    }
+    else if ( this->isECEF() && outSRS->isGeographic() )
+    {
+        double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+        double inputDegrees = input / (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
         return Units::DEGREES.convertTo( outSRS->getUnits(), inputDegrees );
     }
     else if ( this->isGeographic() && outSRS->isProjected() )
     {
         double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
-        double inputMeters = getUnits().convertTo(Units::DEGREES, input) * metersPerEquatorialDegree;
+        double inputMeters = getUnits().convertTo(Units::DEGREES, input) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
         return Units::METERS.convertTo( outSRS->getUnits(), inputMeters );
     }
+    else if ( this->isGeographic() && outSRS->isECEF() )
+    {
+        double metersPerEquatorialDegree = (outSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+        return getUnits().convertTo(Units::DEGREES, input) * (metersPerEquatorialDegree * cos(osg::DegreesToRadians(latitude)));
+    }
     else // both projected or both geographic.
     {
         return getUnits().convertTo( outSRS->getUnits(), input );
diff --git a/src/osgEarth/StateSetCache b/src/osgEarth/StateSetCache
index 56280ca..73e3ca7 100644
--- a/src/osgEarth/StateSetCache
+++ b/src/osgEarth/StateSetCache
@@ -32,8 +32,11 @@ namespace osgEarth
     class OSGEARTH_EXPORT StateSetCache : public osg::Referenced
     {
     public:
-        StateSetCache() { }
-        virtual ~StateSetCache() { }
+        /**
+         * Constructs a new cache.
+         */
+        StateSetCache();
+
 
         /**
          * Check whether a StateSet is eligible for sharing.
@@ -86,6 +89,9 @@ namespace osgEarth
         void clear();
 
     protected: 
+
+        virtual ~StateSetCache();
+
         struct CompareStateSets {
             bool operator()(
                 const osg::ref_ptr<osg::StateSet>& lhs,
@@ -107,6 +113,10 @@ namespace osgEarth
         StateAttributeSet _stateAttributeCache;
 
         mutable Threading::Mutex _mutex;
+
+        void prune();
+        void pruneIfNecessary();
+        unsigned _pruneCount;
     };
 }
 
diff --git a/src/osgEarth/StateSetCache.cpp b/src/osgEarth/StateSetCache.cpp
index 7a0c64b..3f6e44a 100644
--- a/src/osgEarth/StateSetCache.cpp
+++ b/src/osgEarth/StateSetCache.cpp
@@ -23,6 +23,8 @@
 
 #define LC "[StateSetCache] "
 
+#define PRUNE_ACCESS_COUNT 40
+
 using namespace osgEarth;
 
 //---------------------------------------------------------------------------
@@ -156,6 +158,18 @@ namespace
 
 //------------------------------------------------------------------------
 
+StateSetCache::StateSetCache() :
+_pruneCount( 0 )
+{
+    //nop
+}
+
+StateSetCache::~StateSetCache()
+{
+    Threading::ScopedMutexLock lock( _mutex );
+    prune();
+}
+
 void
 StateSetCache::optimize(osg::Node* node)
 {
@@ -236,6 +250,9 @@ StateSetCache::share(osg::ref_ptr<osg::StateSet>& input,
     if ( !checkEligible || eligible(input.get()) )
     {
         Threading::ScopedMutexLock lock( _mutex );
+
+        pruneIfNecessary();
+
         shareattrs = false;
 
         std::pair<StateSetSet::iterator,bool> result = _stateSetCache.insert( input );
@@ -279,6 +296,8 @@ StateSetCache::share(osg::ref_ptr<osg::StateAttribute>& input,
     {
         Threading::ScopedMutexLock lock( _mutex );
 
+        pruneIfNecessary();
+
         std::pair<StateAttributeSet::iterator,bool> result = _stateAttributeCache.insert( input );
         if ( result.second )
         {
@@ -300,6 +319,54 @@ StateSetCache::share(osg::ref_ptr<osg::StateAttribute>& input,
     }
 }
 
+void
+StateSetCache::pruneIfNecessary()
+{
+    // assume an exclusve mutex is taken
+    if ( _pruneCount++ == PRUNE_ACCESS_COUNT )
+    {
+        prune();
+        _pruneCount = 0;
+    }
+}
+
+void
+StateSetCache::prune()
+{
+    // assume an exclusive mutex is taken.
+
+    unsigned ss_count = 0, sa_count = 0;
+
+    for( StateSetSet::iterator i = _stateSetCache.begin(); i != _stateSetCache.end(); )
+    {
+        if ( i->get()->referenceCount() == 1 )
+        {
+            // do not call releaseGLObjects since the attrs themselves might still be shared
+            _stateSetCache.erase( i++ );
+            ss_count++;
+        }
+        else
+        {
+            ++i;
+        }
+    }
+
+    for( StateAttributeSet::iterator i = _stateAttributeCache.begin(); i != _stateAttributeCache.end(); )
+    {
+        if ( i->get()->referenceCount() == 1 )
+        {
+            i->get()->releaseGLObjects( 0L );
+            _stateAttributeCache.erase( i++ );
+            sa_count++;
+        }
+        else
+        {
+            ++i;
+        }
+    }
+
+    OE_DEBUG << LC << "Pruned " << sa_count << " attributes, " << ss_count << " statesets" << std::endl;
+}
 
 void
 StateSetCache::clear()
diff --git a/src/osgEarth/TaskService.cpp b/src/osgEarth/TaskService.cpp
index 59399d8..e05d6ea 100644
--- a/src/osgEarth/TaskService.cpp
+++ b/src/osgEarth/TaskService.cpp
@@ -99,24 +99,6 @@ TaskRequestQueue::add( TaskRequest* request )
     // insert by priority.
     _requests.insert( std::pair<float,TaskRequest*>(request->getPriority(), request) );
 
-#if 0
-    // insert by priority.
-    bool inserted = false;
-    for( TaskRequestList::iterator i = _requests.begin(); i != _requests.end(); i++ )
-    {
-        if ( request->getPriority() > i->get()->getPriority() )
-        {
-            _requests.insert( i, request );
-            inserted = true;
-            //OE_NOTICE << "TaskRequestQueue size=" << _requests.size() << std::endl;
-            break;
-        }
-    }
-
-    if ( !inserted )
-        _requests.push_back( request );
-#endif
-
     // since there is data in the queue, wake up one waiting task thread.
     _cond.signal();
 }
diff --git a/src/osgEarth/Terrain b/src/osgEarth/Terrain
index abf5a68..8989645 100644
--- a/src/osgEarth/Terrain
+++ b/src/osgEarth/Terrain
@@ -243,26 +243,26 @@ namespace osgEarth
      * terrain tile, i.e. one that is not in the scene graph yet -- making it
      * MT-safe.
      */
-     class OSGEARTH_EXPORT TerrainPatch : public TerrainHeightProvider
-   {
-   public:
-       TerrainPatch( osg::Node* patch, const Terrain* terrain );
+    class OSGEARTH_EXPORT TerrainPatch : public TerrainHeightProvider
+    {
+    public:
+        TerrainPatch( osg::Node* patch, const Terrain* terrain );
 
-       /**
+        /**
         * Queries the elevation under the specified point. This method is
         * identical to calling Terrain::getHeight with the specified patch.
         */
-      bool getHeight(
-          const SpatialReference* srs,
-          double                  x,
-          double                  y,
-          double*                 out_heightAboveMSL,
-          double*                 out_heightAboveEllipsoid =0L) const;
-
-   protected:
-       osg::ref_ptr<osg::Node>     _patch;
-       osg::ref_ptr<const Terrain> _terrain;
-   };
+        bool getHeight(
+            const SpatialReference* srs,
+            double                  x,
+            double                  y,
+            double*                 out_heightAboveMSL,
+            double*                 out_heightAboveEllipsoid =0L) const;
+
+    protected:
+        osg::ref_ptr<osg::Node>     _patch;
+        osg::ref_ptr<const Terrain> _terrain;
+    };
 }
 
 #endif // OSGEARTH_COMPOSITING_H
diff --git a/src/osgEarth/Terrain.cpp b/src/osgEarth/Terrain.cpp
index b020820..7ee7a98 100644
--- a/src/osgEarth/Terrain.cpp
+++ b/src/osgEarth/Terrain.cpp
@@ -40,17 +40,41 @@ namespace
     struct OnTileAddedOperation : public BaseOp
     {
         TileKey _key;
-        osg::ref_ptr<osg::Node> _node;
+        osg::observer_ptr<osg::Node> _node;
+        unsigned _count;
+        //osg::ref_ptr<osg::Node> _node;
 
         OnTileAddedOperation(const TileKey& key, osg::Node* node, Terrain* terrain)
-            : BaseOp(terrain), _key(key), _node(node) { }
+            : BaseOp(terrain), _key(key), _node(node), _count(0) { }
 
         void operator()(osg::Object*)
         {
-            if ( _node.valid() && _node->referenceCount() > 1 && _terrain.valid() )
+            ++_count;
+            this->setKeep( false );
+
+            osg::ref_ptr<osg::Node> node;
+            if ( _terrain.valid() && _node.lock(node) )
+            {
+                if ( node->getNumParents() > 0 )
+                {
+                    //OE_NOTICE << LC << "FIRING onTileAdded for " << _key.str() << " (tries=" << _count << ")" << std::endl;
+                    _terrain->fireTileAdded( _key, node.get() );
+                }
+                else
+                {
+                    //OE_NOTICE << LC << "Deferring onTileAdded for " << _key.str() << std::endl;
+                    this->setKeep( true );
+                }
+            }
+            else
             {
-                _terrain->fireTileAdded( _key, _node.get() );
+                // nop; tile expired; let it go.
+                //OE_NOTICE << "Tile expired before notification: " << _key.str() << std::endl;
             }
+            //if ( _node.valid() && _node->referenceCount() > 1 && _terrain.valid() )
+            //{
+            //    _terrain->fireTileAdded( _key, _node.get() );
+            //}
         }
     };
 }
@@ -99,12 +123,10 @@ Terrain::getHeight(osg::Node*              patch,
         const SpatialReference* ecef = getSRS()->getECEF();
         getSRS()->transform(start, ecef, start);
         getSRS()->transform(end,   ecef, end);
-        //getSRS()->transformToECEF(start, start);
-        //getSRS()->transformToECEF(end, end);
     }
 
-    osgUtil::LineSegmentIntersector* lsi = new osgUtil::LineSegmentIntersector(start, end);
-    lsi->setIntersectionLimit(osgUtil::Intersector::LIMIT_ONE);
+    DPLineSegmentIntersector* lsi = new DPLineSegmentIntersector( start, end );
+    lsi->setIntersectionLimit(osgUtil::Intersector::LIMIT_NEAREST);
 
     osgUtil::IntersectionVisitor iv( lsi );
     iv.setTraversalMask( ~_terrainOptions.secondaryTraversalMask().value() );
@@ -148,22 +170,78 @@ Terrain::getWorldCoordsUnderMouse(osg::View* view, float x, float y, osg::Vec3d&
     if ( !view2 || !_graph.valid() )
         return false;
 
-    osgUtil::LineSegmentIntersector::Intersections results;
+    osgUtil::LineSegmentIntersector::Intersections intersections;
 
-    osg::NodePath path;
-    path.push_back( _graph.get() );
+    osg::NodePath nodePath;
+    nodePath.push_back( _graph.get() );
 
     // fine but computeIntersections won't travers a masked Drawable, a la quadtree.
-    unsigned mask = ~_terrainOptions.secondaryTraversalMask().value();
+    unsigned traversalMask = ~_terrainOptions.secondaryTraversalMask().value();
 
-    if ( view2->computeIntersections( x, y, path, results, mask ) )
+
+#if 0
+    // Old code, uses the computeIntersections method directly but sufferes from floating point precision problems.
+    if ( view2->computeIntersections( x, y, nodePath, intersections, traversalMask ) )
     {
         // find the first hit under the mouse:
-        osgUtil::LineSegmentIntersector::Intersection first = *(results.begin());
+        osgUtil::LineSegmentIntersector::Intersection first = *(intersections.begin());
         out_coords = first.getWorldIntersectPoint();
         return true;
     }
-    return false;
+    return false;    
+
+#else
+    // New code, uses the code from osg::View::computeIntersections but uses our DPLineSegmentIntersector instead to get around floating point precision issues.
+    float local_x, local_y = 0.0;
+    const osg::Camera* camera = view2->getCameraContainingPosition(x, y, local_x, local_y);
+    if (!camera) camera = view2->getCamera();
+
+    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;
+
+    // Use a double precision line segment intersector
+    //osg::ref_ptr< osgUtil::LineSegmentIntersector > picker = new osgUtil::LineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
+    osg::ref_ptr< DPLineSegmentIntersector > picker = new DPLineSegmentIntersector(osgUtil::Intersector::MODEL, startVertex, endVertex);
+
+    // Limit it to one intersection, we only care about the first
+    picker->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
+
+    osgUtil::IntersectionVisitor iv(picker.get());
+    iv.setTraversalMask(traversalMask);
+    nodePath.back()->accept(iv);
+
+    if (picker->containsIntersections())
+    {        
+        intersections = picker->getIntersections();
+        // find the first hit under the mouse:
+        osgUtil::LineSegmentIntersector::Intersection first = *(intersections.begin());
+        out_coords = first.getWorldIntersectPoint();        
+        return true;       
+    }
+    return false;        
+#endif
 }
 
 
diff --git a/src/osgEarth/TerrainEffect b/src/osgEarth/TerrainEffect
new file mode 100644
index 0000000..493d6ec
--- /dev/null
+++ b/src/osgEarth/TerrainEffect
@@ -0,0 +1,57 @@
+/* -*-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_TERRAIN_EFFECT_H
+#define OSGEARTH_TERRAIN_EFFECT_H 1
+
+#include <osgEarth/Common>
+#include <osgEarth/Config>
+
+namespace osgEarth
+{
+    class TerrainEngineNode;
+
+    /**
+     * A terrain effect is a class that applies an effect to the 
+     * TerrainEngineNode. You can add effect to the engine by calling
+     * TerrainEngineNode::addEffect(). An effect can do anything, 
+     * but typically it will alter the state set in some way (such as
+     * to customize the shaders).
+     */
+    class TerrainEffect : public osg::Referenced /* header-only */
+    {
+    public:
+        /** Called by the terrain engine when you install the effect */
+        virtual void onInstall(TerrainEngineNode* engine) { }
+
+        /** Called by the terrain engine when you uninstall the effect */
+        virtual void onUninstall(TerrainEngineNode* engine) { }
+
+    public: // serialization
+
+        virtual Config getConfig() const { return Config(); }
+
+    protected:
+        TerrainEffect() { }
+        virtual ~TerrainEffect() { }
+    };
+
+} // namespace osgEarth
+
+#endif // OSGEARTH_TERRAIN_EFFECT_H
diff --git a/src/osgEarth/TerrainEngineNode b/src/osgEarth/TerrainEngineNode
index 6ffb837..61b3875 100644
--- a/src/osgEarth/TerrainEngineNode
+++ b/src/osgEarth/TerrainEngineNode
@@ -22,6 +22,7 @@
 #include <osgEarth/Map>
 #include <osgEarth/MapFrame>
 #include <osgEarth/Terrain>
+#include <osgEarth/TerrainEffect>
 #include <osgEarth/TextureCompositor>
 #include <osgEarth/ShaderUtils>
 #include <osg/CoordinateSystemNode>
@@ -30,6 +31,8 @@
 
 namespace osgEarth
 {
+    class TerrainEffect;
+
     /**
      * TerrainEngineNode is the base class and interface for map engine implementations.
      *
@@ -52,18 +55,29 @@ namespace osgEarth
         /** Accesses the compositor that controls the rendering of image layers */
         TextureCompositor* getTextureCompositor() const;
 
+        /** Adds a terrain effect */
+        void addEffect( TerrainEffect* effect );
+
+        /** Removes a terrain effect */
+        void removeEffect( TerrainEffect* effect );
+
+
     public: // Runtime properties
 
-        /** Sets the scale factor to apply to elevation height values. Default is 1.0 */
+        /** Sets the scale factor to apply to elevation height values. Default is 1.0
+          * @deprecated */
         void setVerticalScale( float value );
 
-        /** Gets the scale factor to apply to elevation height values. */
+        /** Gets the scale factor to apply to elevation height values.
+          * #deprecated */
         float getVerticalScale() const { return _verticalScale; }
 
-        /** Sets the sampling ratio for elevation grid data. Default is 1.0. */
+        /** Sets the sampling ratio for elevation grid data. Default is 1.0.
+          * @deprecated */
         void setElevationSamplingRatio( float value );
 
-        /** Gets the sampling ratio for elevation grid data. */
+        /** Gets the sampling ratio for elevation grid data.
+          * @depreceated */
         float getElevationSamplingRatio() const { return _elevationSamplingRatio; }
 
     protected:
@@ -146,7 +160,7 @@ namespace osgEarth
         float                              _elevationSamplingRatio;
         osg::ref_ptr<Terrain>              _terrainInterface;
         unsigned                           _dirtyCount;
-        UpdateLightingUniformsHelper       _updateLightingUniformsHelper;
+        //UpdateLightingUniformsHelper       _updateLightingUniformsHelper;
         
         enum InitStage {
             INIT_NONE,
@@ -154,6 +168,21 @@ namespace osgEarth
             INIT_POSTINIT_COMPLETE
         };
         InitStage _initStage;
+
+        typedef std::vector<osg::ref_ptr<TerrainEffect> > TerrainEffectVector;
+        TerrainEffectVector effects_;
+
+    public:
+
+        /** Access a typed effect. */
+        template<typename T>
+        T* getEffect() {
+            for(TerrainEffectVector::iterator i = effects_.begin(); i != effects_.end(); ++i ) {
+                T* e = dynamic_cast<T*>(i->get());
+                if ( e ) return e;
+            }
+            return 0L;
+        }
     };
 
     /**
@@ -171,11 +200,11 @@ namespace osgEarth
     class TerrainDecorator : public osg::Group
     {
     public:
-        virtual void onInstall( TerrainEngineNode* engine ) { }
-        virtual void onUninstall( TerrainEngineNode* engine ) { }
+        virtual void onInstall( TerrainEngineNode* engine );
+        virtual void onUninstall( TerrainEngineNode* engine );
 
     protected:
-        virtual ~TerrainDecorator() { }
+        virtual ~TerrainDecorator();
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/TerrainEngineNode.cpp b/src/osgEarth/TerrainEngineNode.cpp
index 3bc03d8..6ae25bd 100644
--- a/src/osgEarth/TerrainEngineNode.cpp
+++ b/src/osgEarth/TerrainEngineNode.cpp
@@ -70,6 +70,30 @@ _engine( engine )
 }
 
 
+void
+TerrainEngineNode::addEffect(TerrainEffect* effect)
+{
+    if ( effect )
+    {
+        effects_.push_back( effect );
+        effect->onInstall( this );
+    }
+}
+
+
+void
+TerrainEngineNode::removeEffect(TerrainEffect* effect)
+{
+    if ( effect )
+    {
+        effect->onUninstall(this);
+        TerrainEffectVector::iterator i = std::find(effects_.begin(), effects_.end(), effect);
+        if ( i != effects_.end() )
+            effects_.erase( i );
+    }
+}
+
+
 TextureCompositor*
 TerrainEngineNode::getTextureCompositor() const
 {
@@ -435,8 +459,11 @@ TerrainEngineNode::traverse( osg::NodeVisitor& nv )
 
         if ( Registry::capabilities().supportsGLSL() )
         {
-            _updateLightingUniformsHelper.cullTraverse( this, &nv );
+            //_updateLightingUniformsHelper.cullTraverse( this, &nv );
 
+            // TODO: the "camera elevation" uniform is only used by the old
+            // multi- and texarray- texture compositors. Once we get rid of those
+            // we can get rid of this too.
             osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
             if ( cv )
             {
@@ -448,6 +475,7 @@ TerrainEngineNode::traverse( osg::NodeVisitor& nv )
                 else
                     elevation = eye.z();
 
+                //TODO: no good. cannot be setting this in cull.
                 _cameraElevationUniform->set( elevation );
             }
         }
@@ -490,3 +518,16 @@ TerrainEngineNodeFactory::create( Map* map, const TerrainOptions& options )
     return result;
 }
 
+//------------------------------------------------------------------------
+TerrainDecorator::~TerrainDecorator()
+{
+}
+
+void TerrainDecorator::onInstall( TerrainEngineNode* engine )
+{
+}
+
+void TerrainDecorator::onUninstall( TerrainEngineNode* engine )
+{
+}
+
diff --git a/src/osgEarth/TerrainLayer b/src/osgEarth/TerrainLayer
index fb2d105..739b6e2 100644
--- a/src/osgEarth/TerrainLayer
+++ b/src/osgEarth/TerrainLayer
@@ -78,6 +78,7 @@ namespace osgEarth
 
         /**
          * The maximum level of detail for which this layer should generate data.
+         * Data from this layer will not appear in map tiles above the maxLevel.
          */
         optional<unsigned>& maxLevel() { return _maxLevel; }
         const optional<unsigned>& maxLevel() const { return _maxLevel; }
@@ -88,6 +89,15 @@ namespace osgEarth
          */
         optional<double>& maxResolution() { return _maxResolution; }
         const optional<double>& maxResolution() const { return _maxResolution; }
+
+        /**
+         * Overrides the maximum available LOD of the underlying tile source. The layer
+         * will never ask the tile source (or the cache) for an LOD higher than this.
+         * Data from this layer may still appear in map tiles above the maxDataLevel,
+         * but it will be upsampled from the maxDataLevel.
+         */
+        optional<unsigned>& maxDataLevel() { return _maxDataLevel; }
+        const optional<unsigned>& maxDataLevel() const { return _maxDataLevel; }
         
         /**
          * Whether to use this layer with the map. Setting this to false means that 
@@ -179,7 +189,8 @@ namespace osgEarth
         optional<bool>              _enabled;
         optional<bool>              _visible;
         optional<unsigned>          _reprojectedTileSize;
-        optional<double>            _edgeBufferRatio;        
+        optional<double>            _edgeBufferRatio;
+        optional<unsigned>          _maxDataLevel;
 
         optional<std::string>       _cacheId;
         optional<std::string>       _cacheFormat;
@@ -196,7 +207,7 @@ namespace osgEarth
         virtual ~TerrainLayerCallback() { }
     };
 
-    typedef void (TerrainLayerCallback::*TerrainLayerCallbackMethodPtr)(TerrainLayer* layer);
+    typedef void (TerrainLayerCallback::*TerrainLayerCallbackMethodPtr)(class TerrainLayer* layer);
 
 
     /**
@@ -242,6 +253,11 @@ namespace osgEarth
          */
         const std::string& getName() const { return getTerrainLayerRuntimeOptions().name(); }
 
+		/**
+		 * Sets the readable name of the map layer
+		 */
+		void setName( const std::string& name ) { _runtimeOptions->name() = name; }
+
         /**
          * Gets the profile of this MapLayer
          */
diff --git a/src/osgEarth/TerrainLayer.cpp b/src/osgEarth/TerrainLayer.cpp
index 8f9143a..d5e5e8e 100644
--- a/src/osgEarth/TerrainLayer.cpp
+++ b/src/osgEarth/TerrainLayer.cpp
@@ -37,14 +37,14 @@ using namespace OpenThreads;
 TerrainLayerOptions::TerrainLayerOptions( const ConfigOptions& options ) :
 ConfigOptions       ( options ),
 _minLevel           ( 0 ),
-_maxLevel           ( 99 ),
+_maxLevel           ( 30 ),
 _cachePolicy        ( CachePolicy::DEFAULT ),
 _loadingWeight      ( 1.0f ),
 _exactCropping      ( false ),
 _enabled            ( true ),
 _visible            ( true ),
-_reprojectedTileSize( 256 )
-
+_reprojectedTileSize( 256 ),
+_maxDataLevel       ( 99 )
 {
     setDefaults();
     fromConfig( _conf ); 
@@ -69,7 +69,8 @@ TerrainLayerOptions::setDefaults()
     _cachePolicy.init( CachePolicy() );
     _loadingWeight.init( 1.0f );
     _minLevel.init( 0 );
-    _maxLevel.init( 99 );
+    _maxLevel.init( 30 );
+    _maxDataLevel.init( 99 );
 }
 
 Config
@@ -85,8 +86,9 @@ TerrainLayerOptions::getConfig( bool isolate ) const
     conf.updateIfSet( "loading_weight", _loadingWeight );
     conf.updateIfSet( "enabled", _enabled );
     conf.updateIfSet( "visible", _visible );
-    conf.updateIfSet( "edge_buffer_ratio", _edgeBufferRatio);    
+    conf.updateIfSet( "edge_buffer_ratio", _edgeBufferRatio);
     conf.updateIfSet( "reprojected_tilesize", _reprojectedTileSize);
+    conf.updateIfSet( "max_data_level", _maxDataLevel );
 
     conf.updateIfSet( "vdatum", _vertDatum );
 
@@ -115,6 +117,7 @@ TerrainLayerOptions::fromConfig( const Config& conf )
     conf.getIfSet( "visible", _visible );
     conf.getIfSet( "edge_buffer_ratio", _edgeBufferRatio);    
     conf.getIfSet( "reprojected_tilesize", _reprojectedTileSize);
+    conf.getIfSet( "max_data_level", _maxDataLevel );
 
     conf.getIfSet( "vdatum", _vertDatum );
     conf.getIfSet( "vsrs", _vertDatum );    // back compat
@@ -205,6 +208,7 @@ TerrainLayer::setCache( Cache* cache )
             if ( _runtimeOptions->cacheId().isSet() && !_runtimeOptions->cacheId()->empty() )
             {
                 // user expliticy set a cacheId in the terrain layer options.
+                // this appears to be a NOP; review for removal -gw
                 cacheId = *_runtimeOptions->cacheId();
             }
             else
@@ -255,6 +259,15 @@ void
 TerrainLayer::setTargetProfileHint( const Profile* profile )
 {
     _targetProfileHint = profile;
+
+    // This will attempt to open and access a cache bin if there
+    // is one. This is important in cache-only mode since we cannot
+    // establish a profile from the tile source.
+    if ( getCachePolicy() != CachePolicy::NO_CACHE )
+    {
+        CacheBinMetadata meta;
+        getCacheBinMetadata( profile, meta );
+    }
 }
 
 TileSource* 
@@ -279,7 +292,7 @@ TerrainLayer::getTileSource() const
             // a policy in the initialization options.
             if ( _tileSource.valid() && !_initOptions.cachePolicy().isSet() )
             {
-                CachePolicy hint = _tileSource->getCachePolicyHint();
+                CachePolicy hint = _tileSource->getCachePolicyHint( _targetProfileHint.get() );
 
                 if ( hint.usage().isSetTo(CachePolicy::USAGE_NO_CACHE) )
                 {
@@ -288,7 +301,17 @@ TerrainLayer::getTileSource() const
                 }
             }
 
-            OE_INFO << LC << "cache policy = " << getCachePolicy().usageString() << std::endl;
+            // 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() )
+            {
+                CachePolicy& cp = _runtimeOptions->cachePolicy().mutable_value();
+                if ( !cp.minTime().isSet() && !cp.maxAge().isSet() )
+                {
+                    cp.minTime() = _tileSource->getLastModifiedTime();
+                }
+                OE_INFO << LC << "cache policy = " << getCachePolicy().usageString() << std::endl;
+            }
         }
     }
 
@@ -416,7 +439,7 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
                 {
                     // in cacheonly mode, create a profile from the first cache bin accessed
                     // (they SHOULD all be the same...)
-                    _profile = Profile::create( *meta._sourceProfile );                    
+                    _profile = Profile::create( *meta._sourceProfile );
                 }
             }
 
@@ -428,7 +451,7 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
                 {
                     // no existing metadata; create some.
                     meta._cacheBinId    = binId;
-                    meta._sourceName    = this->getName();                    
+                    meta._sourceName    = this->getName();
                     meta._sourceDriver  = getTileSource()->getOptions().getDriver();
                     meta._sourceProfile = getProfile()->toProfileOptions();
                     meta._cacheProfile  = profile->toProfileOptions();
@@ -438,25 +461,26 @@ TerrainLayer::getCacheBin( const Profile* profile, const std::string& binId )
                 }
                 else if ( isCacheOnly() )
                 {
-                    OE_WARN << LC << "Failed to create a cache bin for layer"
-                        << " because cache_only mode is enabled and no existing cache could be found."
+                    OE_WARN << LC << "Failed to open a cache for layer [" << getName() << "] "
+                        << " because cache_only policy is in effect and bin [" << binId << "] cound not be located."
                         << std::endl;
                     return 0L;
                 }
                 else
                 {
-                    OE_WARN << LC << "Failed to create a cache bin for layer"
-                        << " because there is no valid tile source."
+                    OE_WARN << LC << "Failed to create cache bin [" << binId << "] "
+                        << "for layer [" << getName() << "] because there is no valid tile source."
                         << std::endl;
                     return 0L;
                 }
             }
 
-
             // store the bin.
             CacheBinInfo& newInfo = _cacheBins[binId];
             newInfo._metadata = meta;
             newInfo._bin      = newBin.get();
+
+            OE_INFO << LC << "Opened cache bin [" << binId << "]" << std::endl;
         }
         else
         {
@@ -581,7 +605,6 @@ TerrainLayer::isKeyValid(const TileKey& key) const
         {
             OE_DEBUG << LC
                 << "TerrainLayer::isKeyValid called with key of a different profile" << std::endl;
-            //return true;
         }
 
         if ( _runtimeOptions->maxResolution().isSet() )
@@ -603,7 +626,12 @@ bool
 TerrainLayer::isCached(const TileKey& key) const
 {
     CacheBin* bin = const_cast<TerrainLayer*>(this)->getCacheBin( key.getProfile() );
-    return bin ? bin->isCached(key.str()) : false;
+    if ( !bin )
+        return false;
+
+    TimeStamp minTime = this->getCachePolicy().getMinAcceptTime();
+
+    return bin->getRecordStatus( key.str(), minTime ) == CacheBin::STATUS_OK;
 }
 
 void
@@ -617,7 +645,7 @@ void
 TerrainLayer::setDBOptions( const osgDB::Options* dbOptions )
 {
     _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
-    initializeCachePolicy( dbOptions );    
+    initializeCachePolicy( dbOptions );
     storeProxySettings( _dbOptions );
 }
 
diff --git a/src/osgEarth/TerrainOptions b/src/osgEarth/TerrainOptions
index 03dc03e..737d836 100644
--- a/src/osgEarth/TerrainOptions
+++ b/src/osgEarth/TerrainOptions
@@ -246,7 +246,7 @@ namespace osgEarth
         const optional<osg::Texture::FilterMode>& magFilter() const { return _magFilter;}
             
         /**
-         * Whether to enable blending
+         * Whether to enable blending on the terrain. Default is true.
          */
         optional<bool>& enableBlending() { return _enableBlending; }
         const optional<bool>& enableBlending() const { return _enableBlending; }
@@ -298,7 +298,6 @@ namespace osgEarth
         optional<unsigned> _firstLOD;
         optional<bool> _enableLighting;
         optional<float> _attenuationDistance;
-        optional<bool> _lodBlending;
         optional<float> _lodTransitionTimeSeconds;
         optional<bool>  _enableMipmapping;
         optional<bool> _clusterCulling;
diff --git a/src/osgEarth/TerrainOptions.cpp b/src/osgEarth/TerrainOptions.cpp
index 1acf939..dd4145e 100644
--- a/src/osgEarth/TerrainOptions.cpp
+++ b/src/osgEarth/TerrainOptions.cpp
@@ -98,11 +98,10 @@ _minLOD( 0 ),
 _firstLOD( 0 ),
 _enableLighting( false ),
 _attenuationDistance( 1000000 ),
-_lodBlending( false ),
 _lodTransitionTimeSeconds( 0.5f ),
 _enableMipmapping( true ),
 _clusterCulling( true ),
-_enableBlending( false ),
+_enableBlending( true ),
 _mercatorFastPath( true ),
 _minFilter( osg::Texture::LINEAR_MIPMAP_LINEAR ),
 _magFilter( osg::Texture::LINEAR),
diff --git a/src/osgEarth/TextureCompositor.cpp b/src/osgEarth/TextureCompositor.cpp
index ce03685..eb98b87 100755
--- a/src/osgEarth/TextureCompositor.cpp
+++ b/src/osgEarth/TextureCompositor.cpp
@@ -340,6 +340,7 @@ TextureCompositor::reserveTextureImageUnit( int& out_unit )
             {
                 out_unit = i;
                 _reservedUnits.insert( i );
+                OE_INFO << LC << "Reserved image unit " << i << std::endl;
                 return true;
             }
         }
@@ -353,6 +354,7 @@ void
 TextureCompositor::releaseTextureImageUnit( int unit )
 {
     _reservedUnits.erase( unit );
+    OE_INFO << LC << "Released image unit " << unit << std::endl;
 
     if ( _tech == TerrainOptions::COMPOSITING_MULTITEXTURE_GPU )
     {
@@ -381,7 +383,7 @@ TextureCompositor::applyMapModelChange( const MapModelChange& change )
     if ( disableLODBlending && layer->getImageLayerOptions().lodBlending() == true )
     {
         OE_WARN << LC << "LOD blending disabled for layer \"" << layer->getName()
-            << "\" becuase it uses Mercator fast-path rendering" << std::endl;
+            << "\" because it uses Mercator fast-path rendering" << std::endl;
     }
 
     _layout.applyMapModelChange(
@@ -514,7 +516,7 @@ TextureCompositor::setTechnique( TextureCompositorTechnique* tech )
 {
     _tech = TerrainOptions::COMPOSITING_USER;
     _impl = tech;
-    OE_INFO << LC << "Custom texture compositing technique installed" << std::endl;
+    //OE_INFO << LC << "Custom texture compositing technique installed" << std::endl;
 }
 
 void
@@ -536,7 +538,7 @@ TextureCompositor::init()
     {
         _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_GPU;
         _impl = new TextureCompositorMultiTexture( true, _options );
-        OE_INFO << LC << "Compositing technique = MULTITEXTURE/GPU" << std::endl;
+        //OE_INFO << LC << "Compositing technique = MULTITEXTURE/GPU" << std::endl;
     }
 
 #if OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
@@ -547,7 +549,7 @@ TextureCompositor::init()
     {
         _tech = TerrainOptions::COMPOSITING_TEXTURE_ARRAY;
         _impl = new TextureCompositorTexArray( _options );
-        OE_INFO << LC << "Compositing technique = TEXTURE ARRAY" << std::endl;
+        //OE_INFO << LC << "Compositing technique = TEXTURE ARRAY" << std::endl;
     }
 
 #endif // OSG_VERSION_GREATER_OR_EQUAL( 2, 9, 8 )
@@ -558,7 +560,7 @@ TextureCompositor::init()
     {
         _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_FFP;
         _impl = new TextureCompositorMultiTexture( false, _options );
-        OE_INFO << LC << "Compositing technique = MULTITEXTURE/FFP" << std::endl;
+        //OE_INFO << LC << "Compositing technique = MULTITEXTURE/FFP" << std::endl;
     }
 
     // Fallback of last resort. The implementation is actually a NO-OP for multipass mode.
@@ -566,6 +568,6 @@ TextureCompositor::init()
     {
         _tech = TerrainOptions::COMPOSITING_MULTIPASS;
         _impl = 0L;
-        OE_INFO << LC << "Compositing technique = MULTIPASS" << std::endl;
+        //OE_INFO << LC << "Compositing technique = MULTIPASS" << std::endl;
     }
 }
diff --git a/src/osgEarth/TextureCompositorMulti.cpp b/src/osgEarth/TextureCompositorMulti.cpp
index 075a658..f49781c 100755
--- a/src/osgEarth/TextureCompositorMulti.cpp
+++ b/src/osgEarth/TextureCompositorMulti.cpp
@@ -146,7 +146,7 @@ namespace
         // install the color filter chain prototypes:
         for( int i=0; i<maxSlots && i <(int)slots.size(); ++i )
         {
-            buf << "void osgearth_runColorFilters_" << i << "(in int slot, inout vec4 color);\n";
+            buf << "void osgearth_runColorFilters_" << i << "(inout vec4 color);\n";
         }
 
         // the main texturing function:
@@ -202,7 +202,7 @@ namespace
 
             buf
                 // color filter:
-                << "            osgearth_runColorFilters_" << i << "(" << slot << ", texel); \n"
+                << "            osgearth_runColorFilters_" << i << "(texel); \n"
 
                 // adjust for opacity
                 << "            float opacity =  texel.a * osgearth_ImageLayerOpacity[" << i << "];\n"
diff --git a/src/osgEarth/ThreadingUtils b/src/osgEarth/ThreadingUtils
index 7463799..2a62f4a 100644
--- a/src/osgEarth/ThreadingUtils
+++ b/src/osgEarth/ThreadingUtils
@@ -165,7 +165,7 @@ namespace osgEarth { namespace Threading
      */
     class ReadWriteMutex
     {
-#if TRACE_THREADS
+#ifdef TRACE_THREADS
         typedef std::set<unsigned> TracedThreads;
         TracedThreads _trace;
         OpenThreads::Mutex _traceMutex;
diff --git a/src/osgEarth/TileKey b/src/osgEarth/TileKey
index d0acd93..c3f7aa8 100644
--- a/src/osgEarth/TileKey
+++ b/src/osgEarth/TileKey
@@ -87,7 +87,7 @@ namespace osgEarth
          * Gets the string representation of the key, formatted like:
          * "lod_x_y"
          */
-        std::string str() const { return _key; }
+        const std::string& str() const { return _key; }
 
         /**
          * Gets a TileID corresponding to this key.
@@ -104,6 +104,11 @@ namespace osgEarth
          */
         const bool valid() const { return _profile.valid(); }
 
+        /**
+         * Get the quadrant relative to this key's parent.
+         */
+        unsigned getQuadrant() const;
+
     public:
         /**
          * Gets a reference to the child key of this key in the specified
@@ -161,16 +166,11 @@ namespace osgEarth
 
         unsigned int getTileX() const { return _x; }
         unsigned int getTileY() const { return _y; }
-        
-		static inline int getLOD(const osgTerrain::TileID& id)
-		{
-			//The name of the lod changed after OSG 2.6 from layer to level
-#if (OPENSCENEGRAPH_MAJOR_VERSION == 2 && OPENSCENEGRAPH_MINOR_VERSION < 7)
-			return id.layer;
-#else
-			return id.level;
-#endif
-		}
+
+        static inline int getLOD(const osgTerrain::TileID& id)
+        {
+            return id.level;
+        }
 
     protected:
         std::string _key;
diff --git a/src/osgEarth/TileKey.cpp b/src/osgEarth/TileKey.cpp
index 6355fa5..e9410dc 100644
--- a/src/osgEarth/TileKey.cpp
+++ b/src/osgEarth/TileKey.cpp
@@ -81,6 +81,19 @@ TileKey::getTileXY(unsigned int& out_tile_x,
     out_tile_y = _y;
 }
 
+unsigned
+TileKey::getQuadrant() const
+{
+    if ( _lod == 0 )
+        return 0;
+    bool xeven = (_x & 1) == 0;
+    bool yeven = (_y & 1) == 0;
+    return 
+        xeven && yeven ? 0 :
+        xeven          ? 2 :
+        yeven          ? 1 : 3;
+}
+
 osgTerrain::TileID
 TileKey::getTileId() const
 {
diff --git a/src/osgEarth/TileSource b/src/osgEarth/TileSource
index 5d9ead7..b964044 100644
--- a/src/osgEarth/TileSource
+++ b/src/osgEarth/TileSource
@@ -32,7 +32,6 @@
 #include <osgEarth/TileKey>
 #include <osgEarth/Profile>
 #include <osgEarth/MemCache>
-#include <osgEarth/Progress>
 #include <osgEarth/ThreadingUtils>
 
 #include <osg/Referenced>
@@ -43,17 +42,13 @@
 #include <osgDB/Options>
 #endif
 #include <osgDB/ReadFile>
-
-#include <OpenThreads/Mutex>
-
 #include <string>
 
 
-#define TILESOURCE_CONFIG "tileSourceConfig"
-
-
 namespace osgEarth
 {
+    class ProgressCallback;
+
     /**
      * Configuration options for a tile source driver.
      */
@@ -61,31 +56,51 @@ namespace osgEarth
     {
     public:
 
+        /** Size (in each dimension) of tiles to generate. */
         optional<int>& tileSize() { return _tileSize; }
         const optional<int>& tileSize() const { return _tileSize; }
 
+        /** For heightfields, treat this value as a "no data" marker. */
         optional<float>& noDataValue() { return _noDataValue; }
         const optional<float>& noDataValue() const { return _noDataValue; }
 
-        optional<float>& noDataMinValue() { return _noDataMinValue; }
-        const optional<float>& noDataMinValue() const { return _noDataMinValue; }
+        /** For heightfields, treat everything below this value as a "no data" marker. */
+        optional<float>& minValidValue() { return _minValidValue; }
+        const optional<float>& minValidValue() const { return _minValidValue; }
 
-        optional<float>& noDataMaxValue() { return _noDataMaxValue; }
-        const optional<float>& noDataMaxValue() const { return _noDataMaxValue; }
+        /** For heightfields, treat everything above this value as a "no data" marker. */
+        optional<float>& maxValidValue() { return _maxValidValue; }
+        const optional<float>& maxValidValue() const { return _maxValidValue; }
 
+        /** File in which to store a tile blacklist. */
         optional<std::string>& blacklistFilename() { return _blacklistFilename; }
         const optional<std::string>& blacklistFilename() const { return _blacklistFilename; }
 
+        /** Define a profile for this source, overriding the one reported by the source. */
         optional<ProfileOptions>& profile() { return _profileOptions; }
         const optional<ProfileOptions>& profile() const { return _profileOptions; }
 
+        /** Size of the in-memory cache (in entries; default=16) */
         optional<int>& L2CacheSize() { return _L2CacheSize; }
         const optional<int>& L2CacheSize() const { return _L2CacheSize; }
 
+        /** Whether to use bilinear sampling when reprojecting data from this source
+         *  (default = true) */
         optional<bool>& bilinearReprojection() { return _bilinearReprojection; }
         const optional<bool>& bilinearReprojection() const { return _bilinearReprojection; }
 
     public:
+        /** For backwards-compatibility; use minValidValue() instead 
+         *  @deprecated */
+        optional<float>& noDataMinValue() { return _minValidValue; }
+        const optional<float>& noDataMinValue() const { return _minValidValue; }
+
+        /** For backwards-compatibility; use maxValidValue() instead 
+         *  @deprecated */
+        optional<float>& noDataMaxValue() { return _maxValidValue; }
+        const optional<float>& noDataMaxValue() const { return _maxValidValue; }
+
+    public:
         TileSourceOptions( const ConfigOptions& options =ConfigOptions() );
 
         /** dtor */
@@ -101,14 +116,13 @@ namespace osgEarth
         void fromConfig( const Config& conf );
 
         optional<int>            _tileSize;
-        optional<float>          _noDataValue, _noDataMinValue, _noDataMaxValue;
+        optional<float>          _noDataValue, _minValidValue, _maxValidValue;
         optional<ProfileOptions> _profileOptions;
         optional<std::string>    _blacklistFilename;
         optional<int>            _L2CacheSize;
         optional<bool>           _bilinearReprojection;
     };
 
-    typedef std::vector<TileSourceOptions> TileSourceOptionsVector;
 
     /**
      * A collection of tiles that should be considered blacklisted
@@ -258,6 +272,14 @@ namespace osgEarth
         bool isValid() const { return isOK(); }
 
         /**
+         * TimeStamp indicating the last time the data at this source changed
+         * Default is 0, i.e. the beginning of time itself (1970). It is up to 
+         * the TileSource implementation whether to populate this field. The
+         * Map engine can use it for per-session caching purposes.
+         */
+        virtual TimeStamp getLastModifiedTime() const { return 0; }
+
+        /**
          * Gets the Profile of the TileSource
          */
         virtual const Profile* getProfile() const;
@@ -278,10 +300,11 @@ namespace osgEarth
          * Gets the nodata max value
          */
         virtual float getNoDataMaxValue() {
-            return _options.noDataMaxValue().value(); }        
+            return _options.noDataMaxValue().value(); }
 
         /**
          * Gets the preferred extension for this TileSource
+         * @deprecated No longer used
          */
         virtual std::string getExtension() const {return "png";}
 
@@ -297,6 +320,12 @@ namespace osgEarth
         virtual bool hasData(const TileKey& key) const;
 
         /**
+         * Whether or not the source has data to create fallback tile for 
+         * the given TileKey
+         */
+        virtual bool hasDataForFallback(const TileKey& key) const;
+
+        /**
          * Whether the tile source can generate data for the specified LOD.
          */
         virtual bool hasDataAtLOD( unsigned lod ) const;
@@ -318,15 +347,8 @@ namespace osgEarth
          * cache if one is configured. But a TileSource can report that caching should
          * not be used (for whatever reason) by returning CachePolicy::NO_CACHE.
          */
-        virtual CachePolicy getCachePolicyHint() const { return CachePolicy::DEFAULT; }
-
-
-        /**
-         * Returns true if data from this source can be cached to disk
-         * @deprecated. Use getCachePolicyHint instead.
-         */
-        bool supportsPersistentCaching() const {
-            return getCachePolicyHint() != CachePolicy::NO_CACHE; }
+        virtual CachePolicy getCachePolicyHint(const Profile* targetProfile) const 
+            { return CachePolicy::DEFAULT; }
 
         /**
          * Starts up the tile source.
diff --git a/src/osgEarth/TileSource.cpp b/src/osgEarth/TileSource.cpp
index 2fd33a8..9790e9c 100644
--- a/src/osgEarth/TileSource.cpp
+++ b/src/osgEarth/TileSource.cpp
@@ -147,8 +147,8 @@ TileSourceOptions::TileSourceOptions( const ConfigOptions& options ) :
 DriverConfigOptions   ( options ),
 _tileSize             ( 256 ),
 _noDataValue          ( (float)SHRT_MIN ),
-_noDataMinValue       ( -32000.0f ),
-_noDataMaxValue       (  32000.0f ),
+_minValidValue        ( -32000.0f ),
+_maxValidValue        (  32000.0f ),
 _L2CacheSize          ( 16 ),
 _bilinearReprojection ( true )
 { 
@@ -162,8 +162,10 @@ TileSourceOptions::getConfig() const
     Config conf = DriverConfigOptions::getConfig();
     conf.updateIfSet( "tile_size", _tileSize );
     conf.updateIfSet( "nodata_value", _noDataValue );
-    conf.updateIfSet( "nodata_min", _noDataMinValue );
-    conf.updateIfSet( "nodata_max", _noDataMaxValue );
+    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 );
@@ -185,8 +187,8 @@ TileSourceOptions::fromConfig( const Config& conf )
 {
     conf.getIfSet( "tile_size", _tileSize );
     conf.getIfSet( "nodata_value", _noDataValue );
-    conf.getIfSet( "nodata_min", _noDataMinValue );
-    conf.getIfSet( "nodata_max", _noDataMaxValue );
+    conf.getIfSet( "nodata_min", _minValidValue );
+    conf.getIfSet( "nodata_max", _maxValidValue );
     conf.getIfSet( "blacklist_filename", _blacklistFilename);
     conf.getIfSet( "l2_cache_size", _L2CacheSize );
     conf.getIfSet( "bilinear_reprojection", _bilinearReprojection );
@@ -316,7 +318,7 @@ TileSource::createImage(const TileKey&        key,
     // Try to get it from the memcache fist
     if (_memCache.valid())
     {
-        ReadResult r = _memCache->getOrCreateDefaultBin()->readImage( key.str() );
+        ReadResult r = _memCache->getOrCreateDefaultBin()->readImage( key.str(), 0 );
         if ( r.succeeded() )
             return r.releaseImage();
     }
@@ -346,7 +348,7 @@ TileSource::createHeightField(const TileKey&        key,
     // Try to get it from the memcache first:
     if (_memCache.valid())
     {
-        ReadResult r = _memCache->getOrCreateDefaultBin()->readObject( key.str() );
+        ReadResult r = _memCache->getOrCreateDefaultBin()->readObject( key.str(), 0 );
         if ( r.succeeded() )
             return r.release<osg::HeightField>();
     }
@@ -473,6 +475,31 @@ TileSource::hasData(const osgEarth::TileKey& key) const
     return intersectsData;
 }
 
+bool
+TileSource::hasDataForFallback(const osgEarth::TileKey& key) const
+{
+    //sematics: might have data.
+
+    //If no data extents are provided, just return true
+    if (_dataExtents.size() == 0) 
+        return true;
+
+    const osgEarth::GeoExtent& keyExtent = key.getExtent();
+    bool intersectsData = false;
+
+    for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr)
+    {
+        if ((keyExtent.intersects( *itr )) && 
+            (!itr->minLevel().isSet() || itr->minLevel() <= key.getLOD()))
+        {
+            intersectsData = true;
+            break;
+        }
+    }
+
+    return intersectsData;
+}
+
 TileBlacklist*
 TileSource::getBlacklist()
 {
diff --git a/src/osgEarth/TraversalData b/src/osgEarth/TraversalData
index 9e6b7f1..eeaad25 100644
--- a/src/osgEarth/TraversalData
+++ b/src/osgEarth/TraversalData
@@ -65,10 +65,10 @@ namespace osgEarth
         MapNodeCullData();
 
     public:
-        osg::observer_ptr<class MapNode>  _mapNode;
-        osg::ref_ptr<osg::StateSet> _stateSet;
-        osg::ref_ptr<osg::Uniform>  _windowScaleMatrix;
-        double                      _cameraAltitude;
+        osg::observer_ptr<class MapNode> _mapNode;
+        osg::ref_ptr<osg::StateSet>      _stateSet;
+        osg::ref_ptr<osg::Uniform>       _windowScaleMatrixUniform;
+        double                           _cameraAltitude;
 
     protected:
         virtual ~MapNodeCullData() { }
diff --git a/src/osgEarth/TraversalData.cpp b/src/osgEarth/TraversalData.cpp
index e474816..25f0fb1 100644
--- a/src/osgEarth/TraversalData.cpp
+++ b/src/osgEarth/TraversalData.cpp
@@ -24,11 +24,11 @@ MapNodeCullData::MapNodeCullData()
 {
     _stateSet = new osg::StateSet();
 
-    _windowScaleMatrix = new osg::Uniform(osg::Uniform::FLOAT_MAT3, "oe_WindowScaleMatrix");
+    _windowScaleMatrixUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT3, "oe_WindowScaleMatrix");
     osg::Matrix3 identity;
     identity.makeIdentity();
-    _windowScaleMatrix->set( identity );
-    _stateSet->addUniform( _windowScaleMatrix.get() );
+    _windowScaleMatrixUniform->set( identity );
+    _stateSet->addUniform( _windowScaleMatrixUniform.get() );
 
     _cameraAltitude = 0.0;
 }
diff --git a/src/osgEarth/URI b/src/osgEarth/URI
index e2b98ca..5ce1f50 100644
--- a/src/osgEarth/URI
+++ b/src/osgEarth/URI
@@ -23,9 +23,7 @@
 #include <osgEarth/CacheBin>
 #include <osgEarth/CachePolicy>
 #include <osgEarth/Containers>
-#include <osgEarth/FileUtils>
 #include <osgEarth/IOTypes>
-#include <osgEarth/Progress>
 #include <osg/Image>
 #include <osg/Node>
 #include <osgDB/ReaderWriter>
@@ -35,6 +33,7 @@
 namespace osgEarth
 {
     class URI;
+    class ProgressCallback;
 
     /**
      * Context for resolving relative URIs.
diff --git a/src/osgEarth/URI.cpp b/src/osgEarth/URI.cpp
index afcec01..ba70221 100644
--- a/src/osgEarth/URI.cpp
+++ b/src/osgEarth/URI.cpp
@@ -21,6 +21,8 @@
 #include <osgEarth/CacheBin>
 #include <osgEarth/HTTPClient>
 #include <osgEarth/Registry>
+#include <osgEarth/Progress>
+#include <osgEarth/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/ReadFile>
 #include <osgDB/ReaderWriter>
@@ -248,9 +250,15 @@ namespace
 
                 osgDB::ReaderWriter::ReadResult rr = archive->readObject(fileName, options.get());
                 if ( rr.success() )
-                    return ReadResult(rr.takeObject());
+                {
+                    ReadResult result( rr.takeObject() );
+                    result.setLastModifiedTime( osgEarth::getLastModifiedTime(uri) );
+                    return result;
+                }
                 else
+                {
                     return ReadResult();
+                }
             }
         }
 
@@ -263,7 +271,9 @@ namespace
             buf << input.rdbuf();
             std::string bufStr;
             bufStr = buf.str();
-            return ReadResult( new StringObject(bufStr) );
+            ReadResult result( new StringObject(bufStr) );
+            result.setLastModifiedTime( osgEarth::getLastModifiedTime(uri) );
+            return result;
         }
 
         // no good
@@ -278,7 +288,7 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_OBJECTS) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readObject(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, double maxAge ) { return bin->readObject(key, maxAge); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readObject(key, minTime); }
         ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readObject(uri, opt, p); }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readObjectFile(uri, opt)); }
     };
@@ -287,7 +297,7 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_NODES) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readNode(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, double maxAge ) { return bin->readObject(key, maxAge); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readObject(key, minTime); }
         ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readNode(uri, opt, p); }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return ReadResult(osgDB::readNodeFile(uri, opt)); }
     };
@@ -302,8 +312,8 @@ namespace
             if ( r.getImage() ) r.getImage()->setFileName(uri);
             return r;
         }                
-        ReadResult fromCache( CacheBin* bin, const std::string& key, double maxAge ) { 
-            ReadResult r = bin->readImage(key, maxAge);
+        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime ) { 
+            ReadResult r = bin->readImage(key, minTime);
             if ( r.getImage() ) r.getImage()->setFileName( key );
             return r;
         }
@@ -323,7 +333,7 @@ namespace
     {
         bool callbackRequestsCaching( URIReadCallback* cb ) const { return !cb || ((cb->cachingSupport() & URIReadCallback::CACHE_STRINGS) != 0); }
         ReadResult fromCallback( URIReadCallback* cb, const std::string& uri, const osgDB::Options* opt ) { return cb->readString(uri, opt); }
-        ReadResult fromCache( CacheBin* bin, const std::string& key, double maxAge ) { return bin->readString(key, maxAge); }
+        ReadResult fromCache( CacheBin* bin, const std::string& key, TimeStamp minTime) { return bin->readString(key, minTime); }
         ReadResult fromHTTP( const std::string& uri, const osgDB::Options* opt, ProgressCallback* p ) { return HTTPClient::readString(uri, opt, p); }
         ReadResult fromFile( const std::string& uri, const osgDB::Options* opt ) { return readStringFile(uri, opt); }
     };
@@ -337,7 +347,9 @@ namespace
         const URI&            inputURI,
         const osgDB::Options* dbOptions,
         ProgressCallback*     progress)
-    {
+    {        
+        //osg::Timer_t startTime = osg::Timer::instance()->tick();
+
         ReadResult result;
 
         if ( !inputURI.empty() )
@@ -417,7 +429,7 @@ namespace
                     // first try to go to the cache if there is one:
                     if ( bin && cp->isCacheReadable() )
                     {
-                        result = reader.fromCache( bin, uri.cacheKey(), *cp->maxAge() );
+                        result = reader.fromCache( bin, uri.cacheKey(), cp->getMinAcceptTime() );
                         if ( result.succeeded() )
                             result.setIsFromCache(true);
                     }
@@ -466,7 +478,7 @@ namespace
                         << std::endl;
                 }
 
-                    
+
                 if ( result.getObject() && !gotResultFromCallback )
                 {
                     result.getObject()->setName( uri.base() );
@@ -486,6 +498,21 @@ namespace
             (*post)(result);
         }
 
+        /*
+        osg::Timer_t endTime = osg::Timer::instance()->tick();
+
+        double time = osg::Timer::instance()->delta_s( startTime, endTime );
+        {
+            OpenThreads::ScopedLock< OpenThreads::Mutex > lock( s_statsLock );            
+            totalTime += time;
+            totalRequests += 1;
+            double avg = (double)totalRequests / totalTime;
+            OE_NOTICE << "total req = " << totalRequests << " totalTime = " << totalTime << " " << avg << " req/s" << std::endl;            
+        }
+        */
+
+        
+
         return result;
     }
 }
diff --git a/src/osgEarth/Utils b/src/osgEarth/Utils
index 5f1d52c..1dc0589 100644
--- a/src/osgEarth/Utils
+++ b/src/osgEarth/Utils
@@ -151,6 +151,29 @@ namespace osgEarth
 
         osg::ref_ptr<T> _prototype;
     };
+
+    /**
+     * Shim to apply vertex cache optimizations to geometry when it's legal.
+     * This is really only here to work around an OSG bug in the VertexAccessOrder
+     * optimizer, which corrupts non-Triangle geometries.
+     */
+    struct OSGEARTH_EXPORT VertexCacheOptimizer : public osg::NodeVisitor
+    {
+        VertexCacheOptimizer();
+        virtual ~VertexCacheOptimizer() { }
+        void apply( osg::Geode& node );
+    };
+
+    /**
+     * Sets the data variance on all discovered drawables.
+     */
+    struct OSGEARTH_EXPORT SetDataVarianceVisitor : public osg::NodeVisitor
+    {
+        SetDataVarianceVisitor(osg::Object::DataVariance value);
+        virtual ~SetDataVarianceVisitor() { }
+        void apply( osg::Geode& node );
+        osg::Object::DataVariance _value;
+    };
 }
 
 #endif // OSGEARTH_UTILS_H
diff --git a/src/osgEarth/Utils.cpp b/src/osgEarth/Utils.cpp
index c02d3c0..7769312 100644
--- a/src/osgEarth/Utils.cpp
+++ b/src/osgEarth/Utils.cpp
@@ -20,6 +20,7 @@
 #include <osg/Version>
 #include <osg/CoordinateSystemNode>
 #include <osg/MatrixTransform>
+#include <osgUtil/MeshOptimizers>
 
 using namespace osgEarth;
 
@@ -260,3 +261,90 @@ PixelAutoTransform::dirty()
     _dirty = true;
     setCullingActive( false );
 }
+
+//-----------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[VertexCacheOptimizer] "
+
+VertexCacheOptimizer::VertexCacheOptimizer() :
+osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ) 
+{
+    //nop
+}
+
+void
+VertexCacheOptimizer::apply(osg::Geode& geode)
+{
+    if (geode.getDataVariance() == osg::Object::DYNAMIC)
+        return;
+
+    for(unsigned i=0; i<geode.getNumDrawables(); ++i )
+    {
+        osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
+
+        if ( geom )
+        {
+            if ( geom->getDataVariance() == osg::Object::DYNAMIC )
+                return;
+
+            // vertex cache optimizations currently only support surface geometries.
+            // all or nothing in the geode.
+            osg::Geometry::PrimitiveSetList& psets = geom->getPrimitiveSetList();
+            for( osg::Geometry::PrimitiveSetList::iterator i = psets.begin(); i != psets.end(); ++i )
+            {
+                switch( (*i)->getMode() )
+                {
+                case GL_TRIANGLES:
+                case GL_TRIANGLE_FAN:
+                case GL_TRIANGLE_STRIP:
+                case GL_QUADS:
+                case GL_QUAD_STRIP:
+                case GL_POLYGON:
+                    break;
+
+                default:
+                    return;
+                }
+            }
+        }
+    }
+
+    //OE_NOTICE << LC << "VC optimizing..." << std::endl;
+
+    // passed the test; run the optimizer.
+    osgUtil::VertexCacheVisitor vcv;
+    geode.accept( vcv );
+    vcv.optimizeVertices();
+
+    osgUtil::VertexAccessOrderVisitor vaov;
+    geode.accept( vaov );
+    vaov.optimizeOrder();
+
+    traverse( geode );
+}
+
+//-----------------------------------------------------------------------------
+
+#undef  LC
+#define LC "[SetDataVarianceVisitor] "
+
+SetDataVarianceVisitor::SetDataVarianceVisitor(osg::Object::DataVariance value) :
+osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ),
+_value( value )
+{
+    //nop
+}
+
+void
+SetDataVarianceVisitor::apply(osg::Geode& geode)
+{
+    for(unsigned i=0; i<geode.getNumDrawables(); ++i)
+    {
+        osg::Drawable* d = geode.getDrawable(i);
+        if ( d )
+            d->setDataVariance( _value );
+    }
+
+    traverse(geode);
+}
diff --git a/src/osgEarth/Version b/src/osgEarth/Version
index cf5e5b9..ef9303d 100644
--- a/src/osgEarth/Version
+++ b/src/osgEarth/Version
@@ -25,7 +25,7 @@
 extern "C" {
 
 #define OSGEARTH_MAJOR_VERSION    2
-#define OSGEARTH_MINOR_VERSION    4
+#define OSGEARTH_MINOR_VERSION    5
 #define OSGEARTH_PATCH_VERSION    0
 #define OSGEARTH_SOVERSION        0
 #define OSGEARTH_RC_VERSION       0
@@ -41,6 +41,8 @@ extern "C" {
 #define OSGEARTH_VERSION_GREATER_THAN(MAJOR, MINOR, PATCH) ((OSGEARTH_MAJOR_VERSION>MAJOR) || (OSGEARTH_MAJOR_VERSION==MAJOR && (OSGEARTH_MINOR_VERSION>MINOR || (OSGEARTH_MINOR_VERSION==MINOR && OSGEARTH_PATCH_VERSION>PATCH))))
 #define OSGEARTH_VERSION_GREATER_OR_EQUAL(MAJOR, MINOR, PATCH) ((OSGEARTH_MAJOR_VERSION>MAJOR) || (OSGEARTH_MAJOR_VERSION==MAJOR && (OSGEARTH_MINOR_VERSION>MINOR || (OSGEARTH_MINOR_VERSION==MINOR && OSGEARTH_PATCH_VERSION>=PATCH))))
 
+/** embedded GIT SHA1 */
+extern OSGEARTH_EXPORT const char* osgEarthGitSHA1();
 
 /**
   * osgEarthGetVersion() returns the library version number.
diff --git a/src/osgEarth/Version.cpp b/src/osgEarth/Version.cpp
index c601f36..55c953f 100644
--- a/src/osgEarth/Version.cpp
+++ b/src/osgEarth/Version.cpp
@@ -32,11 +32,20 @@ const char* osgEarthGetVersion()
     {
         if (OSGEARTH_RC_VERSION == 0 )
         {
-            sprintf(osgearth_version,"%d.%d.%d",OSGEARTH_MAJOR_VERSION,OSGEARTH_MINOR_VERSION,OSGEARTH_PATCH_VERSION);
+            sprintf(osgearth_version,"%d.%d.%d (%s)",
+                OSGEARTH_MAJOR_VERSION,
+                OSGEARTH_MINOR_VERSION,
+                OSGEARTH_PATCH_VERSION,
+                osgEarthGitSHA1() );
         }
         else
         {
-            sprintf(osgearth_version,"%d.%d.%d RC%d",OSGEARTH_MAJOR_VERSION,OSGEARTH_MINOR_VERSION,OSGEARTH_PATCH_VERSION, OSGEARTH_RC_VERSION);
+            sprintf(osgearth_version,"%d.%d.%d RC%d (%s)",
+                OSGEARTH_MAJOR_VERSION,
+                OSGEARTH_MINOR_VERSION,
+                OSGEARTH_PATCH_VERSION,
+                OSGEARTH_RC_VERSION,
+                osgEarthGitSHA1() );
         }
 
         osgearth_version_init = 0;
diff --git a/src/osgEarth/VersionGit.cpp.in b/src/osgEarth/VersionGit.cpp.in
new file mode 100644
index 0000000..b0f86ac
--- /dev/null
+++ b/src/osgEarth/VersionGit.cpp.in
@@ -0,0 +1,7 @@
+#include <osgEarth/Version>
+#define OSGEARTH_GIT_SHA1_STRING "@OSGEARTH_GIT_SHA1@"
+extern "C" {
+    const char* osgEarthGitSHA1() {
+        return OSGEARTH_GIT_SHA1_STRING;
+    }
+}
diff --git a/src/osgEarth/VirtualProgram b/src/osgEarth/VirtualProgram
index 43b8fdd..7883a2e 100644
--- a/src/osgEarth/VirtualProgram
+++ b/src/osgEarth/VirtualProgram
@@ -31,7 +31,7 @@
 
 #ifdef OSG_GLES2_AVAILABLE
 #    define GLSL_VERSION_STR             "100"
-#    define GLSL_DEFAULT_PRECISION_FLOAT "precision mediump float;"
+#    define GLSL_DEFAULT_PRECISION_FLOAT "precision highp float;"
 #else
 #    define GLSL_VERSION_STR             "110" 
 #    define GLSL_DEFAULT_PRECISION_FLOAT ""
@@ -70,33 +70,59 @@ namespace osgEarth
 
 
     /**
-     * VirtualProgram enables basic GLSL shader composition within osgEarth.
+     * VirtualProgram enables GLSL shader composition within osgEarth. It automatically
+     * assembles shader functions into a full shader program at run time. You can add
+     * or remove functions (injection points) at any time.
      *
-     * VirtualProgram has been adapted from the VirtualProgram shader composition work
-     * originally done by Wojciech Lewandowski and found in OSG's osgvirtualprogram
-     * example, and is used by permission.
+     * Read about shader composition:
+     * http://docs.osgearth.org/en/latest/developer/shader_composition.html
      *
-     * VirtualProgram in a state attribute. Once you've installed it on a StateSet,
-     * you can still call the set* functions but you'd better mark the state set with
-     * a DYNAMIC data variance.
+     * VirtualProgram (VP) is an osg::StateAttribute. But unlike most attributes, a VP
+     * will inherit properties from other VPs in the state stack.
+     *
+     * Though the code has evolved quite a bit, VirtualProgram was originally adapted
+     * from the VirtualProgram shader composition work done by Wojciech Lewandowski and
+     * found in OSG's osgvirtualprogram example.
      */
-    class OSGEARTH_EXPORT VirtualProgram : public osg::StateAttribute // osg::Program
+    class OSGEARTH_EXPORT VirtualProgram : public osg::StateAttribute
     {
     public:
         static const osg::StateAttribute::Type SA_TYPE;
 
-        typedef osg::Program::AttribBindingList AttribBindingList;
+        /**
+        * Gets the VP on a stateset, creating and installing one if the stateset
+        * does not already have one. This is a convenient patternt to use, since
+        * the normal use-case is to add functions to an existing VP rather than
+        * to replace it entirely.
+        */
+        static VirtualProgram* getOrCreate(osg::StateSet* on);
+
+        /**
+        * Gets the VP on a stateset, or NULL if one is not found.
+        */
+        static VirtualProgram* get(osg::StateSet* on);
+        static const VirtualProgram* get(const osg::StateSet* on);
+
+        /**
+        * Clones the VP on a stateset, or creates a new one
+        */
+        static VirtualProgram* cloneOrCreate(const osg::StateSet* src, osg::StateSet* dest);
+
 
     public:
         /**
-         * Adds a shader function to the program.
+         * Adds a custom shader function to the program.
+         *
          * Call this method (rather than setShader directly) to inject "user" functions into the
          * shader program.
          *
-         * name: name of the function. This should be the actual function name in the shader source.
-         * source: the shader source code.
+         * name:     Name of the function. This should be the actual function name in the shader source.
+         * source:   The shader source code.
          * location: Function location relative to the built-ins.
          * priority: Lets you control the order of functions that you inject at the same location.
+         *           The default priority is 1.0. Note that many of osgEarth's built-in shaders (like
+         *           those that render the terrain) use priority 0.0 so that by default they run before
+         *           user-injected functions.
          */
         void setFunction( 
             const std::string&           name,
@@ -168,47 +194,73 @@ namespace osgEarth
         void removeBindAttribLocation( const std::string& name );
 
         /** Gets a reference to the attribute bindings. */
+        typedef osg::Program::AttribBindingList AttribBindingList;
         const AttribBindingList& getAttribBindingList() const { return _attribBindingList; }
 
         /** Access to the property template. */
         osg::Program* getTemplate() { return _template.get(); }
         const osg::Program* getTemplate() const { return _template.get(); }
 
+
+    public: // StateAttribute
+        virtual void compileGLObjects(osg::State& state) const;
+        virtual void resizeGLObjectBuffers(unsigned maxSize);
+
+        /** If State is non-zero, this function releases any associated OpenGL objects for
+           * the specified graphics context. Otherwise, releases OpenGL objects
+           * for all graphics contexts. */
+        virtual void releaseGLObjects(osg::State* pState) const;
+
     public:
         typedef std::vector< osg::ref_ptr<osg::Shader> > ShaderVector;
 
-    protected:
-
+    public:
         typedef std::pair< osg::ref_ptr<osg::Shader>, osg::StateAttribute::OverrideValue > ShaderEntry;
         typedef std::map< std::string, ShaderEntry > ShaderMap;
         typedef std::map< ShaderVector, osg::ref_ptr<osg::Program> > ProgramMap;
+        typedef std::map< std::string, std::string > AttribAliasMap;
+        typedef std::pair< std::string, std::string > AttribAlias;
+        typedef std::vector< AttribAlias > AttribAliasVector;
 
-        osg::ref_ptr<osg::Program>   _template;
+    public:
+        // thread-safe functions map getter
+        void getFunctions( ShaderComp::FunctionLocationMap& out ) const;
 
-        ProgramMap                   _programCache;
-        ShaderMap                    _shaderMap;
-        unsigned int                 _mask;
-        AttribBindingList            _attribBindingList;
-        bool                         _useLightingShaders;
-        //osg::ref_ptr<osg::Shader>    _vertMain;
-        //osg::ref_ptr<osg::Shader>    _fragMain;
+        // thread-safe shader map getter
+        void getShaderMap( ShaderMap& out ) const;
 
+    protected:
+        // holds "template" data that should be installed in every auto-generated
+        // Program, like uniform buffer bindings, etc.
+        osg::ref_ptr<osg::Program> _template;
+
+        unsigned int       _mask;
+        AttribBindingList  _attribBindingList;
+        AttribAliasMap     _attribAliases;
+
+        // holds the injection points the user has added to this VP.
+        // _dataModelMutex protects access to this member.
         ShaderComp::FunctionLocationMap _functions;
-        ShaderComp::FunctionLocationMap _accumulatedFunctions;
 
-        Threading::Mutex _functionsMutex;
-        bool _inherit;
+        // holds a map of each named shader installed in this VP.
+        // _dataModelMutex protects access to this member.
+        ShaderMap _shaderMap;
+
+        // protects access to the data members, which may be accessed by other VPs in the state stack.
+        mutable Threading::ReadWriteMutex _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;
 
-        bool hasLocalFunctions() const;
-        void refreshAccumulatedFunctions( const osg::State& state );
-        void addToAccumulatedMap(ShaderMap& accumShaderMap, const std::string& shaderID, const ShaderEntry& newEntry) const;
-        osg::Program* buildProgram( osg::State& state, ShaderMap& accumShaderMap, AttribBindingList& bindings );
-        void addShadersToProgram(const ShaderVector& shaders, const AttribBindingList& attribBindings, osg::Program* program );
-        void addTemplateDataToProgram(osg::Program* program );
+        bool _inherit;
+        bool _inheritSet;
 
-    public:
-        void getFunctions( ShaderComp::FunctionLocationMap& out ) const;
+        bool hasLocalFunctions() const;
+        void accumulateFunctions( const osg::State& state, ShaderComp::FunctionLocationMap& result ) const;
+        const AttribAliasMap& getAttribAliases() const { return _attribAliases; }
     };
 
 } // namespace osgEarth
diff --git a/src/osgEarth/VirtualProgram.cpp b/src/osgEarth/VirtualProgram.cpp
index 30354f7..07a2f91 100644
--- a/src/osgEarth/VirtualProgram.cpp
+++ b/src/osgEarth/VirtualProgram.cpp
@@ -27,6 +27,7 @@
 #include <osg/State>
 #include <osg/Notify>
 #include <sstream>
+#include <OpenThreads/Thread>
 
 #define LC "[VirtualProgram] "
 
@@ -36,6 +37,8 @@ using namespace osgEarth::ShaderComp;
 #define OE_TEST OE_NULL
 //#define OE_TEST OE_NOTICE
 
+//#define USE_ATTRIB_ALIASES
+
 //------------------------------------------------------------------------
 
 // environment variable control
@@ -45,6 +48,7 @@ using namespace osgEarth::ShaderComp;
 namespace
 {
 #ifdef OSG_GLES2_AVAILABLE
+    // GLES requires all shader code be merged into a since source
     bool s_mergeShaders = true;
 #else
     bool s_mergeShaders = false;
@@ -52,8 +56,10 @@ namespace
 
     bool s_dumpShaders = false;        // debugging
 
-    /** A hack for OSG 2.8.x to get access to the state attribute vector. */
-    /** TODO: no longer needed in OSG 3+ ?? */
+    /** A device that lets us do a const search on the State's attribute map. OSG does not yet
+        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 
     {
     public:        
@@ -72,6 +78,301 @@ namespace
             return sh->getAttributeVec( attribute );
         }
     };
+    typedef std::map<std::string, std::string> HeaderMap;
+
+    // removes leading and trailing whitespace, and replaces all other
+    // whitespace with single spaces
+    std::string trimAndCompress(const std::string& in)
+    {
+        bool inwhite = true;
+        std::stringstream buf;
+        for( unsigned i=0; i<in.length(); ++i )
+        {
+            char c = in.at(i);
+            if ( ::isspace(c) )
+            {
+                if ( !inwhite )
+                {
+                    buf << ' ';
+                    inwhite = true;
+                }
+            }
+            else
+            {
+                inwhite = false;
+                buf << c;
+            }
+        }
+        std::string r;
+        r = buf.str();
+        return trim(r);
+    }
+
+
+    void parseShaderForMerging( const std::string& source, unsigned& version, HeaderMap& headers, std::stringstream& body )
+    {
+        // break into lines:
+        StringVector lines;
+        StringTokenizer( source, lines, "\n", "", true, false );
+
+        for( StringVector::const_iterator line_iter = lines.begin(); line_iter != lines.end(); ++line_iter )
+        {
+            std::string line = trimAndCompress(*line_iter);
+
+            if ( line.size() > 0 )
+            {
+                StringVector tokens;
+                StringTokenizer( line, tokens, " \t", "", false, true );
+
+                if (tokens[0] == "#version")
+                {
+                    // find the highest version number.
+                    if ( tokens.size() > 1 )
+                    {
+                        unsigned newVersion = osgEarth::as<unsigned>(tokens[1], 0);
+                        if ( newVersion > version )
+                        {
+                            version = newVersion;
+                        }
+                    }
+                }
+
+                else if (
+                    tokens[0] == "#extension"   ||
+                    tokens[0] == "#define"      ||
+                    tokens[0] == "precision"    ||
+                    tokens[0] == "struct"       ||
+                    tokens[0] == "varying"      ||
+                    tokens[0] == "uniform"      ||
+                    tokens[0] == "attribute")
+                {
+                    std::string& header = headers[line];
+                    header = line;
+                }
+
+                else
+                {
+                    body << (*line_iter) << "\n";
+                }
+            }
+        }
+    }
+
+
+    bool s_attribAliasSortFunc(const std::pair<std::string,std::string>& a, const std::pair<std::string,std::string>& b) {
+        return a.first.size() > b.first.size();
+    }
+
+
+    /**
+    * Replaces a shader's attribute values with their aliases
+    */
+    void applyAttributeAliases(
+        osg::Shader*                             shader, 
+        const VirtualProgram::AttribAliasVector& sortedAliases)
+    {
+        std::string src = shader->getShaderSource();
+        for( VirtualProgram::AttribAliasVector::const_iterator i = sortedAliases.begin(); i != sortedAliases.end(); ++i )
+        {
+            //OE_DEBUG << LC << "Replacing " << i->first << " with " << i->second << std::endl;
+            osgEarth::replaceIn( src, i->first, i->second );
+        }
+        shader->setShaderSource( src );
+    }
+
+
+    /**
+    * Adds a new shader entry to the accumulated shader map, respecting the
+    * override policy of both the existing entry (if there is one) and the 
+    * new entry.
+    */
+    void addToAccumulatedMap(VirtualProgram::ShaderMap&         accumShaderMap,
+                             const std::string&                 shaderID,
+                             const VirtualProgram::ShaderEntry& newEntry)
+    {
+        const osg::StateAttribute::OverrideValue& ov = newEntry.second;
+
+        // see if we're trying to disable a previous entry:
+        if ((ov & osg::StateAttribute::ON) == 0 ) //TODO: check for higher override
+        {
+            // yes? remove it!
+            accumShaderMap.erase( shaderID );
+        }
+
+        else
+        {
+            // see if there's a higher-up entry with the same ID:
+            VirtualProgram::ShaderEntry& accumEntry = accumShaderMap[ shaderID ]; 
+
+            // make sure we can add the new one:
+            if ((accumEntry.first.get() == 0L ) ||                           // empty slot, fill it
+                ((ov & osg::StateAttribute::PROTECTED) != 0) ||              // new entry is protected
+                ((accumEntry.second & osg::StateAttribute::OVERRIDE) == 0) ) // old entry does NOT override
+            {
+                accumEntry = newEntry;
+            }
+        }
+    }
+
+
+    /**
+    * Apply the data binding information from a template program to the
+    * target program.
+    */
+    void addTemplateDataToProgram( const osg::Program* templateProgram, osg::Program* program )
+    {
+        const osg::Program::FragDataBindingList& fbl = templateProgram->getFragDataBindingList();
+        for( osg::Program::FragDataBindingList::const_iterator i = fbl.begin(); i != fbl.end(); ++i )
+            program->addBindFragDataLocation( i->first, i->second );
+
+        const osg::Program::UniformBlockBindingList& ubl = templateProgram->getUniformBlockBindingList();
+        for( osg::Program::UniformBlockBindingList::const_iterator i = ubl.begin(); i != ubl.end(); ++i )
+            program->addBindUniformBlock( i->first, i->second );
+    }
+
+
+    /**
+    * Populates the specified Program with passed-in shaders.
+    */
+    void addShadersToProgram(const VirtualProgram::ShaderVector&      shaders, 
+                             const VirtualProgram::AttribBindingList& attribBindings,
+                             const VirtualProgram::AttribAliasMap&    attribAliases,
+                             osg::Program*                            program )
+    {
+#ifdef USE_ATTRIB_ALIASES
+        // apply any vertex attribute aliases. But first, sort them from longest to shortest 
+        // so we don't get any overlap and bad replacements.
+        VirtualProgram::AttribAliasVector sortedAliases;
+        sortedAliases.reserve( attribAliases.size() );
+        sortedAliases.insert(sortedAliases.begin(), attribAliases.begin(), attribAliases.end());
+        std::sort( sortedAliases.begin(), sortedAliases.end(), s_attribAliasSortFunc );
+
+        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
+        {
+            osg::Shader* shader = i->get();
+            applyAttributeAliases( shader, sortedAliases );
+        }
+#endif
+
+        // merge the shaders if necessary.
+        if ( s_mergeShaders )
+        {
+            unsigned          vertVersion = 0;
+            HeaderMap         vertHeaders;
+            std::stringstream vertBody;
+
+            unsigned          fragVersion = 0;
+            HeaderMap         fragHeaders;
+            std::stringstream fragBody;
+
+            // parse the shaders, combining header lines and finding the highest version:
+            for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
+            {
+                osg::Shader* s = i->get();
+                if ( s->getType() == osg::Shader::VERTEX )
+                {
+                    parseShaderForMerging( s->getShaderSource(), vertVersion, vertHeaders, vertBody );
+                }
+                else if ( s->getType() == osg::Shader::FRAGMENT )
+                {
+                    parseShaderForMerging( s->getShaderSource(), fragVersion, fragHeaders, fragBody );
+                }
+            }
+
+            // write out the merged shader code:
+            std::string vertBodyText;
+            vertBodyText = vertBody.str();
+            std::stringstream vertShaderBuf;
+            if ( vertVersion > 0 )
+                vertShaderBuf << "#version " << vertVersion << "\n";
+            for( HeaderMap::const_iterator h = vertHeaders.begin(); h != vertHeaders.end(); ++h )
+                vertShaderBuf << h->second << "\n";
+            vertShaderBuf << vertBodyText << "\n";
+            vertBodyText = vertShaderBuf.str();
+
+            std::string fragBodyText;
+            fragBodyText = fragBody.str();
+            std::stringstream fragShaderBuf;
+            if ( fragVersion > 0 )
+                fragShaderBuf << "#version " << fragVersion << "\n";
+            for( HeaderMap::const_iterator h = fragHeaders.begin(); h != fragHeaders.end(); ++h )
+                fragShaderBuf << h->second << "\n";
+            fragShaderBuf << fragBodyText << "\n";
+            fragBodyText = fragShaderBuf.str();
+
+            // add them to the program.
+            program->addShader( new osg::Shader(osg::Shader::VERTEX, vertBodyText) );
+            program->addShader( new osg::Shader(osg::Shader::FRAGMENT, fragBodyText) );
+
+            if ( s_dumpShaders )
+            {
+                OE_NOTICE << LC 
+                    << "\nMERGED VERTEX SHADER: \n\n" << vertBodyText << "\n\n"
+                    << "MERGED FRAGMENT SHADER: \n\n" << fragBodyText << "\n" << std::endl;
+            }
+        }
+        else
+        {
+            for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
+            {
+                program->addShader( i->get() );
+                if ( s_dumpShaders )
+                    OE_NOTICE << LC << "SHADER " << i->get()->getName() << ":\n" << i->get()->getShaderSource() << "\n" << std::endl;
+            }
+        }
+
+        // add the attribute bindings
+        for( VirtualProgram::AttribBindingList::const_iterator abl = attribBindings.begin(); abl != attribBindings.end(); ++abl )
+        {
+            program->addBindAttribLocation( abl->first, abl->second );
+        }
+    }
+
+
+    /**
+    * Assemble a new OSG shader Program from the provided components.
+    * Outputs the uniquely-identifying "key vector" and returns the new program.
+    */
+    osg::Program* buildProgram(const std::string&                  programName,
+                               osg::State&                         state,
+                               ShaderComp::FunctionLocationMap&    accumFunctions,
+                               VirtualProgram::ShaderMap&          accumShaderMap,
+                               VirtualProgram::AttribBindingList&  accumAttribBindings,
+                               VirtualProgram::AttribAliasMap&     accumAttribAliases,
+                               osg::Program*                       templateProgram,
+                               VirtualProgram::ShaderVector&       outputKeyVector)
+    {
+
+        // create new MAINs for this function stack.
+        osg::Shader* vertMain = Registry::shaderFactory()->createVertexShaderMain( accumFunctions );
+        osg::Shader* fragMain = Registry::shaderFactory()->createFragmentShaderMain( accumFunctions );
+
+        // build a new "key vector" now that we've changed the shader map.
+        // we call is a key vector because it uniquely identifies this shader program
+        // based on its accumlated function set.
+        for( VirtualProgram::ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
+        {
+            outputKeyVector.push_back( i->second.first.get() );
+        }
+
+        // finally, add the mains (AFTER building the key vector .. we don't want or
+        // need to mains in the key vector since they are completely derived from the
+        // other elements of the key vector.)
+        VirtualProgram::ShaderVector buildVector( outputKeyVector );
+        buildVector.push_back( vertMain );
+        buildVector.push_back( fragMain );
+
+        if ( s_dumpShaders )
+            OE_NOTICE << LC << "---------PROGRAM: " << programName << " ---------------\n" << std::endl;
+
+        // Create the new program.
+        osg::Program* program = new osg::Program();
+        program->setName( programName );
+        addShadersToProgram( buildVector, accumAttribBindings, accumAttribAliases, program );
+        addTemplateDataToProgram( templateProgram, program );
+
+        return program;
+    }
 }
 
 //------------------------------------------------------------------------
@@ -79,16 +380,80 @@ namespace
 // same type as PROGRAM (for proper state sorting)
 const osg::StateAttribute::Type VirtualProgram::SA_TYPE = osg::StateAttribute::PROGRAM;
 
+VirtualProgram* 
+VirtualProgram::getOrCreate(osg::StateSet* stateset)
+{
+    if ( !stateset )
+        return 0L;
+
+    VirtualProgram* vp = dynamic_cast<VirtualProgram*>( stateset->getAttribute(SA_TYPE) );
+    if ( !vp )
+    {
+        vp = new VirtualProgram();
+        vp->setInheritShaders(true);
+        stateset->setAttributeAndModes( vp, osg::StateAttribute::ON );
+    }
+    return vp;
+}
+
+VirtualProgram* 
+VirtualProgram::get(osg::StateSet* stateset)
+{
+    if ( !stateset )
+        return 0L;
+
+    return dynamic_cast<VirtualProgram*>( stateset->getAttribute(SA_TYPE) );
+}
+
+const VirtualProgram* 
+VirtualProgram::get(const osg::StateSet* stateset)
+{
+    if ( !stateset )
+        return 0L;
+
+    return dynamic_cast<const VirtualProgram*>( stateset->getAttribute(SA_TYPE) );
+}
+
+VirtualProgram*
+VirtualProgram::cloneOrCreate(const osg::StateSet* src, osg::StateSet* dest)
+{
+    if ( !dest )
+        return 0L;
+
+    const VirtualProgram* vp = 0L;
+
+    if ( src )
+    {
+        vp = get( src );
+    }
+
+    if ( !vp )
+    {
+        return getOrCreate( dest );
+    }
+
+    else
+    {
+        VirtualProgram* cloneVP = osg::clone( vp, osg::CopyOp::DEEP_COPY_ALL );
+        cloneVP->setInheritShaders(true);
+        dest->setAttributeAndModes(cloneVP, osg::StateAttribute::ON);
+        return cloneVP;
+    }
+}
+
+//------------------------------------------------------------------------
+
 
 VirtualProgram::VirtualProgram( unsigned mask ) : 
 _mask              ( mask ),
 _inherit           ( true ),
-_useLightingShaders( true )
+_inheritSet        ( false )
 {
     // check the the dump env var
     if ( ::getenv(OSGEARTH_DUMP_SHADERS) != 0L )
     {
         s_dumpShaders = true;
+        s_mergeShaders = true;
     }
 
     // check the merge env var
@@ -109,7 +474,7 @@ _shaderMap         ( rhs._shaderMap ),
 _mask              ( rhs._mask ),
 _functions         ( rhs._functions ),
 _inherit           ( rhs._inherit ),
-_useLightingShaders( rhs._useLightingShaders ),
+_inheritSet        ( rhs._inheritSet ),
 _template          ( osg::clone(rhs._template.get()) )
 {
     //nop
@@ -125,35 +490,38 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
     // compare each parameter in turn against the rhs.
     COMPARE_StateAttribute_Parameter(_mask);
     COMPARE_StateAttribute_Parameter(_inherit);
-    //COMPARE_StateAttribute_Parameter(_shaderMap);
 
-    // compare the shader maps.
-    if ( _shaderMap.size() < rhs._shaderMap.size() ) return -1;
-    if ( _shaderMap.size() > rhs._shaderMap.size() ) return 1;
+    // compare the shader maps. Need to lock them while comparing.
+    {
+        Threading::ScopedReadLock shared( _dataModelMutex );
 
-    ShaderMap::const_iterator lhsIter = _shaderMap.begin();
-    ShaderMap::const_iterator rhsIter = rhs._shaderMap.begin();
+        if ( _shaderMap.size() < rhs._shaderMap.size() ) return -1;
+        if ( _shaderMap.size() > rhs._shaderMap.size() ) return 1;
 
-    while( lhsIter != _shaderMap.end() )
-    {
-        int keyCompare = lhsIter->first.compare( rhsIter->first );
-        if ( keyCompare != 0 ) return keyCompare;
+        ShaderMap::const_iterator lhsIter = _shaderMap.begin();
+        ShaderMap::const_iterator rhsIter = rhs._shaderMap.begin();
 
-        const ShaderEntry& lhsEntry = lhsIter->second;
-        const ShaderEntry& rhsEntry = rhsIter->second;
-        int shaderComp = lhsEntry.first->compare( *rhsEntry.first.get() );
-        if ( shaderComp != 0 ) return shaderComp;
+        while( lhsIter != _shaderMap.end() )
+        {
+            int keyCompare = lhsIter->first.compare( rhsIter->first );
+            if ( keyCompare != 0 ) return keyCompare;
 
-        if ( lhsEntry.second < rhsEntry.second ) return -1;
-        if ( lhsEntry.second > rhsEntry.second ) return 1;
+            const ShaderEntry& lhsEntry = lhsIter->second;
+            const ShaderEntry& rhsEntry = rhsIter->second;
+            int shaderComp = lhsEntry.first->compare( *rhsEntry.first.get() );
+            if ( shaderComp != 0 ) return shaderComp;
 
-        lhsIter++;
-        rhsIter++;
-    }
+            if ( lhsEntry.second < rhsEntry.second ) return -1;
+            if ( lhsEntry.second > rhsEntry.second ) return 1;
+
+            lhsIter++;
+            rhsIter++;
+        }
 
-    // compare the template settings.
-    int templateCompare = _template->compare( *(rhs.getTemplate()) );
-    if ( templateCompare != 0 ) return templateCompare;
+        // compare the template settings.
+        int templateCompare = _template->compare( *(rhs.getTemplate()) );
+        if ( templateCompare != 0 ) return templateCompare;
+    }
 
     return 0; // passed all the above comparison macros, must be equal.
 }
@@ -161,19 +529,69 @@ VirtualProgram::compare(const osg::StateAttribute& sa) const
 void
 VirtualProgram::addBindAttribLocation( const std::string& name, GLuint index )
 {
+    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
+#ifdef USE_ATTRIB_ALIASES
+    _attribAliases[name] = Stringify() << "oe_attrib_" << index;
+    _attribBindingList[_attribAliases[name]] = index;
+#else
     _attribBindingList[name] = index;
+#endif
 }
 
 void
 VirtualProgram::removeBindAttribLocation( const std::string& name )
 {
+    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
+#ifdef USE_ATTRIB_ALIASES
+    std::map<std::string,std::string>::iterator i = _attribAliases.find(name);
+    if ( i != _attribAliases.end() )
+        _attribBindingList.erase(i->second);
+#else
     _attribBindingList.erase(name);
+#endif
 }
 
+void
+VirtualProgram::compileGLObjects(osg::State& state) const
+{
+    //nop - precompilation not required
+}
+
+void
+VirtualProgram::resizeGLObjectBuffers(unsigned maxSize)
+{
+  Threading::ScopedWriteLock exclusive( _programCacheMutex );
+
+//  OE_WARN << LC << "Resize VP " << getName() << std::endl;
+
+  for (ProgramMap::iterator i = _programCache.begin();
+    i != _programCache.end(); ++i)
+  {
+    i->second->resizeGLObjectBuffers(maxSize);
+  }
+}
+
+void
+VirtualProgram::releaseGLObjects(osg::State* state) const
+{
+  Threading::ScopedWriteLock exclusive( _programCacheMutex );
+
+//  OE_WARN << LC << "Release VP " << getName() << std::endl;
+
+  for (ProgramMap::const_iterator i = _programCache.begin();
+    i != _programCache.end(); ++i)
+  {
+    i->second->releaseGLObjects(state);
+  }
+}
 
 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.first.get() : 0L;
 }
@@ -187,12 +605,24 @@ VirtualProgram::setShader(const std::string&                 shaderID,
     if ( !shader || shader->getType() ==  osg::Shader::UNDEFINED ) 
         return NULL;
 
+    // set the inherit flag if it's not initialized
+    if ( !_inheritSet )
+    {
+        setInheritShaders( true );
+    }
+
+    // set the name to the ID:
+    shader->setName( shaderID );
+
     // pre-processes the shader's source to include GLES uniforms as necessary
     // (no-op on non-GLES)
     ShaderPreProcessor::run( shader );
 
-    shader->setName( shaderID );
-    _shaderMap[shaderID] = ShaderEntry(shader, ov);
+    // lock the data model and insert the new shader.
+    {
+        Threading::ScopedWriteLock exclusive( _dataModelMutex );
+        _shaderMap[shaderID] = ShaderEntry(shader, ov);
+    }
 
     return shader;
 }
@@ -211,11 +641,21 @@ VirtualProgram::setShader(osg::Shader*                       shader,
         return 0L;
     }
 
+    // set the inherit flag if it's not initialized
+    if ( !_inheritSet )
+    {
+        setInheritShaders( true );
+    }
+
     // pre-processes the shader's source to include GLES uniforms as necessary
     // (no-op on non-GLES)
     ShaderPreProcessor::run( shader );
 
-    _shaderMap[shader->getName()] = ShaderEntry(shader, ov);
+    // lock the data model while changing it.
+    {
+        Threading::ScopedWriteLock exclusive( _dataModelMutex );
+        _shaderMap[shader->getName()] = ShaderEntry(shader, ov);
+    }
 
     return shader;
 }
@@ -227,37 +667,57 @@ VirtualProgram::setFunction(const std::string& functionName,
                             FunctionLocation   location,
                             float              priority)
 {
-    Threading::ScopedMutexLock lock( _functionsMutex );
-
-    OrderedFunctionMap& ofm = _functions[location];
+    // set the inherit flag if it's not initialized
+    if ( !_inheritSet )
+    {
+        setInheritShaders( true );
+    }
 
-    // if there's already a function by this name, remove it
-    for( OrderedFunctionMap::iterator i = ofm.begin(); i != ofm.end(); )
+    // lock the functions map while iterating and then modifying it:
     {
-        if ( i->second.compare(functionName) == 0 )
-        {
-            OrderedFunctionMap::iterator j = i;
-            ++j;
-            ofm.erase( i );
-            i = j;
-        }
-        else
+        Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
+        OrderedFunctionMap& ofm = _functions[location];
+
+        // if there's already a function by this name, remove it
+        for( OrderedFunctionMap::iterator i = ofm.begin(); i != ofm.end(); )
         {
-            ++i;
+            if ( i->second.compare(functionName) == 0 )
+            {
+                OrderedFunctionMap::iterator j = i;
+                ++j;
+                ofm.erase( i );
+                i = j;
+            }
+            else
+            {
+                ++i;
+            }
         }
-    }
-    
-    ofm.insert( std::pair<float,std::string>( priority, functionName ) );
+        
+        ofm.insert( std::pair<float,std::string>( priority, functionName ) );
+
+        // create and add the new shader function.
+        osg::Shader::Type type = (int)location <= (int)LOCATION_VERTEX_CLIP ?
+            osg::Shader::VERTEX : osg::Shader::FRAGMENT;
+
+        osg::Shader* shader = new osg::Shader(type, shaderSource);
+        shader->setName( functionName );
 
-    osg::Shader::Type type = (int)location <= (int)LOCATION_VERTEX_CLIP ?
-        osg::Shader::VERTEX : osg::Shader::FRAGMENT;
+        // pre-processes the shader's source to include GLES uniforms as necessary
+        ShaderPreProcessor::run( shader );
 
-    setShader( functionName, new osg::Shader(type, shaderSource) );
+        _shaderMap[functionName] = ShaderEntry(shader, osg::StateAttribute::ON);
+
+    } // release lock
 }
 
 void
 VirtualProgram::removeShader( const std::string& shaderID )
 {
+    // lock the functions map while making changes:
+    Threading::ScopedWriteLock exclusive( _dataModelMutex );
+
     _shaderMap.erase( shaderID );
 
     for(FunctionLocationMap::iterator i = _functions.begin(); i != _functions.end(); ++i )
@@ -275,288 +735,45 @@ VirtualProgram::removeShader( const std::string& shaderID )
                 {
                     _functions.erase( i );
                 }
-                break;
+                return;
             }
         }
     }
 }
 
-/**
-* Adds a new shader entry to the accumulated shader map, respecting the
-* override policy of both the existing entry (if there is one) and the 
-* new entry.
-*/
-void 
-VirtualProgram::addToAccumulatedMap(ShaderMap&         accumShaderMap,
-                                    const std::string& shaderID,
-                                    const ShaderEntry& newEntry) const
-{
-    const osg::StateAttribute::OverrideValue& ov = newEntry.second;
-
-    // see if we're trying to disable a previous entry:
-    if ((ov & osg::StateAttribute::ON) == 0 ) //TODO: check for higher override
-    {
-        // yes? remove it!
-        accumShaderMap.erase( shaderID );
-    }
-
-    else
-    {
-        // see if there's a higher-up entry with the same ID:
-        ShaderEntry& accumEntry = accumShaderMap[ shaderID ]; 
-
-        // make sure we can add the new one:
-        if ((accumEntry.first.get() == 0L ) ||                           // empty slot, fill it
-            ((ov & osg::StateAttribute::PROTECTED) != 0) ||              // new entry is protected
-            ((accumEntry.second & osg::StateAttribute::OVERRIDE) == 0) ) // old entry does NOT override
-        {
-            accumEntry = newEntry;
-        }
-    }
-}
-
 
 void
 VirtualProgram::setInheritShaders( bool value )
 {
-    if ( _inherit != value )
+    if ( _inherit != value || !_inheritSet )
     {
         _inherit = value;
-        // not particularly thread safe if called after use.. meh
-        _programCache.clear();
-        _accumulatedFunctions.clear();
-    }
-}
-
 
-namespace
-{
-    typedef std::map<std::string, std::string> HeaderMap;
-
-    void parseShaderForMerging( const std::string& source, unsigned& version, HeaderMap& headers, std::stringstream& body )
-    {
-        // break into lines:
-        StringVector lines;
-        StringTokenizer( source, lines, "\n", "", true, false );
-
-        for( StringVector::const_iterator line_iter = lines.begin(); line_iter != lines.end(); ++line_iter )
+        // clear the program cache please
         {
-            std::string line = trim(*line_iter);
-
-            if ( line.size() > 0 )
-            {
-                StringVector tokens;
-                StringTokenizer( line, tokens, " \t", "", false, true );
-
-                if (tokens[0] == "#version")
-                {
-                    // find the highest version number.
-                    if ( tokens.size() > 1 )
-                    {
-                        unsigned newVersion = osgEarth::as<unsigned>(tokens[1], 0);
-                        if ( newVersion > version )
-                        {
-                            version = newVersion;
-                        }
-                    }
-                }
-
-                else if (
-                    tokens[0] == "#extension"   ||
-                    tokens[0] == "precision"    ||
-                    tokens[0] == "struct"       ||
-                    tokens[0] == "varying"      ||
-                    tokens[0] == "uniform"      ||
-                    tokens[0] == "attribute")
-                {
-                    std::string& header = headers[line];
-                    header = line;
-                }
-
-                else
-                {
-                    body << (*line_iter) << "\n";
-                }
-            }
+            Threading::ScopedWriteLock exclusive(_programCacheMutex);
+            _programCache.clear();
         }
-    }
-}
-
-
-void 
-VirtualProgram::addShadersToProgram(const ShaderVector&      shaders, 
-                                    const AttribBindingList& attribBindings,
-                                    osg::Program*            program )
-{
-    if ( s_mergeShaders )
-    {
-        unsigned          vertVersion = 0;
-        HeaderMap         vertHeaders;
-        std::stringstream vertBody;
 
-        unsigned          fragVersion = 0;
-        HeaderMap         fragHeaders;
-        std::stringstream fragBody;
-
-        // parse the shaders, combining header lines and finding the highest version:
-        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
-        {
-            osg::Shader* s = i->get();
-            if ( s->getType() == osg::Shader::VERTEX )
-            {
-                parseShaderForMerging( s->getShaderSource(), vertVersion, vertHeaders, vertBody );
-            }
-            else if ( s->getType() == osg::Shader::FRAGMENT )
-            {
-                parseShaderForMerging( s->getShaderSource(), fragVersion, fragHeaders, fragBody );
-            }
-        }
-
-        // write out the merged shader code:
-        std::string vertBodyText;
-        vertBodyText = vertBody.str();
-        std::stringstream vertShaderBuf;
-        if ( vertVersion > 0 )
-            vertShaderBuf << "#version " << vertVersion << "\n";
-        for( HeaderMap::const_iterator h = vertHeaders.begin(); h != vertHeaders.end(); ++h )
-            vertShaderBuf << h->second << "\n";
-        vertShaderBuf << vertBodyText << "\n";
-        vertBodyText = vertShaderBuf.str();
-
-        std::string fragBodyText;
-        fragBodyText = fragBody.str();
-        std::stringstream fragShaderBuf;
-        if ( fragVersion > 0 )
-            fragShaderBuf << "#version " << fragVersion << "\n";
-        for( HeaderMap::const_iterator h = fragHeaders.begin(); h != fragHeaders.end(); ++h )
-            fragShaderBuf << h->second << "\n";
-        fragShaderBuf << fragBodyText << "\n";
-        fragBodyText = fragShaderBuf.str();
-
-        // add them to the program.
-        program->addShader( new osg::Shader(osg::Shader::VERTEX, vertBodyText) );
-        program->addShader( new osg::Shader(osg::Shader::FRAGMENT, fragBodyText) );
-
-        if ( s_dumpShaders )
-        {
-            OE_NOTICE << LC 
-                << "\nMERGED VERTEX SHADER: \n\n" << vertBodyText << "\n\n"
-                << "MERGED FRAGMENT SHADER: \n\n" << fragBodyText << "\n" << std::endl;
-        }
+        _inheritSet = true;
     }
-    else
-    {
-        for( VirtualProgram::ShaderVector::const_iterator i = shaders.begin(); i != shaders.end(); ++i )
-        {
-            program->addShader( i->get() );
-            if ( s_dumpShaders )
-                OE_NOTICE << LC << "SHADER " << i->get()->getName() << ":\n" << i->get()->getShaderSource() << "\n" << std::endl;
-        }
-    }
-
-    // add the attribute bindings
-    for( VirtualProgram::AttribBindingList::const_iterator abl = attribBindings.begin(); abl != attribBindings.end(); ++abl )
-    {
-        program->addBindAttribLocation( abl->first, abl->second );
-    }
-}
-
-
-void
-VirtualProgram::addTemplateDataToProgram( osg::Program* program )
-{
-    const osg::Program::FragDataBindingList& fbl = _template->getFragDataBindingList();
-    for( osg::Program::FragDataBindingList::const_iterator i = fbl.begin(); i != fbl.end(); ++i )
-        program->addBindFragDataLocation( i->first, i->second );
-
-    const osg::Program::UniformBlockBindingList& ubl = _template->getUniformBlockBindingList();
-    for( osg::Program::UniformBlockBindingList::const_iterator i = ubl.begin(); i != ubl.end(); ++i )
-        program->addBindUniformBlock( i->first, i->second );
 }
 
 
-osg::Program*
-VirtualProgram::buildProgram(osg::State&        state, 
-                             ShaderMap&         accumShaderMap,
-                             AttribBindingList& accumAttribBindings)
+namespace
 {
-    OE_TEST << LC << "Building new Program for VP " << getName() << std::endl;
-
-    // build a new set of accumulated functions, to support the creation of main()
-    refreshAccumulatedFunctions( state );
-
-    // create new MAINs for this function stack.
-    osg::Shader* vertMain = Registry::shaderFactory()->createVertexShaderMain( _accumulatedFunctions );
-    osg::Shader* fragMain = Registry::shaderFactory()->createFragmentShaderMain( _accumulatedFunctions );
-
-    // build a new "key vector" now that we've changed the shader map.
-    // we call is a key vector because it uniquely identifies this shader program
-    // based on its accumlated function set.
-    ShaderVector keyVector;
-    for( ShaderMap::iterator i = accumShaderMap.begin(); i != accumShaderMap.end(); ++i )
-    {
-        keyVector.push_back( i->second.first.get() );
-    }
-
-    // finally, add the mains (AFTER building the key vector .. we don't want or
-    // need to mains in the key vector since they are completely derived from the
-    // other elements of the key vector.)
-    ShaderVector buildVector( keyVector );
-    buildVector.push_back( vertMain );
-    buildVector.push_back( fragMain );
-
-    if ( s_dumpShaders )
-        OE_NOTICE << LC << "---------PROGRAM: " << getName() << " ---------------\n" << std::endl;
-
-    // Create the new program.
-    osg::Program* program = new osg::Program();
-    program->setName(getName());
-    addShadersToProgram( buildVector, accumAttribBindings, program );
-    addTemplateDataToProgram( program );
-
-
-#if 0 // gw - obe, don't do this anymore
-
-    // Since we replaced the "mains", we have to go through the cache and update all its
-    // entries to point at the new mains instead of the old ones.
-    if ( oldVertMain.valid() || oldFragMain.valid() )
-    {
-        ProgramMap newProgramCache;
-
-        for( ProgramMap::iterator m = _programCache.begin(); m != _programCache.end(); ++m )
-        {
-            const ShaderVector& originalKey = m->first;
-
-            osg::Program* newProgram = new osg::Program();
-            newProgram->setName( m->second->getName() );
-
-            ShaderVector newBuildKey( originalKey );
-            newBuildKey.push_back( _vertMain.get() );
-            newBuildKey.push_back( _fragMain.get() );
-            addShadersToProgram( newBuildKey, m->second->getAttribBindingList(), newProgram );
-
-            addTemplateDataToProgram( newProgram );
-
-            newProgramCache[originalKey] = newProgram;
-        }
-
-        _programCache = newProgramCache;
-    }
-#endif
-
-    // finally, put own new program in the cache.
-    _programCache[ keyVector ] = program;
-
-    return program;
 }
 
-
 void
 VirtualProgram::apply( osg::State& state ) const
 {
-    if ( _shaderMap.empty() )
+    if (_shaderMap.empty() && !_inheritSet)
     {
-        // if there's no data in the VP, unload any existing program.
+        // If there's no data in the VP, and never has been, unload any existing program.
+        // NOTE: OSG's State processor creates a "global default attribute" for each type.
+        // 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;
@@ -569,6 +786,7 @@ VirtualProgram::apply( osg::State& state ) const
     // first, find and collect all the VirtualProgram attributes:
     ShaderMap         accumShaderMap;
     AttribBindingList accumAttribBindings;
+    AttribAliasMap    accumAttribAliases;
     
     if ( _inherit )
     {
@@ -590,28 +808,46 @@ VirtualProgram::apply( osg::State& state ) const
                 const VirtualProgram* vp = dynamic_cast<const VirtualProgram*>( (*av)[i].first );
                 if ( vp && (vp->_mask && _mask) )
                 {
-                    for( ShaderMap::const_iterator i = vp->_shaderMap.begin(); i != vp->_shaderMap.end(); ++i )
+                    ShaderMap vpShaderMap;
+                    vp->getShaderMap( vpShaderMap );
+
+                    for( ShaderMap::const_iterator i = vpShaderMap.begin(); i != vpShaderMap.end(); ++i )
                     {
                         addToAccumulatedMap( accumShaderMap, i->first, i->second );
                     }
 
                     const AttribBindingList& abl = vp->getAttribBindingList();
                     accumAttribBindings.insert( abl.begin(), abl.end() );
+
+#ifdef USE_ATTRIB_ALIASES
+                    const AttribAliasMap& aliases = vp->getAttribAliases();
+                    accumAttribAliases.insert( aliases.begin(), aliases.end() );
+#endif
                 }
             }
         }
     }
-    
+
     // next add the local shader components to the map, respecting the override values:
-    for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
     {
-        addToAccumulatedMap( accumShaderMap, i->first, i->second );
+        Threading::ScopedReadLock readonly(_dataModelMutex);
+
+        for( ShaderMap::const_iterator i = _shaderMap.begin(); i != _shaderMap.end(); ++i )
+        {
+            addToAccumulatedMap( accumShaderMap, i->first, i->second );
+        }
+
+        const AttribBindingList& abl = this->getAttribBindingList();
+        accumAttribBindings.insert( abl.begin(), abl.end() );
+
+#ifdef USE_ATTRIB_ALIASES
+        const AttribAliasMap& aliases = this->getAttribAliases();
+        accumAttribAliases.insert( aliases.begin(), aliases.end() );
+#endif
     }
-    const AttribBindingList& abl = this->getAttribBindingList();
-    accumAttribBindings.insert( abl.begin(), abl.end() );
-    
-    
-    if ( accumShaderMap.size() )
+
+
+    if ( true ) //even with nothing in the map, we still want mains! -gw  //accumShaderMap.size() )
     {
         // next, assemble a list of the shaders in the map so we can use it as our
         // program cache key.
@@ -628,7 +864,7 @@ VirtualProgram::apply( osg::State& state ) const
         }
         
         // see if there's already a program associated with this list:
-        osg::Program* program = 0L;
+        osg::ref_ptr<osg::Program> program;
         
         // look up the program:
         {
@@ -642,46 +878,82 @@ VirtualProgram::apply( osg::State& state ) const
         }
         
         // if not found, lock and build it:
-        if ( !program )
+        if ( !program.valid() )
         {
-            Threading::ScopedWriteLock exclusive( _programCacheMutex );
-            
-            // look again in case of contention:
-            ProgramMap::const_iterator p = _programCache.find( vec );
-            if ( p != _programCache.end() )
-            {
-                program = p->second.get();
-            }
-            else
+            // build a new set of accumulated functions, to support the creation of main()
+            ShaderComp::FunctionLocationMap accumFunctions;
+            accumulateFunctions( state, accumFunctions );
+
+            // now double-check the program cache, and failing that, build the
+            // new shader Program.
             {
-                VirtualProgram* nc = const_cast<VirtualProgram*>(this);
-                program = nc->buildProgram( state, accumShaderMap, accumAttribBindings );
+                Threading::ScopedWriteLock exclusive( _programCacheMutex );
+                
+                // double-check: look again ito negate race conditions
+                ProgramMap::const_iterator p = _programCache.find( vec );
+                if ( p != _programCache.end() )
+                {
+                    program = p->second.get();
+                }
+                else
+                {
+                    ShaderVector keyVector;
+
+                    //OE_NOTICE << LC << "Building new Program for VP " << getName() << std::endl;
+
+                    program = buildProgram(
+                        getName(),
+                        state,
+                        accumFunctions,
+                        accumShaderMap, 
+                        accumAttribBindings, 
+                        accumAttribAliases, 
+                        _template.get(),
+                        keyVector);
+
+                    // finally, put own new program in the cache.
+                    _programCache[ keyVector ] = program;
+                }
             }
         }
         
         // finally, apply the program attribute.
-        program->apply( state );
+        if ( program.valid() )
+        {
+            program->apply( state );
+
+#if 0 // test code for detecting race conditions
+            for(int i=0; i<10000; ++i) {
+                state.setLastAppliedProgramObject(0L);
+                program->apply( state );
+            }
+#endif
+        }
     }
 }
 
 void
 VirtualProgram::getFunctions( FunctionLocationMap& out ) const
 {
-    Threading::ScopedMutexLock lock( const_cast<VirtualProgram*>(this)->_functionsMutex );
+    // make a safe copy of the functions map.
+    Threading::ScopedReadLock shared( _dataModelMutex );
     out = _functions;
 }
 
 void
-VirtualProgram::refreshAccumulatedFunctions( const osg::State& state )
+VirtualProgram::getShaderMap( ShaderMap& out ) const
+{
+    // make a safe copy of the functions map.
+    Threading::ScopedReadLock shared( _dataModelMutex );
+    out = _shaderMap;
+}
+
+void
+VirtualProgram::accumulateFunctions(const osg::State&                state,
+                                    ShaderComp::FunctionLocationMap& result) const
 {
     // This method searches the state's attribute stack and accumulates all 
     // the user functions (including those in this program).
-
-    // mutex no longer required since this method is called safely
-    //Threading::ScopedMutexLock lock( _functionsMutex );
-
-    _accumulatedFunctions.clear();
-
     if ( _inherit )
     {
         const StateHack::AttributeVec* av = StateHack::GetAttributeVec( state, this );
@@ -708,7 +980,7 @@ VirtualProgram::refreshAccumulatedFunctions( const osg::State& state )
                     for( FunctionLocationMap::const_iterator j = rhs.begin(); j != rhs.end(); ++j )
                     {
                         const OrderedFunctionMap& source = j->second;
-                        OrderedFunctionMap&       dest   = _accumulatedFunctions[j->first];
+                        OrderedFunctionMap&       dest   = result[j->first];
 
                         for( OrderedFunctionMap::const_iterator k = source.begin(); k != source.end(); ++k )
                         {
@@ -722,7 +994,6 @@ VirtualProgram::refreshAccumulatedFunctions( const osg::State& state )
                                 }
                             }
                             dest.insert( *k );
-                            //_accumulatedFunctions[j->first].insert( *k );
                         }
                     }
                 }
@@ -731,24 +1002,27 @@ VirtualProgram::refreshAccumulatedFunctions( const osg::State& state )
     }
 
     // add the local ones too:
-    for( FunctionLocationMap::const_iterator j = _functions.begin(); j != _functions.end(); ++j )
     {
-        const OrderedFunctionMap& source = j->second;
-        OrderedFunctionMap&       dest   = _accumulatedFunctions[j->first];
+        Threading::ScopedReadLock readonly( _dataModelMutex );
 
-        for( OrderedFunctionMap::const_iterator k = source.begin(); k != source.end(); ++k )
+        for( FunctionLocationMap::const_iterator j = _functions.begin(); j != _functions.end(); ++j )
         {
-            // remove/override an existing function with the same name
-            for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
+            const OrderedFunctionMap& source = j->second;
+            OrderedFunctionMap&       dest   = result[j->first];
+
+            for( OrderedFunctionMap::const_iterator k = source.begin(); k != source.end(); ++k )
             {
-                if ( exists->second.compare( k->second ) == 0 )
+                // remove/override an existing function with the same name
+                for( OrderedFunctionMap::iterator exists = dest.begin(); exists != dest.end(); ++exists )
                 {
-                    dest.erase(exists);
-                    break;
+                    if ( exists->second.compare( k->second ) == 0 )
+                    {
+                        dest.erase(exists);
+                        break;
+                    }
                 }
+                dest.insert( *k );
             }
-            dest.insert( *k );
-            //_accumulatedFunctions[j->first].insert( *k );
         }
-    } 
+    }
 }
diff --git a/src/osgEarth/XmlUtils.cpp b/src/osgEarth/XmlUtils.cpp
index 74d557d..0c212b8 100644
--- a/src/osgEarth/XmlUtils.cpp
+++ b/src/osgEarth/XmlUtils.cpp
@@ -236,16 +236,6 @@ XmlText::getValue() const
     return value;
 }
 
-
-#if 0
-XmlDocument::XmlDocument( const std::string& _source_uri ) :
-XmlElement( "Document" ),
-source_uri( _source_uri )
-{
-    //NOP
-}
-#endif
-
 XmlDocument::XmlDocument() :
 XmlElement( "Document" )
 {
diff --git a/src/osgEarthAnnotation/AnnotationData b/src/osgEarthAnnotation/AnnotationData
index 5016e21..025ec5b 100644
--- a/src/osgEarthAnnotation/AnnotationData
+++ b/src/osgEarthAnnotation/AnnotationData
@@ -22,6 +22,7 @@
 #include <osgEarthAnnotation/Common>
 #include <osg/Referenced>
 #include <osgEarth/Viewpoint>
+#include <osgEarth/Decluttering>
 
 namespace osgEarth { namespace Annotation
 {	
@@ -31,7 +32,8 @@ namespace osgEarth { namespace Annotation
      * Stores annotation metadata/extra information that can be stored in a node's
      * UserData container.
      */
-    class OSGEARTHANNO_EXPORT AnnotationData : public osg::Referenced
+    class OSGEARTHANNO_EXPORT AnnotationData : public osg::Referenced,
+                                               public osgEarth::PriorityProvider
     {
     public:
         /**
@@ -58,12 +60,6 @@ namespace osgEarth { namespace Annotation
         const std::string& getDescription() const { return _description; }
 
         /**
-         * Priority of the item
-         */
-        void setPriority( float value ) { _priority = value; }
-        float getPriority() const { return _priority; }
-
-        /**
          * Viewpoint associated with this annotation.
          */
         void setViewpoint( const Viewpoint& vp ) {
@@ -76,6 +72,15 @@ namespace osgEarth { namespace Annotation
             return _viewpoint;
         }
 
+    public: // PriorityProvider interface
+
+        /**
+         * Priority of the item
+         */
+        void setPriority( float value ) { _priority = value; }
+        float getPriority() const { return _priority; }
+
+
     public: // serialization
 
         virtual void mergeConfig(const Config& conf);
diff --git a/src/osgEarthAnnotation/AnnotationEditing.cpp b/src/osgEarthAnnotation/AnnotationEditing.cpp
index 64f0dd2..0e43770 100644
--- a/src/osgEarthAnnotation/AnnotationEditing.cpp
+++ b/src/osgEarthAnnotation/AnnotationEditing.cpp
@@ -181,7 +181,7 @@ CircleNodeEditor::updateDraggers()
             osg::RadiansToDegrees(lon),
             osg::RadiansToDegrees(lat));
 
-        draggerLocation.z() = osg::maximum(draggerLocation.z(), getPositionDragger()->getPosition().z());
+        draggerLocation.z() = 0;
 
         _radiusDragger->setPosition( draggerLocation, false );
     }
@@ -285,12 +285,12 @@ EllipseNodeEditor::updateDraggers()
         GeoMath::destination(osg::DegreesToRadians( location.y() ), osg::DegreesToRadians( location.x() ), osg::PI_2 - rotation, minorR, lat, lon, em->getRadiusEquator());        
 
         GeoPoint minorLocation(location.getSRS(), osg::RadiansToDegrees( lon ), osg::RadiansToDegrees( lat ));
-        minorLocation.z() = osg::maximum(minorLocation.z(), getPositionDragger()->getPosition().z());
+        minorLocation.z() = 0;       
         _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 ));
-        majorLocation.z() = osg::maximum(majorLocation.z(), getPositionDragger()->getPosition().z());
+        majorLocation.z() = 0;
         _majorDragger->setPosition( majorLocation, false);
     }
 }
diff --git a/src/osgEarthAnnotation/AnnotationNode b/src/osgEarthAnnotation/AnnotationNode
index 71bc7af..f8251ad 100644
--- a/src/osgEarthAnnotation/AnnotationNode
+++ b/src/osgEarthAnnotation/AnnotationNode
@@ -23,10 +23,10 @@
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthAnnotation/Decoration>
 #include <osgEarthSymbology/Style>
+#include <osgEarth/DepthOffset>
 #include <osgEarth/MapNodeObserver>
 #include <osgEarth/Terrain>
 #include <osgEarth/TileKey>
-#include <osg/Switch>
 
 
 #define META_AnnotationNode(library,name) \
@@ -66,6 +66,7 @@ namespace osgEarth { namespace Annotation
          */
         virtual void setAnnotationData( AnnotationData* data );
         AnnotationData* getAnnotationData() const { return _annoData.get(); }
+        AnnotationData* getOrCreateAnnotationData();
 
         /**
          * Sets the node to "dynamic", i.e. sets up the node so that you can
@@ -139,6 +140,8 @@ namespace osgEarth { namespace Annotation
         bool                         _depthAdj;
         osg::ref_ptr<const AltitudeSymbol> _altitude;
 
+        DepthOffsetAdapter _doAdapter;
+
         typedef std::map<std::string, osg::ref_ptr<Decoration> > DecorationMap;
         DecorationMap _dsMap;
         Decoration*   _activeDs;
diff --git a/src/osgEarthAnnotation/AnnotationNode.cpp b/src/osgEarthAnnotation/AnnotationNode.cpp
index 9163d7e..e83698e 100644
--- a/src/osgEarthAnnotation/AnnotationNode.cpp
+++ b/src/osgEarthAnnotation/AnnotationNode.cpp
@@ -134,9 +134,11 @@ AnnotationNode::setMapNode( MapNode* mapNode )
                 if ( mapNode )
                     mapNode->getTerrain()->addTerrainCallback( _autoClampCallback.get() );
             }
-        }
+        }		
 
         _mapNode = mapNode;
+
+		applyStyle( this->getStyle() );
     }
 }
 
@@ -146,6 +148,16 @@ AnnotationNode::setAnnotationData( AnnotationData* data )
     _annoData = data;
 }
 
+AnnotationData*
+AnnotationNode::getOrCreateAnnotationData()
+{
+    if ( !_annoData.valid() )
+    {
+        setAnnotationData( new AnnotationData() );
+    }
+    return _annoData.get();
+}
+
 void
 AnnotationNode::setDynamic( bool value )
 {
@@ -193,7 +205,7 @@ AnnotationNode::setCPUAutoClamping( bool value )
             else
             {
                 // update depth adjustment calculation
-                getOrCreateStateSet()->addUniform( DepthOffsetUtils::createMinOffsetUniform(this) );
+                //getOrCreateStateSet()->addUniform( DepthOffsetUtils::createMinOffsetUniform(this) );
             }
         }
     }
@@ -204,21 +216,13 @@ AnnotationNode::setDepthAdjustment( bool enable )
 {
     if ( enable )
     {
-        osg::StateSet* s = this->getOrCreateStateSet();
-        osg::Program* daProgram = DepthOffsetUtils::getOrCreateProgram(); // cached, not a leak.
-        //TODO: be careful to check for VirtualProgram as well in the future if things change
-        osg::Program* p = dynamic_cast<osg::Program*>( s->getAttribute(osg::StateAttribute::PROGRAM) );
-        if ( !p || p != daProgram )
-            s->setAttributeAndModes( daProgram, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE );
-
-        s->addUniform( DepthOffsetUtils::createMinOffsetUniform(this) );
-        s->addUniform( DepthOffsetUtils::getIsNotTextUniform() );
+        _doAdapter.setGraph(this);
+        _doAdapter.recalculate();
     }
-    else if ( this->getStateSet() )
+    else
     {
-        this->getStateSet()->removeAttribute(osg::StateAttribute::PROGRAM);
+        _doAdapter.setGraph(0L);
     }
-
     _depthAdj = enable;
 }
 
@@ -401,5 +405,18 @@ AnnotationNode::applyGeneralSymbology(const Style& style)
                 GL_LIGHTING,
                 (render->lighting() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
+
+        if ( render->depthOffset().isSet() ) // && !_depthAdj )
+        {
+            _doAdapter.setDepthOffsetOptions( *render->depthOffset() );
+            setDepthAdjustment( true );
+        }
+
+        if ( render->backfaceCulling().isSet() )
+        {
+            getOrCreateStateSet()->setMode(
+                GL_CULL_FACE,
+                (render->backfaceCulling() == true? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
+        }
     }
 }
diff --git a/src/osgEarthAnnotation/AnnotationRegistry.cpp b/src/osgEarthAnnotation/AnnotationRegistry.cpp
index d83d8e0..50a9b10 100644
--- a/src/osgEarthAnnotation/AnnotationRegistry.cpp
+++ b/src/osgEarthAnnotation/AnnotationRegistry.cpp
@@ -17,7 +17,7 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include <osgEarthAnnotation/AnnotationRegistry>
-#include <osgEarthAnnotation/Decluttering>
+#include <osgEarth/Decluttering>
 
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
diff --git a/src/osgEarthAnnotation/AnnotationSettings b/src/osgEarthAnnotation/AnnotationSettings
index 55a0151..fa23d8d 100644
--- a/src/osgEarthAnnotation/AnnotationSettings
+++ b/src/osgEarthAnnotation/AnnotationSettings
@@ -47,11 +47,25 @@ namespace osgEarth { namespace Annotation
         static void setApplyDepthOffsetToClampedLines( bool value ) { _autoDepthOffset = value; }
         static bool getApplyDepthOffsetToClampedLines() { return _autoDepthOffset; }
 
-        static void setOcclusionQueryMaxRange( double value ) { _occlusionQueryMaxRange = value; }
-        static double getOcclusionQueryMaxRange() { return _occlusionQueryMaxRange; }
+        /**
+         * Gets or sets the max camera altitude at which the occlusion culling callback is applied.
+         * DEFAULT: 200000.0 meters
+         */
+        static void setOcclusionCullingMaxAltitude( double value ) { _occlusionCullingMaxAltitude = value; }
+        static double getOcclusionCullingMaxAltitude() { return _occlusionCullingMaxAltitude; }
+
+        /**
+         * Gets or sets the adjustment factor, in meters, for the occlusion culling callback.
+         * This adjusts the target world point "up" by this factor to avoid being very aggressive with the culling
+         * if items are very close to the ground.
+         */
+        static void setOcclusionCullingHeightAdjustment( double value ) { _occlusionCullingHeightAdjustment = value; }
+        static double getOcclusionCullingHeightAdjustment() { return _occlusionCullingHeightAdjustment; }
+
 
     private:
-        static double _occlusionQueryMaxRange;
+        static double _occlusionCullingMaxAltitude;
+        static double _occlusionCullingHeightAdjustment;
         static bool _continuousClamping;
         static bool _autoDepthOffset;
     };
diff --git a/src/osgEarthAnnotation/AnnotationSettings.cpp b/src/osgEarthAnnotation/AnnotationSettings.cpp
index cf6b156..1f98099 100644
--- a/src/osgEarthAnnotation/AnnotationSettings.cpp
+++ b/src/osgEarthAnnotation/AnnotationSettings.cpp
@@ -26,4 +26,5 @@ using namespace osgEarth::Annotation;
 // static defaults
 bool AnnotationSettings::_continuousClamping = true;
 bool AnnotationSettings::_autoDepthOffset = true;
-double AnnotationSettings::_occlusionQueryMaxRange = 200000.0;
+double AnnotationSettings::_occlusionCullingMaxAltitude = 200000.0;
+double AnnotationSettings::_occlusionCullingHeightAdjustment = 5.0;
diff --git a/src/osgEarthAnnotation/AnnotationUtils b/src/osgEarthAnnotation/AnnotationUtils
index c4a2798..178ab5c 100644
--- a/src/osgEarthAnnotation/AnnotationUtils
+++ b/src/osgEarthAnnotation/AnnotationUtils
@@ -25,6 +25,7 @@
 #include <osg/AutoTransform>
 #include <osg/Drawable>
 #include <osg/Geometry>
+#include <osgText/TextBase>
 
 namespace osgEarth { namespace Annotation
 {
@@ -45,6 +46,11 @@ namespace osgEarth { namespace Annotation
         static const std::string& PROGRAM_NAME();
 
         /**
+         * Convert a symbology encoding enum to an osgText enum
+         */
+        static osgText::String::Encoding convertTextSymbolEncoding(const TextSymbol::Encoding encoding);
+
+        /**
          * Creates a drawable representing a symbolized text label in
          * pixel space.
          */
@@ -60,8 +66,9 @@ namespace osgEarth { namespace Annotation
         static osg::Geometry* createImageGeometry(
             osg::Image*       image,
             const osg::Vec2s& pixelOffsets,
-            unsigned          textureUnit       =0,
-            double            heading = 0.0);
+            unsigned          textureUnit,
+            double            heading,
+            double            scale );
 
         /**
          * Creates a fading uniform that the decluttering engine can use
@@ -122,21 +129,14 @@ namespace osgEarth { namespace Annotation
         static osg::Node* createHemisphere( float r, const osg::Vec4& color, float maxAngle =15.0f );
 
         /**
-         * Builds an ellipsoid geometry.
-         * @param xr       X-axis radius
-         * @param yr       Y-axis radius
-         * @param zr       Z-axis radius
-         * @param color    Color
-         * @param maxAngle Maximum angle between verts (controls tessellation)
+         * Builds the geometry for an ellipsoid.
          */
-        static osg::Node* createEllipsoid( float xr, float yr, float zr, const osg::Vec4& color, float maxAngle =15.0f );
-
-        static osg::Node* createEllipsoid( 
-            float xr, float yr, const osg::Vec4& color, float maxAngle =15.0f,
-            float minLat =-90.0, float maxLat=90.0, float minLon=-180.0, float maxLon=180.0);
-
         static osg::Geometry* createEllipsoidGeometry( 
-            float major, float minor, const osg::Vec4& color, float maxAngle =15.0f,
+            float xr, float yr, float zr, const osg::Vec4& color, float maxAngle =10.0f,
+            float minLat =-90.0, float maxLat=90.0, float minLon=-180.0, float maxLon=180.0);
+        
+        static osg::Node* createEllipsoid( 
+            float xr, float yr, float zr, const osg::Vec4& color, float maxAngle =10.0f,
             float minLat =-90.0, float maxLat=90.0, float minLon=-180.0, float maxLon=180.0);
 
         struct AltitudePolicy
diff --git a/src/osgEarthAnnotation/AnnotationUtils.cpp b/src/osgEarthAnnotation/AnnotationUtils.cpp
index df7c198..ace9978 100755
--- a/src/osgEarthAnnotation/AnnotationUtils.cpp
+++ b/src/osgEarthAnnotation/AnnotationUtils.cpp
@@ -18,7 +18,6 @@
 */
 
 #include <osgEarthAnnotation/AnnotationUtils>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthSymbology/Color>
 #include <osgEarthSymbology/MeshSubdivider>
 #include <osgEarth/ThreadingUtils>
@@ -66,6 +65,22 @@ AnnotationUtils::UNIFORM_FADE()
   return s;
 }
 
+osgText::String::Encoding
+AnnotationUtils::convertTextSymbolEncoding (const TextSymbol::Encoding encoding) {
+    osgText::String::Encoding text_encoding = osgText::String::ENCODING_UNDEFINED;
+
+    switch(encoding)
+    {
+    case TextSymbol::ENCODING_ASCII: text_encoding = osgText::String::ENCODING_ASCII; break;
+    case TextSymbol::ENCODING_UTF8: text_encoding = osgText::String::ENCODING_UTF8; break;
+    case TextSymbol::ENCODING_UTF16: text_encoding = osgText::String::ENCODING_UTF16; break;
+    case TextSymbol::ENCODING_UTF32: text_encoding = osgText::String::ENCODING_UTF32; break;
+    default: text_encoding = osgText::String::ENCODING_UNDEFINED; break;
+    }
+
+    return text_encoding;
+}
+
 osg::Drawable* 
 AnnotationUtils::createTextDrawable(const std::string& text,
                                     const TextSymbol*  symbol,
@@ -73,21 +88,33 @@ AnnotationUtils::createTextDrawable(const std::string& text,
                                     
 {
     osgText::Text* t = new osgText::Text();
+    
     osgText::String::Encoding text_encoding = osgText::String::ENCODING_UNDEFINED;
     if ( symbol && symbol->encoding().isSet() )
     {
-        switch(symbol->encoding().value())
-        {
-        case TextSymbol::ENCODING_ASCII: text_encoding = osgText::String::ENCODING_ASCII; break;
-        case TextSymbol::ENCODING_UTF8: text_encoding = osgText::String::ENCODING_UTF8; break;
-        case TextSymbol::ENCODING_UTF16: text_encoding = osgText::String::ENCODING_UTF16; break;
-        case TextSymbol::ENCODING_UTF32: text_encoding = osgText::String::ENCODING_UTF32; break;
-        default: text_encoding = osgText::String::ENCODING_UNDEFINED; break;
-        }
+        text_encoding = convertTextSymbolEncoding(symbol->encoding().value());
     }
 
     t->setText( text, text_encoding );
 
+    // osgText::Text turns on depth writing by default, even if you turned it off..
+    t->setEnableDepthWrites( false );
+
+    if ( symbol && symbol->layout().isSet() )
+    {
+        if(symbol->layout().value() == TextSymbol::LAYOUT_RIGHT_TO_LEFT)
+        {
+            t->setLayout(osgText::TextBase::RIGHT_TO_LEFT);
+        }
+        else if(symbol->layout().value() == TextSymbol::LAYOUT_LEFT_TO_RIGHT)
+        {
+            t->setLayout(osgText::TextBase::LEFT_TO_RIGHT);
+        }
+        else if(symbol->layout().value() == TextSymbol::LAYOUT_VERTICAL)
+        {
+            t->setLayout(osgText::TextBase::VERTICAL);
+        }
+    }
     if ( symbol && symbol->pixelOffset().isSet() )
     {
         t->setPosition( osg::Vec3(
@@ -143,29 +170,6 @@ AnnotationUtils::createTextDrawable(const std::string& text,
     // need to be marked DYNAMIC
     if ( t->getStateSet() )
       t->getStateSet()->setRenderBinToInherit();
-    //osg::StateSet* stateSet = new osg::StateSet();
-    //t->setStateSet( stateSet );
-
-#if 0 // OBE: the decluttering bin is now set higher up (in OrthoNode)
-    //osg::StateSet* stateSet = t->getOrCreateStateSet();
-
-    if ( symbol && symbol->declutter().isSet() )
-    {
-        Decluttering::setEnabled( stateSet, *symbol->declutter() );
-    }
-    else
-    {
-        stateSet->setRenderBinToInherit();
-    }
-#endif
-
-#if 0 // OBE: shadergenerator now takes care of all this
-    // add the static "isText=true" uniform; this is a hint for the annotation shaders
-    // if they get installed.
-    //static osg::ref_ptr<osg::Uniform> s_isTextUniform = new osg::Uniform(osg::Uniform::BOOL, UNIFORM_IS_TEXT());
-    //s_isTextUniform->set( true );
-    //stateSet->addUniform( s_isTextUniform.get() );
-#endif
 
     return t;
 }
@@ -174,7 +178,8 @@ osg::Geometry*
 AnnotationUtils::createImageGeometry(osg::Image*       image,
                                      const osg::Vec2s& pixelOffset,
                                      unsigned          textureUnit,
-                                     double            heading)
+                                     double            heading,
+                                     double            scale)
 {
     if ( !image )
         return 0L;
@@ -189,23 +194,24 @@ AnnotationUtils::createImageGeometry(osg::Image*       image,
     osg::StateSet* dstate = new osg::StateSet;
     dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OFF);
     dstate->setMode(GL_LIGHTING,osg::StateAttribute::OFF);
-    //dstate->setMode(GL_BLEND, 1); // redundant. AnnotationNode sets blending.
-    dstate->setTextureAttributeAndModes(0, texture,osg::StateAttribute::ON);   
+    dstate->setTextureAttributeAndModes(0, texture,osg::StateAttribute::ON);
 
     // set up the geoset.
     osg::Geometry* geom = new osg::Geometry();
-    geom->setUseVertexBufferObjects(true);
-    
+    geom->setUseVertexBufferObjects(true);    
     geom->setStateSet(dstate);
 
-    float x0 = (float)pixelOffset.x() - image->s()/2.0;
-    float y0 = (float)pixelOffset.y() - image->t()/2.0;
+    float s = scale * image->s();
+    float t = scale * image->t();
+
+    float x0 = (float)pixelOffset.x() - s/2.0;
+    float y0 = (float)pixelOffset.y() - t/2.0;
 
     osg::Vec3Array* verts = new osg::Vec3Array(4);
-    (*verts)[0].set( x0, y0, 0 );
-    (*verts)[1].set( x0 + image->s(), y0, 0 );
-    (*verts)[2].set( x0 + image->s(), y0 + image->t(), 0 );
-    (*verts)[3].set( x0, y0 + image->t(), 0 );
+    (*verts)[0].set( x0,     y0,     0 );
+    (*verts)[1].set( x0 + s, y0,     0 );
+    (*verts)[2].set( x0 + s, y0 + t, 0 );
+    (*verts)[3].set( x0,     y0 + t, 0 );
 
     if (heading != 0.0)
     {
@@ -217,8 +223,6 @@ AnnotationUtils::createImageGeometry(osg::Image*       image,
         }
     }
     geom->setVertexArray(verts);
-    if ( verts->getVertexBufferObject() )
-        verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
 
     osg::Vec2Array* tcoords = new osg::Vec2Array(4);
     (*tcoords)[0].set(0, 0);
@@ -234,14 +238,6 @@ AnnotationUtils::createImageGeometry(osg::Image*       image,
 
     geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));
 
-#if 0
-    // add the static "isText=true" uniform; this is a hint for the annotation shaders
-    // if they get installed.
-    static osg::ref_ptr<osg::Uniform> s_isNotTextUniform = new osg::Uniform(osg::Uniform::BOOL, UNIFORM_IS_TEXT());
-    s_isNotTextUniform->set( false );
-    dstate->addUniform( s_isNotTextUniform.get() );
-#endif
-
     return geom;
 }
 
@@ -489,10 +485,11 @@ AnnotationUtils::createHemisphere( float r, const osg::Vec4& color, float maxAng
     return installTwoPassAlpha( geode );
 }
 
-    // constucts an ellipsoidal mesh that we will use to draw the atmosphere
+// constucts an ellipsoidal mesh
 osg::Geometry*
-AnnotationUtils::createEllipsoidGeometry(float majorRadius, 
-                                         float minorRadius,
+AnnotationUtils::createEllipsoidGeometry(float xRadius, 
+                                         float yRadius,
+                                         float zRadius,
                                          const osg::Vec4f& color, 
                                          float maxAngle,
                                          float minLat,
@@ -500,8 +497,6 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
                                          float minLon,
                                          float maxLon)
 {
-    osg::EllipsoidModel em( majorRadius, minorRadius );
-
     osg::Geometry* geom = new osg::Geometry();
     geom->setUseVertexBufferObjects(true);
 
@@ -509,12 +504,10 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
     float lonSpan = maxLon - minLon;
     float aspectRatio = lonSpan/latSpan;
 
-    int latSegments = std::max( 6, (int)(latSpan / maxAngle) );
-    int lonSegments = std::max( 3, (int)(latSegments * aspectRatio) );
-    //int lonSegments = 2 * latSegments;
+    int latSegments = std::max( 6, (int)ceil(latSpan / maxAngle) );
+    int lonSegments = std::max( 3, (int)ceil(latSegments * aspectRatio) );
 
     float segmentSize = latSpan/latSegments; // degrees
-    //double segmentSize = 180.0/(double)latSegments; // degrees
 
     osg::Vec3Array* verts = new osg::Vec3Array();
     verts->reserve( latSegments * lonSegments );
@@ -528,13 +521,10 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
         geom->setTexCoordArray( 0, texCoords );
     }
 
-    osg::Vec3Array* normals = 0;
-    {
-        normals = new osg::Vec3Array();
-        normals->reserve( latSegments * lonSegments );
-        geom->setNormalArray( normals );
-        geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX );
-    }
+    osg::Vec3Array* normals = new osg::Vec3Array();
+    normals->reserve( latSegments * lonSegments );
+    geom->setNormalArray( normals );
+    geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX );
 
     osg::DrawElementsUShort* el = new osg::DrawElementsUShort( GL_TRIANGLES );
     el->reserve( latSegments * lonSegments * 6 );
@@ -542,14 +532,21 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
     for( int y = 0; y <= latSegments; ++y )
     {
         float lat = minLat + segmentSize * (float)y;
-        //double lat = -90.0 + segmentSize * (double)y;
         for( int x = 0; x < lonSegments; ++x )
         {
             float lon = minLon + segmentSize * (float)x;
-            //double lon = -180.0 + segmentSize * (double)x;
-            double gx, gy, gz;
-            em.convertLatLongHeightToXYZ( osg::DegreesToRadians(lat), osg::DegreesToRadians(lon), 0.0, gx, gy, gz );
-            verts->push_back( osg::Vec3(gx, gy, gz) );
+
+            float u = osg::DegreesToRadians( lon );
+            float v = osg::DegreesToRadians( lat );
+            float cos_u = cosf(u);
+            float sin_u = sinf(u);
+            float cos_v = cosf(v);
+            float sin_v = sinf(v);
+            
+            verts->push_back(osg::Vec3(
+                xRadius * cos_u * sin_v,
+                yRadius * sin_u * sin_v,
+                zRadius * cos_v ));
 
             if (genTexCoords)
             {
@@ -560,9 +557,8 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
 
             if (normals)
             {
-                osg::Vec3 normal( gx, gy, gz);
-                normal.normalize();
-                normals->push_back( normal );
+                normals->push_back( verts->back() );
+                normals->back().normalize();
             }
 
             if ( y < latSegments )
@@ -591,8 +587,9 @@ AnnotationUtils::createEllipsoidGeometry(float majorRadius,
 }
 
 osg::Node* 
-AnnotationUtils::createEllipsoid(float majorRadius, 
-                                 float minorRadius,
+AnnotationUtils::createEllipsoid(float xRadius, 
+                                 float yRadius,
+                                 float zRadius,
                                  const osg::Vec4f& color, 
                                  float maxAngle,
                                  float minLat,
@@ -601,7 +598,7 @@ AnnotationUtils::createEllipsoid(float majorRadius,
                                  float maxLon)
 {
     osg::Geode* geode = new osg::Geode();
-    geode->addDrawable( createEllipsoidGeometry(majorRadius, minorRadius, color, maxAngle, minLat, maxLat, minLon, maxLon) );
+    geode->addDrawable( createEllipsoidGeometry(xRadius, yRadius, zRadius, color, maxAngle, minLat, maxLat, minLon, maxLon) );
 
     if ( color.a() < 1.0f )
     {
@@ -625,50 +622,6 @@ AnnotationUtils::createEllipsoid(float majorRadius,
 }
 
 osg::Node* 
-AnnotationUtils::createEllipsoid( float xr, float yr, float zr, const osg::Vec4& color, float maxAngle )
-{
-    osg::Geometry* geom = new osg::Geometry();
-    geom->setUseVertexBufferObjects(true);
-
-    osg::Vec3Array* v = new osg::Vec3Array();
-    v->reserve(6);
-    v->push_back( osg::Vec3(0,0, zr) ); // top
-    v->push_back( osg::Vec3(0,0,-zr) ); // bottom
-    v->push_back( osg::Vec3(-xr,0,0) ); // left
-    v->push_back( osg::Vec3( xr,0,0) ); // right
-    v->push_back( osg::Vec3(0, yr,0) ); // back
-    v->push_back( osg::Vec3(0,-yr,0) ); // front
-    geom->setVertexArray(v);
-    if ( v->getVertexBufferObject() )
-        v->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-
-    osg::DrawElementsUByte* b = new osg::DrawElementsUByte(GL_TRIANGLES);
-    b->reserve(24);
-    b->push_back(0); b->push_back(3); b->push_back(4);
-    b->push_back(0); b->push_back(4); b->push_back(2);
-    b->push_back(0); b->push_back(2); b->push_back(5);
-    b->push_back(0); b->push_back(5); b->push_back(3);
-    b->push_back(1); b->push_back(3); b->push_back(5);
-    b->push_back(1); b->push_back(4); b->push_back(3);
-    b->push_back(1); b->push_back(2); b->push_back(4);
-    b->push_back(1); b->push_back(5); b->push_back(2);
-    geom->addPrimitiveSet( b );
-
-    MeshSubdivider ms;
-    ms.run( *geom, osg::DegreesToRadians(15.0f), GEOINTERP_GREAT_CIRCLE );
-
-    osg::Vec4Array* c = new osg::Vec4Array(1);
-    (*c)[0] = color;
-    geom->setColorArray( c );
-    geom->setColorBinding( osg::Geometry::BIND_OVERALL );
-
-    osg::Geode* geode = new osg::Geode();
-    geode->addDrawable( geom );
-
-    return geode;
-}
-
-osg::Node* 
 AnnotationUtils::createFullScreenQuad( const osg::Vec4& color )
 {
     osg::Geometry* geom = new osg::Geometry();
diff --git a/src/osgEarthAnnotation/CMakeLists.txt b/src/osgEarthAnnotation/CMakeLists.txt
index b6a5a14..5bcb39a 100644
--- a/src/osgEarthAnnotation/CMakeLists.txt
+++ b/src/osgEarthAnnotation/CMakeLists.txt
@@ -17,7 +17,6 @@ set(LIB_PUBLIC_HEADERS
     AnnotationUtils
     CircleNode
     Common
-    Decluttering
     Decoration
     EllipseNode
     Export
@@ -31,7 +30,7 @@ set(LIB_PUBLIC_HEADERS
     ModelNode
     OrthoNode
     PlaceNode
-	RectangleNode
+    RectangleNode
     ScaleDecoration
     TrackNode
 )
@@ -44,7 +43,6 @@ set(LIB_COMMON_FILES
     AnnotationRegistry.cpp
     AnnotationUtils.cpp
     CircleNode.cpp
-    Decluttering.cpp
     Decoration.cpp
     EllipseNode.cpp
     FeatureNode.cpp
@@ -54,8 +52,8 @@ set(LIB_COMMON_FILES
     ImageOverlayEditor.cpp
     LabelNode.cpp
     LocalizedNode.cpp
-	RectangleNode.cpp
-	ModelNode.cpp
+    RectangleNode.cpp
+    ModelNode.cpp
     OrthoNode.cpp
     PlaceNode.cpp
     TrackNode.cpp
@@ -64,14 +62,14 @@ set(LIB_COMMON_FILES
 if( NOT ${OPENSCENEGRAPH_VERSION} VERSION_LESS "2.9.6" )
 
     set(LIB_PUBLIC_HEADERS ${LIB_PUBLIC_HEADERS}
-	    AnnotationEditing
-	    FeatureEditing
-	    ImageOverlayEditor
+        AnnotationEditing
+        FeatureEditing
+        ImageOverlayEditor
     )
-    	
+        
     set(LIB_COMMON_FILES ${LIB_COMMON_FILES} 
-	    AnnotationEditing.cpp
-	    FeatureEditing.cpp
+        AnnotationEditing.cpp
+        FeatureEditing.cpp
         ImageOverlayEditor.cpp
     )
         
@@ -80,7 +78,7 @@ endif()
 
 ADD_LIBRARY( ${LIB_NAME} ${OSGEARTH_USER_DEFINED_DYNAMIC_OR_STATIC}
     ${LIB_PUBLIC_HEADERS}  
-	${LIB_COMMON_FILES}
+    ${LIB_COMMON_FILES}
 )
 
 
diff --git a/src/osgEarthAnnotation/CircleNode.cpp b/src/osgEarthAnnotation/CircleNode.cpp
index ad45fb8..a176953 100644
--- a/src/osgEarthAnnotation/CircleNode.cpp
+++ b/src/osgEarthAnnotation/CircleNode.cpp
@@ -196,23 +196,20 @@ _numSegments ( 0 )
     conf.getObjIfSet( "style",  _style );
     conf.getIfSet   ( "num_segments", _numSegments );
 
-    if ( conf.hasChild("position") )
-        setPosition( GeoPoint(conf.child("position")) );
-
     rebuild();
 }
 
 Config
 CircleNode::getConfig() const
 {
-    Config conf( "circle" );
+    Config conf = LocalizedNode::getConfig();
+    conf.key() = "circle";
+
     conf.addObj( "radius", _radius );
     conf.addObj( "style",  _style );
 
     if ( _numSegments != 0 )
         conf.add( "num_segments", _numSegments );
 
-    conf.addObj( "position", getPosition() );
-
     return conf;
 }
diff --git a/src/osgEarthAnnotation/EllipseNode.cpp b/src/osgEarthAnnotation/EllipseNode.cpp
index 2581187..dff1c44 100644
--- a/src/osgEarthAnnotation/EllipseNode.cpp
+++ b/src/osgEarthAnnotation/EllipseNode.cpp
@@ -236,15 +236,14 @@ _numSegments ( 0 )
     conf.getIfSet   ( "num_segments", _numSegments );
 
     rebuild();
-
-    if ( conf.hasChild("position") )
-        setPosition( GeoPoint(conf.child("position")) );
 }
 
 Config
 EllipseNode::getConfig() const
 {
-    Config conf( "ellipse" );
+    Config conf = LocalizedNode::getConfig();
+    conf.key() = "ellipse";
+
     conf.addObj( "radius_major", _radiusMajor );
     conf.addObj( "radius_minor", _radiusMinor );
     conf.addObj( "rotation", _rotationAngle );
@@ -253,7 +252,5 @@ EllipseNode::getConfig() const
     if ( _numSegments != 0 )
         conf.add( "num_segments", _numSegments );
 
-    conf.addObj( "position", getPosition() );
-
     return conf;
 }
diff --git a/src/osgEarthAnnotation/FeatureEditing b/src/osgEarthAnnotation/FeatureEditing
index 92f1453..bd4b02a 100644
--- a/src/osgEarthAnnotation/FeatureEditing
+++ b/src/osgEarthAnnotation/FeatureEditing
@@ -22,11 +22,12 @@
 
 #include <osgEarthAnnotation/Common>
 #include <osgEarth/MapNode>
-#include <osgEarthFeatures/FeatureListSource>
 #include <osgGA/GUIEventHandler>
 #include <osgViewer/View>
 
-namespace osgEarth { namespace Annotation {
+#include <osgEarthAnnotation/FeatureNode>
+
+namespace osgEarth { namespace Annotation {    
 
     /**
      * AddPointHandler is a GUIEventHandler that allows you to append points to a Feature's Geometry
@@ -36,14 +37,10 @@ namespace osgEarth { namespace Annotation {
     public:
         /**
          * Constructs a new AddPointHandler
-         * @param feature
-         *      The Feature to edit
-         * @param source
-         *      The FeatureSource that the Feature belongs to
-         * @param mapSRS
-         *      The srs of the Map
+         * @param featureNode
+         *      The FeatureNode
          */
-        AddPointHandler(osgEarth::Features::Feature* feature, osgEarth::Features::FeatureSource* source, const osgEarth::SpatialReference* mapSRS);
+        AddPointHandler(FeatureNode* featureNode);
 
         bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );
 
@@ -67,9 +64,9 @@ namespace osgEarth { namespace Annotation {
         osgGA::GUIEventAdapter::MouseButtonMask _mouseButton;
         bool _mouseDown;
         bool _firstMove;
-        osg::ref_ptr< osgEarth::Features::FeatureSource > _source;
-        osg::ref_ptr< osgEarth::Features::Feature > _feature;
-        osg::ref_ptr<const SpatialReference> _mapSRS;
+
+        osg::ref_ptr< FeatureNode > _featureNode;
+
         osg::Node::NodeMask _intersectionMask;
     };
 
@@ -82,14 +79,10 @@ namespace osgEarth { namespace Annotation {
     public:
          /**
          * Constructs a new FeatureEditor
-         * @param feature
-         *      The Feature to edit
-         * @param source
-         *      The FeatureSource that the Feature belongs to
-         * @param mapNode
-         *      The MapNode that is being displayed
+         * @param featureNode
+         *      The FeatureNode to edit         
          */
-        FeatureEditor( osgEarth::Features::Feature* feature, osgEarth::Features::FeatureSource* source, osgEarth::MapNode* mapNode );
+        FeatureEditor( FeatureNode* featureNode );
 
         /**
          *Gets the color of the draggers when they are selected
@@ -130,9 +123,7 @@ namespace osgEarth { namespace Annotation {
         osg::Vec4f _color;
         float _size;
 
-        osg::ref_ptr< osgEarth::Features::Feature > _feature;
-        osg::ref_ptr< osgEarth::Features::FeatureSource > _source;
-        osg::ref_ptr< osgEarth::MapNode > _mapNode;
+        osg::ref_ptr< FeatureNode > _featureNode;        
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/FeatureEditing.cpp b/src/osgEarthAnnotation/FeatureEditing.cpp
index 5d90590..a773abf 100644
--- a/src/osgEarthAnnotation/FeatureEditing.cpp
+++ b/src/osgEarthAnnotation/FeatureEditing.cpp
@@ -26,10 +26,8 @@ using namespace osgEarth::Symbology;
 using namespace osgEarth::Features;
 
 /****************************************************************/
-AddPointHandler::AddPointHandler(Feature* feature, FeatureSource* source, const osgEarth::SpatialReference* mapSRS):
-_feature(feature),
-_source( source ),
-_mapSRS( mapSRS ),
+AddPointHandler::AddPointHandler( FeatureNode* featureNode):
+_featureNode( featureNode ),
 _mouseDown( false ),
 _firstMove( false ),
 _mouseButton( osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ),
@@ -52,28 +50,26 @@ AddPointHandler::getMouseButton() const
 bool
 AddPointHandler::addPoint( float x, float y, osgViewer::View* view )
 {
-    osgUtil::LineSegmentIntersector::Intersections results;
-    if ( view->computeIntersections( x, y, results, _intersectionMask ) )
-    {
-        // find the first hit under the mouse:
-        osgUtil::LineSegmentIntersector::Intersection first = *(results.begin());
-        osg::Vec3d point = first.getWorldIntersectPoint();
+    osg::Vec3d world;    
+    MapNode* mapNode = _featureNode->getMapNode();
 
-        // transform it to map coordinates:
-        double lat_rad, lon_rad, dummy;
-        _mapSRS->getEllipsoid()->convertXYZToLatLongHeight( point.x(), point.y(), point.z(), lat_rad, lon_rad, dummy );
+    if ( mapNode->getTerrain()->getWorldCoordsUnderMouse( view, x, y, world ) )
+    {
+        // Get the map point from the world
+        GeoPoint mapPoint;
+        mapPoint.fromWorld( mapNode->getMapSRS(), world );
 
-        double lat_deg = osg::RadiansToDegrees( lat_rad );
-        double lon_deg = osg::RadiansToDegrees( lon_rad );
+        Feature* feature = _featureNode->getFeature();
 
-        if (_feature.valid())            
+        if ( feature )            
         {
-            _feature->getGeometry()->push_back( osg::Vec3d(lon_deg, lat_deg, 0) );
-            _source->dirty();
-            //Also must dirty the feature profile since the geometry has changed
-            _source->dirtyFeatureProfile();
-        }
-        return true;
+            // Convert the map point to the feature's SRS
+            GeoPoint featurePoint = mapPoint.transform( feature->getSRS() );
+
+            feature->getGeometry()->push_back( featurePoint.vec3d() );            
+            _featureNode->init();            
+            return true;
+        }        
     }
     return false;
 }
@@ -119,31 +115,26 @@ AddPointHandler::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
 class MoveFeatureDraggerCallback : public Dragger::PositionChangedCallback
 {
 public:
-    MoveFeatureDraggerCallback(Feature* feature, FeatureSource* source, int point):
-      _feature(feature),
-      _source(source),
+    MoveFeatureDraggerCallback(FeatureNode* featureNode, int point):
+      _featureNode( featureNode ),      
       _point(point)
       {}
 
       virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
       {
-          (*_feature->getGeometry())[_point] = osg::Vec3d(position.x(), position.y(), 0);
-          _source->dirty();
-          _source->dirtyFeatureProfile();
+          (*_featureNode->getFeature()->getGeometry())[_point] =  osg::Vec3d(position.x(), position.y(), 0);
+          _featureNode->init();
       }
 
-      osg::ref_ptr< Feature > _feature;
-      osg::ref_ptr< FeatureSource > _source;
-
+      osg::ref_ptr< FeatureNode > _featureNode;
+      
       int _point;
 
 };
 
 /****************************************************************/
-FeatureEditor::FeatureEditor( Feature* feature, FeatureSource* source, MapNode* mapNode ):
-_feature( feature ),
-_source( source ),
-_mapNode( mapNode ),
+FeatureEditor::FeatureEditor( FeatureNode* featureNode):
+_featureNode( featureNode ),
 _color(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f)),
 _pickColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)),
 _size( 5.0f )
@@ -205,15 +196,16 @@ FeatureEditor::init()
 {
     removeChildren( 0, getNumChildren() );
 
+    Feature* feature = _featureNode->getFeature();
     //Create a dragger for each point
-    for (unsigned int i = 0; i < _feature->getGeometry()->size(); i++)
+    for (unsigned int i = 0; i < _featureNode->getFeature()->getGeometry()->size(); i++)
     {
-        SphereDragger* dragger = new SphereDragger( _mapNode );
+        SphereDragger* dragger = new SphereDragger( _featureNode->getMapNode() );
         dragger->setColor( _color );
         dragger->setPickColor( _pickColor );
         dragger->setSize( _size );
-        dragger->setPosition(GeoPoint(_feature->getSRS(),  (*_feature->getGeometry())[i].x(),  (*_feature->getGeometry())[i].y()));
-        dragger->addPositionChangedCallback(new MoveFeatureDraggerCallback(_feature.get(), _source.get(), i) );
+        dragger->setPosition(GeoPoint(feature->getSRS(),  (*feature->getGeometry())[i].x(),  (*feature->getGeometry())[i].y()));
+        dragger->addPositionChangedCallback(new MoveFeatureDraggerCallback( _featureNode.get(), i) );
 
         addChild(dragger);        
     }
diff --git a/src/osgEarthAnnotation/FeatureNode b/src/osgEarthAnnotation/FeatureNode
index 70f5399..fe2e6f6 100644
--- a/src/osgEarthAnnotation/FeatureNode
+++ b/src/osgEarthAnnotation/FeatureNode
@@ -47,8 +47,17 @@ namespace osgEarth { namespace Annotation
          */
         FeatureNode( 
             MapNode* mapNode, 
+            Feature* feature,
+            const GeometryCompilerOptions& options = GeometryCompilerOptions() );
+
+        /**
+         * Constructs a new Feautre Node.
+         * @deprecated - please use the ctor above.
+         */
+        FeatureNode( 
+            MapNode* mapNode, 
             Feature* feature, 
-            bool     draped   =false, 
+            bool     draped,
             const GeometryCompilerOptions& options = GeometryCompilerOptions() );
 
         virtual ~FeatureNode() { }
@@ -60,7 +69,8 @@ namespace osgEarth { namespace Annotation
         Feature* getFeature() { return _feature.get(); }
         const Feature* getFeature() const { return _feature.get(); }
 
-        /** Whether the feature is draped on the terrain */
+        /** Whether the feature is draped on the terrain
+         *  @deprecated - check the style instead */
         bool isDraped() const { return _draped; }
 
         void init();
@@ -69,6 +79,8 @@ namespace osgEarth { namespace Annotation
 
         virtual osg::Group* getAttachPoint();
 
+         virtual const Style& getStyle() const;
+
         virtual void setStyle(const Style& style);
 
     public: // MapNodeObserver
@@ -83,7 +95,7 @@ namespace osgEarth { namespace Annotation
     protected:
         osg::ref_ptr<Feature>        _feature;
         GeometryCompilerOptions      _options;
-        bool                         _draped;
+        bool                         _draped; // to remove
         osg::Group*                  _attachPoint;
         osg::Polytope                _featurePolytope;
 
diff --git a/src/osgEarthAnnotation/FeatureNode.cpp b/src/osgEarthAnnotation/FeatureNode.cpp
index abdd0ad..69d6a21 100644
--- a/src/osgEarthAnnotation/FeatureNode.cpp
+++ b/src/osgEarthAnnotation/FeatureNode.cpp
@@ -32,7 +32,6 @@
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Utils>
 #include <osgEarth/Registry>
-#include <osgEarth/ShaderGenerator>
 
 #include <osg/BoundingSphere>
 #include <osg/Polytope>
@@ -58,6 +57,17 @@ _options      ( options )
     init();
 }
 
+FeatureNode::FeatureNode(MapNode* mapNode,
+                         Feature* feature,
+                         const GeometryCompilerOptions& options ) :
+AnnotationNode( mapNode ),
+_feature      ( feature ),
+_draped       ( false ),
+_options      ( options )
+{
+    init();
+}
+
 void
 FeatureNode::init()
 {
@@ -110,6 +120,8 @@ FeatureNode::init()
             node = AnnotationUtils::installTwoPassAlpha( node );
         }
 
+        //OE_NOTICE << GeometryUtils::geometryToGeoJSON( _feature->getGeometry() ) << std::endl;
+
         _attachPoint = new osg::Group();
         _attachPoint->addChild( node );
 
@@ -131,7 +143,7 @@ FeatureNode::init()
             const RenderSymbol* render = _feature->style()->get<RenderSymbol>();
             if ( render && render->depthOffset().isSet() )
             {
-                clampable->depthOffset() = *render->depthOffset();
+                clampable->setDepthOffsetOptions( *render->depthOffset() );
             }
         }
 
@@ -156,7 +168,9 @@ FeatureNode::init()
 
                 // do an initial clamp to get started.
                 clampMesh( getMapNode()->getTerrain()->getGraph() );
-            }
+            } 
+
+            applyGeneralSymbology( *_feature->style() );
         }
     }
 }
@@ -178,6 +192,15 @@ FeatureNode::setFeature( Feature* feature )
     init();
 }
 
+const Style& FeatureNode::getStyle() const
+{
+    if ( _feature.valid() )
+    {
+        return *_feature->style();
+    }
+    return AnnotationNode::getStyle();
+}
+
 void
 FeatureNode::setStyle(const Style& style)
 {
@@ -206,7 +229,7 @@ FeatureNode::getAttachPoint()
 
 // This will be called by AnnotationNode when a new terrain tile comes in.
 void
-FeatureNode::reclamp( const TileKey& key, osg::Node* tile, const Terrain* )
+FeatureNode::reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain )
 {
     if ( _featurePolytope.contains( tile->getBound() ) )
     {
@@ -233,7 +256,7 @@ FeatureNode::clampMesh( osg::Node* terrainModel )
         }
 
         MeshClamper clamper( terrainModel, getMapNode()->getMapSRS(), getMapNode()->isGeocentric(), relative, scale, offset );
-        this->accept( clamper );
+        getAttachPoint()->accept( clamper );
 
         this->dirtyBound();
     }
@@ -271,7 +294,7 @@ AnnotationNode( mapNode, conf )
 
     if ( srs.valid() && geom.valid() )
     {
-        _draped = conf.value<bool>("draped",false);
+        //_draped = conf.value<bool>("draped",false);
         Feature* feature = new Feature(geom.get(), srs.get(), style);
 
         conf.getIfSet( "geointerp", "greatcircle", feature->geoInterp(), GEOINTERP_GREAT_CIRCLE );
diff --git a/src/osgEarthAnnotation/ImageOverlay.cpp b/src/osgEarthAnnotation/ImageOverlay.cpp
index 32d8749..4dde087 100644
--- a/src/osgEarthAnnotation/ImageOverlay.cpp
+++ b/src/osgEarthAnnotation/ImageOverlay.cpp
@@ -196,17 +196,6 @@ ImageOverlay::postCTOR()
 
     init();
 
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        ShaderGenerator gen;
-        d->accept( gen );
-        //// need a shader that supports one texture
-        //VirtualProgram* vp = new VirtualProgram();
-        //vp->setName( "imageoverlay");
-        //vp->installDefaultColoringShaders(1);
-        //d->getOrCreateStateSet()->setAttributeAndModes( vp, 1 );
-    }
-
     ADJUST_UPDATE_TRAV_COUNT( this, 1 );
 }
 
@@ -312,6 +301,14 @@ ImageOverlay::init()
         applyStyle( style );
         setLightingIfNotSet( false );
         clampMesh( getMapNode()->getTerrain()->getGraph() );
+
+        if ( Registry::capabilities().supportsGLSL() )
+        {
+            //OE_WARN << LC << "ShaderGen RUNNING" << std::endl;
+            ShaderGenerator gen;
+            gen.setProgramName( "osgEarth.ImageOverlay" );
+            gen.run( _geode );
+        }
     }
 }
 
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor b/src/osgEarthAnnotation/ImageOverlayEditor
index 062cd3c..99c3586 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor
+++ b/src/osgEarthAnnotation/ImageOverlayEditor
@@ -33,7 +33,7 @@ namespace osgEarth { namespace Annotation
     public:
         typedef std::map< ImageOverlay::ControlPoint, osg::ref_ptr< Dragger > >  ControlPointDraggerMap;
 
-        ImageOverlayEditor(ImageOverlay* overlay);
+        ImageOverlayEditor(ImageOverlay* overlay, bool singleVert=false);
 
         ControlPointDraggerMap& getDraggers() { return _draggers; }
 
@@ -41,6 +41,12 @@ namespace osgEarth { namespace Annotation
 
         void updateDraggers();
 
+        /**
+         * Gets or sets whether to move individual verts, allowing you to skew the image.
+         * Otherwise the image will remain north up and rectangular.
+         */
+        bool getSingleVert() const;        
+
     protected:
 
         virtual ~ImageOverlayEditor();
@@ -49,6 +55,7 @@ namespace osgEarth { namespace Annotation
         osg::ref_ptr< ImageOverlay > _overlay;
         osg::ref_ptr< ImageOverlay::ImageOverlayCallback > _overlayCallback;
         ControlPointDraggerMap _draggers;
+        bool _singleVert;
     };
 
 } } // namespace osgEarth::Annotation
diff --git a/src/osgEarthAnnotation/ImageOverlayEditor.cpp b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
index 2e0008d..7853c50 100644
--- a/src/osgEarthAnnotation/ImageOverlayEditor.cpp
+++ b/src/osgEarthAnnotation/ImageOverlayEditor.cpp
@@ -32,9 +32,10 @@ using namespace osgEarth::Annotation;
 class ImageOverlayDraggerCallback : public Dragger::PositionChangedCallback
 {
 public:
-    ImageOverlayDraggerCallback(ImageOverlay* overlay, ImageOverlay::ControlPoint controlPoint):
+    ImageOverlayDraggerCallback(ImageOverlay* overlay, ImageOverlay::ControlPoint controlPoint, bool singleVert):
       _overlay(overlay),
-      _controlPoint(controlPoint)
+      _controlPoint(controlPoint),
+      _singleVert( singleVert )
       {}
 
       virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position)
@@ -42,11 +43,12 @@ public:
           //Convert to lat/lon
           GeoPoint p;
           position.transform(SpatialReference::create( "epsg:4326"), p);
-          _overlay->setControlPoint(_controlPoint, p.x(), p.y());
+          _overlay->setControlPoint(_controlPoint, p.x(), p.y(), _singleVert);
       }
 
       osg::ref_ptr<ImageOverlay>           _overlay;
       ImageOverlay::ControlPoint _controlPoint;
+      bool _singleVert;
 };
 
 struct OverlayCallback : public ImageOverlay::ImageOverlayCallback
@@ -69,8 +71,9 @@ struct OverlayCallback : public ImageOverlay::ImageOverlayCallback
 
 
 
-ImageOverlayEditor::ImageOverlayEditor(ImageOverlay* overlay):
-_overlay  (overlay)
+ImageOverlayEditor::ImageOverlayEditor(ImageOverlay* overlay, bool singleVert):
+_overlay  (overlay),
+_singleVert( singleVert )
 {   
     _overlayCallback = new OverlayCallback(this);
     _overlay->addCallback( _overlayCallback.get() );
@@ -93,7 +96,7 @@ ImageOverlayEditor::addDragger( ImageOverlay::ControlPoint controlPoint )
     
     SphereDragger* dragger = new SphereDragger(_overlay->getMapNode());
     dragger->setPosition( GeoPoint( SpatialReference::create( "epsg:4326"), location.x(), location.y()));
-    dragger->addPositionChangedCallback( new ImageOverlayDraggerCallback(_overlay.get(), controlPoint));
+    dragger->addPositionChangedCallback( new ImageOverlayDraggerCallback(_overlay.get(), controlPoint, _singleVert));
     addChild(dragger);
     _draggers[ controlPoint ] = dragger;
 }
diff --git a/src/osgEarthAnnotation/LabelNode b/src/osgEarthAnnotation/LabelNode
index ea217fc..82d375d 100644
--- a/src/osgEarthAnnotation/LabelNode
+++ b/src/osgEarthAnnotation/LabelNode
@@ -100,7 +100,7 @@ namespace osgEarth { namespace Annotation
         /**
          * Gets a copy of the text style.
          */
-        const Style& style() const { return _style; }
+        const Style& getStyle() const { return _style; }
 
         /**
          * Sets a new text style
diff --git a/src/osgEarthAnnotation/LabelNode.cpp b/src/osgEarthAnnotation/LabelNode.cpp
index 65b6c85..78b2bf7 100644
--- a/src/osgEarthAnnotation/LabelNode.cpp
+++ b/src/osgEarthAnnotation/LabelNode.cpp
@@ -18,7 +18,6 @@
 */
 
 #include <osgEarthAnnotation/LabelNode>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthAnnotation/AnnotationRegistry>
 #include <osgEarthSymbology/Color>
@@ -142,8 +141,9 @@ LabelNode::setStyle( const Style& style )
 
     setLightingIfNotSet( false );
 
-    ShaderGenerator gen( Registry::stateSetCache() );
-    this->accept( gen );
+    ShaderGenerator gen;
+    gen.setProgramName( "osgEarth.LabelNode" );
+    gen.run( this, Registry::stateSetCache() );
 }
 
 void
diff --git a/src/osgEarthAnnotation/LocalGeometryNode.cpp b/src/osgEarthAnnotation/LocalGeometryNode.cpp
index 5362df2..440edff 100644
--- a/src/osgEarthAnnotation/LocalGeometryNode.cpp
+++ b/src/osgEarthAnnotation/LocalGeometryNode.cpp
@@ -167,9 +167,6 @@ LocalizedNode( mapNode, conf )
             conf.getObjIfSet( "style", _style );
 
             init( dbOptions );
-
-            if ( conf.hasChild("position") )
-                setPosition( GeoPoint(conf.child("position")) );
         }
     }
 }
@@ -177,14 +174,14 @@ LocalizedNode( mapNode, conf )
 Config
 LocalGeometryNode::getConfig() const
 {
-    Config conf("local_geometry");
+    Config conf = LocalizedNode::getConfig();
+    conf.key() = "local_geometry";
 
     if ( _geom.valid() )
     {
         conf.add( Config("geometry", GeometryUtils::geometryToWKT(_geom.get())) );
         if ( !_style.empty() )
             conf.addObj( "style", _style );
-        conf.addObj( "position", getPosition() );
     }
     else
     {
diff --git a/src/osgEarthAnnotation/LocalizedNode b/src/osgEarthAnnotation/LocalizedNode
index 5472d32..7b50148 100644
--- a/src/osgEarthAnnotation/LocalizedNode
+++ b/src/osgEarthAnnotation/LocalizedNode
@@ -101,12 +101,14 @@ namespace osgEarth { namespace Annotation
         void setHorizonCulling( bool value );
         bool getHorizonCulling() const;
 
+    public: // AnnotationNode
+
+        virtual Config getConfig() const;
 
     public: // osg::Node
 
         virtual osg::BoundingSphere computeBound() const;
 
-
     protected:
 
         /**
@@ -114,6 +116,9 @@ namespace osgEarth { namespace Annotation
          */
         virtual osg::MatrixTransform* getTransform() =0;
 
+	public:
+        // refreshed the main transform with data from an asbolute point
+        bool updateTransform(const GeoPoint& absPt, osg::Node* patch =0L);
 
     protected:
         bool                               _initComplete;
@@ -129,9 +134,6 @@ namespace osgEarth { namespace Annotation
         // re-clamped the vert mesh based on a new terrain tile coming in
         virtual void reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain );
 
-        // refreshed the main transform with data from an asbolute point
-        bool updateTransform(const GeoPoint& absPt, osg::Node* patch =0L);
-
         // checks for overlay requirements, and if needed, installs a decorator node above
         // the passed-in node to facilitate the clamping/draping. The proper usage pattern
         // is:  node = applyAltitudePolicy(node, style)
diff --git a/src/osgEarthAnnotation/LocalizedNode.cpp b/src/osgEarthAnnotation/LocalizedNode.cpp
index 5885fc8..f92c661 100644
--- a/src/osgEarthAnnotation/LocalizedNode.cpp
+++ b/src/osgEarthAnnotation/LocalizedNode.cpp
@@ -42,15 +42,6 @@ _mapPosition            ( position )
     init();
 }
 
-LocalizedNode::LocalizedNode(MapNode* mapNode, const Config& conf) :
-PositionedAnnotationNode( mapNode, conf ),
-_initComplete           ( false ),
-_horizonCulling         ( true ),
-_scale                  ( 1.0f, 1.0f, 1.0f )
-{
-    init();
-}
-
 
 void
 LocalizedNode::init()
@@ -274,7 +265,7 @@ LocalizedNode::applyAltitudePolicy(osg::Node* node, const Style& style)
         const RenderSymbol* render = style.get<RenderSymbol>();
         if ( render && render->depthOffset().isSet() )
         {
-            clampable->depthOffset() = *render->depthOffset();
+            clampable->setDepthOffsetOptions( *render->depthOffset() );
         }
     }
 
@@ -290,3 +281,77 @@ LocalizedNode::applyAltitudePolicy(osg::Node* node, const Style& style)
 
     return node;
 }
+
+
+//-------------------------------------------------------------------
+
+LocalizedNode::LocalizedNode(MapNode* mapNode, const Config& conf) :
+PositionedAnnotationNode( mapNode, conf ),
+_initComplete           ( false ),
+_horizonCulling         ( true ),
+_scale                  ( 1.0f, 1.0f, 1.0f )
+{
+    if ( conf.hasChild( "position" ) )
+        setPosition( GeoPoint(conf.child("position")) );
+
+    if ( conf.hasChild( "scale" ) )
+    {
+        const Config* c = conf.child_ptr("scale");
+        osg::Vec3f s( c->value("x", 1.0f), c->value("y", 1.0f), c->value("z", 1.0f) );
+        setScale( s );
+    }
+
+    if ( conf.hasChild( "local_offset" ) )
+    {
+        const Config* c = conf.child_ptr("local_offset");
+        osg::Vec3d o( c->value("x", 0.0), c->value("y", 0.0), c->value("z", 0.0) );
+        setLocalOffset( o );
+    }
+
+    if ( conf.hasChild( "local_rotation" ) )
+    {
+        const Config* c = conf.child_ptr("local_rotation");
+        osg::Quat q( c->value("x", 0.0), c->value("y", 0.0), c->value("z", 0.0), c->value("w", 1.0) );
+        setLocalRotation( q );
+    }
+
+    init();
+}
+
+Config
+LocalizedNode::getConfig() const
+{
+    Config conf = PositionedAnnotationNode::getConfig();
+
+    conf.addObj( "position", getPosition() );
+    
+    if ( _scale.x() != 1.0f || _scale.y() != 1.0f || _scale.z() != 1.0f )
+    {
+        Config c( "scale" );
+        c.add( "x", _scale.x() );
+        c.add( "y", _scale.y() );
+        c.add( "z", _scale.z() );
+        conf.add( c );
+    }
+
+    if ( _localOffset != osg::Vec3d(0,0,0) )
+    {
+        Config c( "local_offset" );
+        c.set( "x", _localOffset.x() );
+        c.set( "y", _localOffset.y() );
+        c.set( "z", _localOffset.z() );
+        conf.add( c );
+    }
+
+    if ( !_localRotation.zeroRotation() )
+    {
+        Config c( "local_rotation" );
+        c.set( "x", _localRotation.x() );
+        c.set( "y", _localRotation.y() );
+        c.set( "z", _localRotation.z() );
+        c.set( "w", _localRotation.w() );
+        conf.add( c );
+    }
+
+    return conf;
+}
diff --git a/src/osgEarthAnnotation/ModelNode.cpp b/src/osgEarthAnnotation/ModelNode.cpp
index 3f1d384..b592211 100644
--- a/src/osgEarthAnnotation/ModelNode.cpp
+++ b/src/osgEarthAnnotation/ModelNode.cpp
@@ -21,6 +21,7 @@
 #include <osgEarthAnnotation/AnnotationRegistry>
 #include <osgEarthSymbology/Style>
 #include <osgEarthSymbology/InstanceSymbol>
+#include <osgEarth/AutoScale>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderGenerator>
@@ -111,17 +112,15 @@ ModelNode::init()
                 if ( Registry::capabilities().supportsGLSL() )
                 {
                     // generate shader code for the loaded model:
-                    ShaderGenerator gen( Registry::stateSetCache() );
-                    node->accept( gen );
-
-                    // need a top-level shader too:
-                    // gw: why?
-                    //VirtualProgram* vp = new VirtualProgram();
-                    //vp->installDefaultColoringAndLightingShaders();
-                    //this->getOrCreateStateSet()->setAttributeAndModes( vp, 1 );
+                    ShaderGenerator gen;
+                    gen.setProgramName( "osgEarth.ModelNode" );
+                    gen.run( node, Registry::stateSetCache() );
 
                     // do we really need this? perhaps
+#if 0
+                    // better: put your modelnode under the mapnode?
                     node->addCullCallback( new UpdateLightingUniformsHelper() );
+#endif
                 }
 
                 // attach to the transform:
@@ -136,6 +135,13 @@ ModelNode::init()
                     this->setScale( osg::Vec3f(s, s, s) );
                 }
 
+                // auto scaling?
+                if ( sym->autoScale() == true )
+                {
+                    this->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN );
+                }
+
+                // rotational offsets?
                 if (sym && (sym->heading().isSet() || sym->pitch().isSet() || sym->roll().isSet()) )
                 {
                     osg::Matrix rot;
@@ -185,20 +191,16 @@ _dbOptions   ( dbOptions )
         _style.getOrCreate<ModelSymbol>()->url() = StringExpression(uri);
 
     init();
-
-    if ( conf.hasChild( "position" ) )
-        setPosition( GeoPoint(conf.child("position")) );
 }
 
 Config
 ModelNode::getConfig() const
 {
-    Config conf("model");
+    Config conf = LocalizedNode::getConfig();
+    conf.key() = "model";
 
     if ( !_style.empty() )
         conf.addObj( "style", _style );
 
-    conf.addObj( "position", getPosition() );
-
     return conf;
 }
diff --git a/src/osgEarthAnnotation/OrthoNode b/src/osgEarthAnnotation/OrthoNode
index 1272a0c..3ab2656 100644
--- a/src/osgEarthAnnotation/OrthoNode
+++ b/src/osgEarthAnnotation/OrthoNode
@@ -20,7 +20,7 @@
 #define OSGEARTH_ANNO_ORTHO_NODE_H 1
 
 #include <osgEarthAnnotation/AnnotationNode>
-#include <osgEarthAnnotation/Decluttering>
+#include <osgEarth/Decluttering>
 #include <osgEarth/SpatialReference>
 #include <osgEarth/CullingUtils>
 #include <osg/AutoTransform>
@@ -95,6 +95,12 @@ namespace osgEarth { namespace Annotation
         bool getOcclusionCulling() const;
         void setOcclusionCulling( bool value );
 
+        /**
+         * Gets or sets the maximum altitude that the occlusion culling takes place.
+         */
+        double getOcclusionCullingMaxAltitude() const;
+        void setOcclusionCullingMaxAltitude( double occlusionCullingMaxAltitude );
+
     public: // AnnotationNode
 
         virtual void applyStyle(const Style& style);
@@ -121,6 +127,7 @@ namespace osgEarth { namespace Annotation
         osg::Group*                    _attachPoint;
         bool                           _horizonCulling;
         bool                           _occlusionCulling;
+        optional< double >             _occlusionCullingMaxAltitude;
         GeoPoint                       _mapPosition;
         osg::Vec3d                     _localOffset;
 
diff --git a/src/osgEarthAnnotation/OrthoNode.cpp b/src/osgEarthAnnotation/OrthoNode.cpp
index 04727bd..a2deb45 100644
--- a/src/osgEarthAnnotation/OrthoNode.cpp
+++ b/src/osgEarthAnnotation/OrthoNode.cpp
@@ -20,7 +20,6 @@
 #include <osgEarthAnnotation/OrthoNode>
 #include <osgEarthAnnotation/AnnotationUtils>
 #include <osgEarthAnnotation/AnnotationSettings>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthSymbology/Color>
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/CullingUtils>
@@ -32,6 +31,8 @@
 #include <osg/Point>
 #include <osg/Depth>
 
+#define LC "[OrthoNode] "
+
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
 
@@ -284,30 +285,55 @@ OrthoNode::applyStyle(const Style& style)
     const TextSymbol* text = style.get<TextSymbol>();
     if ( text && text->declutter().isSet() )
     {
-        if ( text->declutter() == true )
-        {
-            this->getOrCreateStateSet()->setRenderBinDetails(
-                12,
-                OSGEARTH_DECLUTTER_BIN );
-        }
-        else
+        Decluttering::setEnabled( this->getOrCreateStateSet(), (text->declutter() == true) );
+        //if ( text->declutter() == true )
+        //{
+        //    this->getOrCreateStateSet()->setRenderBinDetails(
+        //        0,
+        //        OSGEARTH_DECLUTTER_BIN );
+        //}
+        //else
+        //{
+        //    this->getOrCreateStateSet()->setRenderBinToInherit();
+        //}
+    }
+
+
+    // check for occlusion culling
+    if ( text && text->occlusionCull().isSet() )
+    {
+        setOcclusionCulling( *text->occlusionCull() );
+
+        if (text->occlusionCullAltitude().isSet())
         {
-            this->getOrCreateStateSet()->setRenderBinToInherit();
+            setOcclusionCullingMaxAltitude( *text->occlusionCullAltitude() );
         }
     }
 
     const IconSymbol* icon = style.get<IconSymbol>();
     if ( icon && icon->declutter().isSet() )
     {
-        if ( icon->declutter() == true )
-        {
-            this->getOrCreateStateSet()->setRenderBinDetails(
-                12,
-                OSGEARTH_DECLUTTER_BIN );
-        }
-        else
+        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
+    if ( icon && icon->occlusionCull().isSet() )
+    {
+        this->setOcclusionCulling( *icon->occlusionCull() );
+
+        if (icon->occlusionCullAltitude().isSet())
         {
-            this->getOrCreateStateSet()->setRenderBinToInherit();
+            setOcclusionCullingMaxAltitude( *icon->occlusionCullAltitude() );
         }
     }
 
@@ -417,7 +443,7 @@ OrthoNode::adjustOcclusionCullingPoint( const osg::Vec3d& world )
     {
         const osg::EllipsoidModel* em = getMapNode()->getMapSRS()->getEllipsoid();
         osg::Vec3d up = em ? em->computeLocalUpVector( world.x(), world.y(), world.z() ) : osg::Vec3d(0,0,1);
-        osg::Vec3d adjust = up * 0.1;
+        osg::Vec3d adjust = up * AnnotationSettings::getOcclusionCullingHeightAdjustment();        
         return world + adjust;
     }
     else
@@ -442,8 +468,8 @@ OrthoNode::setOcclusionCulling( bool value )
         if ( _occlusionCulling && getMapNode() )
         {
             osg::Vec3d world = _autoxform->getPosition();
-            _occlusionCuller = new OcclusionCullingCallback(adjustOcclusionCullingPoint(world), getMapNode());
-            _occlusionCuller->setMaxRange( AnnotationSettings::getOcclusionQueryMaxRange() );
+            _occlusionCuller = new OcclusionCullingCallback( getMapNode()->getMapSRS(),  adjustOcclusionCullingPoint(world), getMapNode()->getTerrainEngine() );
+            _occlusionCuller->setMaxAltitude( getOcclusionCullingMaxAltitude() );
             addCullCallback( _occlusionCuller.get()  );
         }
         else
@@ -460,6 +486,25 @@ OrthoNode::setOcclusionCulling( bool value )
     }
 }
 
+double
+OrthoNode::getOcclusionCullingMaxAltitude() const
+{
+    if (_occlusionCullingMaxAltitude.isSet())
+    {
+        return *_occlusionCullingMaxAltitude;
+    }
+    return AnnotationSettings::getOcclusionCullingMaxAltitude();
+}
+
+void OrthoNode::setOcclusionCullingMaxAltitude( double occlusionCullingMaxAltitude )
+{
+    _occlusionCullingMaxAltitude = occlusionCullingMaxAltitude;
+    if ( _occlusionCuller.valid() )
+    {
+        _occlusionCuller->setMaxAltitude( getOcclusionCullingMaxAltitude() );         
+    }
+}
+
 void
 OrthoNode::reclamp( const TileKey& key, osg::Node* tile, const Terrain* terrain )
 {
diff --git a/src/osgEarthAnnotation/PlaceNode.cpp b/src/osgEarthAnnotation/PlaceNode.cpp
index eac1f39..f0071f3 100644
--- a/src/osgEarthAnnotation/PlaceNode.cpp
+++ b/src/osgEarthAnnotation/PlaceNode.cpp
@@ -88,13 +88,17 @@ PlaceNode::init()
 
     // If there's no explicit text, look to the text symbol for content.
     if ( _text.empty() && _style.has<TextSymbol>() )
+    {
         _text = _style.get<TextSymbol>()->content()->eval();
+    }
 
     osg::ref_ptr<const InstanceSymbol> instance = _style.get<InstanceSymbol>();
 
     // backwards compability, support for deprecated MarkerSymbol
     if ( !instance.valid() && _style.has<MarkerSymbol>() )
+    {
         instance = _style.get<MarkerSymbol>()->convertToInstanceSymbol();
+    }
 
     const IconSymbol* icon = instance->asIcon();
 
@@ -119,44 +123,54 @@ PlaceNode::init()
     // found an image; now format it:
     if ( _image.get() )
     {
+        // Scale the icon if necessary
+        double scale = 1.0;
+        if ( icon && icon->scale().isSet() )
+        {
+            scale = icon->scale()->eval();
+        }
+
+        double s = scale * _image->s();
+        double t = scale * _image->t();
+
         // this offset anchors the image at the bottom
         osg::Vec2s offset;
         if ( !icon || !icon->alignment().isSet() )
         {	
             // default to bottom center
-            offset.set(0.0, (_image->t() / 2.0));
+            offset.set(0.0, t / 2.0);
         }
         else
         {	// default to bottom center
             switch (icon->alignment().value())
             {
             case IconSymbol::ALIGN_LEFT_TOP:
-                offset.set((_image->s() / 2.0), -(_image->t() / 2.0));
+                offset.set((s / 2.0), -(t / 2.0));
                 break;
             case IconSymbol::ALIGN_LEFT_CENTER:
-                offset.set((_image->s() / 2.0), 0.0);
+                offset.set((s / 2.0), 0.0);
                 break;
             case IconSymbol::ALIGN_LEFT_BOTTOM:
-                offset.set((_image->s() / 2.0), (_image->t() / 2.0));
+                offset.set((s / 2.0), (t / 2.0));
                 break;
             case IconSymbol::ALIGN_CENTER_TOP:
-                offset.set(0.0, -(_image->t() / 2.0));
+                offset.set(0.0, -(t / 2.0));
                 break;
             case IconSymbol::ALIGN_CENTER_CENTER:
                 offset.set(0.0, 0.0);
                 break;
             case IconSymbol::ALIGN_CENTER_BOTTOM:
             default:
-                offset.set(0.0, (_image->t() / 2.0));
+                offset.set(0.0, (t / 2.0));
                 break;
             case IconSymbol::ALIGN_RIGHT_TOP:
-                offset.set(-(_image->s() / 2.0), -(_image->t() / 2.0));
+                offset.set(-(s / 2.0), -(t / 2.0));
                 break;
             case IconSymbol::ALIGN_RIGHT_CENTER:
-                offset.set(-(_image->s() / 2.0), 0.0);
+                offset.set(-(s / 2.0), 0.0);
                 break;
             case IconSymbol::ALIGN_RIGHT_BOTTOM:
-                offset.set(-(_image->s() / 2.0), (_image->t() / 2.0));
+                offset.set(-(s / 2.0), (t / 2.0));
                 break;
             }
         }
@@ -170,14 +184,16 @@ PlaceNode::init()
 
         //We must actually rotate the geometry itself and not use a MatrixTransform b/c the 
         //decluttering doesn't respect Transforms above the drawable.
-        osg::Geometry* imageGeom = AnnotationUtils::createImageGeometry( _image.get(), offset, 0, heading );
+        osg::Geometry* imageGeom = AnnotationUtils::createImageGeometry( _image.get(), offset, 0, heading, scale );
         if ( imageGeom )
+        {
             _geode->addDrawable( imageGeom );
+        }
 
         text = AnnotationUtils::createTextDrawable(
             _text,
             _style.get<TextSymbol>(),
-            osg::Vec3( (offset.x() + (_image->s() / 2.0) + 2), offset.y(), 0 ) );
+            osg::Vec3( (offset.x() + (s / 2.0) + 2), offset.y(), 0 ) );
     }
     else
     {
@@ -195,13 +211,15 @@ PlaceNode::init()
 
     getAttachPoint()->addChild( _geode );
 
-    // for clamping
+    // for clamping and occlusion culling    
+    //OE_WARN << LC << "PlaceNode::applyStyle: " << _style.getConfig().toJSON(true) << std::endl;
     applyStyle( _style );
 
     setLightingIfNotSet( false );
 
-    ShaderGenerator gen( Registry::stateSetCache() );
-    this->accept( gen );
+    ShaderGenerator gen;
+    gen.setProgramName( "osgEarth.PlaceNode" );
+    gen.run( this, Registry::stateSetCache() );
 
     // re-apply annotation drawable-level stuff as neccesary.
     AnnotationData* ad = getAnnotationData();
@@ -230,7 +248,14 @@ PlaceNode::setText( const std::string& text )
         osgText::Text* d = dynamic_cast<osgText::Text*>( i->get() );
         if ( d )
         {
-            d->setText( text );
+			TextSymbol* symbol =  _style.getOrCreate<TextSymbol>();
+			osgText::String::Encoding text_encoding = osgText::String::ENCODING_UNDEFINED;
+			if ( symbol && symbol->encoding().isSet() )
+			{
+				text_encoding = AnnotationUtils::convertTextSymbolEncoding(symbol->encoding().value());
+			}
+
+            d->setText( text, text_encoding );
             break;
         }
     }
diff --git a/src/osgEarthAnnotation/RectangleNode.cpp b/src/osgEarthAnnotation/RectangleNode.cpp
index 9872630..5b012d8 100644
--- a/src/osgEarthAnnotation/RectangleNode.cpp
+++ b/src/osgEarthAnnotation/RectangleNode.cpp
@@ -378,20 +378,18 @@ LocalizedNode( mapNode, conf )
     conf.getObjIfSet( "height", _height );
     conf.getObjIfSet( "style",  _style );
 
-    if ( conf.hasChild("position") )
-        setPosition( GeoPoint(conf.child("position")) );
-
     rebuild();
 }
 
 Config
 RectangleNode::getConfig() const
 {
-    Config conf( "rectangle" );
+    Config conf = LocalizedNode::getConfig();
+    conf.key() = "rectangle";
+
     conf.addObj( "width",  _width );
     conf.addObj( "height", _height );
     conf.addObj( "style",  _style );
-    conf.addObj( "position", getPosition() );
 
     return conf;
 }
diff --git a/src/osgEarthAnnotation/TrackNode.cpp b/src/osgEarthAnnotation/TrackNode.cpp
index 7eb6d11..1a5bb27 100644
--- a/src/osgEarthAnnotation/TrackNode.cpp
+++ b/src/osgEarthAnnotation/TrackNode.cpp
@@ -75,7 +75,8 @@ TrackNode::init( const TrackNodeFieldSchema& schema )
             image,                    // image
             osg::Vec2s(0,0),          // offset
             0,                        // tex image unit
-            icon->heading()->eval() );
+            icon->heading()->eval(),
+            icon->scale()->eval() );
 
         if ( imageGeom )
         {
@@ -122,8 +123,9 @@ TrackNode::init( const TrackNodeFieldSchema& schema )
 
     getAttachPoint()->addChild( _geode );
 
-    ShaderGenerator gen( Registry::stateSetCache() );
-    this->accept( gen );
+    ShaderGenerator gen;
+    gen.setProgramName( "osgEarth.TrackNode" );
+    gen.run( this, Registry::stateSetCache() );
 }
 
 void
diff --git a/src/osgEarthDrivers/CMakeLists.txt b/src/osgEarthDrivers/CMakeLists.txt
index bdb270b..f41aaf4 100644
--- a/src/osgEarthDrivers/CMakeLists.txt
+++ b/src/osgEarthDrivers/CMakeLists.txt
@@ -51,6 +51,12 @@ ADD_SUBDIRECTORY(cache_filesystem)
 ADD_SUBDIRECTORY(ocean_surface)
 ADD_SUBDIRECTORY(refresh)
 ADD_SUBDIRECTORY(xyz)
+ADD_SUBDIRECTORY(bing)
+ADD_SUBDIRECTORY(tileindex)
+
+IF(LIBNOISE_FOUND)
+    ADD_SUBDIRECTORY(noise)
+ENDIF(LIBNOISE_FOUND)
 
 IF(GDAL_FOUND)
   ADD_SUBDIRECTORY(gdal)
@@ -78,6 +84,10 @@ IF(V8_FOUND)
   ADD_SUBDIRECTORY(script_engine_v8)
 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)
diff --git a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
index 6aab362..4bb5f91 100644
--- a/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
+++ b/src/osgEarthDrivers/agglite/AGGLiteRasterizerTileSource.cpp
@@ -53,23 +53,18 @@ using namespace OpenThreads;
 class AGGLiteRasterizerTileSource : public FeatureTileSource
 {
 public:
+    struct RenderFrame {
+        double xmin, ymin;
+        double xf, yf;
+    };
+
+public:
     AGGLiteRasterizerTileSource( const TileSourceOptions& options ) : FeatureTileSource( options ),
         _options( options )
     {
         //nop
     }
 
-    struct BuildData : public osg::Referenced {
-        BuildData() : _pass(0) { }
-        int _pass;
-    };
-
-    //override
-    osg::Referenced* createBuildData()
-    {
-        return new BuildData();
-    }
-
     //override
     bool preProcess(osg::Image* image, osg::Referenced* buildData)
     {
@@ -82,92 +77,54 @@ public:
     //override
     bool renderFeaturesForStyle(
         const Style&       style,
-        const FeatureList& inFeatures,
+        const FeatureList& features,
         osg::Referenced*   buildData,
         const GeoExtent&   imageExtent,
         osg::Image*        image )
     {
-        // local copy of the features that we can process
-        FeatureList features = inFeatures;
-
-        BuildData* bd = static_cast<BuildData*>( buildData );
-
         // A processing context to use with the filters:
         FilterContext context;
         context.profile() = getFeatureSource()->getFeatureProfile();
 
-        const LineSymbol* masterLine = style.getSymbol<LineSymbol>();
+        const LineSymbol*    masterLine = style.getSymbol<LineSymbol>();
         const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>();
 
-        //bool embeddedStyles = getFeatureSource()->hasEmbeddedStyles();
-
-        // if only a line symbol exists, and there are polygons in the mix, draw them
-        // as outlines (line rings).
-        //OE_INFO << LC << "Line Symbol = " << (masterLine == 0L ? "null" : masterLine->getConfig().toString()) << std::endl;
-        //OE_INFO << LC << "Poly SYmbol = " << (masterPoly == 0L ? "null" : masterPoly->getConfig().toString()) << std::endl;
+        // sort into bins, making a copy for lines that require buffering.
+        FeatureList polygons;
+        FeatureList lines;
 
-        //bool convertPolysToRings = poly == 0L && line != 0L;
-        //if ( convertPolysToRings )
-        //    OE_INFO << LC << "No PolygonSymbol; will draw polygons to rings" << std::endl;
-
-        // initialize:
-        double xmin = imageExtent.xMin();
-        double ymin = imageExtent.yMin();
-        double xf = (double)image->s() / imageExtent.width();
-        double yf = (double)image->t() / imageExtent.height();
-
-        // strictly speaking we should iterate over the features and buffer each one that's a line,
-        // rather then checking for the existence of a LineSymbol.
-        FeatureList linesToBuffer;
-        for(FeatureList::iterator i = features.begin(); i != features.end(); i++)
+        for(FeatureList::const_iterator f = features.begin(); f != features.end(); ++f)
         {
-            Feature* feature = i->get();
-            Geometry* geom = feature->getGeometry();
-
-            if ( geom )
+            if ( f->get()->getGeometry() )
             {
-                // check for an embedded style:
-                const LineSymbol* line = feature->style().isSet() ? 
-                    feature->style()->getSymbol<LineSymbol>() : masterLine;
-
-                const PolygonSymbol* poly =
-                    feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly;
-
-                // if we have polygons but only a LineSymbol, draw the poly as a line.
-                if ( geom->getComponentType() == Geometry::TYPE_POLYGON )
+                if ( masterPoly || f->get()->style()->has<PolygonSymbol>() )
                 {
-                    if ( !poly && line )
-                    {
-                        Feature* outline = new Feature( *feature );
-                        geom = geom->cloneAs( Geometry::TYPE_RING );
-                        outline->setGeometry( geom );
-                        *i = outline;
-                        feature = outline;
-                    }
-                    //TODO: fix to enable outlined polys. doesn't work, not sure why -gw
-                    //else if ( poly && line )
-                    //{
-                    //    Feature* outline = new Feature();
-                    //    geom = geom->cloneAs( Geometry::TYPE_LINESTRING );
-                    //    outline->setGeometry( geom );
-                    //    features.push_back( outline );
-                    //}
+                    polygons.push_back( f->get() );
                 }
 
-                bool needsBuffering =
-                    geom->getComponentType() == Geometry::TYPE_LINESTRING || 
-                    geom->getComponentType() == Geometry::TYPE_RING;
-
-                if ( needsBuffering )
+                if ( masterLine || f->get()->style()->has<LineSymbol>() )
                 {
-                    linesToBuffer.push_back( feature );
+                    Feature* newFeature = new Feature( *f->get() );
+                    if ( !newFeature->getGeometry()->isLinear() )
+                    {
+                        newFeature->setGeometry( newFeature->getGeometry()->cloneAs(Geometry::TYPE_RING) );
+                    }
+                    lines.push_back( newFeature );
                 }
             }
         }
 
-        if ( linesToBuffer.size() > 0 )
+        // initialize:
+        RenderFrame frame;
+        frame.xmin = imageExtent.xMin();
+        frame.ymin = imageExtent.yMin();
+        frame.xf   = (double)image->s() / imageExtent.width();
+        frame.yf   = (double)image->t() / imageExtent.height();
+
+        if ( lines.size() > 0 )
         {
-            //We are buffering in the features native extent, so we need to use the transform extent to get the proper "resolution" for the image
+            // We are buffering in the features native extent, so we need to use the
+            // transformed extent to get the proper "resolution" for the image
             const SpatialReference* featureSRS = context.profile()->getSRS();
             GeoExtent transformedExtent = imageExtent.transform(featureSRS);
 
@@ -185,7 +142,7 @@ public:
             {
                 ResampleFilter resample;
                 resample.minLength() = osg::minimum( xres, yres );
-                context = resample.push( linesToBuffer, context );
+                context = resample.push( lines, context );
             }
 
             // now run the buffer operation on all lines:
@@ -244,13 +201,14 @@ public:
             }
 
             buffer.distance() = lineWidth * 0.5;   // since the distance is for one side
-            buffer.push( linesToBuffer, context );
+            buffer.push( lines, context );
         }
 
         // Transform the features into the map's SRS:
         TransformFilter xform( imageExtent.getSRS() );
         xform.setLocalizeCoordinates( false );
-        context = xform.push( features, context );
+        FilterContext polysContext = xform.push( polygons, context );
+        FilterContext linesContext = xform.push( lines, context );
 
         // set up the AGG renderer:
         agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 );
@@ -263,6 +221,8 @@ public:
         ras.gamma(1.3);
         ras.filling_rule(agg::fill_even_odd);
 
+        // construct an extent for cropping the geometry to our tile.
+        // extend just outside the actual extents so we don't get edge artifacts:
         GeoExtent cropExtent = GeoExtent(imageExtent);
         cropExtent.scale(1.1, 1.1);
 
@@ -272,79 +232,42 @@ public:
         cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 ));
         cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 ));
 
-        double lineWidth = 1.0;
-        if ( masterLine )
-            lineWidth = (double)masterLine->stroke()->width().value();
-
-        osg::Vec4 color = osg::Vec4(1, 1, 1, 1);
-        if ( masterLine )
-            color = masterLine->stroke()->color();
-
-        // render the features
-        for(FeatureList::iterator i = features.begin(); i != features.end(); i++)
+        // render the polygons
+        for(FeatureList::iterator i = polygons.begin(); i != polygons.end(); i++)
         {
-            Feature* feature = i->get();
-
+            Feature*  feature  = i->get();
             Geometry* geometry = feature->getGeometry();
 
-            osg::ref_ptr< Geometry > croppedGeometry;
-            if ( ! geometry->crop( cropPoly.get(), croppedGeometry ) )
-                continue;
-
-            // set up a default color:
-            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 );
-
-            GeometryIterator gi( croppedGeometry.get() );
-            while( gi.hasMore() )
+            osg::ref_ptr<Geometry> croppedGeometry;
+            if ( geometry->crop( cropPoly.get(), croppedGeometry ) )
             {
-                c = color;
-                Geometry* g = gi.next();
-            
-                const LineSymbol* line = feature->style().isSet() ? 
-                    feature->style()->getSymbol<LineSymbol>() : masterLine;
-
                 const PolygonSymbol* poly =
-                    feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly;
-
-                if (g->getType() == Geometry::TYPE_RING || g->getType() == Geometry::TYPE_LINESTRING)
-                {
-                    if ( line )
-                        c = line->stroke()->color();
-                    else if ( poly )
-                        c = poly->fill()->color();
-                }
-
-                else if ( g->getType() == Geometry::TYPE_POLYGON )
-                {
-                    if ( poly )
-                        c = poly->fill()->color();
-                    else if ( line )
-                        c = line->stroke()->color();
-                }
+                    feature->style().isSet() && feature->style()->has<PolygonSymbol>() ? feature->style()->get<PolygonSymbol>() :
+                    masterPoly;
+                
+                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);
+            }
+        }
 
-                a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up
-                fgColor = agg::rgba8( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a );
+        // render the lines
+        for(FeatureList::iterator i = lines.begin(); i != lines.end(); i++)
+        {
+            Feature*  feature  = i->get();
+            Geometry* geometry = feature->getGeometry();
 
-                ras.filling_rule( agg::fill_even_odd );
-                for( Geometry::iterator p = g->begin(); p != g->end(); p++ )
-                {
-                    const osg::Vec3d& p0 = *p;
-                    double x0 = xf*(p0.x()-xmin);
-                    double y0 = yf*(p0.y()-ymin);
-
-                    if ( p == g->begin() )
-                        ras.move_to_d( x0, y0 );
-                    else
-                        ras.line_to_d( x0, y0 );
-                }
+            osg::ref_ptr<Geometry> croppedGeometry;
+            if ( geometry->crop( cropPoly.get(), croppedGeometry ) )
+            {
+                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);
             }
-            ras.render(ren, fgColor);
-            ras.reset();
         }
 
-        bd->_pass++;
         return true;
     }
 
@@ -361,6 +284,39 @@ public:
         return true;
     }
 
+    // rasterizes a geometry.
+    void rasterize(const Geometry* geometry, const osg::Vec4& color, RenderFrame& frame, 
+                   agg::rasterizer& ras, agg::renderer<agg::span_abgr32>& ren)
+    {
+        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 );
+
+        ras.filling_rule( agg::fill_even_odd );
+
+        ConstGeometryIterator gi( geometry );
+        while( gi.hasMore() )
+        {
+            c = color;
+            const Geometry* g = gi.next();
+
+            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 );
+            }
+        }
+        ras.render(ren, fgColor);
+        ras.reset();
+    }
+
+
     virtual std::string getExtension()  const 
     {
         return "png";
@@ -371,7 +327,10 @@ private:
     std::string _configPath;
 };
 
-// Reads tiles from a TileCache disk cache.
+
+/**
+ * Plugin entry point for the AGGLite feature rasterizer
+ */
 class AGGLiteRasterizerTileSourceDriver : public TileSourceDriver
 {
     public:
@@ -384,7 +343,9 @@ class AGGLiteRasterizerTileSourceDriver : public TileSourceDriver
         
         virtual bool acceptsExtension(const std::string& extension) const
         {
-            return osgDB::equalCaseInsensitive( extension, "osgearth_agglite" );
+            return
+                osgDB::equalCaseInsensitive( extension, "osgearth_agglite" ) ||
+                osgDB::equalCaseInsensitive( extension, "osgearth_rasterize" );
         }
 
         virtual ReadResult readObject(const std::string& file_name, const Options* options) const
diff --git a/src/osgEarthDrivers/arcgis/ArcGISOptions b/src/osgEarthDrivers/arcgis/ArcGISOptions
index fe27a64..4f82e03 100644
--- a/src/osgEarthDrivers/arcgis/ArcGISOptions
+++ b/src/osgEarthDrivers/arcgis/ArcGISOptions
@@ -30,12 +30,18 @@ namespace osgEarth { namespace Drivers
     class ArcGISOptions : public TileSourceOptions // NO EXPORT; header only
     {
     public:
+        /** URL of the MapService */
         optional<URI>& url() { return _url; }
         const optional<URI>& url() const { return _url; }
 
+        /** ArcGIS security token */
         optional<std::string>& token() { return _token; }
         const optional<std::string>& token() const { return _token; }
 
+        /** Override the image format read from the server metadata */
+        optional<std::string>& format() { return _format; }
+        const optional<std::string>& format() const { return _format; }
+
     public:
         ArcGISOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
         {
@@ -51,6 +57,7 @@ namespace osgEarth { namespace Drivers
             Config conf = TileSourceOptions::getConfig();
             conf.updateIfSet("url", _url );
             conf.updateIfSet("token", _token );
+            conf.updateIfSet("format", _format );
             return conf;
         }
 
@@ -62,11 +69,13 @@ namespace osgEarth { namespace Drivers
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "url", _url );
             conf.getIfSet( "token", _token);
+            conf.getIfSet( "format", _format);
         }
 
     private:
         optional<URI>         _url;
         optional<std::string> _token;
+        optional<std::string> _format;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
index fe81566..19b4069 100644
--- a/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
+++ b/src/osgEarthDrivers/arcgis/ReaderWriterArcGIS.cpp
@@ -53,9 +53,19 @@ public:
         if ( _layer.empty() )
             _layer = "_alllayers"; // default to the AGS "fused view"
 
-        //TODO: detect the format
-        if ( _format.empty() )
+        if ( _options.format().isSet() ) 
+            _format = *_options.format();
+        else
+            _format = _map_service.getTileInfo().getFormat();
+
+        std::transform( _format.begin(), _format.end(), _format.begin(), tolower );
+        if ( _format.length() > 3 && _format.substr( 0, 3 ) == "png" )
             _format = "png";
+
+        if ( _format == "mixed" )
+            _format = "";
+        if ( !_format.empty() )
+            _dot_format = "." + _format;
     }
 
     // override
@@ -126,17 +136,12 @@ public:
         unsigned int tile_x, tile_y;
         key.getTileXY( tile_x, tile_y );
 
-        std::string f = _map_service.getTileInfo().getFormat();
-        std::transform( f.begin(), f.end(), f.begin(), tolower );
-        if ( f.length() > 3 && f.substr( 0, 3 ) == "png" )
-            f = "png";
-
         if ( _map_service.isTiled() )
         {
             buf << _options.url()->full() << "/tile"
                 << "/" << level
                 << "/" << tile_y
-                << "/" << tile_x << "." << f;
+                << "/" << tile_x << _dot_format;
         }
         else
         {
@@ -145,11 +150,11 @@ public:
             buf << std::setprecision(16)
                 << _options.url()->full() << "/export"
                 << "?bbox=" << ex.xMin() << "," << ex.yMin() << "," << ex.xMax() << "," << ex.yMax()
-                << "&format=" << f 
+                << "&format=" << _format 
                 << "&size=256,256"
                 << "&transparent=true"
-                << "&f=image"
-                << "&" << "." << f;
+                << "&f=image";
+                //<< "&" << "." << f;
         }
 
         //Add the token if necessary
@@ -189,7 +194,7 @@ private:
     optional<ProfileOptions> _profileConf;
     std::string _map;
     std::string _layer;
-    std::string _format;
+    std::string _format, _dot_format;
     MapService _map_service;
     osg::ref_ptr<osgDB::Options> _dbOptions;
 };
diff --git a/src/osgEarthDrivers/bing/BingOptions b/src/osgEarthDrivers/bing/BingOptions
new file mode 100644
index 0000000..b353268
--- /dev/null
+++ b/src/osgEarthDrivers/bing/BingOptions
@@ -0,0 +1,113 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2013 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#ifndef OSGEARTH_DRIVER_BING_OPTIONS
+#define OSGEARTH_DRIVER_BING_OPTIONS 1
+
+#include <osgEarth/TileSource>
+
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+
+    /**
+     * Configuration structure for the Microsoft Bing driver.
+     * http://www.bing.com/developers/
+     *
+     * Using Bing requires an API key. You can get one from the URL above.
+     * You are responsible for complying with the Bing terms of service.
+     */
+    class BingOptions : public TileSourceOptions // NO EXPORT; header only
+    {
+    public:
+
+        /**
+         * API Key to use to access Bing REST services. Required!
+         */
+        optional<std::string>& key() { return _apiKey; }
+        const optional<std::string>& key() const { return _apiKey; }
+
+        /**
+         * Imagery set to access. Default is "Aerial".
+         *
+         * As of this writing, options are:
+         *    Aerial
+         *    AerialWithImagery
+         *    Road
+         *
+         * (The "Birdseye" layers don't work with this driver at this time.)
+         *
+         * See (http://msdn.microsoft.com/en-us/library/ff701716.aspx) for more information.
+         */
+        optional<std::string>& imagerySet() { return _imagerySet; }
+        const optional<std::string>& imagerySet() const { return _imagerySet; }
+
+        /**
+         * Base URL for the Bing REST API. By default this will point to the
+         * Internet Bing services.
+         */
+        optional<std::string>& imageryMetadataAPI() { return _imageryMetadataAPI; }
+        const optional<std::string>& imageryMetadataAPI() const { return _imageryMetadataAPI; }
+
+
+    public:
+        /**
+         * Constructs a Bing configuration structure.
+         */
+        BingOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt ),
+            _imagerySet        ( "Aerial" ),
+            _imageryMetadataAPI( "http://dev.virtualearth.net/REST/v1/Imagery/Metadata" )
+        {
+            setDriver( "bing" );
+            fromConfig( _conf );
+        }
+
+        /** dtor */
+        virtual ~BingOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = TileSourceOptions::getConfig();
+            conf.updateIfSet("key",                  _apiKey);
+            conf.updateIfSet("imagery_set",          _imagerySet );
+            conf.updateIfSet("imagery_metadata_api", _imageryMetadataAPI );
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            TileSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet("key",                  _apiKey);
+            conf.getIfSet("imagery_set",          _imagerySet );
+            conf.getIfSet("imagery_metadata_api", _imageryMetadataAPI );
+        }
+
+        optional<std::string> _apiKey;
+        optional<std::string> _imagerySet;
+        optional<std::string> _imageryMetadataAPI;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_BING_OPTIONS
diff --git a/src/osgEarthDrivers/bing/BingTileSource.cpp b/src/osgEarthDrivers/bing/BingTileSource.cpp
new file mode 100644
index 0000000..b74e387
--- /dev/null
+++ b/src/osgEarthDrivers/bing/BingTileSource.cpp
@@ -0,0 +1,309 @@
+#include "BingOptions"
+
+#include <osgEarth/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Random>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/Containers>
+
+#include <osgEarthSymbology/Geometry>
+#include <osgEarthSymbology/GeometryRasterizer>
+
+#include <osgDB/FileNameUtils>
+#include <osgText/Font>
+
+#include <OpenThreads/Atomic>
+
+using namespace osgEarth;
+using namespace osgEarth::Symbology;
+
+#define LC "[Bing] "
+
+namespace
+{
+    struct AlphaBlend
+    {
+        bool operator()( const osg::Vec4f& src, osg::Vec4f& dest )
+        {
+            float sa = src.a();
+            dest.set(
+                dest.r()*(1.0f-sa) + src.r()*sa,
+                dest.g()*(1.0f-sa) + src.g()*sa,
+                dest.b()*(1.0f-sa) + src.b()*sa,
+                dest.a() );
+            return true;
+        }
+    };
+
+    typedef LRUCache<std::string, std::string> TileURICache;
+}
+
+
+class BingTileSource : public TileSource
+{
+private:
+    osgEarth::Drivers::BingOptions _options;
+    osg::ref_ptr<osgDB::Options>   _dbOptions;
+    Random                         _prng;
+    bool                           _debugDirect;
+    osg::ref_ptr<Geometry>         _geom;
+    osg::ref_ptr<osgText::Font>    _font;
+    TileURICache                   _tileURICache;
+    OpenThreads::Atomic            _apiCount;
+
+public:
+    /**
+     * Constructs the tile source
+     */
+    BingTileSource(const TileSourceOptions& options) : 
+      TileSource   ( options ),
+      _options     ( options ),
+      _debugDirect ( false ),
+      _tileURICache( true, 1024u )
+    {
+        if ( ::getenv("OSGEARTH_BING_DIRECT") )
+            _debugDirect = true;
+        
+        if ( ::getenv("OSGEARTH_BING_DEBUG") )
+        {
+            _geom = new Ring();
+            _geom->push_back( osg::Vec3(10, 10, 0) );
+            _geom->push_back( osg::Vec3(245, 10, 0) );
+            _geom->push_back( osg::Vec3(245, 245, 0) );
+            _geom->push_back( osg::Vec3(10, 245, 0) );
+            _font = Registry::instance()->getDefaultFont();
+        }
+    }
+
+    /**
+     * One-tile tile source initialization.
+     */
+    Status initialize(const osgDB::Options* dbOptions)
+    {
+        // Always apply the NO CACHE policy.
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
+        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+
+        // If the user did not include an API key, fail.
+        if ( !_options.key().isSet() )
+        {
+            return Status::Error("Bing API key is required");
+        }
+
+        // If the user did not specify an imagery set, default to aerial.
+        if ( !_options.imagerySet().isSet() )
+        {
+            _options.imagerySet() = "Aerial";
+        }
+
+        // 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 );
+
+        return STATUS_OK;
+    }
+    
+    /**
+     * Tell the terrain engine not to cache tiles form this source.
+     */
+    CachePolicy getCachePolicyHint(const Profile*) const
+    {
+        return CachePolicy::NO_CACHE;
+    }
+
+
+    /**
+     * Create and return an image for the given TileKey.
+     */
+    osg::Image* createImage( const TileKey& key, ProgressCallback* progress )
+    {
+        if (_debugDirect)
+        {
+            //osg::Image* image = new osg::Image;
+            //image->allocateImage(256,256,1, GL_RGB, GL_UNSIGNED_BYTE);
+            //return image;
+
+            //return osgDB::readImageFile( getDirectURI(key) );
+            return URI(getDirectURI(key)).getImage(_dbOptions.get(), progress);
+        }
+
+        // center point of the tile (will be in spherical mercator)
+        double x, y;
+        key.getExtent().getCentroid(x, y);
+
+        // transform it to lat/long:
+        GeoPoint geo;
+
+        GeoPoint( getProfile()->getSRS(), x, y ).transform(
+            getProfile()->getSRS()->getGeographicSRS(),
+            geo );
+
+        // contact the REST API. Docs are here:
+        // http://msdn.microsoft.com/en-us/library/ff701716.aspx
+
+        // construct the request URI:
+        std::string request = Stringify()
+            << std::setprecision(12)
+            << _options.imageryMetadataAPI().get()     // base REST API
+            << "/"    << _options.imagerySet().get()   // imagery set to use
+            << "/"    << geo.y() << "," << geo.x()     // center point in lat/long
+            << "?zl=" << key.getLOD() + 1              // zoom level
+            << "&o=json"                               // response format
+            << "&key=" << _options.key().get();        // API key
+
+        // check the URI cache.
+        URI                  location;
+        TileURICache::Record rec;
+
+        if ( _tileURICache.get(request, rec) )
+        {
+            location = URI(rec.value());
+            //CacheStats stats = _tileURICache.getStats();
+            //OE_INFO << "Ratio = " << (stats._hitRatio*100) << "%" << std::endl;
+        }
+        else
+        {
+            unsigned c = ++_apiCount;
+            if ( c % 25 == 0 )
+                OE_INFO << LC << "API calls = " << c << std::endl;
+            
+            // fetch it:
+            ReadResult metadataResult = URI(request).readString(_dbOptions, progress);
+
+            if ( metadataResult.failed() )
+            {
+                // check for a REST error:
+                if ( metadataResult.code() == ReadResult::RESULT_SERVER_ERROR )
+                {
+                    OE_WARN << LC << "REST API request error!" << std::endl;
+
+                    Config metadata;
+                    std::string content = metadataResult.getString();
+                    metadata.fromJSON( content );
+                    ConfigSet errors = metadata.child("errorDetails").children();
+                    for(ConfigSet::const_iterator i = errors.begin(); i != errors.end(); ++i )
+                    {
+                        OE_WARN << LC << "REST API: " << i->value() << std::endl;
+                    }
+                    return 0L;
+                }
+                else
+                {
+                    OE_WARN << LC << "Request error: " << metadataResult.getResultCodeString() << std::endl;
+                }
+                return 0L;
+            }
+
+            // decode it:
+            Config metadata;
+            if ( !metadata.fromJSON(metadataResult.getString()) )
+            {
+                OE_WARN << LC << "Error decoding REST API response" << std::endl;
+                return 0L;
+            }
+
+            // check the vintage field. If it's empty, that means we got a "no data" tile.
+            Config* vintageEnd = metadata.find("vintageEnd");
+            if ( !vintageEnd || vintageEnd->value().empty() )
+            {
+                OE_DEBUG << LC << "NO data image encountered." << std::endl;
+                return 0L;
+            }
+
+            // find the tile URI:
+            Config* locationConf= metadata.find("imageUrl");
+            if ( !locationConf )
+            {
+                OE_WARN << LC << "REST API JSON parsing error (imageUrl not found)" << std::endl;
+                return 0L;
+            }
+
+            location = URI( locationConf->value() );
+            _tileURICache.insert( request, location.full() );
+        }
+
+        // request the actual tile
+        //OE_INFO << "key = " << key.str() << ", URL = " << location->value() << std::endl;
+
+        //osg::Image* image = location.getImage(_dbOptions.get(), progress);
+        osg::Image* image = osgDB::readImageFile( location.full() );
+
+        if ( image &&  _geom.valid() )
+        {
+            GeometryRasterizer rasterizer( image->s(), image->t() );
+            rasterizer.draw( _geom.get(), osg::Vec4(1,1,1,1) );
+            osg::ref_ptr<osg::Image> overlay = rasterizer.finalize();
+            ImageUtils::PixelVisitor<AlphaBlend> blend;
+            blend.accept( overlay.get(), image );
+        }
+
+        return image;
+    }
+
+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();
+    }
+
+    std::string getDirectURI(const TileKey& key)
+    {
+        return Stringify()
+            << "http://ecn.t"
+            << _prng.next(4)
+            << ".tiles.virtualearth.net/tiles/h"
+            << getQuadKey(key)
+            << ".jpeg?g=1236";
+    }
+};
+
+
+class BingTileSourceDriver : public TileSourceDriver
+{
+public:
+    BingTileSourceDriver()
+    {
+        supportsExtension( "osgearth_bing", "Microsoft Bing Driver" );
+    }
+
+    virtual const char* className()
+    {
+        return "Microsoft Bing 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 BingTileSource( getTileSourceOptions(options) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_bing, BingTileSourceDriver)
diff --git a/src/osgEarthDrivers/bing/CMakeLists.txt b/src/osgEarthDrivers/bing/CMakeLists.txt
new file mode 100644
index 0000000..904158a
--- /dev/null
+++ b/src/osgEarthDrivers/bing/CMakeLists.txt
@@ -0,0 +1,19 @@
+
+SET(TARGET_SRC
+    BingTileSource.cpp
+)
+
+# headers to show in IDE
+SET(TARGET_H
+    BingOptions
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
+
+SETUP_PLUGIN(osgearth_bing)
+
+
+# to install public driver includes:
+SET(LIB_NAME bing)
+SET(LIB_PUBLIC_HEADERS BingOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
index 7e6fa3d..9f389a9 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache
@@ -54,7 +54,7 @@ namespace osgEarth { namespace Drivers
             return conf;
         }
         virtual void mergeConfig( const Config& conf ) {
-            ConfigOptions::mergeConfig( conf );            
+            ConfigOptions::mergeConfig( conf );
             fromConfig( conf );
         }
 
diff --git a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
index a80d4aa..c40b298 100644
--- a/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
+++ b/src/osgEarthDrivers/cache_filesystem/FileSystemCache.cpp
@@ -22,10 +22,12 @@
 #include <osgEarth/ThreadingUtils>
 #include <osgEarth/XmlUtils>
 #include <osgEarth/URI>
+#include <osgEarth/FileUtils>
 #include <osgEarth/Registry>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <fstream>
+#include <sys/stat.h>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
@@ -64,7 +66,7 @@ namespace
 
         void init();
 
-        std::string            _rootPath;
+        std::string _rootPath;
     };
 
     /** 
@@ -78,17 +80,21 @@ namespace
 
     public: // CacheBin interface
 
-        ReadResult readObject( const std::string& key, double maxAge =DBL_MAX );
+        ReadResult readObject(const std::string& key, TimeStamp minTime);
 
-        ReadResult readImage( const std::string& key, double maxAge =DBL_MAX );
+        ReadResult readImage(const std::string& key, TimeStamp minTime);
 
-        ReadResult readNode( const std::string& key, double maxAge =DBL_MAX );
+        ReadResult readNode(const std::string& key, TimeStamp minTime);
 
-        ReadResult readString( const std::string& key, double maxAge =DBL_MAX );
+        ReadResult readString(const std::string& key, TimeStamp minTime);
 
-        bool write( const std::string& key, const osg::Object* object, const Config& meta );
+        bool write(const std::string& key, const osg::Object* object, const Config& meta);
 
-        bool isCached( const std::string& key, double maxAge =DBL_MAX );
+        bool remove(const std::string& key);
+
+        bool touch(const std::string& key);
+
+        RecordStatus getRecordStatus(const std::string& key, TimeStamp minTime);
 
         bool purge();
 
@@ -99,8 +105,14 @@ namespace
     protected:
         bool purgeDirectory( const std::string& dir );
 
+        bool binValidForReading();
+
+        bool binValidForWriting();
+
         bool                              _ok;
-        std::string                       _metaPath;
+        bool                              _binPathExists;
+        std::string                       _metaPath;       // full path to the bin's metadata file
+        std::string                       _binPath;        // full path to the bin's root folder
         osg::ref_ptr<osgDB::ReaderWriter> _rw;
         osg::ref_ptr<osgDB::Options>      _rwOptions;
         Threading::ReadWriteMutex         _rwmutex;
@@ -154,12 +166,7 @@ namespace
     void
     FileSystemCache::init()
     {
-        osgDB::makeDirectory( _rootPath );
-        if ( !osgDB::fileExists( _rootPath ) )
-        {
-            OE_WARN << LC << "FAILED to create root folder for cache at \"" << _rootPath << "\"" << std::endl;
-            _ok = false;
-        }
+        //nop
     }
 
     CacheBin*
@@ -185,131 +192,189 @@ namespace
 
     //------------------------------------------------------------------------
 
-    FileSystemCacheBin::FileSystemCacheBin(const std::string&   binID,
-                                           const std::string&   rootPath) :
-    CacheBin ( binID ),
-    _ok      ( true )
+    bool
+    FileSystemCacheBin::binValidForReading()
     {
-        std::string binPath = osgDB::concatPaths( rootPath, binID );
-        _metaPath = osgDB::concatPaths( binPath, "osgearth_cacheinfo.json" );
-
-        OE_INFO << LC << "Initializing cache bin: " << _metaPath << std::endl;
-        osgDB::makeDirectoryForFile( _metaPath );
-        if ( !osgDB::fileExists( binPath ) )
+        if ( !_binPathExists )
         {
-            OE_WARN << LC << "FAILED to create folder for cache bin at \"" << binPath << "\"" << std::endl;
-            _ok = false;
+            if ( osgDB::fileExists(_binPath) )
+            {
+                // ready to go
+                _binPathExists = true;
+                _ok = true;
+            }
+            else if ( _ok )
+            {
+                // one-time error.
+                OE_WARN << LC << "Failed to locate cache bin at [" << _binPath << "]" << std::endl;
+                _ok = false;
+            }
         }
-        else
+
+        return _ok;
+    }
+
+    bool
+    FileSystemCacheBin::binValidForWriting()
+    {
+        if ( !_binPathExists )
         {
-            _rw = osgDB::Registry::instance()->getReaderWriterForExtension( "osgb" );
+            osgDB::makeDirectoryForFile( _metaPath );
+
+            if ( osgDB::fileExists(_binPath) )
+            {
+                // ready to go
+                _binPathExists = true;
+                _ok = true;
+            }
+            else
+            {
+                // one-time error.
+                OE_WARN << LC << "FAILED to find or create cache bin at [" << _metaPath << "]" << std::endl;
+                _ok = false;
+            }
+        }
+
+        return _ok;
+    }
+
+    FileSystemCacheBin::FileSystemCacheBin(const std::string&   binID,
+                                           const std::string&   rootPath) :
+    CacheBin            ( binID ),
+    _binPathExists      ( false )
+    {
+        _binPath = osgDB::concatPaths( rootPath, binID );
+        _metaPath = osgDB::concatPaths( _binPath, "osgearth_cacheinfo.json" );
+
+        _rw = osgDB::Registry::instance()->getReaderWriterForExtension( "osgb" );
 #ifdef OSGEARTH_HAVE_ZLIB
-            _rwOptions = Registry::instance()->cloneOrCreateOptions();
-            _rwOptions->setOptionString( "Compressor=zlib" );
+        _rwOptions = Registry::instance()->cloneOrCreateOptions();
+        _rwOptions->setOptionString( "Compressor=zlib" );
 #endif
-            CachePolicy::NO_CACHE.apply(_rwOptions.get());
-        }
+        CachePolicy::NO_CACHE.apply(_rwOptions.get());
     }
 
     ReadResult
-    FileSystemCacheBin::readImage(const std::string& key, double maxAge)
+    FileSystemCacheBin::readImage(const std::string& key, TimeStamp minTime)
     {
-        if ( !_ok ) return 0L;
-
-        //todo: handle maxAge
+        if ( !binValidForReading() ) 
+            return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
         URI fileURI( toLegalFileName(key), _metaPath );
+        std::string path = fileURI.full() + ".osgb";
+
+        if ( !osgDB::fileExists(path) )
+            return ReadResult( ReadResult::RESULT_NOT_FOUND );
+
+        if ( osgEarth::getLastModifiedTime(path) < minTime )
+            return ReadResult( ReadResult::RESULT_EXPIRED );
 
         osgDB::ReaderWriter::ReadResult r;
         {
             ScopedReadLock sharedLock( _rwmutex );
-            r = _rw->readImage( fileURI.full() + ".osgb", _rwOptions.get() );
-            if ( r.success() )
-            {
-                // read metadata
-                Config meta;
-                std::string metafile = fileURI.full() + ".meta";
-                if ( osgDB::fileExists(metafile) )
-                    readMeta( metafile, meta );
+            r = _rw->readImage( path, _rwOptions.get() );
+            if ( !r.success() )
+                return ReadResult();
 
-                return ReadResult( r.getImage(), meta );
-            }
-        }
+            // read metadata
+            Config meta;
+            std::string metafile = fileURI.full() + ".meta";
+            if ( osgDB::fileExists(metafile) )
+                readMeta( metafile, meta );
 
-        return ReadResult(); //error
+            return ReadResult( r.getImage(), meta );
+        }
     }
 
     ReadResult
-    FileSystemCacheBin::readObject(const std::string& key, double maxAge)
+    FileSystemCacheBin::readObject(const std::string& key, TimeStamp minTime)
     {
-        if ( !_ok ) return 0L;
-
-        //todo: handle maxAge
+        if ( !binValidForReading() ) 
+            return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
         URI fileURI( toLegalFileName(key), _metaPath );
+        std::string path = fileURI.full() + ".osgb";
+
+        if ( !osgDB::fileExists(path) )
+            return ReadResult( ReadResult::RESULT_NOT_FOUND );
+
+        if ( osgEarth::getLastModifiedTime(path) < minTime )
+            return ReadResult( ReadResult::RESULT_EXPIRED );
 
         osgDB::ReaderWriter::ReadResult r;
         {
             ScopedReadLock sharedLock( _rwmutex );
-            r = _rw->readObject( fileURI.full() + ".osgb", _rwOptions.get() );
-            if ( r.success() )
-            {
-                // read metadata
-                Config meta;
-                std::string metafile = fileURI.full() + ".meta";
-                if ( osgDB::fileExists(metafile) )
-                    readMeta( metafile, meta );
-
-                // TODO: read metadata
-                return ReadResult( r.getObject(), meta );
-            }
-        }
+            r = _rw->readObject( path, _rwOptions.get() );
+            if ( !r.success() )
+                return ReadResult();
+
+            // read metadata
+            Config meta;
+            std::string metafile = fileURI.full() + ".meta";
+            if ( osgDB::fileExists(metafile) )
+                readMeta( metafile, meta );
 
-        return ReadResult();
+             return ReadResult( r.getObject(), meta );
+        }
     }
 
     ReadResult
-    FileSystemCacheBin::readNode(const std::string& key, double maxAge)
+    FileSystemCacheBin::readNode(const std::string& key, TimeStamp minTime)
     {
-        if ( !_ok ) return 0L;
-
-        //todo: handle maxAge
+        if ( !binValidForReading() ) 
+            return ReadResult(ReadResult::RESULT_NOT_FOUND);
 
         // mangle "key" into a legal path name
         URI fileURI( toLegalFileName(key), _metaPath );
+        std::string path = fileURI.full() + ".osgb";
+
+        if ( !osgDB::fileExists(path) )
+            return ReadResult( ReadResult::RESULT_NOT_FOUND );
+
+        if ( osgEarth::getLastModifiedTime(path) < minTime )
+            return ReadResult( ReadResult::RESULT_EXPIRED );
 
         osgDB::ReaderWriter::ReadResult r;
         {
             ScopedReadLock sharedLock( _rwmutex );
-            r = _rw->readNode( fileURI.full() + ".osgb", _rwOptions.get() );
-            if ( r.success() )
-            {            
-                // read metadata
-                Config meta;
-                std::string metafile = fileURI.full() + ".meta";
-                if ( osgDB::fileExists(metafile) )
-                    readMeta( metafile, meta );
-
-                return ReadResult( r.getNode(), meta );
-            }
-        }
+            r = _rw->readNode( path, _rwOptions.get() );
+            if ( !r.success() )
+                return ReadResult();
+
+            // read metadata
+            Config meta;
+            std::string metafile = fileURI.full() + ".meta";
+            if ( osgDB::fileExists(metafile) )
+                readMeta( metafile, meta );
 
-        return ReadResult();
+            return ReadResult( r.getNode(), meta );
+        }
     }
 
     ReadResult
-    FileSystemCacheBin::readString(const std::string& key, double maxAge)
+    FileSystemCacheBin::readString(const std::string& key, TimeStamp minTime)
     {
-        ReadResult r = readObject(key, maxAge);
-        return r.succeeded() && r.get<StringObject>() ? r : ReadResult();
+        ReadResult r = readObject(key, minTime);
+        if ( r.succeeded() )
+        {
+            if ( r.get<StringObject>() )
+                return r;
+            else
+                return ReadResult();
+        }
+        else
+        {
+            return r;
+        }
     }
 
     bool
     FileSystemCacheBin::write( const std::string& key, const osg::Object* object, const Config& meta )
     {
-        if ( !_ok || !object ) return false;
+        if ( !binValidForWriting() || !object ) 
+            return false;
 
         // convert the key into a legal filename:
         URI fileURI( toLegalFileName(key), _metaPath );
@@ -324,7 +389,7 @@ namespace
                 osgDB::makeDirectoryForFile( fileURI.full() );
 
             // write it.  
-            osgDB::ReaderWriter::WriteResult r;      
+            osgDB::ReaderWriter::WriteResult r;
 
             if ( dynamic_cast<const osg::Image*>(object) )
             {
@@ -365,18 +430,45 @@ namespace
         return objWriteOK;
     }
 
+    CacheBin::RecordStatus
+    FileSystemCacheBin::getRecordStatus(const std::string& key, TimeStamp minTime)
+    {
+        if ( !binValidForReading() ) 
+            return STATUS_NOT_FOUND;
+
+        URI fileURI( toLegalFileName(key), _metaPath );
+        std::string path( fileURI.full() + ".osgb" );
+        if ( !osgDB::fileExists(path) )
+            return STATUS_NOT_FOUND;
+
+        struct stat s;
+        ::stat( path.c_str(), &s );
+        return s.st_mtime >= minTime ? STATUS_OK : STATUS_EXPIRED;
+    }
+
     bool
-    FileSystemCacheBin::isCached( const std::string& key, double maxAge )
+    FileSystemCacheBin::remove(const std::string& key)
     {
-        if ( !_ok ) return false;
+        if ( !binValidForReading() ) return false;
+        URI fileURI( toLegalFileName(key), _metaPath );
+        std::string path( fileURI.full() + ".osgb" );
+        return ::unlink( path.c_str() ) == 0;
+    }
 
+    bool
+    FileSystemCacheBin::touch(const std::string& key)
+    {
+        if ( !binValidForReading() ) return false;
         URI fileURI( toLegalFileName(key), _metaPath );
-        return osgDB::fileExists( fileURI.full() + ".osgb" );
+        std::string path( fileURI.full() + ".osgb" );
+        return osgEarth::touchFile( path );
     }
 
     bool
     FileSystemCacheBin::purgeDirectory( const std::string& dir )
     {
+        if ( !binValidForReading() ) return false;
+
         bool allOK = true;
         osgDB::DirectoryContents dc = osgDB::getDirectoryContents( dir );
 
@@ -416,7 +508,7 @@ namespace
     bool
     FileSystemCacheBin::purge()
     {
-        if ( !_ok ) return false;
+        if ( !binValidForReading() ) return false;
         {
             ScopedWriteLock exclusiveLock( _rwmutex );
             std::string binDir = osgDB::getFilePath( _metaPath );
@@ -427,7 +519,7 @@ namespace
     Config
     FileSystemCacheBin::readMetadata()
     {
-        if ( !_ok ) return Config();
+        if ( !binValidForReading() ) return Config();
 
         ScopedReadLock sharedLock( _rwmutex );
         
@@ -440,7 +532,7 @@ namespace
     bool
     FileSystemCacheBin::writeMetadata( const Config& conf )
     {
-        if ( !_ok ) return false;
+        if ( !binValidForWriting() ) return false;
 
         ScopedWriteLock exclusiveLock( _rwmutex );
 
diff --git a/src/osgEarthDrivers/debug/DebugTileSource.cpp b/src/osgEarthDrivers/debug/DebugTileSource.cpp
index 9ff19c4..cee5395 100644
--- a/src/osgEarthDrivers/debug/DebugTileSource.cpp
+++ b/src/osgEarthDrivers/debug/DebugTileSource.cpp
@@ -131,8 +131,8 @@ public:
         return "png";
     }
 
-    /** Tell the terrain engine not to cache tiles form this source. */
-    CachePolicy getCachePolicyHint() const
+    /** Tell the terrain engine never to cache tiles form this source. */
+    CachePolicy getCachePolicyHint(const Profile*) const
     {
         return CachePolicy::NO_CACHE;
     }
diff --git a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
index f411446..046bc5f 100644
--- a/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
+++ b/src/osgEarthDrivers/earth/EarthFileSerializer2.cpp
@@ -67,11 +67,10 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& refere
         for( ConfigSet::const_iterator i = heightfields.begin(); i != heightfields.end(); i++ )
         {
             Config layerDriverConf = *i;
-            layerDriverConf.add( "default_tile_size", "16" );
+            layerDriverConf.add( "default_tile_size", "15" );
 
             ElevationLayerOptions layerOpt( layerDriverConf );
             layerOpt.name() = layerDriverConf.value( "name" );
-            //layerOpt.driver() = TileSourceOptions( layerDriverConf );
 
             map->addElevationLayer( new ElevationLayer(layerOpt) );
         }
@@ -88,23 +87,6 @@ EarthFileSerializer2::deserialize( const Config& conf, const std::string& refere
         layerOpt.driver() = ModelSourceOptions( layerDriverConf );
 
         map->addModelLayer( new ModelLayer(layerOpt) );
-        //map->addModelLayer( new ModelLayer( layerDriverConf.value("name"), ModelSourceOptions(*i) ) );
-    }
-
-    // Overlay layers (just an alias for Model Layer with overlay=true)
-    ConfigSet overlays = conf.children( "overlay" );
-    for( ConfigSet::const_iterator i = overlays.begin(); i != overlays.end(); i++ )
-    {
-        Config layerDriverConf = *i;
-        if ( !layerDriverConf.hasValue("driver") )
-            layerDriverConf.set("driver", "feature_geom");
-
-        ModelLayerOptions layerOpt( layerDriverConf );
-        layerOpt.name() = layerDriverConf.value( "name" );
-        layerOpt.driver() = ModelSourceOptions( layerDriverConf );
-        layerOpt.overlay() = true; // forced on when "overlay" specified
-
-        map->addModelLayer( new ModelLayer(layerOpt) );
     }
 
     // Mask layer:
diff --git a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
index 7271d29..3ece305 100644
--- a/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
+++ b/src/osgEarthDrivers/earth/ReaderWriterOsgEarth.cpp
@@ -37,14 +37,25 @@ using namespace osgEarth;
 #define Q2(x) #x
 #define Q(x)  Q2(x)
 
-#if defined(_DEBUG) && defined(OSGEARTH_DEBUG_POSTFIX)
-#    define LIBNAME_UTIL "osgEarthUtil" ## Q(OSGEARTH_DEBUG_POSTFIX)
+#if (defined(_DEBUG) || defined(QT_DEBUG)) && defined(OSGEARTH_DEBUG_POSTFIX)
+#   define LIBNAME_UTIL_POSTFIX Q(OSGEARTH_DEBUG_POSTFIX)
 #elif defined(OSGEARTH_RELEASE_POSTFIX)
-#    define LIBNAME_UTIL "osgEarthUtil" ## Q(OSGEARTH_RELEASE_POSTFIX)
+#   define LIBNAME_UTIL_POSTFIX Q(OSGEARTH_RELEASE_POSTFIX)
 #else
-#    define LIBNAME_UTIL "osgEarthUtil"
+#   define LIBNAME_UTIL_POSTFIX ""
 #endif
 
+#if defined(WIN32)
+#   define LIBNAME_UTIL "osgEarthUtil"
+#   define LIBNAME_UTIL_EXTENSION ".dll"
+#else
+#   define LIBNAME_UTIL "libosgEarthUtil"
+#   if defined(__APPLE__)
+#       define LIBNAME_UTIL_EXTENSION ".dylib"
+#   else
+#       define LIBNAME_UTIL_EXTENSION ".so"
+#   endif
+#endif
 
 
 class ReaderWriterEarth : public osgDB::ReaderWriter
@@ -55,8 +66,8 @@ class ReaderWriterEarth : public osgDB::ReaderWriter
             // force the loading of other osgEarth libraries that might be needed to 
             // deserialize an earth file. 
             // osgEarthUtil: contains ColorFilter implementations
-            OE_DEBUG << LC << "Forced load: " << LIBNAME_UTIL << std::endl;
-            osgDB::Registry::instance()->loadLibrary( LIBNAME_UTIL );
+            OE_DEBUG << LC << "Forced load: " << LIBNAME_UTIL LIBNAME_UTIL_POSTFIX LIBNAME_UTIL_EXTENSION << std::endl;
+            osgDB::Registry::instance()->loadLibrary( LIBNAME_UTIL LIBNAME_UTIL_POSTFIX LIBNAME_UTIL_EXTENSION );
         }
 
         virtual const char* className()
diff --git a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
index e24f6bc..e311de9 100644
--- a/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_byo/BYOTerrainEngineNode.cpp
@@ -62,8 +62,9 @@ BYOTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& optio
         {
             if ( myoptions.shaderPolicy() == SHADERPOLICY_GENERATE )
             {
-                ShaderGenerator gen( Registry::stateSetCache() );
-                node->accept( gen );
+                ShaderGenerator gen;
+                gen.setProgramName( "osgEarth.BYOTerrainEngine" );
+                gen.run( node, new StateSetCache() );
             }
             else if ( myoptions.shaderPolicy() == SHADERPOLICY_DISABLE )
             {
diff --git a/src/osgEarthDrivers/engine_mp/CMakeLists.txt b/src/osgEarthDrivers/engine_mp/CMakeLists.txt
index 1e3680b..0ac03e2 100644
--- a/src/osgEarthDrivers/engine_mp/CMakeLists.txt
+++ b/src/osgEarthDrivers/engine_mp/CMakeLists.txt
@@ -2,38 +2,39 @@
 SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthSymbology)
 
 SET(TARGET_SRC
-    CustomPagedLOD.cpp
     KeyNodeFactory.cpp
-    LODFactorCallback.cpp
     MPGeometry.cpp
     MPTerrainEngineNode.cpp
     MPTerrainEngineDriver.cpp
-    SerialKeyNodeFactory.cpp
+    SingleKeyNodeFactory.cpp
     TerrainNode.cpp
+    TileGroup.cpp
+    TileModel.cpp
     TileModelCompiler.cpp
     TileNode.cpp
     TileNodeRegistry.cpp
     TileModelFactory.cpp
+    TilePagedLOD.cpp
 )
 
 SET(TARGET_H
     Common
-    CustomPagedLOD
     DynamicLODScaleCallback
     FileLocationCallback
     KeyNodeFactory
-    LODFactorCallback
     MPGeometry
     MPTerrainEngineNode
     MPTerrainEngineOptions
     QuickReleaseGLObjects
-    SerialKeyNodeFactory
+    SingleKeyNodeFactory
     TerrainNode
+    TileGroup
     TileModel
     TileModelCompiler
     TileNode
     TileNodeRegistry
     TileModelFactory
+    TilePagedLOD
 )
 
 SETUP_PLUGIN(osgearth_engine_mp)
diff --git a/src/osgEarthDrivers/engine_mp/CustomPagedLOD b/src/osgEarthDrivers/engine_mp/CustomPagedLOD
deleted file mode 100644
index df31593..0000000
--- a/src/osgEarthDrivers/engine_mp/CustomPagedLOD
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#ifndef OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD
-#define OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD 1
-
-#include "Common"
-#include "TileNode"
-#include "TileNodeRegistry"
-#include <osg/PagedLOD>
-
-using namespace osgEarth;
-
-namespace osgEarth_engine_mp
-{
-    /**
-     * Customized PagedLOD node that automatically registers TileNodes
-     * with a TileNodeRegistry when they are added to the scene graph.
-     */
-    class CustomPagedLOD : public osg::PagedLOD
-    {
-    public:
-        /**
-         * Constructs a PagedLOD node that will register TileNode's with the 
-         * provided registry 
-         */
-        CustomPagedLOD(
-            TileNodeRegistry* liveTiles,
-            TileNodeRegistry* deadTiles );
-
-        /**
-         * Destructor 
-         */
-        virtual ~CustomPagedLOD();
-
-    public: // osg::Group
-
-        /**
-         * Override Group methods so that a TileNode gets registered when the 
-         * DatabasePager adds it to the scene graph. Of course this would happen
-         * if you simply added the TileNode to any parent, but in this 
-         * implmementation the DatabasePager is the only entity adding a TileNode
-         * to a parent.
-         *
-         * DatabasePager only calls addChild or removeChild, but we will implement the
-         * other methods (insert, replace, etc) later if necessary
-         */
-        bool addChild( osg::Node* child );
-        bool removeChildren(unsigned pos, unsigned numChildrenToRemove );
-
-    private:
-
-        osg::ref_ptr<TileNodeRegistry> _live, _dead;
-    };
-
-} // namespace osgEarth_engine_mp
-
-#endif // OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD
diff --git a/src/osgEarthDrivers/engine_mp/CustomPagedLOD.cpp b/src/osgEarthDrivers/engine_mp/CustomPagedLOD.cpp
deleted file mode 100644
index 282abac..0000000
--- a/src/osgEarthDrivers/engine_mp/CustomPagedLOD.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2013 Pelican Mapping
-* http://osgearth.org
-*
-* osgEarth is free software; you can redistribute it and/or modify
-* it under the terms of the GNU Lesser General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-* GNU Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public License
-* along with this program.  If not, see <http://www.gnu.org/licenses/>
-*/
-#include "CustomPagedLOD"
-
-using namespace osgEarth_engine_mp;
-using namespace osgEarth;
-
-#define LC "[CustomPagedLOD] "
-
-
-//----------------------------------------------------------------------------
-
-CustomPagedLOD::CustomPagedLOD(TileNodeRegistry* live,
-                               TileNodeRegistry* dead) :
-_live( live ),
-_dead( dead )
-{
-    //nop
-}
-
-
-CustomPagedLOD::~CustomPagedLOD()
-{
-    if ( _live.valid() || _dead.valid() )
-    {
-        for( unsigned i=0; i < getNumChildren(); ++i )
-        {
-            osg::ref_ptr<TileNode> node = dynamic_cast<TileNode*>( getChild(i) );
-            if ( node.valid() )
-            {
-                if ( _live.valid() )
-                    _live->remove( node.get() );
-                if ( _dead.valid() )
-                    _dead->add( node.get() );
-            }
-        }
-    }
-}
-
-
-bool
-CustomPagedLOD::addChild( osg::Node* child )
-{
-    bool ok = osg::PagedLOD::addChild( child );
-    if ( ok && _live.valid() )
-    {
-        TileNodeGroup* tileGroup = dynamic_cast<TileNodeGroup*>( child );
-        if ( tileGroup )
-        {
-            TileNodeVector tileNodes;
-            tileNodes.reserve( 4 );
-
-            for( unsigned i=0; i<tileGroup->getNumChildren(); ++i )
-            {
-                osg::Node* subChild = tileGroup->getChild(i);
-                TileNode* tileNode = 0L;
-                osg::Group* group = dynamic_cast<osg::PagedLOD*>(subChild);
-                if ( group && group->getNumChildren() > 0 )
-                    tileNode = dynamic_cast<TileNode*>( group->getChild(0) );
-                else
-                    tileNode = dynamic_cast<TileNode*>( child );
-                if ( tileNode )
-                    tileNodes.push_back( tileNode );
-            }
-
-            if ( !tileNodes.empty() )
-                _live->add( tileNodes );
-        }
-    }
-    return ok;
-}
-
-
-bool 
-CustomPagedLOD::removeChildren(unsigned pos, unsigned numChildrenToRemove)
-{
-    if ( _live.valid() || _dead.valid() )
-    {
-        for( unsigned i=pos; i<pos+numChildrenToRemove; ++i )
-        {
-            if ( i < getNumChildren() )
-            {
-                osg::ref_ptr<TileNode> tile = dynamic_cast<TileNode*>( getChild(i) );
-                if ( tile.valid() )
-                {
-                    if ( _live.valid() )
-                        _live->remove( tile.get() );
-                    if ( _dead.valid() )
-                        _dead->add( tile.get() );
-                }
-            }
-        }
-    }
-    return osg::PagedLOD::removeChildren( pos, numChildrenToRemove );
-}
diff --git a/src/osgEarthDrivers/engine_mp/FileLocationCallback b/src/osgEarthDrivers/engine_mp/FileLocationCallback
index 9400031..254c4e9 100644
--- a/src/osgEarthDrivers/engine_mp/FileLocationCallback
+++ b/src/osgEarthDrivers/engine_mp/FileLocationCallback
@@ -46,7 +46,6 @@ namespace osgEarth_engine_mp
         virtual Location fileLocation(const std::string& filename, const osgDB::Options* options)
         {
             Location result = REMOTE_FILE;
-            //OE_NOTICE<<"fileLocation = "<<filename<<std::endl;
 
             unsigned int lod, x, y, id;
             sscanf(filename.c_str(), "%d/%d/%d.%d", &lod, &x, &y, &id);
diff --git a/src/osgEarthDrivers/engine_mp/KeyNodeFactory b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
index 43a467f..1bf7aae 100644
--- a/src/osgEarthDrivers/engine_mp/KeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/KeyNodeFactory
@@ -21,6 +21,7 @@
 
 #include "Common"
 #include <osgEarth/TileKey>
+#include <osgEarth/Progress>
 #include <osg/Node>
 
 using namespace osgEarth;
@@ -33,17 +34,12 @@ namespace osgEarth_engine_mp
     class KeyNodeFactory : public osg::Referenced
     {
     public:
-        /**
-        * Creates a node for a tile key at the root level.
-        */
-        virtual osg::Node* createRootNode( const TileKey& key ) =0;
 
         /**
         * Creates a node for a tile key.
         */
-        virtual osg::Node* createNode( const TileKey& key ) =0;
+        virtual osg::Node* createNode( const TileKey& key, bool setupChildren, ProgressCallback* progress) =0;
 
-        virtual class TileModelCompiler* getCompiler() const =0;
 
     protected:
         KeyNodeFactory();
diff --git a/src/osgEarthDrivers/engine_mp/LODFactorCallback.cpp b/src/osgEarthDrivers/engine_mp/LODFactorCallback.cpp
deleted file mode 100644
index 7303b43..0000000
--- a/src/osgEarthDrivers/engine_mp/LODFactorCallback.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2011 Pelican Mapping
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-#include "LODFactorCallback"
-
-#include <osgEarth/CullingUtils>
-#include <osg/Math>
-#include <osg/PagedLOD>
-#include <osg/StateSet>
-#include <osg/Uniform>
-
-using namespace osgEarth_engine_mp;
-
-// This callback sets a uniform, osgearth_LODRangeFactor, based on the
-// distance from the camera and its relation to the minimum and
-// maximum distance for a tile. The maximum distance isn't actually
-// available, so 2 * min distance is used as an estimate. The range
-// factor's value goes from 0 - at the maximum range - to 1 for the
-// minimum range.
-void LODFactorCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
-{
-    // test the type since this is not always a PagedLOD.
-    osg::PagedLOD* lod = static_cast<osg::PagedLOD*>(node);
-    osgUtil::CullVisitor* cv = osgEarth::Culling::asCullVisitor(nv);
-    osg::LOD::RangeMode rangeMode = lod->getRangeMode();
-    float requiredRange = 0.0f;
-    float rangeFactor = 1.0f;
-    const osg::LOD::RangeList& rangeList = lod->getRangeList();
-    if (rangeMode == osg::LOD::DISTANCE_FROM_EYE_POINT)
-    {
-        requiredRange = cv->getDistanceToViewPoint(lod->getCenter(), true);
-    }
-    else if (cv->getLODScale() > 0.0f)
-    {
-        requiredRange = cv->clampedPixelSize(lod->getBound()) / cv->getLODScale();
-    }
-    else
-    {
-        // The comment in osg/PagedLOD.cpp says that this algorithm
-        // finds the highest res tile, but it actually finds the
-        // lowest res tile!
-        for (osg::LOD::RangeList::const_iterator itr = rangeList.begin(), end = rangeList.end();
-            itr != end;
-            ++itr)
-        {
-            requiredRange = osg::maximum(requiredRange, itr->first);
-        }
-    }
-    // We're counting on only finding one valid LOD, unlike the
-    // general OSG behavior.
-    if (!rangeList.empty() && rangeList[0].first <= requiredRange
-        && requiredRange < rangeList[0].second)
-    {
-        rangeFactor = 1.0f - (requiredRange - rangeList[0].first) / rangeList[0].first;
-        rangeFactor = osg::clampTo(rangeFactor, 0.0f, 1.0f);
-    }
-    osg::ref_ptr<osg::Uniform> ufact
-        = new osg::Uniform("osgearth_LODRangeFactor", rangeFactor);
-    osg::ref_ptr<osg::StateSet> ss = new osg::StateSet;
-    ss->addUniform(ufact.get());
-
-    cv->pushStateSet(ss.get());
-    traverse(node, nv);
-    cv->popStateSet();
-}
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry b/src/osgEarthDrivers/engine_mp/MPGeometry
index 53c8374..ab250c5 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry
@@ -16,13 +16,14 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
-#ifndef OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD
-#define OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD 1
+#ifndef OSGEARTH_ENGINE_MP_MPGEOMETRY
+#define OSGEARTH_ENGINE_MP_MPGEOMETRY 1
 
 #include "Common"
 #include "TileNode"
 #include "TileNodeRegistry"
 #include <osg/Geometry>
+#include <osg/buffered_value>
 #include <osgEarth/Map>
 #include <osgEarth/MapFrame>
 
@@ -48,7 +49,10 @@ namespace osgEarth_engine_mp
             osg::ref_ptr<const ImageLayer> _imageLayer;
             osg::ref_ptr<osg::Texture>     _tex;
             osg::ref_ptr<osg::Vec2Array>   _texCoords;
+            osg::ref_ptr<osg::Texture>     _texParent;
+            osg::Matrixf                   _texMatParent; // yes, must be a float matrix
             float                          _alphaThreshold;
+            bool                           _opaque;
 
             // in support of std::find
             inline bool operator == (const osgEarth::UID& rhs) const {
@@ -57,18 +61,41 @@ namespace osgEarth_engine_mp
         };
 
     public:
-        mutable MapFrame                     _map;
-        mutable std::vector<Layer>           _layers;
-        mutable Threading::Mutex             _mapSyncMutex;
-        mutable osg::ref_ptr<osg::Uniform>   _layerUIDUniform;
-        mutable osg::ref_ptr<osg::Uniform>   _layerOrderUniform;
-        mutable osg::ref_ptr<osg::Uniform>   _opacityUniform;
-        int                                  _textureImageUnit;
+        mutable MapFrame           _frame;
+        mutable std::vector<Layer> _layers;
+        mutable Threading::Mutex   _frameSyncMutex;
+
+        // uniform name IDs.
+        unsigned _uidUniformNameID;
+        unsigned _birthTimeUniformNameID;
+        unsigned _orderUniformNameID;
+        unsigned _opacityUniformNameID;
+        unsigned _texMatParentUniformNameID;
+        unsigned _tileKeyUniformNameID;
+
+        // Data stored for each graphics context:
+        struct PerContextData {
+            PerContextData() : birthTime(-1.0f), lastFrame(0) { }
+            float    birthTime;
+            unsigned lastFrame;
+        };
+        mutable osg::buffered_object<PerContextData> _pcd;
+
+
+        mutable osg::Vec4f _tileKeyValue;
+
+        osg::ref_ptr<osg::Vec2Array> _tileCoords; // [0..1] unified tile coordinates
+
+        int _imageUnit;          // image unit for primary texture
+        int _imageUnitParent;    // image unit for secondary (parent) texture
 
     public:
         
         // construct a new MPGeometry.
-        MPGeometry(const Map* map, int textureImageUnit);
+        MPGeometry(const TileKey& key, const MapFrame& frame, int primaryImageUnit);
+
+        // sets an image unit to use for parent texture blending.
+        void setParentImageUnit(int value) { _imageUnitParent = value; }
 
         // render all passes of the geometry.
         void renderPrimitiveSets(osg::State& state, bool usingVBOs) const;
@@ -80,22 +107,24 @@ namespace osgEarth_engine_mp
         // that are not tracked by the Geometry itself but rather are
         // stored in the LayerRenderData.
         void releaseGLObjects(osg::State* state) const;
+        void resizeGLObjectBuffers(unsigned maxSize);
         void compileGLObjects( osg::RenderInfo& renderInfo ) const;
 
-
         // this is copied mostly from osg::Geometry, but we've removed 
         // all the code that handles non-fastPath (don't need it) and
         // called into our custom renderPrimitiveSets method.
         void drawImplementation(osg::RenderInfo& renderInfo) const;
-        
+
+        // recalculate the bound for the tile key uniform.
+        osg::BoundingBox computeBound() const;
 
     public:
         META_Object(osgEarth, MPGeometry);
-        MPGeometry() : osg::Geometry(), _map(0L) { }
-        MPGeometry(const MPGeometry& rhs, const osg::CopyOp& cop) : osg::Geometry(rhs, cop), _map(rhs._map) { }
+        MPGeometry() : osg::Geometry(), _frame(0L) { }
+        MPGeometry(const MPGeometry& rhs, const osg::CopyOp& cop) : osg::Geometry(rhs, cop), _frame(rhs._frame) { }
         virtual ~MPGeometry() { }
     };
 
 } // namespace osgEarth_engine_mp
 
-#endif // OSGEARTH_ENGINE_MP_CUSTOM_PAGED_LOD
+#endif // OSGEARTH_ENGINE_MP_MPGEOMETRY
diff --git a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
index a6a015b..46526c1 100644
--- a/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPGeometry.cpp
@@ -18,6 +18,11 @@
 */
 #include "MPGeometry"
 
+#include <osg/Version>
+#include <osgUtil/MeshOptimizers>
+#include <iterator>
+
+using namespace osg;
 using namespace osgEarth_engine_mp;
 using namespace osgEarth;
 
@@ -26,19 +31,29 @@ using namespace osgEarth;
 
 //----------------------------------------------------------------------------
 
-MPGeometry::MPGeometry(const Map* map, int textureImageUnit) : 
-osg::Geometry    ( ), 
-_map             (map, Map::IMAGE_LAYERS),
-_textureImageUnit( textureImageUnit )
-{
-    _opacityUniform = new osg::Uniform( osg::Uniform::FLOAT, "oe_layer_opacity" );
-    _opacityUniform->set( 1.0f );
-
-    _layerUIDUniform = new osg::Uniform( osg::Uniform::INT, "oe_layer_uid" );
-    _layerUIDUniform->set( 0 );
+//osg::buffered_object<MPGeometry::PerGC> MPGeometry::_perGC;
 
-    _layerOrderUniform = new osg::Uniform( osg::Uniform::INT, "oe_layer_order" );
-    _layerOrderUniform->set( 0 );
+MPGeometry::MPGeometry(const TileKey& key, const MapFrame& frame, int imageUnit) : 
+osg::Geometry    ( ),
+_frame           ( frame ),
+_imageUnit       ( imageUnit )
+{
+    unsigned tw, th;
+    key.getProfile()->getNumTiles(key.getLOD(), tw, th);
+    _tileKeyValue.set( key.getTileX(), th-key.getTileY()-1.0f, key.getLOD(), -1.0f );
+
+    _imageUnitParent = _imageUnit + 1; // 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" );
+
+    this->setUseVertexBufferObjects(true);
+    this->setUseDisplayList(false);
 }
 
 
@@ -47,18 +62,18 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                                 bool        usingVBOs) const
 {
     // check the map frame to see if it's up to date
-    if ( _map.needsSync() )
+    if ( _frame.needsSync() )
     {
         // this lock protects a MapFrame sync when we have multiple DRAW threads.
-        Threading::ScopedMutexLock exclusive( _mapSyncMutex );
+        Threading::ScopedMutexLock exclusive( _frameSyncMutex );
 
-        if ( _map.needsSync() && _map.sync() ) // always double check
+        if ( _frame.needsSync() && _frame.sync() ) // always double check
         {
             // This should only happen is the layer ordering changes;
             // If layers are added or removed, the Tile gets rebuilt and
             // the point is moot.
             std::vector<Layer> reordered;
-            const ImageLayerVector& layers = _map.imageLayers();
+            const ImageLayerVector& layers = _frame.imageLayers();
             reordered.reserve( layers.size() );
             for( ImageLayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i )
             {
@@ -73,64 +88,165 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
     unsigned layersDrawn = 0;
 
 
-    osg::ref_ptr<osg::GL2Extensions> ext = osg::GL2Extensions::Get( state.getContextID(), true );
+    // access the GL extensions interface for the current GC:
+    unsigned contextID = state.getContextID();
+    osg::ref_ptr<osg::GL2Extensions> ext = osg::GL2Extensions::Get( contextID, true );
     const osg::Program::PerContextProgram* pcp = state.getLastAppliedProgramObject();
 
+    // cannot store these in the object since there could be multiple GCs (and multiple
+    // PerContextPrograms) at large
+    GLint tileKeyLocation;
+    GLint birthTimeLocation;
     GLint opacityLocation;
     GLint uidLocation;
     GLint orderLocation;
+    GLint texMatParentLocation;
 
-    // yes, it's possible that the PCP is not set up yet.
-    // TODO: can we optimize this so we don't need to get uni locations every time?
+    // The PCP can change (especially in a VirtualProgram environment). So we do need to
+    // requery the uni locations each time unfortunately. TODO: explore optimizations.
     if ( pcp )
     {
-        opacityLocation = pcp->getUniformLocation( _opacityUniform->getNameID() );
-        uidLocation     = pcp->getUniformLocation( _layerUIDUniform->getNameID() );
-        orderLocation   = pcp->getUniformLocation( _layerOrderUniform->getNameID() );
+        tileKeyLocation      = pcp->getUniformLocation( _tileKeyUniformNameID );
+        birthTimeLocation    = pcp->getUniformLocation( _birthTimeUniformNameID );
+        opacityLocation      = pcp->getUniformLocation( _opacityUniformNameID );
+        uidLocation          = pcp->getUniformLocation( _uidUniformNameID );
+        orderLocation        = pcp->getUniformLocation( _orderUniformNameID );
+        texMatParentLocation = pcp->getUniformLocation( _texMatParentUniformNameID );
+    }
+    
+    // apply the tilekey uniform once.
+    if ( tileKeyLocation >= 0 )
+    {
+        ext->glUniform4fv( tileKeyLocation, 1, _tileKeyValue.ptr() );
+    }
+
+    // set the "birth time" - i.e. the time this tile last entered the scene in the current GC.
+    if ( birthTimeLocation >= 0 )
+    {
+        PerContextData& pcd = _pcd[contextID];
+        if ( pcd.birthTime < 0.0f )
+        {
+            const osg::FrameStamp* stamp = state.getFrameStamp();
+            if ( stamp )
+            {
+                pcd.birthTime = stamp->getReferenceTime();
+            }
+        }
+        ext->glUniform1f( birthTimeLocation, pcd.birthTime );
     }
 
+    // activate the tile coordinate set - same for all layers
+    state.setTexCoordPointer( _imageUnit+1, _tileCoords.get() );
+
+#ifndef OSG_GLES2_AVAILABLE
+    // emit a default terrain color since we're not binding a color array:
+    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+#endif
+
     if ( _layers.size() > 0 )
     {
         float prev_opacity        = -1.0f;
         float prev_alphaThreshold = -1.0f;
 
-        // activate the image unit.
-        state.setActiveTextureUnit( _textureImageUnit );
+        // first bind any shared layers
+        // TODO: optimize by pre-storing shared indexes
+        for(unsigned i=0; i<_layers.size(); ++i)
+        {
+            const Layer& layer = _layers[i];
+
+            // a "shared" layer binds to a secondary texture unit so that other layers
+            // can see it and use it.
+            if ( layer._imageLayer->isShared() )
+            {
+                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.
+                }
+            }
+        }
+
+        // track the active image unit.
+        int activeImageUnit = -1;
+
+        // find the first opaque layer, top-down, and start there:
+        unsigned first = 0;
+        for(first = _layers.size()-1; first > 0; --first)
+        {
+            const Layer& layer = _layers[first];
+            if (layer._opaque && 
+                layer._imageLayer->getVisible() &&
+                layer._imageLayer->getOpacity() >= 1.0f)
+            {
+                break;
+            }
+        }
 
         // interate over all the image layers
-        for(unsigned i=0; i<_layers.size(); ++i)
+        for(unsigned i=first; i<_layers.size(); ++i)
         {
             const Layer& layer = _layers[i];
 
             if ( layer._imageLayer->getVisible() )
-            {
+            {       
+                // activate the visible unit if necessary:
+                if ( activeImageUnit != _imageUnit )
+                {
+                    state.setActiveTextureUnit( _imageUnit );
+                    activeImageUnit = _imageUnit;
+                }
+
                 // bind the texture for this layer:
                 layer._tex->apply( state );
 
+                // if we're using a parent texture for blending, activate that now
+                if ( texMatParentLocation >= 0 && layer._texParent.valid() )
+                {
+                    state.setActiveTextureUnit( _imageUnitParent );
+                    activeImageUnit = _imageUnitParent;
+                    layer._texParent->apply( state );
+                }
+
                 // bind the texture coordinates for this layer.
                 // TODO: can probably optimize this by sharing or using texture matrixes.
                 // State::setTexCoordPointer does some redundant work under the hood.
-                state.setTexCoordPointer( _textureImageUnit, layer._texCoords.get() );
+                state.setTexCoordPointer( _imageUnit, layer._texCoords.get() );
 
                 // apply uniform values:
                 if ( pcp )
                 {
                     // apply opacity:
-                    float opacity = layer._imageLayer->getOpacity();
-                    if ( opacity != prev_opacity )
+                    if ( opacityLocation >= 0 )
                     {
-                        _opacityUniform->set( opacity );
-                        _opacityUniform->apply( ext, opacityLocation );
-                        prev_opacity = opacity;
+                        float opacity = layer._imageLayer->getOpacity();
+                        if ( opacity != prev_opacity )
+                        {
+                            ext->glUniform1f( opacityLocation, (GLfloat)opacity );
+                            prev_opacity = opacity;
+                        }
                     }
 
                     // assign the layer UID:
-                    _layerUIDUniform->set( layer._layerID );
-                    _layerUIDUniform->apply( ext, uidLocation );
+                    if ( uidLocation >= 0 )
+                    {
+                        ext->glUniform1i( uidLocation, (GLint)layer._layerID );
+                    }
 
                     // assign the layer order:
-                    _layerOrderUniform->set( (int)layersDrawn );
-                    _layerOrderUniform->apply( ext, orderLocation );
+                    if ( orderLocation >= 0 )
+                    {
+                        ext->glUniform1i( orderLocation, (GLint)layersDrawn );
+                    }
+
+                    // assign the parent texture matrix
+                    if ( texMatParentLocation >= 0 && layer._texParent.valid() )
+                    {
+                        ext->glUniformMatrix4fv( texMatParentLocation, 1, GL_FALSE, layer._texMatParent.ptr() );
+                    }
                 }
 
                 // draw the primitive sets.
@@ -143,22 +259,17 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
                 ++layersDrawn;
             }
         }
-
-        // prevent texture leakage
-        glBindTexture( GL_TEXTURE_2D, 0 );
     }
 
     // if we didn't draw anything, draw the raw tiles anyway with no texture.
     if ( layersDrawn == 0 )
     {
-        _opacityUniform->set( 1.0f );
-        _opacityUniform->apply( ext, opacityLocation );
-
-        _layerUIDUniform->set( (int)-1 ); // indicates a non-textured layer
-        _layerUIDUniform->apply( ext, uidLocation );
-
-        _layerOrderUniform->set( (int)0 );
-        _layerOrderUniform->apply( ext, orderLocation );
+        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)
@@ -167,6 +278,28 @@ MPGeometry::renderPrimitiveSets(osg::State& state,
             primitiveset->draw(state, usingVBOs);
         }
     }
+
+    else // at least one textured layer was drawn:
+    {
+        // prevent texture leakage
+        // TODO: find a way to remove this to speed things up
+        glBindTexture( GL_TEXTURE_2D, 0 );
+    }
+}
+
+
+osg::BoundingBox
+MPGeometry::computeBound() const
+{
+    osg::BoundingBox bbox = osg::Geometry::computeBound();
+    {
+        // update the uniform.
+        Threading::ScopedMutexLock exclusive(_frameSyncMutex);
+        osg::BoundingSphere bs(bbox);
+        osg::Vec4f tk;
+        _tileKeyValue.w() = bs.radius();
+    }
+    return bbox;
 }
 
 
@@ -178,14 +311,38 @@ MPGeometry::releaseGLObjects(osg::State* state) const
     for(unsigned i=0; i<_layers.size(); ++i)
     {
         const Layer& layer = _layers[i];
-        if ( layer._tex.valid() )
-            layer._tex->releaseGLObjects( state );
-        if ( layer._texCoords.valid() )
+
+        //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 );
     }
 }
 
 
+void
+MPGeometry::resizeGLObjectBuffers(unsigned maxSize)
+{
+    osg::Geometry::resizeGLObjectBuffers( maxSize );
+
+    for(unsigned i=0; i<_layers.size(); ++i)
+    {
+        const Layer& layer = _layers[i];
+        if ( layer._tex.valid() )
+            layer._tex->resizeGLObjectBuffers( maxSize );
+    }
+
+    if ( _pcd.size() < maxSize )
+    {
+        _pcd.resize(maxSize);
+    }
+}
+
+
 void 
 MPGeometry::compileGLObjects( osg::RenderInfo& renderInfo ) const
 {
@@ -212,18 +369,36 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
 
     arrayDispatchers.reset();
     arrayDispatchers.setUseVertexAttribAlias(state.getUseVertexAttributeAliasing());
+
+
+    //Remove 
+#if OSG_VERSION_LESS_THAN(3,1,8)
     arrayDispatchers.setUseGLBeginEndAdapter(false);
+#endif
 
+
+#if OSG_MIN_VERSION_REQUIRED(3,1,8)
+    arrayDispatchers.activateNormalArray(_normalArray.get());
+    arrayDispatchers.activateColorArray(_colorArray.get());
+    arrayDispatchers.activateSecondaryColorArray(_secondaryColorArray.get());
+    arrayDispatchers.activateFogCoordArray(_fogCoordArray.get());
+#else
     arrayDispatchers.activateNormalArray(_normalData.binding, _normalData.array.get(), _normalData.indices.get());
     arrayDispatchers.activateColorArray(_colorData.binding, _colorData.array.get(), _colorData.indices.get());
     arrayDispatchers.activateSecondaryColorArray(_secondaryColorData.binding, _secondaryColorData.array.get(), _secondaryColorData.indices.get());
     arrayDispatchers.activateFogCoordArray(_fogCoordData.binding, _fogCoordData.array.get(), _fogCoordData.indices.get());
+#endif
+    
 
     if (handleVertexAttributes)
     {
         for(unsigned int unit=0;unit<_vertexAttribList.size();++unit)
         {
+#if OSG_MIN_VERSION_REQUIRED(3,1,8)
+            arrayDispatchers.activateVertexAttribArray(unit, _vertexAttribList[unit].get());
+#else
             arrayDispatchers.activateVertexAttribArray(_vertexAttribList[unit].binding, unit, _vertexAttribList[unit].array.get(), _vertexAttribList[unit].indices.get());
+#endif             
         }
     }
 
@@ -231,7 +406,24 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
     arrayDispatchers.dispatch(BIND_OVERALL,0);
     state.lazyDisablingOfVertexAttributes();
 
+
     // set up arrays
+#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8 )
+    if( _vertexArray.valid() )
+        state.setVertexPointer(_vertexArray.get());
+
+    if (_normalArray.valid() && _normalArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+        state.setNormalPointer(_normalArray.get());
+
+    if (_colorArray.valid() && _colorArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+        state.setColorPointer(_colorArray.get());
+
+    if (_secondaryColorArray.valid() && _secondaryColorArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+        state.setSecondaryColorPointer(_secondaryColorArray.get());
+
+    if (_fogCoordArray.valid() && _fogCoordArray->getBinding()==osg::Array::BIND_PER_VERTEX)
+        state.setFogCoordPointer(_fogCoordArray.get());
+#else
     if( _vertexData.array.valid() )
         state.setVertexPointer(_vertexData.array.get());
 
@@ -246,23 +438,50 @@ MPGeometry::drawImplementation(osg::RenderInfo& renderInfo) const
 
     if (_fogCoordData.binding==BIND_PER_VERTEX && _fogCoordData.array.valid())
         state.setFogCoordPointer(_fogCoordData.array.get());
-
+#endif    
+        
     for(unsigned int unit=0;unit<_texCoordList.size();++unit)
     {
+#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8)
+        const Array* array = _texCoordList[unit].get();
+        if (array)
+        {
+            state.setTexCo