[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.setTexCoordPointer(unit,array);
+        }
+#else
         const osg::Array* array = _texCoordList[unit].array.get();
         if (array) state.setTexCoordPointer(unit,array);
+#endif        
     }
 
     if( handleVertexAttributes )
     {
         for(unsigned int index = 0; index < _vertexAttribList.size(); ++index )
         {
+#if OSG_MIN_VERSION_REQUIRED( 3, 1, 8)
+            const Array* array = _vertexAttribList[index].get();
+            if (array && array->getBinding()==osg::Array::BIND_PER_VERTEX)
+            {
+                if (array->getPreserveDataType())
+                {
+                    GLenum dataType = array->getDataType();
+                    if (dataType==GL_FLOAT) state.setVertexAttribPointer( index, array );
+                    else if (dataType==GL_DOUBLE) state.setVertexAttribLPointer( index, array );
+                    else state.setVertexAttribIPointer( index, array );
+                }
+                else
+                {
+                    state.setVertexAttribPointer( index, array );
+                }
+            }
+#else            
             const osg::Array* array = _vertexAttribList[index].array.get();
             const AttributeBinding ab = _vertexAttribList[index].binding;
             if( ab == BIND_PER_VERTEX && array )
             {
                 state.setVertexAttribPointer( index, array, _vertexAttribList[index].normalize );
             }
+#endif
         }
     }
 
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
index 20e4155..e2e9996 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineDriver.cpp
@@ -18,7 +18,9 @@
  */
 #include "MPTerrainEngineNode"
 #include "MPTerrainEngineOptions"
+#include "TileNode"
 #include <osgEarth/Registry>
+#include <osgEarth/Progress>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/Registry>
@@ -26,21 +28,17 @@
 
 #define LC "[engine_mp driver] "
 
+
 using namespace osgEarth::Drivers;
 using namespace osgEarth_engine_mp;
 
 /**
  * osgEarth driver for the MP terrain engine.
- *
- * TODO LIST:
- * - integrate support for Quick Release of GL objects
- * - integrate support for LOD Blending (access to parent state set)
- * - consider TileNodeCompiler cacheing of TexCoord arrays and other tile-shareable data
  */
 class osgEarth_MPTerrainEngineDriver : public osgDB::ReaderWriter
 {
 public:
-    osgEarth_MPTerrainEngineDriver() {}
+    osgEarth_MPTerrainEngineDriver() { }
 
     virtual const char* className()
     {
@@ -51,7 +49,8 @@ public:
     {
         return
             osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp" ) ||
-            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_tile" );
+            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_tile" ) ||
+            osgDB::equalCaseInsensitive( extension, "osgearth_engine_mp_standalone_tile" );
     }
 
     virtual ReadResult readObject(const std::string& uri, const Options* options) const
@@ -77,15 +76,9 @@ public:
 
     virtual ReadResult readNode(const std::string& uri, const Options* options) const
     {
-        static int s_tileCount = 0;
-        static double s_tileTime = 0.0;
-        static osg::Timer_t s_startTime;
-
-        if ( "osgearth_engine_mp_tile" == osgDB::getFileExtension(uri) )
+        std::string ext = osgDB::getFileExtension(uri);
+        if ( acceptsExtension(ext) )
         {
-            if ( s_tileCount == 0 )
-                s_startTime = osg::Timer::instance()->tick();
-
             // See if the filename starts with server: and strip it off.  This will trick OSG
             // into passing in the filename to our plugin instead of using the CURL plugin if
             // the filename contains a URL.  So, if you want to read a URL, you can use the
@@ -107,29 +100,67 @@ public:
             {
                 osg::Timer_t start = osg::Timer::instance()->tick();
 
+                // see if we have a progress tracker
+                ProgressCallback* progress = 
+                    options ? const_cast<ProgressCallback*>(
+                    dynamic_cast<const ProgressCallback*>(options->getUserData())) : 0L;
+
                 // assemble the key and create the node:
                 const Profile* profile = engineNode->getMap()->getProfile();
                 TileKey key( lod, x, y, profile );
-                osg::ref_ptr< osg::Node > node = engineNode->createNode( key );
+
+                osg::ref_ptr<osg::Node> node;
+
+                if ( "osgearth_engine_mp_tile" == ext )
+                {
+                    node = engineNode->createNode(key, progress);
+                }
+                else if ( "osgearth_engine_mp_standalone_tile" == ext )
+                {
+                    node = engineNode->createStandaloneNode(key, progress);
+                }
+
+
+#if 0
+                osg::Timer_t end = osg::Timer::instance()->tick();
+
+                //if ( osgEarth::getNotifyLevel() >= osg::DEBUG_INFO )
+                {
+                    static Threading::Mutex s_statsMutex;
+                    static std::vector<double> s_times;
+                    Threading::ScopedMutexLock lock(s_statsMutex);
+                    s_times.push_back( osg::Timer::instance()->delta_s(start, end) );
+                    if ( s_times.size() % 50 == 0 )
+                    {
+                        double t = 0.0;
+                        for(unsigned i=0; i<s_times.size(); ++i)
+                            t += s_times[i];
+                        OE_NOTICE << LC << "Average time = " << (t/s_times.size()) << " s." << std::endl;
+                    }
+                }
+#endif
+
                 
-                // Blacklist the tile if we couldn't load it
+                // Deal with failed loads.
                 if ( !node.valid() )
                 {
-                    OE_DEBUG << LC << "Blacklisting " << uri << std::endl;
-                    osgEarth::Registry::instance()->blacklist( uri );
-                    return ReadResult::FILE_NOT_FOUND;
+                    if ( key.getLOD() == 0 || (progress && progress->isCanceled()) )
+                    {
+                        // the tile will ask again next time.
+                        return ReadResult::FILE_NOT_FOUND;
+                    }
+                    else
+                    {
+                        // the parent tile will never ask again as long as it remains in memory.
+                        node = new InvalidTileNode( key );
+                    }
                 }
                 else
                 {   
-                    // make safe ref/unref so we can reference from multiple threads
-                    node->setThreadSafeRefUnref( true );
-
                     // notify the Terrain interface of a new tile
                     osg::Timer_t start = osg::Timer::instance()->tick();
                     engineNode->getTerrain()->notifyTileAdded(key, node.get());
                     osg::Timer_t end = osg::Timer::instance()->tick();
-
-                    //OE_DEBUG << "Took " << osg::Timer::instance()->delta_m(start, end) << "ms to fire terrain callbacks" << std::endl;
                 }
 
                 return ReadResult( node.get(), ReadResult::FILE_LOADED );
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
index abfdec6..4d2dbe0 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode
@@ -47,7 +47,11 @@ namespace osgEarth_engine_mp
         virtual ~MPTerrainEngineNode();
 
     public:
-        osg::Node* createNode(const TileKey& key);
+        /** Creates a tile node that is set up to page in subtiles. */
+        osg::Node* createNode(const TileKey& key, ProgressCallback* progress =0L);
+
+        /** Creates a tile node with no support for paging in subtiles. */
+        osg::Node* createStandaloneNode(const TileKey& key, ProgressCallback* progress =0L);
 
     public: // TerrainEngineNode
 
@@ -62,7 +66,12 @@ namespace osgEarth_engine_mp
         virtual const TerrainOptions& getTerrainOptions() const { return _terrainOptions; }
         virtual osg::BoundingSphere computeBound() const;
 
+    public: // osg::Node
+
+        void traverse(osg::NodeVisitor& nv);
+
     public: // MapCallback adapter functions
+
         void onMapInfoEstablished( const MapInfo& mapInfo ); // not virtual!
         void onMapModelChanged( const MapModelChange& change ); // not virtual!
 
@@ -86,8 +95,6 @@ namespace osgEarth_engine_mp
         };
 
     protected:
-        virtual void onVerticalScaleChanged();
-
         // override from TerrainEngineNode
         virtual void updateTextureCombining() { updateShaders(); }
 
@@ -96,7 +103,7 @@ namespace osgEarth_engine_mp
         void syncMapModel();
 
         // Reloads all the tiles in the terrain due to a data model change
-        void refresh();
+        void refresh(bool force =false);
         void createTerrain();
 
         void addImageLayer( ImageLayer* layer );
@@ -104,6 +111,7 @@ namespace osgEarth_engine_mp
 
         void removeImageLayer( ImageLayer* layerRemoved );
         void removeElevationLayer( ElevationLayer* layerRemoved );
+        void toggleElevationLayer( ElevationLayer* layer );
 
         void moveImageLayer( unsigned int oldIndex, unsigned int newIndex );
         void moveElevationLayer( unsigned int oldIndex, unsigned int newIndex );
@@ -115,10 +123,14 @@ namespace osgEarth_engine_mp
 
         class TerrainNode* _terrain;
         UID                _uid;
+        Revision           _mapModelRev;  // tracks revision changes int the map model
+        Revision           _terrainRev;   // the revisinon of the rendered terrain (slightly different)
         Revision           _shaderLibRev;
         bool               _batchUpdateInProgress;
         bool               _refreshRequired;
         bool               _shaderUpdateRequired;
+        bool               _rootTilesRegistered;
+        Threading::Mutex   _rootTilesRegisteredMutex;
 
         osg::ref_ptr< ElevationChangedCallback > _elevationCallback;
 
@@ -134,7 +146,8 @@ namespace osgEarth_engine_mp
         osg::Timer _timer;
         unsigned   _tileCount;
         double     _tileCreationTime;
-        int        _textureImageUnit;
+        int        _primaryUnit;
+        int        _secondaryUnit;
 
         osg::Uniform* _verticalScaleUniform;
 
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
index e290e99..d16a8f0 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineNode.cpp
@@ -17,10 +17,11 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include "MPTerrainEngineNode"
-#include "SerialKeyNodeFactory"
+#include "SingleKeyNodeFactory"
 #include "TerrainNode"
 #include "TileModelFactory"
 #include "TileModelCompiler"
+#include "TilePagedLOD"
 
 #include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
@@ -28,6 +29,7 @@
 #include <osgEarth/VirtualProgram>
 #include <osgEarth/ShaderFactory>
 #include <osgEarth/MapModelChange>
+#include <osgEarth/Progress>
 
 #include <osg/TexEnv>
 #include <osg/TexEnvCombine>
@@ -35,6 +37,7 @@
 #include <osg/Timer>
 #include <osg/Depth>
 #include <osg/BlendFunc>
+#include <osgDB/DatabasePager>
 
 #define LC "[MPTerrainEngineNode] "
 
@@ -52,11 +55,15 @@ namespace
         osg::observer_ptr<MPTerrainEngineNode> _node;
 
         void onMapInfoEstablished( const MapInfo& mapInfo ) {
-            _node->onMapInfoEstablished( mapInfo );
+            osg::ref_ptr<MPTerrainEngineNode> node;
+            if ( _node.lock(node) )
+                node->onMapInfoEstablished( mapInfo );
         }
 
         void onMapModelChanged( const MapModelChange& change ) {
-            _node->onMapModelChanged( change );
+            osg::ref_ptr<MPTerrainEngineNode> node;
+            if ( _node.lock(node) )
+                node->onMapModelChanged( change );
         }
     };
 }
@@ -123,9 +130,8 @@ _terrain( terrain )
 
 void
 MPTerrainEngineNode::ElevationChangedCallback::onVisibleChanged( TerrainLayer* layer )
-{    
-    osgEarth::Registry::instance()->clearBlacklist();
-    _terrain->refresh();
+{
+    _terrain->refresh(true); // true => force a dirty
 }
 
 //------------------------------------------------------------------------
@@ -136,7 +142,8 @@ _terrain              ( 0L ),
 _update_mapf          ( 0L ),
 _tileCount            ( 0 ),
 _tileCreationTime     ( 0.0 ),
-_textureImageUnit     ( 0 ),
+_primaryUnit          ( 0 ),
+_secondaryUnit        ( 1 ),
 _batchUpdateInProgress( false ),
 _refreshRequired      ( false ),
 _shaderUpdateRequired ( false )
@@ -180,8 +187,13 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     // merge in the custom options:
     _terrainOptions.merge( options );
 
-    // a shared registry for tile nodes in the scene graph.
+    // A shared registry for tile nodes in the scene graph. Enable revision tracking
+    // if requested in the options. Revision tracking lets the registry notify all
+    // live tiles of the current map revision so they can inrementally update
+    // themselves if necessary.
     _liveTiles = new TileNodeRegistry("live");
+    _liveTiles->setRevisioningEnabled( _terrainOptions.incrementalUpdate() == true );
+    _liveTiles->setMapRevision( _update_mapf->getRevision() );
 
     // set up a registry for quick release:
     if ( _terrainOptions.quickReleaseGLObjects() == true )
@@ -190,8 +202,8 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     }
     
     // initialize the model factory:
-    _tileModelFactory = new TileModelFactory(getMap(), _liveTiles.get(), _terrainOptions );
-
+    //_tileModelFactory = new TileModelFactory(getMap(), _liveTiles.get(), _terrainOptions );
+    _tileModelFactory = new TileModelFactory(_liveTiles.get(), _terrainOptions );
 
     // handle an already-established map profile:
     if ( _update_mapf->getProfile() )
@@ -203,20 +215,36 @@ MPTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& optio
     // populate the terrain with whatever data is in the map to begin with:
     if ( _terrain )
     {
-        this->getTextureCompositor()->reserveTextureImageUnit( _textureImageUnit );
-        updateShaders();
+        // reserve a GPU image unit and two attribute indexes.
+        this->getTextureCompositor()->reserveTextureImageUnit( _primaryUnit );
+        this->getTextureCompositor()->reserveTextureImageUnit( _secondaryUnit );
     }
 
     // install a layer callback for processing further map actions:
     map->addMapCallback( new MPTerrainEngineNodeMapCallbackProxy(this) );
 
-    // Attach to all of the existing elevation layers
+    // Prime with existing layers:
+    _batchUpdateInProgress = true;
+
     ElevationLayerVector elevationLayers;
     map->getElevationLayers( elevationLayers );
     for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
-    {
-        i->get()->addCallback( _elevationCallback.get() );
-    }
+        addElevationLayer( i->get() );
+
+    ImageLayerVector imageLayers;
+    map->getImageLayers( imageLayers );
+    for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
+        addImageLayer( i->get() );
+
+    _batchUpdateInProgress = false;
+
+    // install some terrain-wide uniforms
+    this->getOrCreateStateSet()->getOrCreateUniform(
+        "oe_min_tile_range_factor",
+        osg::Uniform::FLOAT)->set( *_terrainOptions.minTileRangeFactor() );
+
+    // set up the initial shaders
+    updateShaders();
 
     // register this instance to the osgDB plugin can find it.
     registerEngine( this );
@@ -239,9 +267,8 @@ MPTerrainEngineNode::computeBound() const
     }
 }
 
-
 void
-MPTerrainEngineNode::refresh()
+MPTerrainEngineNode::refresh(bool forceDirty)
 {
     if ( _batchUpdateInProgress )
     {
@@ -249,11 +276,20 @@ MPTerrainEngineNode::refresh()
     }
     else
     {
-        // remove the old one:
-        this->removeChild( _terrain );
+        if ( _terrainOptions.incrementalUpdate() == true )
+        {
+            // run an atomic "dirty" operation:
+            //_update_mapf->sync();
+            //_liveTiles->setMapRevision( _update_mapf->getRevision(), forceDirty );
+        }
+        else
+        {
+            // remove the old one:
+            this->removeChild( _terrain );
 
-        // and create a new one.
-        createTerrain();
+            // and create a new one.
+            createTerrain();
+        }
 
         _refreshRequired = false;
     }
@@ -267,6 +303,8 @@ MPTerrainEngineNode::onMapInfoEstablished( const MapInfo& mapInfo )
     createTerrain();
 }
 
+bool reg = false;
+
 void
 MPTerrainEngineNode::createTerrain()
 {
@@ -294,23 +332,90 @@ MPTerrainEngineNode::createTerrain()
     _update_mapf->getProfile()->getAllKeysAtLOD( *_terrainOptions.firstLOD(), keys );
 
     // create a root node for each root tile key.
-    OE_INFO << LC << "Creating root keys (" << keys.size() << ")" << std::flush;
+    OE_INFO << LC << "Creating " << keys.size() << " root keys.." << std::endl;
+
+    TilePagedLOD* root = new TilePagedLOD( _uid, _liveTiles, _deadTiles );
+    //osg::Group* root = new osg::Group();
+    _terrain->addChild( root );
+
+    osg::ref_ptr<osgDB::Options> dbOptions = Registry::instance()->cloneOrCreateOptions();
 
+    unsigned child = 0;
     for( unsigned i=0; i<keys.size(); ++i )
     {
-        osg::Node* node = factory->createRootNode( keys[i] );
-        OE_INFO_CONTINUE << "." << std::flush;
-        if ( node )
-            _terrain->addChild( node );
+        osg::ref_ptr<osg::Node> node = factory->createNode( keys[i], true, 0L );
+        if ( node.valid() )
+        {
+            root->addChild( node.get() );
+            root->setRange( child++, 0.0f, FLT_MAX );
+            root->setCenter( node->getBound().center() );
+            root->setNumChildrenThatCannotBeExpired( child );
+        }
         else
+        {
             OE_WARN << LC << "Couldn't make tile for root key: " << keys[i].str() << std::endl;
+        }
     }
 
-    OE_INFO_CONTINUE << "done." << std::endl;
+    _rootTilesRegistered = false;
 
     updateShaders();
 }
 
+namespace
+{
+    // debugging
+    struct CheckForOrphans : public TileNodeRegistry::ConstOperation {
+        void operator()( const TileNodeRegistry::TileNodeMap& tiles ) const {
+            unsigned count = 0;
+            for(TileNodeRegistry::TileNodeMap::const_iterator i = tiles.begin(); i != tiles.end(); ++i ) {
+                if ( i->second->referenceCount() == 1 ) {
+                    count++;
+                }
+            }
+            if ( count > 0 )
+                OE_WARN << LC << "Oh no! " << count << " orphaned tiles in the reg" << std::endl;
+        }
+    };
+}
+
+
+void
+MPTerrainEngineNode::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        // since the root tiles are manually added, the pager never has a change to 
+        // register the PagedLODs in their children. So we have to do it manually here.
+        if ( !_rootTilesRegistered )
+        {
+            Threading::ScopedMutexLock lock(_rootTilesRegisteredMutex);
+
+            if ( !_rootTilesRegistered )
+            {
+                osgDB::DatabasePager* pager = dynamic_cast<osgDB::DatabasePager*>(nv.getDatabaseRequestHandler());
+                if ( pager )
+                {
+                    //OE_WARN << "Registering plods." << std::endl;
+                    pager->registerPagedLODs( _terrain );
+                    _rootTilesRegistered = true;
+                }
+            }
+        }
+    }
+
+#if 0
+    static int c = 0;
+    if ( ++c % 60 == 0 )
+    {
+        OE_NOTICE << LC << "Live = " << _liveTiles->size() << ", Dead = " << _deadTiles->size() << std::endl;
+        _liveTiles->run( CheckForOrphans() );
+    }
+#endif
+
+    TerrainEngineNode::traverse( nv );
+}
+
 
 KeyNodeFactory*
 MPTerrainEngineNode::getKeyNodeFactory()
@@ -325,18 +430,18 @@ MPTerrainEngineNode::getKeyNodeFactory()
         // A compiler specific to this thread:
         TileModelCompiler* compiler = new TileModelCompiler(
             _update_mapf->terrainMaskLayers(),
-            _textureImageUnit,
+            _primaryUnit,
             optimizeTriangleOrientation,
             _terrainOptions );
 
         // initialize a key node factory.
-        knf = new SerialKeyNodeFactory( 
+        knf = new SingleKeyNodeFactory(
+            getMap(),
             _tileModelFactory.get(),
             compiler,
             _liveTiles.get(),
             _deadTiles.get(),
-            _terrainOptions, 
-            MapInfo( getMap() ),
+            _terrainOptions,
             _terrain, 
             _uid );
     }
@@ -344,9 +449,9 @@ MPTerrainEngineNode::getKeyNodeFactory()
     return knf.get();
 }
 
-
 osg::Node*
-MPTerrainEngineNode::createNode( const TileKey& key )
+MPTerrainEngineNode::createNode(const TileKey&    key,
+                                ProgressCallback* progress)
 {
     // if the engine has been disconnected from the scene graph, bail out and don't
     // create any more tiles
@@ -355,14 +460,28 @@ MPTerrainEngineNode::createNode( const TileKey& key )
 
     OE_DEBUG << LC << "Create node for \"" << key.str() << "\"" << std::endl;
 
-    osg::Node* result =  getKeyNodeFactory()->createNode( key );
-    return result;
+    return getKeyNodeFactory()->createNode( key, true, progress );
+}
+
+osg::Node*
+MPTerrainEngineNode::createStandaloneNode(const TileKey&    key,
+                                          ProgressCallback* progress)
+{
+    // if the engine has been disconnected from the scene graph, bail out and don't
+    // create any more tiles
+    if ( getNumParents() == 0 )
+        return 0L;
+
+    OE_DEBUG << LC << "Create standalone node for \"" << key.str() << "\"" << std::endl;
+
+    return getKeyNodeFactory()->createNode( key, false, progress );
 }
 
 osg::Node*
 MPTerrainEngineNode::createTile( const TileKey& key )
 {
-    return getKeyNodeFactory()->createNode( key );
+    // make a node, but don't include any subtile information
+    return getKeyNodeFactory()->createNode( key, false, 0L );
 }
 
 
@@ -388,7 +507,10 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
     else
     {
         // update the thread-safe map model copy:
-        _update_mapf->sync();
+        if ( _update_mapf->sync() )
+        {
+            _liveTiles->setMapRevision( _update_mapf->getRevision() );
+        }
 
         // dispatch the change handler
         if ( change.getLayer() )
@@ -420,6 +542,9 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
             case MapModelChange::MOVE_ELEVATION_LAYER:
                 moveElevationLayer( change.getFirstIndex(), change.getSecondIndex() );
                 break;
+            case MapModelChange::TOGGLE_ELEVATION_LAYER:
+                toggleElevationLayer( change.getElevationLayer() );
+                break;
             case MapModelChange::ADD_MODEL_LAYER:
             case MapModelChange::REMOVE_MODEL_LAYER:
             case MapModelChange::MOVE_MODEL_LAYER:
@@ -434,6 +559,28 @@ MPTerrainEngineNode::onMapModelChanged( const MapModelChange& change )
 void
 MPTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
 {
+    if ( layerAdded )
+    {
+        // for a shared layer, allocate a shared image unit if necessary.
+        if ( layerAdded->isShared() )
+        {
+            optional<int>& unit = layerAdded->shareImageUnit();
+            if ( !unit.isSet() )
+            {
+                int temp;
+                if ( getTextureCompositor()->reserveTextureImageUnit(temp) )
+                {
+                    unit = temp;
+                    OE_INFO << LC << "Image unit " << temp << " assigned to shared layer " << layerAdded->getName() << std::endl;
+                }
+                else
+                {
+                    OE_WARN << LC << "Insufficient GPU image units to share layer " << layerAdded->getName() << std::endl;
+                }
+            }
+        }
+    }
+
     refresh();
 }
 
@@ -441,6 +588,19 @@ MPTerrainEngineNode::addImageLayer( ImageLayer* layerAdded )
 void
 MPTerrainEngineNode::removeImageLayer( ImageLayer* layerRemoved )
 {
+    if ( layerRemoved )
+    {
+        // for a shared layer, release the shared image unit.
+        if ( layerRemoved->isShared() )
+        {
+            if ( layerRemoved->shareImageUnit().isSet() )
+            {
+                getTextureCompositor()->releaseTextureImageUnit( *layerRemoved->shareImageUnit() );
+                layerRemoved->shareImageUnit().unset();
+            }
+        }
+    }
+
     refresh();
 }
 
@@ -476,6 +636,12 @@ MPTerrainEngineNode::moveElevationLayer( unsigned int oldIndex, unsigned int new
 }
 
 void
+MPTerrainEngineNode::toggleElevationLayer( ElevationLayer* layer )
+{
+    refresh();
+}
+
+void
 MPTerrainEngineNode::validateTerrainOptions( TerrainOptions& options )
 {
     TerrainEngineNode::validateTerrainOptions( options );
@@ -500,72 +666,101 @@ MPTerrainEngineNode::updateShaders()
         osg::StateSet* terrainStateSet = _terrain->getOrCreateStateSet();
 
         VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "engine_mp:TerrainNode" );
+        vp->setName( "osgEarth::engine_mp:TerrainNode" );
         terrainStateSet->setAttributeAndModes( vp, osg::StateAttribute::ON );
 
-        // Vertex shader template:
-        std::string vs =
+        // bind the vertex attributes generated by the tile compiler.
+        vp->addBindAttribLocation( "oe_terrain_attr",  osg::Drawable::ATTRIBUTE_6 );
+        vp->addBindAttribLocation( "oe_terrain_attr2", osg::Drawable::ATTRIBUTE_7 );
+
+        // Vertex shader:
+        std::string vs = Stringify() <<
             "#version " GLSL_VERSION_STR "\n"
             GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_tc;\n"
+            "varying vec4 oe_layer_texc;\n"
+            "varying vec4 oe_layer_tilec;\n"
             "void oe_mp_setup_coloring(inout vec4 VertexModel) \n"
             "{ \n"
-            "    oe_layer_tc = __GL_MULTITEXCOORD__;\n"
+            "    oe_layer_texc  = gl_MultiTexCoord" << _primaryUnit << ";\n"
+            "    oe_layer_tilec = gl_MultiTexCoord" << _secondaryUnit << ";\n"
             "}\n";
 
-        // Fragment shader for normal blending:
-        std::string fs =
+        bool useTerrainColor = _terrainOptions.color().isSet();
+
+        bool useBlending = _terrainOptions.enableBlending() == true;
+
+        // Fragment Shader for normal blending:
+        std::string fs = Stringify() <<
             "#version " GLSL_VERSION_STR "\n"
             GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_tc; \n"
+            "varying vec4 oe_layer_texc; \n"
             "uniform sampler2D oe_layer_tex; \n"
             "uniform int oe_layer_uid; \n"
             "uniform int oe_layer_order; \n"
             "uniform float oe_layer_opacity; \n"
-            "void oe_mp_apply_coloring( inout vec4 color ) \n"
+            << (useTerrainColor ?
+            "uniform vec4 oe_terrain_color; \n" : ""
+            ) <<
+            "void oe_mp_apply_coloring(inout vec4 color) \n"
             "{ \n"
+            << (useTerrainColor ?
+            "    color = oe_terrain_color; \n" : ""
+            ) <<
+            //"    color = vec4(1,1,1,1); \n"
             "    vec4 texel; \n"
-            "    if ( oe_layer_uid >= 0 ) \n"
-            "        texel = texture2D(oe_layer_tex, oe_layer_tc.st); \n"
+            "    if ( oe_layer_uid >= 0 ) { \n"
+            "        texel = texture2D(oe_layer_tex, oe_layer_texc.st); \n"
+            "        texel.a *= oe_layer_opacity; \n"
+            "    } \n"
             "    else \n"
             "        texel = color; \n"
-            "    texel.a *= oe_layer_opacity; \n"
-            "    if (oe_layer_order == 0 ) \n"
-            "        color = vec4(color.rgb*(1.0-texel.a) + texel.rgb*texel.a, color.a); \n"
-            "    else \n"
+            "    "
+            << (useBlending ?
+            "    if ( oe_layer_order == 0 ) \n"
+            "        color = texel*texel.a + color*(1.0-texel.a); \n" // simulate src_alpha, 1-src_alpha blens
+            "    else \n" : ""
+            ) <<
             "        color = texel; \n"
             "} \n";
 
         // Fragment shader with pre-multiplied alpha blending:
-        std::string fs_pma =
+        std::string fs_pma = Stringify() <<
             "#version " GLSL_VERSION_STR "\n"
             GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "varying vec4 oe_layer_tc; \n"
+            "varying vec4 oe_layer_texc; \n"
             "uniform sampler2D oe_layer_tex; \n"
             "uniform int oe_layer_uid; \n"
             "uniform int oe_layer_order; \n"
             "uniform float oe_layer_opacity; \n"
+            << (useTerrainColor ?
+            "uniform vec4 oe_terrain_color; \n" : ""
+            ) <<
+
             "void oe_mp_apply_coloring_pma( inout vec4 color ) \n"
             "{ \n"
             "    vec4 texelpma; \n"
+            << (useTerrainColor ?
+            "    color = oe_terrain_color; \n" : ""
+            ) <<
 
             // a UID < 0 means no texture.
             "    if ( oe_layer_uid >= 0 ) \n"
-            "        texelpma = texture2D(oe_layer_tex, oe_layer_tc.st) * oe_layer_opacity; \n"
+            "        texelpma = texture2D(oe_layer_tex, oe_layer_texc.st) * oe_layer_opacity; \n"
             "    else \n"
             "        texelpma = color * color.a * oe_layer_opacity; \n" // to PMA.
 
             // first layer must PMA-blend with the globe color.
+            << (useBlending ?
             "    if (oe_layer_order == 0) { \n"
-            "        color *= color.a; \n"
-            "        color = vec4(texelpma.rgb + color.rgb*(1.0-texelpma.a), color.a); \n"
-            "    } \n"
-
-            "    else { \n"
+            "        color.rgb *= color.a; \n"
+            "        color = texelpma + color*(1.0-texelpma.a); \n" // simulate one, 1-src_alpha blend
+            "    } \n" : ""
+            ) <<
+            "    else \n"
             "        color = texelpma; \n"
-            "    } \n"
             "} \n";
 
+
         // Color filter frag function:
         std::string fs_colorfilters =
             "#version " GLSL_VERSION_STR "\n"
@@ -577,11 +772,6 @@ MPTerrainEngineNode::updateShaders()
                 "__COLOR_FILTER_BODY__"
             "} \n";
 
-
-        // install the gl_MultiTexCoord* variable that uses the proper texture
-        // image unit:
-        replaceIn( vs, "__GL_MULTITEXCOORD__", Stringify() << "gl_MultiTexCoord" << _textureImageUnit );
-
         vp->setFunction( "oe_mp_setup_coloring", vs, ShaderComp::LOCATION_VERTEX_MODEL, 0.0 );
 
         if ( _terrainOptions.premultipliedAlpha() == true )
@@ -603,7 +793,7 @@ MPTerrainEngineNode::updateShaders()
                 cf_body << I << "if (color.a > 0.0) color.rgb /= color.a; \n";
             }
 
-            // second, install the per-layer color filter functions.
+            // second, install the per-layer color filter functions AND shared layer bindings.
             bool ifStarted = false;
             int numImageLayers = _update_mapf->imageLayers().size();
             for( int i=0; i<numImageLayers; ++i )
@@ -611,6 +801,7 @@ MPTerrainEngineNode::updateShaders()
                 ImageLayer* layer = _update_mapf->getImageLayerAt(i);
                 if ( layer->getEnabled() )
                 {
+                    // install Color Filter function calls:
                     const ColorFilterChain& chain = layer->getColorFilters();
                     if ( chain.size() > 0 )
                     {
@@ -621,8 +812,8 @@ MPTerrainEngineNode::updateShaders()
                         for( ColorFilterChain::const_iterator j = chain.begin(); j != chain.end(); ++j )
                         {
                             const ColorFilter* filter = j->get();
-                            cf_head << "void " << filter->getEntryPointFunctionName() << "(in int slot, inout vec4 color);\n";
-                            cf_body << I << I << filter->getEntryPointFunctionName() << "(" << _textureImageUnit << ", color);\n";
+                            cf_head << "void " << filter->getEntryPointFunctionName() << "(inout vec4 color);\n";
+                            cf_body << I << I << filter->getEntryPointFunctionName() << "(color);\n";
                             filter->install( terrainStateSet );
                         }
                         cf_body << I << "}\n";
@@ -673,7 +864,17 @@ MPTerrainEngineNode::updateShaders()
 
         // binding for the terrain texture
         terrainStateSet->getOrCreateUniform( 
-            "oe_layer_tex", osg::Uniform::SAMPLER_2D )->set( _textureImageUnit );
+            "oe_layer_tex", osg::Uniform::SAMPLER_2D )->set( _primaryUnit );
+
+        // binding for the secondary texture (for LOD blending)
+        terrainStateSet->getOrCreateUniform(
+            "oe_layer_tex_parent", osg::Uniform::SAMPLER_2D )->set( _secondaryUnit );
+
+        // binding for the default secondary texture matrix
+        osg::Matrixf parent_mat;
+        parent_mat(0,0) = 0.0f;
+        terrainStateSet->getOrCreateUniform(
+            "oe_layer_parent_matrix", osg::Uniform::FLOAT_MAT4 )->set( parent_mat );
 
         // uniform that controls per-layer opacity
         terrainStateSet->getOrCreateUniform(
@@ -690,41 +891,13 @@ MPTerrainEngineNode::updateShaders()
         terrainStateSet->getOrCreateUniform(
             "oe_layer_order", osg::Uniform::INT )->set( 0 );
 
+        // base terrain color.
+        if ( useTerrainColor )
+        {
+            terrainStateSet->getOrCreateUniform(
+                "oe_terrain_color", osg::Uniform::FLOAT_VEC4 )->set( *_terrainOptions.color() );
+        }
+
         _shaderUpdateRequired = false;
     }
 }
-
-
-namespace
-{
-    class UpdateElevationVisitor : public osg::NodeVisitor
-    {
-    public:
-        UpdateElevationVisitor( TileModelCompiler* compiler ):
-          osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-          _compiler(compiler)
-          {}
-
-          void apply(osg::Node& node)
-          {
-              TileNode* tile = dynamic_cast<TileNode*>(&node);
-              if (tile)
-              {
-                  tile->compile( _compiler );
-              }
-
-              traverse(node);
-          }
-
-          TileModelCompiler* _compiler;
-    };
-}
-
-
-void
-MPTerrainEngineNode::onVerticalScaleChanged()
-{
-    _terrainOptions.verticalScale() = getVerticalScale();
-    UpdateElevationVisitor visitor( getKeyNodeFactory()->getCompiler() );
-    this->accept(visitor);
-}
diff --git a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
index 075f546..0b6791f 100644
--- a/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_mp/MPTerrainEngineOptions
@@ -35,14 +35,15 @@ namespace osgEarth { namespace Drivers
     {
     public:
         MPTerrainEngineOptions( const ConfigOptions& options =ConfigOptions() ) : TerrainOptions( options ),
-            _skirtRatio    ( 0.05 ),
-            _quickRelease  ( true ),
-            _lodFallOff    ( 0.0 ),
-            _normalizeEdges( true ),
-            _rangeMode     ( osg::LOD::DISTANCE_FROM_EYE_POINT ),
-            _tilePixelSize ( 256 ),
-            _premultAlpha  ( true ),
-            _color         ( Color::White )
+            _skirtRatio        ( 0.05 ),
+            _quickRelease      ( true ),
+            _lodFallOff        ( 0.0 ),
+            _normalizeEdges    ( true ),
+            _rangeMode         ( osg::LOD::DISTANCE_FROM_EYE_POINT ),
+            _tilePixelSize     ( 256 ),
+            _premultAlpha      ( false ),
+            _color             ( Color::White ),
+            _incrementalUpdate ( false )
         {
             setDriver( "mp" );
             fromConfig( _conf );
@@ -52,30 +53,44 @@ namespace osgEarth { namespace Drivers
         virtual ~MPTerrainEngineOptions() { }
 
     public:
+        /** Ratio of terrain tile skirt height to tile radius */
         optional<float>& heightFieldSkirtRatio() { return _skirtRatio; }
         const optional<float>& heightFieldSkirtRatio() const { return _skirtRatio; }
 
+        /** Whether to run a post-render process that releases GL objects as quickly as
+          * possible, freeing up memory faster */
         optional<bool>& quickReleaseGLObjects() { return _quickRelease; }
         const optional<bool>& quickReleaseGLObjects() const { return _quickRelease; }
 
-        optional<float>& lodFallOff() { return _lodFallOff; }
-        const optional<float>& lodFallOff() const { return _lodFallOff; }
-
+        /** Whether to average normal vectors on tile boundaries */
         optional<bool>& normalizeEdges() { return _normalizeEdges; }
         const optional<bool>& normalizeEdges() const { return _normalizeEdges; }
 
+        /** Mode to use when calculating LOD switching distances */
         optional<osg::LOD::RangeMode>& rangeMode() { return _rangeMode;}
         const optional<osg::LOD::RangeMode>& rangeMode() const { return _rangeMode;}
 
+        /** The size of the tile, in pixels, when using rangeMode = PIXEL_SIZE_ON_SCREEN */
         optional<float>& tilePixelSize() { return _tilePixelSize; }
         const optional<float>& tilePixelSize() const { return _tilePixelSize; }
 
+        /** Whether to use pre-multiplied alpha blending for terrain imagery */
         optional<bool>& premultipliedAlpha() { return _premultAlpha; }
         const optional<bool>& premultipliedAlpha() const { return _premultAlpha; }
 
+        /** The color of the globe surface where no images are applied */
         optional<Color>& color() { return _color; }
         const optional<Color>& color() const { return _color; }
 
+        /** Whether to update tiles incrementally as needed when the map model changes (true),
+          * or to rebuild the entire terrain when enything changes (false) */
+        optional<bool>& incrementalUpdate() { return _incrementalUpdate; }
+        const optional<bool>& incrementalUpdate() const { return _incrementalUpdate; }
+
+        /** TODO: document this very obscure feature */
+        optional<float>& lodFallOff() { return _lodFallOff; }
+        const optional<float>& lodFallOff() const { return _lodFallOff; }
+
     protected:
         virtual Config getConfig() const {
             Config conf = TerrainOptions::getConfig();
@@ -88,6 +103,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
             conf.updateIfSet( "premultiplied_alpha", _premultAlpha );
             conf.updateIfSet( "color", _color );
+            conf.updateIfSet( "incremental_update", _incrementalUpdate );
 
             return conf;
         }
@@ -109,6 +125,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
             conf.getIfSet( "premultiplied_alpha", _premultAlpha );
             conf.getIfSet( "color", _color );
+            conf.getIfSet( "incremental_update", _incrementalUpdate );
         }
 
         optional<float>               _skirtRatio;
@@ -119,6 +136,7 @@ namespace osgEarth { namespace Drivers
         optional<float>               _tilePixelSize;
         optional<bool>                _premultAlpha;
         optional<Color>               _color;
+        optional<bool>                _incrementalUpdate;
     };
 
 } } // namespace osgEarth::Drivers
diff --git a/src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory.cpp
deleted file mode 100644
index 107c26f..0000000
--- a/src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory.cpp
+++ /dev/null
@@ -1,224 +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 "SerialKeyNodeFactory"
-#include "DynamicLODScaleCallback"
-#include "FileLocationCallback"
-#include "LODFactorCallback"
-#include "CustomPagedLOD"
-
-#include <osgEarth/Registry>
-#include <osgEarth/HeightFieldUtils>
-#include <osg/PagedLOD>
-#include <osg/CullStack>
-#include <osg/Uniform>
-
-#include <osgEarth/MapNode>
-
-using namespace osgEarth_engine_mp;
-using namespace osgEarth;
-using namespace OpenThreads;
-
-#define LC "[SerialKeyNodeFactory] "
-
-
-SerialKeyNodeFactory::SerialKeyNodeFactory(TileModelFactory*        modelFactory,
-                                           TileModelCompiler*       modelCompiler,
-                                           TileNodeRegistry*        liveTiles,
-                                           TileNodeRegistry*        deadTiles,
-                                           const MPTerrainEngineOptions& options,
-                                           const MapInfo&           mapInfo,
-                                           TerrainNode*             terrain,
-                                           UID                      engineUID ) :
-_modelFactory    ( modelFactory ),
-_modelCompiler   ( modelCompiler ),
-_liveTiles       ( liveTiles ),
-_deadTiles       ( deadTiles ),
-_options         ( options ),
-_mapInfo         ( mapInfo ),
-_terrain         ( terrain ),
-_engineUID       ( engineUID )
-{
-    //nop
-}
-
-void
-SerialKeyNodeFactory::addTile(TileModel* model, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent )
-{
-    // create a node:
-    TileNode* tileNode = new TileNode( model->_tileKey, model->_tileLocator );
-
-    // install the tile model and compile it:
-    tileNode->setTileModel( model );
-    tileNode->compile( _modelCompiler.get() );
-
-    // assemble a URI for this tile's child group:
-    std::string uri = Stringify() << model->_tileKey.str() << "." << _engineUID << ".osgearth_engine_mp_tile";
-
-    osg::Node* result = 0L;
-
-    // Only add the next tile if all the following are true:
-    // 1. Either there's real tile data, or a minLOD is explicity set in the options;
-    // 2. The tile isn't blacklisted; and
-    // 3. We are still below the maximim LOD.
-    bool wrapInPagedLOD =
-        (tileHasRealData || (_options.minLOD().isSet() && model->_tileKey.getLOD() < *_options.minLOD())) &&
-        !osgEarth::Registry::instance()->isBlacklisted( uri ) &&
-        model->_tileKey.getLOD() < *_options.maxLOD();
-
-    if ( wrapInPagedLOD )
-    {
-        osg::BoundingSphere bs = tileNode->getBound();
-      
-        float maxRange = FLT_MAX;
-        
-        //Compute the min range based on the 2D size of the tile
-        GeoExtent extent = model->_tileKey.getExtent();
-        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
-        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
-        osg::Vec3d ll, ur;
-        lowerLeft.toWorld( ll );
-        upperRight.toWorld( ur );
-        double radius = (ur - ll).length() / 2.0;
-        float minRange = (float)(radius * _options.minTileRangeFactor().value());
-
-        
-        // create a PLOD so we can keep subdividing:
-        osg::PagedLOD* plod = new CustomPagedLOD( _liveTiles.get(), _deadTiles.get() );
-        plod->setCenter( bs.center() );
-        plod->addChild( tileNode );
-        plod->setRangeMode( *_options.rangeMode() );
-        plod->setFileName( 1, uri );
-  
-
-        if (plod->getRangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN)
-        {
-            static const float sqrt2 = sqrt(2.0f);
-
-            minRange = 0;
-            maxRange = (*_options.tilePixelSize()) * sqrt2;
-            plod->setRange( 0, minRange, maxRange  );
-            plod->setRange( 1, maxRange, FLT_MAX );            
-        }
-        else
-        {
-            plod->setRange( 0, minRange, maxRange );                
-            plod->setRange( 1, 0, minRange );        
-        }        
-                        
-
-        plod->setUserData( new MapNode::TileRangeData(minRange, maxRange) );
-
-#if USE_FILELOCATIONCALLBACK
-        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
-        options->setFileLocationCallback( new FileLocationCallback() );
-        plod->setDatabaseOptions( options );
-
-#endif
-        result = plod;
-        
-        if ( tileHasLodBlending )
-        {
-            // Make the LOD transition distance, and a measure of how
-            // close the tile is to an LOD change, to shaders.
-            result->addCullCallback(new LODFactorCallback);
-        }
-    }
-    else
-    {
-        result = tileNode;
-    }
-
-    // this cull callback dynamically adjusts the LOD scale based on distance-to-camera:
-    if ( _options.lodFallOff().isSet() && *_options.lodFallOff() > 0.0 )
-    {
-        result->addCullCallback( new DynamicLODScaleCallback(*_options.lodFallOff()) );
-    }
-
-    // this one rejects back-facing tiles:
-    if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true )
-    {
-        osg::HeightField* hf =
-            model->_elevationData.getHFLayer()->getHeightField();
-
-        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
-            hf,
-            tileNode->getLocator()->getEllipsoidModel(),
-            *_options.verticalScale() ) );
-    }
-
-    parent->addChild( result );
-}
-
-osg::Node*
-SerialKeyNodeFactory::createRootNode( const TileKey& key )
-{
-    osg::ref_ptr<TileModel> model;
-    bool                    real;
-    bool                    lodBlending;
-
-    _modelFactory->createTileModel( key, model, real, lodBlending );
-
-    // yes, must put the single tile under a tile node group so that it
-    // gets registered in the tile node registry
-    osg::Group* root = new TileNodeGroup();
-
-    addTile( model.get(), real, lodBlending, root );
-    
-    return root;
-}
-
-osg::Node*
-SerialKeyNodeFactory::createNode( const TileKey& parentKey )
-{
-    osg::ref_ptr<TileModel> models[4];
-    bool                   realData[4];
-    bool                   lodBlending[4];
-    bool                   tileHasAnyRealData = false;
-
-    for( unsigned i = 0; i < 4; ++i )
-    {
-        TileKey child = parentKey.createChildKey( i );
-
-        _modelFactory->createTileModel( child, models[i], realData[i], lodBlending[i] );
-
-        if ( models[i].valid() && realData[i] )
-        {
-            tileHasAnyRealData = true;
-        }
-    }
-
-    osg::Group* root = 0L;
-
-    // assemble the tile.
-    if ( tileHasAnyRealData || _options.minLOD().isSet() || parentKey.getLevelOfDetail() == 0 )
-    {
-        // Now create TileNodes for them and assemble into a tile group.
-        root = new TileNodeGroup();
-
-        for( unsigned i = 0; i < 4; ++i )
-        {
-            if ( models[i].valid() )
-            {
-                addTile( models[i].get(), realData[i], lodBlending[i], root );
-            }
-        }
-    }
-
-    return root;
-}
diff --git a/src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
similarity index 59%
rename from src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory
rename to src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
index e8b0502..6a55948 100644
--- a/src/osgEarthDrivers/engine_mp/SerialKeyNodeFactory
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory
@@ -16,59 +16,66 @@
 * You 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_SERIAL_KEY_NODE_FACTORY
-#define OSGEARTH_ENGINE_MP_SERIAL_KEY_NODE_FACTORY 1
+#ifndef OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY
+#define OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY 1
 
 #include "Common"
 #include "KeyNodeFactory"
 #include "TerrainNode"
+#include "TileModelCompiler"
 #include "TileModelFactory"
 #include "TileNodeRegistry"
-#include <osgEarth/MapInfo>
+#include <osgEarth/Map>
+#include <osgEarth/Progress>
 
 using namespace osgEarth;
 using namespace osgEarth::Drivers;
 
 namespace osgEarth_engine_mp
 {
-    class SerialKeyNodeFactory : public KeyNodeFactory
+    class SingleKeyNodeFactory : public KeyNodeFactory
     {
     public:
-        SerialKeyNodeFactory(
+        SingleKeyNodeFactory(
+            const Map*                          map,
             TileModelFactory*                   modelFactory,
             TileModelCompiler*                  modelCompiler,
             TileNodeRegistry*                   liveTiles,
             TileNodeRegistry*                   deadTiles,
-            const MPTerrainEngineOptions& options,
-            const MapInfo&                      mapInfo,
+            const MPTerrainEngineOptions&       options,
             TerrainNode*                        terrain,
             UID                                 engineUID );
 
         /** dtor */
-        virtual ~SerialKeyNodeFactory() { }
+        virtual ~SingleKeyNodeFactory() { }
 
 
     public: // KeyNodeFactory
 
-        osg::Node* createRootNode( const TileKey& key );
-
-        osg::Node* createNode( const TileKey& key );
-
-        TileModelCompiler* getCompiler() const { return _modelCompiler.get(); }
+        /**
+         * Creates a TileNode or TileGroup corresponding to the TileKey.
+         *
+         * @param key           TileKey for which to create a new node
+         * @param setupChildren When true, build and include the necessary structures to
+         *                      page in subtiles if and when necessary. If false, you just get
+         *                      the tile alone with no paging support.
+         * @param progress      Callback for cancelation and progress reporting
+         */
+        osg::Node* createNode( const TileKey& key, bool setupChildren, ProgressCallback* progress );
 
     protected:
-        void addTile(TileModel* model, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent );
+        osg::Node* createTile(TileModel* model, bool setupChildren);
 
+        MapFrame                            _frame;
         osg::ref_ptr<TileModelFactory>      _modelFactory;
         osg::ref_ptr<TileModelCompiler>     _modelCompiler;
         osg::ref_ptr<TileNodeRegistry>      _liveTiles;
         osg::ref_ptr<TileNodeRegistry>      _deadTiles;
-        const MPTerrainEngineOptions& _options;
-        const MapInfo                       _mapInfo;
+        const MPTerrainEngineOptions&       _options;
         osg::ref_ptr< TerrainNode >         _terrain;
         UID                                 _engineUID;
     };
 
 } // namespace osgEarth_engine_mp
 
-#endif // OSGEARTH_ENGINE_MP_SERIAL_KEY_NODE_FACTORY
+#endif // OSGEARTH_ENGINE_MP_SINGLE_KEY_NODE_FACTORY
diff --git a/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
new file mode 100644
index 0000000..35ee710
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/SingleKeyNodeFactory.cpp
@@ -0,0 +1,215 @@
+/* -*-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 "SingleKeyNodeFactory"
+#include "DynamicLODScaleCallback"
+#include "FileLocationCallback"
+#include "TilePagedLOD"
+#include "TileGroup"
+
+#include <osgEarth/Registry>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/Progress>
+
+using namespace osgEarth_engine_mp;
+using namespace osgEarth;
+using namespace OpenThreads;
+
+#define LC "[SingleKeyNodeFactory] "
+
+namespace
+{
+    struct MyProgressCallback : public osgEarth::ProgressCallback
+    {
+        osg::observer_ptr<osg::PagedLOD> _plod;
+
+        MyProgressCallback( osg::PagedLOD* plod )
+            : _plod(plod) { }
+
+        bool isCanceled() const
+        {
+            if ( _canceled )
+                return true;
+
+            if ( !_plod.valid() )
+            {
+                _canceled = true;
+                OE_INFO << "CANCEL, plod = null." << std::endl;
+            }
+            else
+            {
+                osg::ref_ptr<osg::PagedLOD> plod;
+                if ( _plod.lock(plod) )
+                {
+                    osg::ref_ptr<osg::Referenced> dbr = plod->getDatabaseRequest( 1 );
+                    if ( !dbr.valid() || dbr->referenceCount() < 2 )
+                    {
+                        _canceled = true;
+                        OE_INFO << "CANCEL, REFCOUNT = " << dbr->referenceCount() << std::endl;
+                    }
+                }
+                else
+                {
+                    _canceled = true;
+                    OE_INFO << "CANCEL, plod = null." << std::endl;
+                }
+            }
+
+            return _canceled;
+        }
+    };
+}
+
+
+SingleKeyNodeFactory::SingleKeyNodeFactory(const Map*                    map,
+                                           TileModelFactory*             modelFactory,
+                                           TileModelCompiler*            modelCompiler,
+                                           TileNodeRegistry*             liveTiles,
+                                           TileNodeRegistry*             deadTiles,
+                                           const MPTerrainEngineOptions& options,
+                                           TerrainNode*                  terrain,
+                                           UID                           engineUID ) :
+_frame           ( map ),
+_modelFactory    ( modelFactory ),
+_modelCompiler   ( modelCompiler ),
+_liveTiles       ( liveTiles ),
+_deadTiles       ( deadTiles ),
+_options         ( options ),
+_terrain         ( terrain ),
+_engineUID       ( engineUID )
+{
+    //nop
+}
+
+
+osg::Node*
+SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary)
+{
+    // compile the model into a node:
+    TileNode* tileNode = _modelCompiler->compile( model, _frame );
+
+    // see if this tile might have children.
+    bool prepareForChildren =
+        setupChildrenIfNecessary &&
+        model->_tileKey.getLOD() < *_options.maxLOD();
+
+    osg::Node* result = 0L;
+
+    if ( prepareForChildren )
+    {
+        //Compute the min range based on the 2D size of the tile
+        osg::BoundingSphere bs = tileNode->getBound();
+        GeoExtent extent = model->_tileKey.getExtent();
+        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
+        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
+        osg::Vec3d ll, ur;
+        lowerLeft.toWorld( ll );
+        upperRight.toWorld( ur );
+        double radius = (ur - ll).length() / 2.0;
+        float minRange = (float)(radius * _options.minTileRangeFactor().value());
+
+        TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles );
+        plod->setCenter  ( bs.center() );
+        plod->addChild   ( tileNode );
+        plod->setRange   ( 0, minRange, FLT_MAX );
+        plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );
+        plod->setRange   ( 1, 0, minRange );
+
+#if USE_FILELOCATIONCALLBACK
+        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
+        options->setFileLocationCallback( new FileLocationCallback() );
+        plod->setDatabaseOptions( options );
+#endif
+        
+        result = plod;
+    }
+    else
+    {
+        result = tileNode;
+    }
+
+    // this one rejects back-facing tiles:
+    if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
+    {
+        osg::HeightField* hf =
+            model->_elevationData.getHeightField();
+
+        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
+            hf,
+            tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
+            *_options.verticalScale() ) );
+    }
+
+    return result;
+}
+
+
+osg::Node*
+SingleKeyNodeFactory::createNode(const TileKey&    key, 
+                                 bool              setupChildren,
+                                 ProgressCallback* progress )
+{
+    if ( progress && progress->isCanceled() )
+        return 0L;
+
+    _frame.sync();
+    
+    osg::ref_ptr<TileModel> model[4];
+    for(unsigned q=0; q<4; ++q)
+    {
+        TileKey child = key.createChildKey(q);
+        _modelFactory->createTileModel( child, _frame, model[q] );
+    }
+
+    bool subdivide =
+        _options.minLOD().isSet() && 
+        key.getLOD() < _options.minLOD().value();
+
+    if ( !subdivide )
+    {
+        for(unsigned q=0; q<4; ++q)
+        {
+            if ( model[q]->hasRealData() )
+            {
+                subdivide = true;
+                break;
+            }
+        }
+    }
+
+    osg::ref_ptr<osg::Group> quad;
+
+    if ( subdivide )
+    {
+        if ( _options.incrementalUpdate() == true )
+        {
+            quad = new TileGroup(key, _engineUID, _liveTiles.get(), _deadTiles.get());
+        }
+        else
+        {
+            quad = new osg::Group();
+        }
+
+        for( unsigned q=0; q<4; ++q )
+        {
+            quad->addChild( createTile(model[q].get(), setupChildren) );
+        }
+    }
+
+    return quad.release();
+}
diff --git a/src/osgEarthDrivers/engine_mp/TerrainNode b/src/osgEarthDrivers/engine_mp/TerrainNode
index 0f4558e..c7be201 100644
--- a/src/osgEarthDrivers/engine_mp/TerrainNode
+++ b/src/osgEarthDrivers/engine_mp/TerrainNode
@@ -27,7 +27,6 @@ namespace osgEarth_engine_mp
     class TileFactory;
 
     using namespace osgEarth;
-    using namespace osgEarth::Drivers;
 
     /**
      * Parent node for the TileNode mp hierarchy.
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup b/src/osgEarthDrivers/engine_mp/TileGroup
new file mode 100644
index 0000000..921edcd
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/TileGroup
@@ -0,0 +1,71 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2013 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTH_ENGINE_MP_TILE_GROUP
+#define OSGEARTH_ENGINE_MP_TILE_GROUP 1
+
+#include "Common"
+#include "TileNode"
+#include "TileNodeRegistry"
+#include <osg/Group>
+#include <osgEarth/ThreadingUtils>
+
+namespace osgEarth_engine_mp
+{
+    using namespace osgEarth;
+
+    /**
+     * TileGroup is a group node that contians four sibling TileNodes
+     * in the quadtree structure. It has the ability to update itself
+     * by checking the map revision and scheduling an incremental
+     * update of its TileNode set.
+     */
+    class TileGroup : public osg::Group
+    {
+    public:
+        TileGroup(const TileKey&    key, 
+                  const UID&        engineUID,
+                  TileNodeRegistry* live,
+                  TileNodeRegistry* dead);
+
+        const UID& getEngineUID() const { return _engineUID; }
+
+        const TileKey& getKey() const { return _key; }
+
+    public: // osg::Node
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    public: // internal
+        void applyUpdate(osg::Node* node);
+
+    protected:
+        virtual ~TileGroup() { }
+
+        TileNode* getTileNode(unsigned q);
+
+        osg::ref_ptr<osg::Node>        _updateAgent;
+        mutable Threading::Mutex       _updateMutex;
+        UID                            _engineUID;
+        TileKey                        _key;
+        osg::ref_ptr<TileNodeRegistry> _live;
+        osg::ref_ptr<TileNodeRegistry> _dead;
+    };
+
+} // namespace osgEarth_engine_mp
+
+#endif // OSGEARTH_ENGINE_MP_TILE_NODE
diff --git a/src/osgEarthDrivers/engine_mp/TileGroup.cpp b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
new file mode 100644
index 0000000..8aa737b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/TileGroup.cpp
@@ -0,0 +1,193 @@
+/* -*-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 "TileGroup"
+#include "TilePagedLOD"
+#include "TileNode"
+
+#include <osg/NodeVisitor>
+
+using namespace osgEarth_engine_mp;
+using namespace osgEarth;
+
+#define LC "[TileGroup] "
+
+namespace
+{
+    struct UpdateAgent : public osg::PagedLOD
+    {
+        UpdateAgent(TileGroup* tilegroup) : _tilegroup(tilegroup)
+        {
+            std::string fn = Stringify()
+                << tilegroup->getKey().str()
+                << "." << tilegroup->getEngineUID()
+                << ".osgearth_engine_mp_standalone_tile";
+
+            this->setFileName(0, fn);
+            this->setRange   (0, 0, FLT_MAX);
+            this->setCenter  (tilegroup->getBound().center());
+        }
+
+        virtual bool addChild(osg::Node* node)
+        {
+            if ( node )
+            {
+                osg::ref_ptr<TileGroup> tilegroup;
+                if ( _tilegroup.lock(tilegroup) )
+                {
+                    tilegroup->applyUpdate( node );
+                    this->_perRangeDataList.resize(0);
+                }
+            }
+            else
+            {
+                OE_DEBUG << LC << "Internal: UpdateAgent for " << _tilegroup->getKey().str() << "received a NULL add."
+                    << std::endl;
+            }
+            return true;
+        }
+
+        osg::observer_ptr<TileGroup> _tilegroup;
+    };
+}
+
+//------------------------------------------------------------------------
+
+TileGroup::TileGroup(const TileKey&    key, 
+                     const UID&        engineUID,
+                     TileNodeRegistry* live,
+                     TileNodeRegistry* dead) :
+_key      ( key ),
+_engineUID( engineUID ),
+_live     ( live ),
+_dead     ( dead )
+{
+    this->setName( key.str() );
+}
+
+TileNode*
+TileGroup::getTileNode(unsigned q)
+{
+    osg::Node* child = getChild(q);
+    TilePagedLOD* plod = dynamic_cast<TilePagedLOD*>( child );
+    if ( plod ) return plod->getTileNode();
+    return static_cast<TileNode*>( child );
+}
+
+void
+TileGroup::applyUpdate(osg::Node* node)
+{
+    if ( node )
+    {
+        OE_DEBUG << LC << "Update received for tile " << _key.str() << std::endl;
+
+        TileGroup* update = dynamic_cast<TileGroup*>( node );
+        if ( !update )
+        {
+            OE_WARN << LC << "Internal error: update was not a TileGroup" << std::endl;
+            return;
+        }
+
+        if ( update->getNumChildren() < 4 )
+        {
+            OE_WARN << LC << "Internal error: update did not have 4 children" << std::endl;
+            return;
+        }
+
+        for(unsigned i=0; i<4; ++i)
+        {
+            TileNode* newTileNode = dynamic_cast<TileNode*>( update->getChild(i) );
+            if ( !newTileNode )
+            {
+                OE_WARN << LC << "Internal error; update child was not a TileNode" << std::endl;
+                return;
+            }
+
+            osg::ref_ptr<TileNode> oldTileNode = 0L;
+
+            TilePagedLOD* plod = dynamic_cast<TilePagedLOD*>(_children[i].get());
+            if ( plod )
+            {
+                oldTileNode = plod->getTileNode();
+                plod->setTileNode( newTileNode );
+                if ( _live.valid() )
+                    _live->move( oldTileNode.get(), _dead.get() );
+            }
+            else
+            {
+                // must be a TileNode leaf, so replace it here.
+                oldTileNode = dynamic_cast<TileNode*>(_children[i].get());
+                if ( !oldTileNode.valid() )
+                {
+                    OE_WARN << LC << "Internal error; existing child was not a TilePagedLOD or a TileNode" << std::endl;
+                    return;
+                }
+
+                this->setChild( i, newTileNode );
+                if ( _live.valid() )
+                    _live->move( oldTileNode.get(), _dead.get() );
+            }
+
+            if ( _live.valid() )
+                _live->add( newTileNode );
+        }
+    }
+
+    // deactivate the update agent
+    _updateAgent = 0L;
+}
+
+void
+TileGroup::traverse(osg::NodeVisitor& nv)
+{
+    if ( nv.getVisitorType() == nv.CULL_VISITOR )
+    {
+        // only check for update if an update isn't already in progress:
+        if ( !_updateAgent.valid() )
+        {
+            bool updateRequired = false;
+            for( unsigned q=0; q<4; ++q)
+            {
+                if ( getTileNode(q)->isOutOfDate() )
+                {
+                    updateRequired = true;
+                    break;
+                }
+            }
+
+            if ( updateRequired )
+            {
+                // lock keeps multiple traversals from doing the same thing
+                Threading::ScopedMutexLock exclusive( _updateMutex );
+
+                // double check to prevent a race condition:
+                if ( !_updateAgent.valid() )
+                {
+                    _updateAgent = new UpdateAgent(this);
+                }
+            }
+        }
+
+        if ( _updateAgent.valid() )
+        {
+            _updateAgent->accept( nv );
+        }
+    }
+
+    osg::Group::traverse( nv );
+}
diff --git a/src/osgEarthDrivers/engine_mp/TileModel b/src/osgEarthDrivers/engine_mp/TileModel
index 488d846..418ee1f 100644
--- a/src/osgEarthDrivers/engine_mp/TileModel
+++ b/src/osgEarthDrivers/engine_mp/TileModel
@@ -25,10 +25,15 @@
 #include <osgEarth/Map>
 #include <osgEarth/ImageLayer>
 #include <osgEarth/TileKey>
+#include <osgEarth/Locators>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/MapInfo>
 #include <osgTerrain/Locator>
 #include <osgTerrain/Layer>
 #include <osg/Image>
 #include <osg/StateSet>
+#include <osg/Texture2D>
+#include <osg/State>
 #include <map>
 
 namespace osgEarth_engine_mp
@@ -38,6 +43,10 @@ namespace osgEarth_engine_mp
     class TileModel : public osg::Referenced
     {
     public:
+        void resizeGLObjectBuffers(unsigned maxSize);
+        void releaseGLObjects(osg::State* state) const;
+
+    public:
         // do not change the order of these.
         enum Neighbor
         {
@@ -54,43 +63,61 @@ namespace osgEarth_engine_mp
         class ElevationData
         {
         public:
-            ElevationData() { }
+            ElevationData() : _fallbackData(true) { }
+            ElevationData(const ElevationData& rhs);
+
             virtual ~ElevationData() { }
-            ElevationData( osgTerrain::HeightFieldLayer* hfLayer, bool fallbackData =false )
-                : _hfLayer(hfLayer), _fallbackData(fallbackData) { }
 
-            osgTerrain::HeightFieldLayer* getHFLayer() const { return _hfLayer.get(); }
+            ElevationData(
+                osg::HeightField* hf,
+                GeoLocator*       locator,
+                bool              fallbackData);
+
+            osg::HeightField* getHeightField() const { return _hf.get(); }
+            GeoLocator* getLocator() const { return _locator.get(); }
             bool isFallbackData() const { return _fallbackData; }
+
+            // get a height value using a normalized coord and a locator.
+            bool getHeight( const osg::Vec3d& ndc, const GeoLocator* ndcLocator, float& output, ElevationInterpolation interp ) const;
+
+            // get a normal vector (in woord space)
+            bool getNormal( const osg::Vec3d& ndc, const GeoLocator* ndcLocator, osg::Vec3& output, ElevationInterpolation interp ) const;
             
             osg::HeightField* getNeighbor(int xoffset, int yoffset) const
             {
-                int index = getNeighborIndex(xoffset, yoffset);
-                return _neighbors[index];
+                return _neighbors.getNeighbor(xoffset, yoffset);
             }
 
             void setNeighbor(int xoffset, int yoffset, osg::HeightField* hf )
             {
-                int index = getNeighborIndex(xoffset, yoffset);
-                _neighbors[index] = hf;
+                _neighbors.setNeighbor(xoffset, yoffset, hf);
             }
 
-        private:
-            static int getNeighborIndex(int xoffset, int yoffset)
+            void setParent(osg::HeightField* hf)
+            {
+                _parent = hf;
+            }
+
+            osg::HeightField* getParent() const
             {
-                int index = 3*(yoffset+1)+(xoffset+1);
-                if (index > 4) index--;
-                return index;
+                return _parent.get();
             }
-            osg::ref_ptr<osgTerrain::HeightFieldLayer> _hfLayer;
-            bool _fallbackData;
-            osg::ref_ptr<osg::HeightField> _neighbors[8];
+
+        private:
+            osg::ref_ptr<osg::HeightField> _hf;
+            osg::ref_ptr<GeoLocator>       _locator;
+            bool                           _fallbackData;
+            osg::ref_ptr<osg::HeightField> _parent;
+
+            HeightFieldNeighborhood _neighbors;
         };
 
 
         class ColorData
         {
         public:
-            ColorData() { }
+            ColorData() : _fallbackData(true) { }
+            ColorData(const ColorData& rhs);
 
             /** dtor */
             virtual ~ColorData() { }
@@ -99,20 +126,13 @@ namespace osgEarth_engine_mp
                 const osgEarth::ImageLayer* imageLayer,
                 unsigned                    order,
                 osg::Image*                 image,
-                const osgTerrain::Locator*  locator,
-                int                         lod,
+                GeoLocator*                 locator,
                 const osgEarth::TileKey&    tileKey,
-                bool                        fallbackData =false )
-                  : _layer(imageLayer), _order(order), _locator(locator), _image(image),  _tileKey(tileKey), _lod(lod), _fallbackData(fallbackData) { }
-
-            ColorData( const ColorData& rhs ) :
-                _layer( rhs._layer.get() ),
-                _order( rhs._order ),
-                _locator( rhs._locator.get() ),
-                _image( rhs._image.get() ),
-                _tileKey( rhs._tileKey ),
-                _lod( rhs._lod ),
-                _fallbackData( rhs._fallbackData ) { }
+                bool                        fallbackData =false );
+    
+            void resizeGLObjectBuffers(unsigned maxSize);
+            void releaseGLObjects(osg::State* state) const;
+
 
             osgEarth::UID getUID() const {
                 return _layer->getUID();
@@ -122,12 +142,13 @@ namespace osgEarth_engine_mp
                 return _order;
             }
 
-            const osgTerrain::Locator* getLocator() const {
+            const GeoLocator* getLocator() const {
                 return _locator.get();
             }
 
-            osg::Image* getImage() const { 
-                return _image.get(); }
+            osg::Texture2D* getTexture() const {
+                return _texture.get();
+            }
 
             const osgEarth::TileKey& getTileKey() const {
                 return _tileKey; }
@@ -135,11 +156,13 @@ namespace osgEarth_engine_mp
             const osgEarth::ImageLayer* getMapLayer() const {
                 return _layer.get(); }
 
-            int getLevelOfDetail() const {
-                return _lod; }
-
             bool isFallbackData() const {
-                return _fallbackData; }
+                return _fallbackData;}
+
+            bool hasAlpha() const {
+                return _hasAlpha;
+            }
+
 
             osg::BoundingSphere computeBound() const {
                 osg::BoundingSphere bs;
@@ -154,14 +177,14 @@ namespace osgEarth_engine_mp
             }
 
 
-        private:
             osg::ref_ptr<const osgEarth::ImageLayer> _layer;
-            osg::ref_ptr<const osgTerrain::Locator>  _locator;
+            osg::ref_ptr<GeoLocator>                 _locator;
             osg::ref_ptr<osg::Image>                 _image;
+            osg::ref_ptr<osg::Texture2D>             _texture;
             osgEarth::TileKey                        _tileKey;
-            int                                      _lod;
             bool                                     _fallbackData;
             unsigned                                 _order;
+            bool                                     _hasAlpha;
         };
 
         class ColorDataRef : public osg::Referenced
@@ -176,16 +199,39 @@ namespace osgEarth_engine_mp
 
 
     public:
-        TileModel() { }
+        TileModel( const osgEarth::Revision& mapModelRevision, const MapInfo& mapInfo )
+            : _revision(mapModelRevision), _mapInfo(mapInfo) { }
+        TileModel(const TileModel& rhs);
         virtual ~TileModel() { }
 
-        osg::ref_ptr<const Map>     _map; // observer_ptr?
-        TileKey                     _tileKey;
-        osg::ref_ptr<GeoLocator>    _tileLocator;
-        ColorDataByUID              _colorData;
-        ElevationData               _elevationData;
-        float                       _sampleRatio;
-        osg::ref_ptr<osg::StateSet> _parentStateSet;
+        /**
+         * Creates a TileModel representing a quadrant of this tile model.
+         * Used for upsampling.
+         */
+        TileModel* createQuadrant(unsigned q) const;
+
+        /** Map revision used to build this model */
+        const Revision& getMapRevision() const { return _revision; }
+
+        /** Whether this tile contains any real data (versus being comprised entirely of fallback data) */
+        bool hasRealData() const;
+
+        /** Parent model pointer. */
+        void setParentTileModel(const TileModel* parent);
+        const TileModel* getParentTileModel() const { return _parentModel.get(); }
+
+        /** Whether there is a heightfield */
+        bool hasElevation() const { return _elevationData.getHeightField() != 0L; }
+
+        MapInfo                      _mapInfo;
+        Revision                     _revision;
+        TileKey                      _tileKey;
+        osg::ref_ptr<GeoLocator>     _tileLocator;
+        ColorDataByUID               _colorData;
+        ElevationData                _elevationData;
+        float                        _sampleRatio;
+        osg::ref_ptr<osg::StateSet>  _parentStateSet;
+        osg::observer_ptr<const TileModel> _parentModel;
 
         // convenience funciton to pull out a layer by its UID.
         bool getColorData( UID layerUID, ColorData& out ) const {
diff --git a/src/osgEarthDrivers/engine_mp/TileModel.cpp b/src/osgEarthDrivers/engine_mp/TileModel.cpp
new file mode 100644
index 0000000..6357cb0
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/TileModel.cpp
@@ -0,0 +1,231 @@
+/* -*-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 "TileModel"
+#include <osgEarth/MapInfo>
+#include <osgEarth/HeightFieldUtils>
+#include <osgEarth/ImageUtils>
+#include <osgTerrain/Locator>
+
+using namespace osgEarth_engine_mp;
+using namespace osgEarth;
+
+#define LC "[TileModel] "
+
+//------------------------------------------------------------------
+
+
+TileModel::ElevationData::ElevationData(osg::HeightField* hf,
+                                        GeoLocator*       locator,
+                                        bool              fallbackData) :
+_hf          ( hf ),
+_locator     ( locator ),
+_fallbackData( fallbackData )
+{
+    _neighbors._center = hf;
+}
+
+TileModel::ElevationData::ElevationData(const TileModel::ElevationData& rhs) :
+_hf          ( rhs._hf.get() ),
+_locator     ( rhs._locator.get() ),
+_fallbackData( rhs._fallbackData ),
+_parent      ( rhs._parent )
+{
+    _neighbors._center = rhs._neighbors._center.get();
+    for(unsigned i=0; i<8; ++i)
+        _neighbors._neighbors[i] = rhs._neighbors._neighbors[i];
+}
+
+bool
+TileModel::ElevationData::getHeight(const osg::Vec3d&      ndc,
+                                    const GeoLocator*      ndcLocator,
+                                    float&                 output,
+                                    ElevationInterpolation interp ) const
+{
+    if ( !_locator.valid() || !ndcLocator )
+        return false;
+
+    osg::Vec3d hf_ndc;
+    GeoLocator::convertLocalCoordBetween( *ndcLocator, ndc, *_locator.get(), hf_ndc );
+    output = HeightFieldUtils::getHeightAtNormalizedLocation( _hf.get(), hf_ndc.x(), hf_ndc.y(), interp );
+    return true;
+}
+
+bool
+TileModel::ElevationData::getNormal(const osg::Vec3d&      ndc,
+                                    const GeoLocator*      ndcLocator,
+                                    osg::Vec3&             output,
+                                    ElevationInterpolation interp ) const
+{
+    if ( !_locator.valid() || !ndcLocator )
+    {
+        output.set(0,0,1);
+        return false;
+    }
+
+    double xcells = (double)(_hf->getNumColumns()-1);
+    double ycells = (double)(_hf->getNumRows()-1);
+    double xres = 1.0/xcells;
+    double yres = 1.0/ycells;
+
+    osg::Vec3d hf_ndc;
+    GeoLocator::convertLocalCoordBetween( *ndcLocator, ndc, *_locator.get(), hf_ndc );
+
+    osg::Vec3d west ( hf_ndc.x()-xres, hf_ndc.y(), 0.0 );
+    osg::Vec3d east ( hf_ndc.x()+xres, hf_ndc.y(), 0.0 );
+    osg::Vec3d south( hf_ndc.x(), hf_ndc.y()-yres, 0.0 );
+    osg::Vec3d north( hf_ndc.x(), hf_ndc.y()+yres, 0.0 );
+
+    west.z()  = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, west.x(),  west.y(),  interp);
+    east.z()  = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, east.x(),  east.y(),  interp);
+    south.z() = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, south.x(), south.y(), interp);
+    north.z() = HeightFieldUtils::getHeightAtNormalizedLocation(_neighbors, north.x(), north.y(), interp);
+
+    osg::Vec3d westWorld, eastWorld, southWorld, northWorld;
+    _locator->unitToModel(west,  westWorld);
+    _locator->unitToModel(east,  eastWorld);
+    _locator->unitToModel(south, southWorld);
+    _locator->unitToModel(north, northWorld);
+
+    output = (eastWorld-westWorld) ^ (northWorld-southWorld);
+    output.normalize();
+
+    return true;
+}
+
+//------------------------------------------------------------------
+
+TileModel::ColorData::ColorData(const osgEarth::ImageLayer* layer,
+                                unsigned                    order,
+                                osg::Image*                 image,
+                                GeoLocator*                 locator,
+                                const osgEarth::TileKey&    tileKey,
+                                bool                        fallbackData) :
+_layer       ( layer ),
+_order       ( order ),
+_locator     ( locator ),
+_tileKey     ( tileKey ),
+_fallbackData( fallbackData )
+{
+    osg::Texture::FilterMode minFilter = layer->getImageLayerOptions().minFilter().get();
+    osg::Texture::FilterMode magFilter = layer->getImageLayerOptions().magFilter().get();
+
+    _texture = new osg::Texture2D( image );
+    _texture->setUnRefImageDataAfterApply( true );
+    _texture->setMaxAnisotropy( 16.0f );
+    _texture->setResizeNonPowerOfTwoHint(false);
+    _texture->setFilter( osg::Texture::MAG_FILTER, magFilter );
+    _texture->setFilter( osg::Texture::MIN_FILTER, minFilter );
+    _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
+    _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
+
+    _hasAlpha = image && ImageUtils::hasTransparency(image);
+}
+
+TileModel::ColorData::ColorData(const TileModel::ColorData& rhs) :
+_layer       ( rhs._layer.get() ),
+_locator     ( rhs._locator.get() ),
+_texture     ( rhs._texture.get() ),
+_tileKey     ( rhs._tileKey ),
+_fallbackData( rhs._fallbackData ),
+_order       ( rhs._order ),
+_hasAlpha    ( rhs._hasAlpha )
+{
+    //nop
+}
+
+void
+TileModel::ColorData::resizeGLObjectBuffers(unsigned maxSize)
+{
+    if ( _texture.valid() )
+    {
+        _texture->resizeGLObjectBuffers( maxSize );
+    }
+}
+
+void
+TileModel::ColorData::releaseGLObjects(osg::State* state) const
+{
+    if ( _texture.valid() && _texture->referenceCount() == 1 )
+    {
+        _texture->releaseGLObjects( state );
+    }
+}
+
+//------------------------------------------------------------------
+
+TileModel::TileModel(const TileModel& rhs) :
+_mapInfo       ( rhs._mapInfo ),
+_revision      ( rhs._revision ),
+_tileKey       ( rhs._tileKey ),
+_tileLocator   ( rhs._tileLocator.get() ),
+_colorData     ( rhs._colorData ),
+_elevationData ( rhs._elevationData ),
+_sampleRatio   ( rhs._sampleRatio ),
+_parentStateSet( rhs._parentStateSet )
+{
+    //nop
+}
+
+
+TileModel*
+TileModel::createQuadrant(unsigned q) const
+{
+    // copy this object:
+    TileModel* model = new TileModel( *this );
+
+    // then modify it for the quadrant.
+    TileKey childKey = _tileKey.createChildKey( q );
+    model->_tileKey = childKey;
+    model->_tileLocator = _tileLocator->createSameTypeForKey( childKey, _mapInfo );
+
+    return model;
+}
+
+bool
+TileModel::hasRealData() const
+{
+    for(ColorDataByUID::const_iterator i = _colorData.begin(); i != _colorData.end(); ++i )
+        if ( !i->second.isFallbackData() )
+            return true;
+
+    if ( hasElevation() && !_elevationData.isFallbackData() )
+        return true;
+
+    return false;
+}
+
+void
+TileModel::setParentTileModel(const TileModel* parent)
+{
+    _parentModel = parent;
+}
+
+void
+TileModel::resizeGLObjectBuffers(unsigned maxSize)
+{
+    for(ColorDataByUID::iterator i = _colorData.begin(); i != _colorData.end(); ++i )
+        i->second.resizeGLObjectBuffers( maxSize );
+}
+
+void
+TileModel::releaseGLObjects(osg::State* state) const
+{
+    for(ColorDataByUID::const_iterator i = _colorData.begin(); i != _colorData.end(); ++i )
+        i->second.releaseGLObjects( state );
+}
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler b/src/osgEarthDrivers/engine_mp/TileModelCompiler
index 4e7a8dc..0e4def2 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler
@@ -21,6 +21,7 @@
 
 #include "Common"
 #include "TileModel"
+#include "TileNode"
 #include "MPTerrainEngineOptions"
 
 #include <osgEarth/Map>
@@ -53,8 +54,11 @@ namespace osgEarth_engine_mp
             osg::Vec4d                     _mat;
             unsigned                       _cols, _rows;
         };
+        
         typedef std::pair< TexCoordTableKey, osg::ref_ptr<osg::Vec2Array> > LocatorTexCoordPair;
-        struct TexCoordArrayCache : public std::list<LocatorTexCoordPair> {
+        
+        struct TexCoordArrayCache : public std::list<LocatorTexCoordPair>
+        {
             osg::ref_ptr<osg::Vec2Array>& get( const osg::Vec4d& mat, unsigned cols, unsigned rows );
         };
 
@@ -74,25 +78,21 @@ namespace osgEarth_engine_mp
     {
     public:
         TileModelCompiler(
-            const MaskLayerVector&              masks,
-            int                                 textureImageUnit,
-            bool                                optimizeTriangleOrientation,
+            const MaskLayerVector&        masks,
+            int                           textureImageUnit,
+            bool                          optimizeTriangleOrientation,
             const MPTerrainEngineOptions& options);
 
         /**
-         * Compiles a tile model into an OSG scene graph. The scene graph will
-         * include a MatrixTransform to localize the tile data.
+         * Compiles a tile model into a TileNode.
          */
-        bool compile(
-            const TileModel* model,
-            osg::Node*&      out_node,
-            osg::StateSet*&  out_stateSet );
+        TileNode* compile(const TileModel* model, const MapFrame& frame);
 
     protected:
         const MaskLayerVector&                    _masks;
         int                                       _textureImageUnit;
         bool                                      _optimizeTriOrientation;
-        const MPTerrainEngineOptions&       _options;
+        const MPTerrainEngineOptions&             _options;
         osg::ref_ptr<osg::Drawable::CullCallback> _cullByTraversalMask;
         CompilerCache                             _cache;
     };
diff --git a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
index a4af319..d4b48a8 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelCompiler.cpp
@@ -23,7 +23,9 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/MapFrame>
+#include <osgEarth/HeightFieldUtils>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/Utils>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/MeshConsolidator>
 
@@ -33,6 +35,7 @@
 #include <osg/GL2Extensions>
 #include <osgUtil/DelaunayTriangulator>
 #include <osgUtil/Optimizer>
+#include <osgUtil/MeshOptimizers>
 
 using namespace osgEarth_engine_mp;
 using namespace osgEarth;
@@ -41,6 +44,7 @@ using namespace osgEarth::Symbology;
 
 #define LC "[TileModelCompiler] "
 
+//#define USE_TEXCOORD_CACHE 1
 
 //------------------------------------------------------------------------
 
@@ -66,6 +70,7 @@ CompilerCache::TexCoordArrayCache::get(const osg::Vec4d& mat,
     return this->back().second;
 }
 
+
 //------------------------------------------------------------------------
 
 
@@ -77,14 +82,13 @@ namespace
     struct RenderLayer
     {
         TileModel::ColorData           _layer;
+        TileModel::ColorData           _layerParent;
         osg::ref_ptr<const GeoLocator> _locator;
         osg::ref_ptr<osg::Vec2Array>   _texCoords;
-        osg::ref_ptr<osg::Vec2Array>   _skirtTexCoords;
         osg::ref_ptr<osg::Vec2Array>   _stitchTexCoords;
-        osg::ref_ptr<osg::Vec2Array>   _stitchSkirtTexCoords;
         bool _ownsTexCoords;
-        bool _ownsSkirtTexCoords;
-        RenderLayer() : _ownsTexCoords(false), _ownsSkirtTexCoords(false) { }
+        RenderLayer() : 
+            _ownsTexCoords( false ) { }
     };
 
     typedef std::vector< RenderLayer > RenderLayerVector;
@@ -110,46 +114,59 @@ namespace
 
     struct Data
     {
-        Data(const TileModel* in_model, const MaskLayerVector& in_maskLayers)
+        Data(const TileModel* in_model, const MapFrame& in_frame, const MaskLayerVector& in_maskLayers)
             : model     ( in_model ), 
+              frame     ( in_frame ),
               maskLayers( in_maskLayers )
         {
             surfaceGeode     = 0L;
             surface          = 0L;
-            skirt            = 0L;
-            stitching_skirts = 0L;
-            ss_verts         = 0L;
+//            ss_verts         = 0L;
             scaleHeight      = 1.0f;
             createSkirt      = false;
             i_sampleFactor   = 1.0f;
             j_sampleFactor   = 1.0f;
-            useVBOs = !Registry::capabilities().preferDisplayListsForStaticGeometry();
+            useVBOs = true; //!Registry::capabilities().preferDisplayListsForStaticGeometry();
             textureImageUnit = 0;
+            renderTileCoords = 0L;
+            ownsTileCoords   = false;
+            stitchTileCoords = 0L;
+//            stitchSkirtTileCoords = 0L;
         }
 
+        const MapFrame& frame;
+
         bool                     useVBOs;
         int                      textureImageUnit;
 
-        const TileModel*         model;                         // the tile's data model
+        const TileModel*              model;                   // the tile's data model
+        osg::ref_ptr<const TileModel> parentModel;             // parent model reference
+
         const MaskLayerVector&   maskLayers;                    // map-global masking layer set
-        osg::ref_ptr<GeoLocator> geoLocator;                    // tile locator adjusted to geocentric
+        osg::ref_ptr<GeoLocator> geoLocator;                    // tile locator adjusted to geographic
         osg::Vec3d               centerModel;                   // tile center in model (world) coords
 
-        RenderLayerVector        renderLayers;
+        RenderLayerVector            renderLayers;
+        osg::ref_ptr<osg::Vec2Array> renderTileCoords;
+        bool                         ownsTileCoords;
+
+        // tile coords for masked areas; always owned (never shared)
+        osg::ref_ptr<osg::Vec2Array> stitchTileCoords;
 
         // surface data:
         osg::Geode*                   surfaceGeode;
         MPGeometry*                   surface;
         osg::Vec3Array*               surfaceVerts;
         osg::Vec3Array*               normals;
-        osg::Vec4Array*               surfaceElevData;
+        osg::Vec4Array*               surfaceAttribs;
+        osg::Vec4Array*               surfaceAttribs2;
         unsigned                      numVerticesInSurface;
         osg::ref_ptr<osg::FloatArray> elevations;
         Indices                       indices;
         osg::BoundingSphere           surfaceBound;
 
         // skirt data:
-        MPGeometry*              skirt;
+        //MPGeometry*              skirt;
         unsigned                 numVerticesInSkirt;
         bool                     createSkirt;
 
@@ -164,8 +181,8 @@ namespace
         
         // for masking/stitching:
         MaskRecordVector         maskRecords;
-        MPGeometry*              stitching_skirts;
-        osg::Vec3Array*          ss_verts;
+//        MPGeometry*              stitching_skirts;
+//        osg::Vec3Array*          ss_verts;
     };
 
 
@@ -230,19 +247,19 @@ namespace
 
               if (x_match && y_match)
               {
-                //osg::Geometry* mask_geom = new osg::Geometry();
-                MPGeometry* mask_geom = new MPGeometry( d.model->_map.get(), d.textureImageUnit );
-                mask_geom->setUseVertexBufferObjects(d.useVBOs);
-                d.surfaceGeode->addDrawable(mask_geom);
-                d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, mask_geom) );
+                  MPGeometry* mask_geom = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
+                  mask_geom->setUseVertexBufferObjects(d.useVBOs);
+                  d.surfaceGeode->addDrawable(mask_geom);
+                  d.maskRecords.push_back( MaskRecord(boundary, min_ndc, max_ndc, mask_geom) );
               }
            }
         }
 
+#if 0
         if (d.maskRecords.size() > 0)
         {
           //d.stitching_skirts = new osg::Geometry();
-          d.stitching_skirts = new MPGeometry( d.model->_map.get(), d.textureImageUnit );
+          d.stitching_skirts = new MPGeometry( d.model->_tileKey, d.frame, d.textureImageUnit );
           d.stitching_skirts->setUseVertexBufferObjects(d.useVBOs);
           d.surfaceGeode->addDrawable( d.stitching_skirts );
 
@@ -252,6 +269,7 @@ namespace
           if ( d.ss_verts->getVertexBufferObject() )
               d.ss_verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
         }
+#endif
     }
 
 
@@ -259,7 +277,7 @@ namespace
      * Calculates the sample rate and allocates all the vertex, normal, and color
      * arrays for the tile.
      */
-    void setupGeometryAttributes( Data& d, double sampleRatio, const osg::Vec4& color )
+    void setupGeometryAttributes( Data& d, double sampleRatio )
     {
         d.numRows = 8;
         d.numCols = 8;
@@ -267,11 +285,11 @@ namespace
         d.originalNumCols = 8;        
 
         // read the row/column count and skirt size from the model:
-        osgTerrain::HeightFieldLayer* hflayer = d.model->_elevationData.getHFLayer();
-        if (hflayer)
+        osg::HeightField* hf = d.model->_elevationData.getHeightField();
+        if ( hf )
         {
-            d.numCols = hflayer->getNumColumns();
-            d.numRows = hflayer->getNumRows();         
+            d.numCols = hf->getNumColumns();
+            d.numRows = hf->getNumRows();
             d.originalNumCols = d.numCols;
             d.originalNumRows = d.numRows;
         }
@@ -292,36 +310,34 @@ namespace
 
 
         // calculate the total number of verts:
-        d.numVerticesInSurface = d.numCols * d.numRows;
         d.numVerticesInSkirt   = d.createSkirt ? (2 * (d.numCols*2 + d.numRows*2 - 4)) : 0;
+        d.numVerticesInSurface = d.numCols * d.numRows + d.numVerticesInSkirt;
 
         // allocate and assign vertices
         d.surfaceVerts = new osg::Vec3Array();
         d.surfaceVerts->reserve( d.numVerticesInSurface );
         d.surface->setVertexArray( d.surfaceVerts );
 
-        if ( d.surfaceVerts->getVertexBufferObject() )
-            d.surfaceVerts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-
         // allocate and assign normals
         d.normals = new osg::Vec3Array();
         d.normals->reserve( d.numVerticesInSurface );
         d.surface->setNormalArray( d.normals );
         d.surface->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
 
-        // allocate and assign color
-        osg::Vec4Array* colors = new osg::Vec4Array(1);
-        (*colors)[0] = color;
-        d.surface->setColorArray( colors );
-        d.surface->setColorBinding( osg::Geometry::BIND_OVERALL );
-
-        // elevation attribution
+        // vertex attribution
         // for each vertex, a vec4 containing a unit extrusion vector in [0..2] and the raw elevation in [3]
-        d.surfaceElevData = new osg::Vec4Array();
-        d.surfaceElevData->reserve( d.numVerticesInSurface );
-        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceElevData );
+        d.surfaceAttribs = new osg::Vec4Array();
+        d.surfaceAttribs->reserve( d.numVerticesInSurface );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceAttribs );
         d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
         d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
+
+        // for each vertex, index 0 holds the interpolated elevation from the lower lod (for morphing)
+        d.surfaceAttribs2 = new osg::Vec4Array();
+        d.surfaceAttribs2->reserve( d.numVerticesInSurface );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceAttribs2 );
+        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
+        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
         
         // temporary data structures for triangulation support
         d.elevations = new osg::FloatArray();
@@ -339,6 +355,32 @@ namespace
         // array, saving on memory.
         d.renderLayers.reserve( d.model->_colorData.size() );
 
+#ifdef USE_TEXCOORD_CACHE
+        // unit tile coords - [0..1] always across the tile.
+        osg::Vec4d idmat;
+        idmat[0] = 0.0;
+        idmat[1] = 0.0;
+        idmat[2] = 1.0;
+        idmat[3] = 1.0;
+
+        osg::ref_ptr<osg::Vec2Array>& tileCoords = cache._surfaceTexCoordArrays.get( idmat, d.numCols, d.numRows );
+        if ( !tileCoords.valid() )
+        {
+            // Note: anything in the cache must have its own VBO. No sharing!
+            tileCoords = new osg::Vec2Array();
+            tileCoords->setVertexBufferObject( new osg::VertexBufferObject() );
+            tileCoords->reserve( d.numVerticesInSurface );
+            d.ownsTileCoords = true;
+        }
+        d.renderTileCoords = tileCoords.get();
+
+#else // not USE_TEXCOORD_CACHE
+        d.renderTileCoords = new osg::Vec2Array();
+        d.renderTileCoords->reserve( d.numVerticesInSurface );
+        d.ownsTileCoords = true;
+#endif
+
+
         // build a list of "render layers", in rendering order, sharing texture coordinate
         // arrays wherever possible.
         for( TileModel::ColorDataByUID::const_iterator i = d.model->_colorData.begin(); i != d.model->_colorData.end(); ++i )
@@ -347,11 +389,11 @@ namespace
             RenderLayer r;
             r._layer = colorLayer;
 
-            const GeoLocator* locator = dynamic_cast<const GeoLocator*>( r._layer.getLocator() );
+            const GeoLocator* locator = r._layer.getLocator();
             if ( locator )
             {
                 // if we have no mask records, we can use the texture coordinate array cache.
-                if ( d.maskRecords.size() == 0 )
+                if ( d.maskLayers.size() == 0 && locator->isLinear() )
                 {
                     const GeoExtent& locex = locator->getDataExtent();
                     const GeoExtent& keyex = d.model->_tileKey.getExtent();
@@ -365,6 +407,7 @@ namespace
                     //OE_DEBUG << "key=" << d.model->_tileKey.str() << ": off=[" <<mat[0]<< ", " <<mat[1] << "] scale=["
                     //    << mat[2]<< ", " << mat[3] << "]" << std::endl;
 
+#ifdef USE_TEXCOORD_CACHE
                     osg::ref_ptr<osg::Vec2Array>& surfaceTexCoords = cache._surfaceTexCoordArrays.get( mat, d.numCols, d.numRows );
                     if ( !surfaceTexCoords.valid() )
                     {
@@ -376,6 +419,13 @@ namespace
                     }
                     r._texCoords = surfaceTexCoords.get();
 
+#else // not USE_TEXCOORD_CACHE
+                    r._texCoords = new osg::Vec2Array();
+                    r._texCoords->reserve( d.numVerticesInSurface );
+                    r._ownsTexCoords = true;
+#endif
+
+#if 0
                     osg::ref_ptr<osg::Vec2Array>& skirtTexCoords = cache._skirtTexCoordArrays.get( mat, d.numCols, d.numRows );
                     if ( !skirtTexCoords.valid() )
                     {
@@ -386,29 +436,39 @@ namespace
                         r._ownsSkirtTexCoords = true;
                     }
                     r._skirtTexCoords = skirtTexCoords.get();
+#endif
                 }
 
-                else // if ( d.maskRecords.size() > 0 )
+                else
                 {
                     // cannot use the tex coord array cache if there are masking records.
                     r._texCoords = new osg::Vec2Array();
                     r._texCoords->reserve( d.numVerticesInSurface );
                     r._ownsTexCoords = true;
 
-                    r._skirtTexCoords = new osg::Vec2Array();
-                    r._skirtTexCoords->reserve( d.numVerticesInSkirt );
-                    r._ownsSkirtTexCoords = true;
-
-                    r._stitchTexCoords = new osg::Vec2Array();
-                    r._stitchSkirtTexCoords = new osg::Vec2Array();
+                    if ( d.maskRecords.size() > 0 )
+                    {
+                        r._stitchTexCoords = new osg::Vec2Array();
+                        if ( !d.stitchTileCoords.valid() )
+                            d.stitchTileCoords = new osg::Vec2Array();
+                    }
                 }
 
+                // install the locator:
                 r._locator = locator;
                 if ( locator->getCoordinateSystemType() == osgTerrain::Locator::GEOCENTRIC )
                 {
-                    const GeoLocator* geo = dynamic_cast<const GeoLocator*>(locator);
-                    if ( geo )
-                        r._locator = geo->getGeographicFromGeocentric();
+                    r._locator = locator->getGeographicFromGeocentric();
+                }
+
+                // install the parent color data layer if necessary.
+                if ( d.parentModel.valid() )
+                {
+                    d.parentModel->getColorData( r._layer.getUID(), r._layerParent );
+                }
+                else
+                {
+                    r._layerParent = r._layer;
                 }
 
                 d.renderLayers.push_back( r );
@@ -433,7 +493,10 @@ namespace
     {
         d.surfaceBound.init();
 
-        osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer();
+        //osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer();
+
+        osg::HeightField* hf            = d.model->_elevationData.getHeightField();
+        GeoLocator*       hfLocator     = d.model->_elevationData.getLocator();
 
         // populate vertex and tex coord arrays    
         for(unsigned j=0; j < d.numRows; ++j)
@@ -443,19 +506,20 @@ namespace
                 unsigned int iv = j*d.numCols + i;
                 osg::Vec3d ndc( ((double)i)/(double)(d.numCols-1), ((double)j)/(double)(d.numRows-1), 0.0);
 
-                bool validValue = true;
-
-                // use the sampling factor to determine the lookup index:
-                unsigned i_equiv = d.i_sampleFactor==1.0 ? i : (unsigned) (double(i)*d.i_sampleFactor);
-                unsigned j_equiv = d.j_sampleFactor==1.0 ? j : (unsigned) (double(j)*d.j_sampleFactor);
-
                 // raw height:
                 float heightValue = 0.0f;
+                bool  validValue  = true;
 
-                if ( elevationLayer )
+                if ( hf )
                 {
-                    validValue = elevationLayer->getValidValue(i_equiv,j_equiv, heightValue);
-                    ndc.z() = heightValue; //*scaleHeight; // scaling will be done in the shader
+                    validValue = d.model->_elevationData.getHeight( ndc, d.model->_tileLocator, heightValue, INTERP_TRIANGULATE );
+                }
+
+                ndc.z() = heightValue * d.scaleHeight;
+
+                if ( !validValue )
+                {
+                    d.indices[iv] = -1;
                 }
 
                 // First check whether the sampling point falls within a mask's bounding box.
@@ -507,23 +571,61 @@ namespace
                         }
                     }
 
+                    if ( d.ownsTileCoords )
+                    {
+                        d.renderTileCoords->push_back( osg::Vec2(ndc.x(), ndc.y()) );
+                    }
+
                     // record the raw elevation value in our float array for later
                     (*d.elevations).push_back(ndc.z());
 
-                    // compute the local normal
-                    osg::Vec3d ndc_one = ndc; ndc_one.z() += 1.0;
-                    osg::Vec3d model_one;
-                    d.model->_tileLocator->unitToModel(ndc_one, model_one);
-                    model_one = model_one - model;
-                    model_one.normalize();    
+                    // compute the local normal (up vector)
+                    osg::Vec3d ndc_plus_one(ndc.x(), ndc.y(), ndc.z() + 1.0);
+                    osg::Vec3d model_up;
+                    d.model->_tileLocator->unitToModel(ndc_plus_one, model_up);
+                    model_up = model_up - model;
+                    model_up.normalize();
 
-                    (*d.normals).push_back(model_one);
+                    (*d.normals).push_back(model_up);
 
-                    // store the unit extrusion vector and the raw height value.
-                    (*d.surfaceElevData).push_back( osg::Vec4f(model_one.x(), model_one.y(), model_one.z(), heightValue) );
+                    // Calculate and store the "old height", i.e the height value from
+                    // the parent LOD.
+                    float     oldHeightValue = heightValue;
+                    osg::Vec3 oldNormal;
+
+                    // This only works if the tile size is an odd number in both directions.
+                    if (d.model->_tileKey.getLOD() > 0 && (d.numCols&1) && (d.numRows&1) && d.parentModel.valid())
+                    {
+                        d.parentModel->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), oldHeightValue, INTERP_TRIANGULATE );
+                        d.parentModel->_elevationData.getNormal( ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
+                    }
+                    else
+                    {
+                        d.model->_elevationData.getNormal(ndc, d.model->_tileLocator.get(), oldNormal, INTERP_TRIANGULATE );
+                    }
+
+                    // first attribute set has the unit extrusion vector and the
+                    // raw height value.
+                    (*d.surfaceAttribs).push_back( osg::Vec4f(
+                        model_up.x(),
+                        model_up.y(),
+                        model_up.z(),
+                        heightValue) );
+
+                    // second attribute set has the old height value in "w"
+                    (*d.surfaceAttribs2).push_back( osg::Vec4f(
+                        oldNormal.x(),
+                        oldNormal.y(),
+                        oldNormal.z(),
+                        oldHeightValue ) );
                 }
             }
         }
+
+        //if ( d.renderLayers[0]._texCoords->size() < d.surfaceVerts->size() )
+        //{
+        //    OE_WARN << LC << "not good. mask error." << std::endl;
+        //}
     }
 
 
@@ -534,7 +636,7 @@ namespace
      */
     void createMaskGeometry( Data& d )
     {
-        osgTerrain::HeightFieldLayer* elevationLayer = d.model->_elevationData.getHFLayer();
+        bool hasElev = d.model->hasElevation();
 
         for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
         {
@@ -568,14 +670,12 @@ namespace
                     {
                         osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)min_j)/(double)(d.numRows-1), 0.0);
 
-                        if (elevationLayer)
+                        //if (elevationLayer)
+                        if ( hasElev )
                         {
-                            unsigned i_equiv = d.i_sampleFactor==1.0 ? i + min_i : (unsigned) (double(i + min_i)*d.i_sampleFactor);
-                            unsigned j_equiv = d.j_sampleFactor==1.0 ? min_j : (unsigned) (double(min_j)*d.j_sampleFactor);
-
                             float value = 0.0f;
-                            if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
-                                ndc.z() = value*d.scaleHeight;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.scaleHeight;
                         }
 
                         (*maskSkirtPoly)[i] = ndc;
@@ -585,14 +685,11 @@ namespace
                     {
                         osg::Vec3d ndc( ((double)(i + min_i))/(double)(d.numCols-1), ((double)max_j)/(double)(d.numRows-1), 0.0);
 
-                        if (elevationLayer)
+                        if ( hasElev )
                         {
-                            unsigned i_equiv = d.i_sampleFactor==1.0 ? i + min_i : (unsigned) (double(i + min_i)*d.i_sampleFactor);
-                            unsigned j_equiv = d.j_sampleFactor==1.0 ? max_j : (unsigned) (double(max_j)*d.j_sampleFactor);
-
                             float value = 0.0f;
-                            if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
-                                ndc.z() = value*d.scaleHeight;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.scaleHeight;
                         }
 
                         (*maskSkirtPoly)[i + (2 * num_i + num_j - 3) - 2 * i] = ndc;
@@ -604,6 +701,14 @@ namespace
                     {
                         osg::Vec3d ndc( ((double)max_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
 
+                        if ( hasElev )
+                        {
+                            float value = 0.0f;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.scaleHeight;
+                        }
+
+#if 0
                         if (elevationLayer)
                         {
                             unsigned int i_equiv = d.i_sampleFactor==1.0 ? max_i : (unsigned int) (double(max_i)*d.i_sampleFactor);
@@ -613,6 +718,7 @@ namespace
                             if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
                                 ndc.z() = value*d.scaleHeight;
                         }
+#endif
 
                         (*maskSkirtPoly)[j + num_i] = ndc;
                     }
@@ -621,14 +727,11 @@ namespace
                     {
                         osg::Vec3d ndc( ((double)min_i)/(double)(d.numCols-1), ((double)(min_j + j + 1))/(double)(d.numRows-1), 0.0);
 
-                        if (elevationLayer)
+                        if ( hasElev )
                         {
-                            unsigned int i_equiv = d.i_sampleFactor==1.0 ? min_i : (unsigned int) (double(min_i)*d.i_sampleFactor);
-                            unsigned int j_equiv = d.j_sampleFactor==1.0 ? min_j + j + 1 : (unsigned int) (double(min_j + j + 1)*d.j_sampleFactor);
-
                             float value = 0.0f;
-                            if (elevationLayer->getValidValue(i_equiv,j_equiv, value))
-                                ndc.z() = value*d.scaleHeight;
+                            if ( d.model->_elevationData.getHeight( ndc, d.model->_tileLocator.get(), value, INTERP_BILINEAR ) )
+                                ndc.z() = value * d.scaleHeight;
                         }
 
                         (*maskSkirtPoly)[j + (2 * num_i + 2 * num_j - 5) - 2 * j] = ndc;
@@ -660,9 +763,6 @@ namespace
                 osg::Vec3Array* maskConstraint = new osg::Vec3Array();
                 dc->setVertexArray(maskConstraint);
 
-                if ( maskConstraint->getVertexBufferObject() )
-                    maskConstraint->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-
                 //Crop the mask to the stitching poly (for case where mask crosses tile edge)
                 osg::ref_ptr<Geometry> maskCrop;
                 maskPoly->crop(maskSkirtPoly.get(), maskCrop);
@@ -809,8 +909,6 @@ namespace
                 osg::Vec3Array* stitch_verts = new osg::Vec3Array();
                 stitch_verts->reserve(trig->getInputPointArray()->size());
                 stitch_geom->setVertexArray(stitch_verts);
-                if ( stitch_verts->getVertexBufferObject() )
-                    stitch_verts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
                 osg::Vec3Array* stitch_norms = new osg::Vec3Array(trig->getInputPointArray()->size());
                 stitch_geom->setNormalArray( stitch_norms );
                 stitch_geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
@@ -824,6 +922,7 @@ namespace
                         d.renderLayers[i]._stitchTexCoords->reserve(trig->getInputPointArray()->size());
                     }
                 }
+                d.stitchTileCoords->reserve(trig->getInputPointArray()->size());
 
                 // Iterate through point to convert to model coords, calculate normals, and set up tex coords
                 int norm_i = -1;
@@ -854,7 +953,6 @@ namespace
                             {
                                 osg::Vec3d color_ndc;
                                 osgTerrain::Locator::convertLocalCoordBetween(*d.geoLocator.get(), (*it), *d.renderLayers[i]._locator.get(), color_ndc);
-                                //osgTerrain::Locator::convertLocalCoordBetween(*masterTextureLocator.get(), (*it), *renderLayers[i]._locator.get(), color_ndc);
                                 d.renderLayers[i]._stitchTexCoords->push_back(osg::Vec2(color_ndc.x(), color_ndc.y()));
                             }
                             else
@@ -863,6 +961,7 @@ namespace
                             }
                         }
                     }
+                    d.stitchTileCoords->push_back(osg::Vec2((*it).x(), (*it).y()));
                 }
 
 
@@ -887,17 +986,12 @@ namespace
         double skirtHeight = d.surfaceBound.radius() * skirtRatio;
 
         // build the verts first:
-        osg::Vec3Array* skirtVerts = new osg::Vec3Array();
-        osg::Vec3Array* skirtNormals = new osg::Vec3Array();
-        osg::Vec4Array* skirtElevData = new osg::Vec4Array();
+        osg::Vec3Array* skirtVerts = static_cast<osg::Vec3Array*>(d.surface->getVertexArray());
+        osg::Vec3Array* skirtNormals = static_cast<osg::Vec3Array*>(d.surface->getNormalArray());
+        osg::Vec4Array* skirtAttribs = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_6)); //new osg::Vec4Array();
+        osg::Vec4Array* skirtAttribs2 = static_cast<osg::Vec4Array*>(d.surface->getVertexAttribArray(osg::Drawable::ATTRIBUTE_7)); //new osg::Vec4Array();
 
-        skirtVerts->reserve( d.numVerticesInSkirt );
-        skirtNormals->reserve( d.numVerticesInSkirt );
-        skirtElevData->reserve( d.numVerticesInSkirt );
-
-        Indices skirtBreaks;
-        skirtBreaks.reserve( d.numVerticesInSkirt );
-        skirtBreaks.push_back(0);
+        osg::ref_ptr<osg::DrawElementsUShort> elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
 
         // bottom:
         for( unsigned int c=0; c<d.numCols-1; ++c )
@@ -906,35 +1000,38 @@ namespace
 
             if (orig_i < 0)
             {
-                if (skirtBreaks.back() != skirtVerts->size())
-                    skirtBreaks.push_back(skirtVerts->size());
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
             }
             else
             {
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert );
                 skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
-                skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
-                skirtElevData->push_back( elevData );
-                skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
                 if ( d.renderLayers.size() > 0 )
                 {
                     for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                     {
-                        if ( d.renderLayers[i]._ownsSkirtTexCoords )
+                        if ( d.renderLayers[i]._ownsTexCoords )
                         {
                             const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
+                            d.renderLayers[i]._texCoords->push_back( tc );
                         }
                     }
                 }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
             }
         }
 
@@ -944,35 +1041,38 @@ namespace
             int orig_i = d.indices[r*d.numCols+(d.numCols-1)];
             if (orig_i < 0)
             {
-                if (skirtBreaks.back() != skirtVerts->size())
-                    skirtBreaks.push_back(skirtVerts->size());
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
             }
             else
             {
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert );
                 skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
-                skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
-                skirtElevData->push_back( elevData );
-                skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
                 if ( d.renderLayers.size() > 0 )
                 {
                     for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                     {
-                        if ( d.renderLayers[i]._ownsSkirtTexCoords )
+                        if ( d.renderLayers[i]._ownsTexCoords )
                         {
                             const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
+                            d.renderLayers[i]._texCoords->push_back( tc );
                         }
                     }
                 }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
             }
         }
 
@@ -982,35 +1082,38 @@ namespace
             int orig_i = d.indices[(d.numRows-1)*d.numCols+c];
             if (orig_i < 0)
             {
-                if (skirtBreaks.back() != skirtVerts->size())
-                    skirtBreaks.push_back(skirtVerts->size());
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
             }
             else
             {
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert );
                 skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
-                skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
-                skirtElevData->push_back( elevData );
-                skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
                 if ( d.renderLayers.size() > 0 )
                 {
                     for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                     {
-                        if ( d.renderLayers[i]._ownsSkirtTexCoords )
+                        if ( d.renderLayers[i]._ownsTexCoords )
                         {
                             const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
+                            d.renderLayers[i]._texCoords->push_back( tc );
                         }
                     }
                 }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
             }
         }
 
@@ -1020,65 +1123,46 @@ namespace
             int orig_i = d.indices[r*d.numCols];
             if (orig_i < 0)
             {
-                if (skirtBreaks.back() != skirtVerts->size())
-                    skirtBreaks.push_back(skirtVerts->size());
+                if ( elements->size() > 0 )
+                    d.surface->addPrimitiveSet( elements.get() );
+                elements = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
             }
             else
             {
                 const osg::Vec3f& surfaceVert = (*d.surfaceVerts)[orig_i];
-                skirtVerts->push_back( surfaceVert );
                 skirtVerts->push_back( surfaceVert - ((*skirtVectors)[orig_i])*skirtHeight );
 
                 const osg::Vec3f& surfaceNormal = (*d.normals)[orig_i];
                 skirtNormals->push_back( surfaceNormal );
-                skirtNormals->push_back( surfaceNormal );
 
-                const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
-                skirtElevData->push_back( elevData );
-                skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const osg::Vec4f& surfaceAttribs = (*d.surfaceAttribs)[orig_i];
+                skirtAttribs->push_back( surfaceAttribs - osg::Vec4f(0,0,0,skirtHeight) );
+
+                const osg::Vec4f& surfaceAttribs2 = (*d.surfaceAttribs2)[orig_i];
+                skirtAttribs2->push_back( surfaceAttribs2 - osg::Vec4f(0,0,0,skirtHeight) );
 
                 if ( d.renderLayers.size() > 0 )
                 {
                     for (unsigned int i = 0; i < d.renderLayers.size(); ++i)
                     {
-                        if ( d.renderLayers[i]._ownsSkirtTexCoords )
+                        if ( d.renderLayers[i]._ownsTexCoords )
                         {
                             const osg::Vec2& tc = (*d.renderLayers[i]._texCoords.get())[orig_i];
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
-                            d.renderLayers[i]._skirtTexCoords->push_back( tc );
+                            d.renderLayers[i]._texCoords->push_back( tc );
                         }
                     }
                 }
+
+                elements->addElement(orig_i);
+                elements->addElement(skirtVerts->size()-1);
             }
         }
 
-        d.skirt->setVertexArray( skirtVerts );
-        if ( skirtVerts->getVertexBufferObject() )
-            skirtVerts->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
-
-        d.skirt->setNormalArray( skirtNormals );
-        d.skirt->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
-
-        if ( d.surface->getColorArray() )
+        // add the final prim set.
+        if ( elements->size() > 0 )
         {
-            d.skirt->setColorArray( d.surface->getColorArray() );
-            d.skirt->setColorBinding( osg::Geometry::BIND_OVERALL );
+            d.surface->addPrimitiveSet( elements.get() );
         }
-
-        d.skirt->setVertexAttribArray    (osg::Drawable::ATTRIBUTE_6, skirtElevData );
-        d.skirt->setVertexAttribBinding  (osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX);
-        d.skirt->setVertexAttribNormalize(osg::Drawable::ATTRIBUTE_6, false);
-
-
-        // GW: not sure why this break stuff is here...?
-#if 0
-        //Add a primative set for each continuous skirt strip
-        skirtBreaks.push_back(skirtVerts->size());
-        for (int p=1; p < (int)skirtBreaks.size(); p++)
-            d.skirt->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLE_STRIP, skirtBreaks[p-1], skirtBreaks[p] - skirtBreaks[p-1] ) );
-#else
-        d.skirt->addPrimitiveSet( new osg::DrawArrays(GL_TRIANGLE_STRIP, 0, skirtVerts->size()) );
-#endif
     }
 
 
@@ -1090,26 +1174,22 @@ namespace
     void tessellateSurfaceGeometry( Data& d, bool optimizeTriangleOrientation, bool normalizeEdges )
     {    
         bool swapOrientation = !(d.model->_tileLocator->orientationOpenGL());
-        bool recalcNormals   = d.model->_elevationData.getHFLayer() != 0L;
 
-        osg::DrawElements* elements;
-
-        if ( d.surfaceVerts->size() < 0xFF )
-            elements = new osg::DrawElementsUByte(GL_TRIANGLES);
-        else if ( d.surfaceVerts->size() < 0xFFFF )
-            elements = new osg::DrawElementsUShort(GL_TRIANGLES);
-        else
-            elements = new osg::DrawElementsUShort(GL_TRIANGLES);
+        bool recalcNormals   = d.model->hasElevation();
+        unsigned numSurfaceNormals = d.numRows * d.numCols;
 
+        osg::DrawElements* elements = new osg::DrawElementsUShort(GL_TRIANGLES);
         elements->reserveElements((d.numRows-1) * (d.numCols-1) * 6);
-        d.surface->addPrimitiveSet( elements );
+        d.surface->insertPrimitiveSet(0, elements); // because we always want this first.
 
         if ( recalcNormals )
         {
-            // first clear out all the normals:
-            for( osg::Vec3Array::iterator nitr = d.normals->begin(); nitr != d.normals->end(); ++nitr )
+            // first clear out all the normals on the surface (but not the skirts)
+            // TODO: someday go back and re-apply the skirt normals to match the
+            // corresponding recalculated surface normals.
+            for(unsigned n=0; n<numSurfaceNormals && n<d.normals->size(); ++n)
             {
-                nitr->set( 0.0f, 0.0f, 0.0f );
+                (*d.normals)[n].set( 0.0f, 0.0f, 0.0f );
             }
         }
 
@@ -1174,7 +1254,7 @@ namespace
                         osg::Vec3f& v01 = (*d.surfaceVerts)[i01];
                         osg::Vec3f& v11 = (*d.surfaceVerts)[i11];
 
-                        if (!optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
                         {
                             elements->addElement(i01);
                             elements->addElement(i00);
@@ -1227,22 +1307,27 @@ namespace
         
         if (recalcNormals && normalizeEdges)
         {            
-            OE_DEBUG << "Normalizing edges" << std::endl;
+            //OE_DEBUG << LC << "Normalizing edges" << std::endl;
+
             //Compute the edge normals if we have neighbor data
             //Get all the neighbors
-            osg::ref_ptr< osg::HeightField > w_neighbor  = d.model->_elevationData.getNeighbor( -1, 0 );
-            osg::ref_ptr< osg::HeightField > e_neighbor  = d.model->_elevationData.getNeighbor( 1, 0 );            
-            osg::ref_ptr< osg::HeightField > s_neighbor  = d.model->_elevationData.getNeighbor( 0, 1 );
-            osg::ref_ptr< osg::HeightField > n_neighbor  = d.model->_elevationData.getNeighbor( 0, -1 );
-            
-            //Recalculate the west side
-            if (w_neighbor.valid() && w_neighbor->getNumColumns() == d.originalNumCols && w_neighbor->getNumRows() == d.originalNumRows)            
-            {                                     
-                osg::ref_ptr< osg::Vec3Array > boundaryVerts = new osg::Vec3Array();
-                boundaryVerts->reserve( 2 * d.numRows );
+            osg::HeightField* w_neighbor  = d.model->_elevationData.getNeighbor( -1, 0 );
+            osg::HeightField* e_neighbor  = d.model->_elevationData.getNeighbor( 1, 0 );
+            osg::HeightField* s_neighbor  = d.model->_elevationData.getNeighbor( 0, 1 );
+            osg::HeightField* n_neighbor  = d.model->_elevationData.getNeighbor( 0, -1 );
+
+            // Utility arrays:
+            std::vector<osg::Vec3> boundaryVerts;
+            boundaryVerts.reserve( 2 * std::max(d.numRows, d.numCols) );
 
-                std::vector< float > boundaryElevations;
-                boundaryElevations.reserve( 2 * d.numRows );
+            std::vector< float > boundaryElevations;
+            boundaryElevations.reserve( 2 * std::max(d.numRows, d.numCols) );
+
+            //Recalculate the west side
+            if (w_neighbor && w_neighbor->getNumColumns() == d.originalNumCols && w_neighbor->getNumRows() == d.originalNumRows)
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
                 
                 //Compute the verts for the west side
                 for (int j = 0; j < (int)d.numRows; j++)
@@ -1257,13 +1342,13 @@ namespace
 
                         //TODO:  Should probably use an interpolated method here
                         float heightValue = w_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;                        
+                        ndc.z() = heightValue;
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
                         osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts->push_back( v );
-                        boundaryElevations.push_back( heightValue );                        
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
                     }
                 }   
 
@@ -1290,48 +1375,48 @@ namespace
                     i00 = d.indices[i00];
                     i01 = d.indices[i01];
 
-                    int baseIndex = 2 * j;
-                    osg::Vec3f& v00 = (*boundaryVerts)[baseIndex    ];
-                    osg::Vec3f& v10 = (*boundaryVerts)[baseIndex + 1];
-                    osg::Vec3f& v01 = (*boundaryVerts)[baseIndex + 2];
-                    osg::Vec3f& v11 = (*boundaryVerts)[baseIndex + 3];
-
-                    float e00 = boundaryElevations[baseIndex];
-                    float e10 = boundaryElevations[baseIndex + 1];
-                    float e01 = boundaryElevations[baseIndex + 2];
-                    float e11 = boundaryElevations[baseIndex + 3];
-
-                   
-                    if (!optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
-                        (*d.normals)[i01] += normal1;                        
-
-                        osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
-                        (*d.normals)[i00] += normal2;                        
-                        (*d.normals)[i01] += normal2;                                                
-                    }
-                    else
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                        (*d.normals)[i00] += normal1;                                               
+                    if ( i00 >= 0 && i01 >= 0 )
+                    {
+                        int baseIndex = 2 * j;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + 2];
+                        float e11 = boundaryElevations[baseIndex + 3];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);
+                            (*d.normals)[i01] += normal1;                        
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);
+                            (*d.normals)[i00] += normal2;                        
+                            (*d.normals)[i01] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                               
 
-                        osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                        (*d.normals)[i00] += normal2;                                               
-                        (*d.normals)[i01] += normal2;                        
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i00] += normal2;                                               
+                            (*d.normals)[i01] += normal2;                        
+                        }
                     }
                 }
             }
 
                         
             //Recalculate the east side
-            if (e_neighbor.valid() && e_neighbor->getNumColumns() == d.originalNumCols && e_neighbor->getNumRows() == d.originalNumRows)            
-            {                           
-                osg::ref_ptr< osg::Vec3Array > boundaryVerts = new osg::Vec3Array();
-                boundaryVerts->reserve( 2 * d.numRows );
-
-                std::vector< float > boundaryElevations;
-                boundaryElevations.reserve( 2 * d.numRows );
+            if (e_neighbor && e_neighbor->getNumColumns() == d.originalNumCols && e_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
 
                 //Compute the verts for the east side
                 for (int j = 0; j < (int)d.numRows; j++)
@@ -1345,19 +1430,19 @@ namespace
                         
                         //TODO:  Should probably use an interpolated method here
                         float heightValue = e_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;                        
+                        ndc.z() = heightValue;
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
                         osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts->push_back( v );
-                        boundaryElevations.push_back( heightValue );                        
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
                     }
                 }   
 
                 //The boundary verts are now populated, so go through and triangulate them add add the normals to the existing normal array
                 for (int j = 0; j < (int)d.numRows-1; j++)
-                {                    
+                {
                     int i00;
                     int i01;
                     int i = d.numCols-1;
@@ -1376,47 +1461,47 @@ namespace
                     i00 = d.indices[i00];
                     i01 = d.indices[i01];
 
-                    int baseIndex = 2 * j;
-                    osg::Vec3f& v00 = (*boundaryVerts)[baseIndex    ];
-                    osg::Vec3f& v10 = (*boundaryVerts)[baseIndex + 1];
-                    osg::Vec3f& v01 = (*boundaryVerts)[baseIndex + 2];
-                    osg::Vec3f& v11 = (*boundaryVerts)[baseIndex + 3];
-
-                    float e00 = boundaryElevations[baseIndex];
-                    float e10 = boundaryElevations[baseIndex + 1];
-                    float e01 = boundaryElevations[baseIndex + 2];
-                    float e11 = boundaryElevations[baseIndex + 3];
-
-                   
-                    if (!optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                        (*d.normals)[i00] += normal1;                        
-                        (*d.normals)[i01] += normal1;
-
-                        osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                        (*d.normals)[i00] += normal2;                                                
-                    }
-                    else
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                        (*d.normals)[i00] += normal1;                        
-                        (*d.normals)[i01] += normal1;                                                                        
+                    if ( i00 >= 0 && i01 >= 0 )
+                    {
+                        int baseIndex = 2 * j;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + 2];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + 3];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + 2];
+                        float e11 = boundaryElevations[baseIndex + 3];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i01] += normal1;
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i00] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i01] += normal1;                                                                        
 
-                        osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                        (*d.normals)[i01] += normal2;                        
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i01] += normal2;                        
+                        }
                     }
                 }
             }
 
             //Recalculate the north side
-            if (n_neighbor.valid() && n_neighbor->getNumColumns() == d.originalNumCols && n_neighbor->getNumRows() == d.originalNumRows)            
-            {                 
-                osg::ref_ptr< osg::Vec3Array > boundaryVerts = new osg::Vec3Array();
-                boundaryVerts->reserve( 2 * d.numCols );
-
-                std::vector< float > boundaryElevations;
-                boundaryElevations.reserve( 2 * d.numCols );
+            if (n_neighbor && n_neighbor->getNumColumns() == d.originalNumCols && n_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
 
                 //Compute the verts for the north side               
                 for (int j = 0; j <= 1; j++)
@@ -1430,13 +1515,13 @@ namespace
                         
                         //TODO:  Should probably use an interpolated method here
                         float heightValue = n_neighbor->getHeight( i_equiv, j_equiv );
-                        ndc.z() = heightValue;                        
+                        ndc.z() = heightValue;
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
                         osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts->push_back( v );
-                        boundaryElevations.push_back( heightValue );                        
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue );
                     }
                 }   
 
@@ -1461,47 +1546,48 @@ namespace
                     i00 = d.indices[i00];
                     i10 = d.indices[i10];
 
-                    int baseIndex = i;
-                    osg::Vec3f& v00 = (*boundaryVerts)[baseIndex    ];
-                    osg::Vec3f& v10 = (*boundaryVerts)[baseIndex + 1];
-                    osg::Vec3f& v01 = (*boundaryVerts)[baseIndex + d.numCols];
-                    osg::Vec3f& v11 = (*boundaryVerts)[baseIndex + d.numCols + 1];
-
-                    float e00 = boundaryElevations[baseIndex];
-                    float e10 = boundaryElevations[baseIndex + 1];
-                    float e01 = boundaryElevations[baseIndex + d.numCols];
-                    float e11 = boundaryElevations[baseIndex + d.numCols + 1];
-
-                   
-                    if (!optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                        (*d.normals)[i00] += normal1;                        
-                        (*d.normals)[i10] += normal1;
-
-                        osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                        (*d.normals)[i10] += normal2;                                                
-                    }
-                    else
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                        (*d.normals)[i00] += normal1;                                                
 
-                        osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
-                        (*d.normals)[i00] += normal2;                                                
-                        (*d.normals)[i10] += normal2;                        
+                    if ( i00 >= 0 && i10 >= 0 )
+                    {
+                        int baseIndex = i;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + d.numCols];
+                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                        
+                            (*d.normals)[i10] += normal1;
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i10] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);
+                            (*d.normals)[i00] += normal2;                                                
+                            (*d.normals)[i10] += normal2;                        
+                        }
                     }
                 }
             }
 
             //Recalculate the south side
-            if (s_neighbor.valid() && s_neighbor->getNumColumns() == d.originalNumCols && s_neighbor->getNumRows() == d.originalNumRows)            
-            {                
-                osg::ref_ptr< osg::Vec3Array > boundaryVerts = new osg::Vec3Array();
-                boundaryVerts->reserve( 2 * d.numCols );
-
-                std::vector< float > boundaryElevations;
-                boundaryElevations.reserve( 2 * d.numCols );
+            if (s_neighbor && s_neighbor->getNumColumns() == d.originalNumCols && s_neighbor->getNumRows() == d.originalNumRows)            
+            {
+                boundaryVerts.clear();
+                boundaryElevations.clear();
 
                 //Compute the verts for the south side               
                 for (int j = (int)d.numRows-2; j <= (int)d.numRows-1; j++)
@@ -1515,13 +1601,13 @@ namespace
                         
                         //TODO:  Should probably use an interpolated method here
                         float heightValue = s_neighbor->getHeight( i_equiv, j_equiv );                        
-                        ndc.z() = heightValue;                        
+                        ndc.z() = heightValue;
 
                         osg::Vec3d model;
                         d.model->_tileLocator->unitToModel( ndc, model );
                         osg::Vec3d v = model - d.centerModel;
-                        boundaryVerts->push_back( v );
-                        boundaryElevations.push_back( heightValue );                        
+                        boundaryVerts.push_back( v );
+                        boundaryElevations.push_back( heightValue ); 
                     }
                 }   
 
@@ -1548,35 +1634,39 @@ namespace
                     i00 = d.indices[i00];
                     i10 = d.indices[i10];
 
-                    int baseIndex = i;
-                    osg::Vec3f& v00 = (*boundaryVerts)[baseIndex    ];
-                    osg::Vec3f& v10 = (*boundaryVerts)[baseIndex + 1];
-                    osg::Vec3f& v01 = (*boundaryVerts)[baseIndex + d.numCols];
-                    osg::Vec3f& v11 = (*boundaryVerts)[baseIndex + d.numCols + 1];
-
-                    float e00 = boundaryElevations[baseIndex];
-                    float e10 = boundaryElevations[baseIndex + 1];
-                    float e01 = boundaryElevations[baseIndex + d.numCols];
-                    float e11 = boundaryElevations[baseIndex + d.numCols + 1];
-
-                   
-                    if (!optimizeTriangleOrientation || (e00-e11)<fabsf(e01-e10))
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
-                        (*d.normals)[i00] += normal1;                                                
-
-                        osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
-                        (*d.normals)[i00] += normal2;                                                
-                        (*d.normals)[i10] += normal2;                                                
-                    }
-                    else
-                    {                            
-                        osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
-                        (*d.normals)[i00] += normal1;                                                
-                        (*d.normals)[i10] += normal1;                                                
 
-                        osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);                        
-                        (*d.normals)[i10] += normal2;                        
+                    if ( i00 >= 0 && i10 >= 0 )
+                    {
+                        int baseIndex = i;
+                        osg::Vec3f& v00 = boundaryVerts[baseIndex    ];
+                        osg::Vec3f& v10 = boundaryVerts[baseIndex + 1];
+                        osg::Vec3f& v01 = boundaryVerts[baseIndex + d.numCols];
+                        osg::Vec3f& v11 = boundaryVerts[baseIndex + d.numCols + 1];
+
+                        float e00 = boundaryElevations[baseIndex];
+                        float e10 = boundaryElevations[baseIndex + 1];
+                        float e01 = boundaryElevations[baseIndex + d.numCols];
+                        float e11 = boundaryElevations[baseIndex + d.numCols + 1];
+
+                       
+                        if (!optimizeTriangleOrientation || fabsf(e00-e11)<fabsf(e01-e10))
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v11-v01);                       
+                            (*d.normals)[i00] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v00) ^ (v11-v00);                        
+                            (*d.normals)[i00] += normal2;                                                
+                            (*d.normals)[i10] += normal2;                                                
+                        }
+                        else
+                        {                            
+                            osg::Vec3 normal1 = (v00-v01) ^ (v10-v01);
+                            (*d.normals)[i00] += normal1;                                                
+                            (*d.normals)[i10] += normal1;                                                
+
+                            osg::Vec3 normal2 = (v10-v01) ^ (v11-v01);                        
+                            (*d.normals)[i10] += normal2;                        
+                        }
                     }
                 }
             }            
@@ -1598,61 +1688,123 @@ namespace
         unsigned size = d.renderLayers.size();
 
         d.surface->_layers.resize( size );
-        if ( d.skirt )
-            d.skirt->_layers.resize( size );
+
         for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
             mr->_geom->_layers.resize( size );
-        if ( d.stitching_skirts )
-            d.stitching_skirts->_layers.resize( size );
+        
+        if ( d.renderTileCoords.valid() )
+            d.surface->_tileCoords = d.renderTileCoords;
+            
+        // TODO: evaluate this suspicious code. -gw
+        if ( d.stitchTileCoords.valid() )
+            d.surface->_tileCoords = d.stitchTileCoords.get();
 
         // install the render data for each layer:
         for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
         {
-            osg::Image* image = r->_layer.getImage();
-
-            osg::Texture2D* tex = new osg::Texture2D( image );
-            tex->setUnRefImageDataAfterApply( true );
-            tex->setMaxAnisotropy( 16.0f );
-            tex->setResizeNonPowerOfTwoHint(false);
-            tex->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
-            tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR );
-            //tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
-            tex->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
-            tex->setWrap( osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE );
-            tex->setWrap( osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE );
-
             unsigned order = r->_layer.getOrder();
 
             MPGeometry::Layer layer;
             layer._layerID        = r->_layer.getUID();
             layer._imageLayer     = r->_layer.getMapLayer();
-            layer._tex            = tex;
+            layer._tex            = r->_layer.getTexture();
+            layer._texParent      = r->_layerParent.getTexture();
+
+            // cache stock opacity. Disable if a color filter is installed, since
+            // it can modify the alpha.
+            layer._opaque =
+                (r->_layer.getMapLayer()->getColorFilters().size() == 0 ) &&
+                (layer._tex.valid() && !r->_layer.hasAlpha()) &&
+                (!layer._texParent.valid() || !r->_layerParent.hasAlpha());
+
+            // parent texture matrix: it's a scale/bias matrix encoding the difference
+            // between the two locators.
+            if ( r->_layerParent.getLocator() )
+            {
+                osg::Matrixd sbmatrix;
+
+                r->_layerParent.getLocator()->createScaleBiasMatrix(
+                    r->_layer.getLocator()->getDataExtent(),
+                    sbmatrix );
+
+                layer._texMatParent = sbmatrix;
+            }
 
             // the surface:
             layer._texCoords  = r->_texCoords;
             d.surface->_layers[order] = layer;
 
-            // the skirt:
-            if ( d.skirt )
-            {
-                layer._texCoords  = r->_skirtTexCoords;
-                d.skirt->_layers[order] = layer;
-            }
-
             // the mask geometries:
             for ( MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr )
             {
                 layer._texCoords = r->_stitchTexCoords.get();
                 mr->_geom->_layers[order] = layer;
             }
+        }
+    }
+
+
+    // Optimize the data. Convert all modes to GL_TRIANGLES and run the
+    // critical vertex cache optimizations.
+    void optimize( Data& d )
+    {
+        // the optimization pass is incompatible with the shared arrays used
+        // during masking.
+        if ( d.maskRecords.size() > 0 )
+        {
+            OE_DEBUG
+                << LC << "Skipping optimization pass for tile " << d.model->_tileKey.str()
+                << " because it contains masking geometry" <<std::endl;
+            return;
+        }
+ 
+        // For vertex cache optimization to work, all the arrays must be in
+        // the geometry. MP doesn't store texture/tile coords in the geometry
+        // so we need to temporarily add them.
+
+#if OSG_MIN_VERSION_REQUIRED(3, 1, 8) // after osg::Geometry API changes
+
+        osg::Geometry::ArrayList& tdl = d.surface->getTexCoordArrayList();
+        int u=0;
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            if ( r->_texCoords.valid() && r->_ownsTexCoords )
+            {
+                r->_texCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+                tdl.push_back( r->_texCoords.get() );
+            }
+        }
+        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
+        {
+            d.renderTileCoords->setBinding( osg::Array::BIND_PER_VERTEX );
+            tdl.push_back( d.renderTileCoords.get() );
+        }
+
+#else // OSG version < 3.1.8 (before osg::Geometry API changes)
 
-            // the stitching skirts:
-            if ( d.stitching_skirts )
+        osg::Geometry::ArrayDataList& tdl = d.surface->getTexCoordArrayList();
+        int u=0;
+        for( RenderLayerVector::const_iterator r = d.renderLayers.begin(); r != d.renderLayers.end(); ++r )
+        {
+            if ( r->_ownsTexCoords && r->_texCoords.valid() )
             {
-                layer._texCoords = r->_stitchSkirtTexCoords.get();
-                d.stitching_skirts->_layers[order] = layer;
+                tdl.push_back( osg::Geometry::ArrayData(r->_texCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
             }
         }
+        if ( d.renderTileCoords.valid() && d.ownsTileCoords )
+        {
+            tdl.push_back( osg::Geometry::ArrayData(d.renderTileCoords.get(), osg::Geometry::BIND_PER_VERTEX) );
+        }
+
+#endif
+
+        osgUtil::Optimizer o;
+        o.optimize( d.surfaceGeode, 
+            osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+            osgUtil::Optimizer::INDEX_MESH |
+            osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+
+        tdl.clear();
     }
 
 
@@ -1683,52 +1835,42 @@ _textureImageUnit      ( texImageUnit )
 }
 
 
-bool
+TileNode*
 TileModelCompiler::compile(const TileModel* model,
-                           osg::Node*&      out_node,
-                           osg::StateSet*&  out_stateSet)
+                           const MapFrame&  frame)
 {
+    TileNode* tile = new TileNode( model->_tileKey, model );
+
     // Working data for the build.
-    Data d(model, _masks);
+    Data d(model, frame, _masks);
 
+    d.parentModel = model->getParentTileModel();
     d.scaleHeight = *_options.verticalScale();
 
-    // build the transform matrix for this tile:
+    // build the localization matrix for this tile:
     model->_tileLocator->unitToModel( osg::Vec3(0.5f, 0.5f, 0.0), d.centerModel );
-    osg::MatrixTransform* xform = new osg::MatrixTransform( osg::Matrix::translate(d.centerModel) );
+    tile->setMatrix( osg::Matrix::translate(d.centerModel) );
 
     // A Geode/Geometry for the surface:
-    d.surface = new MPGeometry( model->_map.get(), _textureImageUnit );
+    d.surface = new MPGeometry( d.model->_tileKey, d.frame, _textureImageUnit );
     d.surface->setUseVertexBufferObjects(d.useVBOs);
+    d.surface->setUseDisplayList(!d.useVBOs);
     d.surfaceGeode = new osg::Geode();
     d.surfaceGeode->addDrawable( d.surface );
     d.surfaceGeode->setNodeMask( *_options.primaryTraversalMask() );
 
-    xform->addChild( d.surfaceGeode );
+    tile->addChild( d.surfaceGeode );
 
     // A Geode/Geometry for the skirt. This is good for traversal masking (e.g. shadows)
     // but bad since we're not combining the entire tile into a single geometry.
     // TODO: make this optional?
     d.createSkirt = (_options.heightFieldSkirtRatio().value() > 0.0);
 
-    if ( d.createSkirt )
-    {
-        d.skirt = new MPGeometry( model->_map.get(), _textureImageUnit );
-        d.skirt->setUseVertexBufferObjects(d.useVBOs);
-
-        // slightly faster than a separate geode:
-        //d.skirt->setDataVariance( osg::Object::DYNAMIC ); // since we're using a custom cull callback
-        d.skirt->setCullCallback( _cullByTraversalMask.get() );
-        d.surfaceGeode->addDrawable( d.skirt );
-    }
-
-
     // adjust the tile locator for geocentric mode:
     d.geoLocator = model->_tileLocator->getCoordinateSystemType() == GeoLocator::GEOCENTRIC ? 
         model->_tileLocator->getGeographicFromGeocentric() :
         model->_tileLocator.get();
 
-
     // Set up any geometry-cutting masks:
     if ( d.maskLayers.size() > 0 )
         setupMaskRecords( d );
@@ -1736,9 +1878,9 @@ TileModelCompiler::compile(const TileModel* model,
     // allocate all the vertex, normal, and color arrays.
     double sampleRatio = *_options.heightFieldSampleRatio();
     if ( sampleRatio <= 0.0f )
-        sampleRatio = osg::clampBetween( model->_tileKey.getLevelOfDetail()/20.0, 0.0625, 1.0 );
+        sampleRatio = osg::clampBetween( model->_tileKey.getLOD()/20.0, 0.0625, 1.0 );
 
-    setupGeometryAttributes( d, sampleRatio, *_options.color() );
+    setupGeometryAttributes( d, sampleRatio ); //, *_options.color() );
 
     // set up the list of layers to render and their shared arrays.
     setupTextureAttributes( d, _cache );
@@ -1760,32 +1902,31 @@ TileModelCompiler::compile(const TileModel* model,
     // installs the per-layer rendering data into the Geometry objects.
     installRenderData( d );
 
-    // lastly, optimize the results.
-    // unnecessary (I think) since tessellateSurfaceGeometry already makes an optimal
-    // triangle set
-    //if ( d.maskRecords.size() > 0 )
-    //{
-    //    MeshConsolidator::convertToTriangles( *d.surface );
-    //}
-
-    if ( d.skirt )
-    {
-        MeshConsolidator::convertToTriangles( *d.skirt );
-    }
+    // performance optimizations.
+    optimize( d );
 
+#if 0 // this is covered by the opt above.
+    // convert mask geometry to tris.
     for (MaskRecordVector::iterator mr = d.maskRecords.begin(); mr != d.maskRecords.end(); ++mr)
     {
-        MeshConsolidator::convertToTriangles( *((*mr)._geom) );
+        MeshConsolidator::convertToTriangles( *((*mr)._geom), true );
     }
+#endif
     
     if (osgDB::Registry::instance()->getBuildKdTreesHint()==osgDB::ReaderWriter::Options::BUILD_KDTREES &&
         osgDB::Registry::instance()->getKdTreeBuilder())
     {            
         osg::ref_ptr<osg::KdTreeBuilder> builder = osgDB::Registry::instance()->getKdTreeBuilder()->clone();
-        xform->accept(*builder);
+        tile->accept(*builder);
     }
 
-    out_node     = xform;
-    out_stateSet = 0L;
-    return true;
+    // Temporary solution to the OverlayDecorator techniques' inappropriate setting of
+    // uniform values during the CULL traversal, which causes corruption of the RTT 
+    // camera matricies when DRAW overlaps the next frame's CULL. Please see my comments
+    // in DrapingTechnique.cpp for more information.
+    // NOTE: cannot set this until optimizations (above) are complete
+    SetDataVarianceVisitor sdv( osg::Object::DYNAMIC );
+    tile->accept( sdv );
+
+    return tile;
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory b/src/osgEarthDrivers/engine_mp/TileModelFactory
index 46f0498..3c19d11 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory
@@ -37,12 +37,15 @@ namespace osgEarth_engine_mp
 
     struct HFKey {
         TileKey _key;
+        Revision _revision;
         bool    _fallback;
         bool    _convertToHAE;
         ElevationSamplePolicy _samplePolicy;
         bool operator < (const HFKey& rhs) const {
             if ( _key < rhs._key ) return true;
             if ( rhs._key < _key ) return false;
+            if ( _revision < rhs._revision ) return true;
+            if ( _revision > rhs._revision ) return false;
             if ( _fallback != rhs._fallback ) return true;
             if ( _convertToHAE != rhs._convertToHAE ) return true;
             return _samplePolicy < rhs._samplePolicy;
@@ -76,6 +79,7 @@ namespace osgEarth_engine_mp
             // check the quick cache.
             HFKey cachekey;
             cachekey._key          = key;
+            cachekey._revision     = frame.getRevision();
             cachekey._fallback     = fallback;
             cachekey._convertToHAE = convertToHAE;
             cachekey._samplePolicy = samplePolicy;
@@ -87,6 +91,8 @@ namespace osgEarth_engine_mp
                 out_hf = rec.value()._hf.get();
                 if ( out_isFallback )
                     *out_isFallback = rec.value()._isFallback;
+
+                //OE_NOTICE << "Found HF " << key.str() << " in HF cache." << std::endl;
                 return true;
             }
 
@@ -138,8 +144,7 @@ namespace osgEarth_engine_mp
     {
     public:
         TileModelFactory(
-            const Map*                                   map,
-            TileNodeRegistry*                            liveTiles,
+            TileNodeRegistry*                      liveTiles,
             const Drivers::MPTerrainEngineOptions& terrainOptions );
 
         HeightFieldCache* getHeightFieldCache() const;
@@ -149,16 +154,16 @@ namespace osgEarth_engine_mp
 
         void createTileModel(
             const TileKey&           key,
-            osg::ref_ptr<TileModel>& out_model,
-            bool&                    out_hasRealData,
-            bool&                    out_hasLodBlendedLayers );
+            const MapFrame&          frame,
+            osg::ref_ptr<TileModel>& out_model);//,
+            //bool&                    out_hasRealData);
 
     private:        
 
-        const Map*                                   _map;
-        osg::ref_ptr<TileNodeRegistry>               _liveTiles;
+        //const Map*                             _map;
+        osg::ref_ptr<TileNodeRegistry>         _liveTiles;
         const Drivers::MPTerrainEngineOptions& _terrainOptions;
-        osg::ref_ptr< HeightFieldCache > _hfCache;
+        osg::ref_ptr< HeightFieldCache >       _hfCache;
     };
 
 } // namespace osgEarth_engine_mp
diff --git a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
index ac83bc9..ecb50ec 100644
--- a/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileModelFactory.cpp
@@ -39,7 +39,7 @@ namespace
                    ImageLayer*                         layer, 
                    unsigned                            order,
                    const MapInfo&                      mapInfo,
-                   const MPTerrainEngineOptions& opt, 
+                   const MPTerrainEngineOptions&       opt, 
                    TileModel*                          model )
         {
             _key      = key;
@@ -61,10 +61,10 @@ namespace
                 _layer->getProfile()                    &&
                 _layer->getProfile()->getSRS()->isSphericalMercator();
 
-            // fetch the image from the layer, falling back on parent keys utils we are 
-            // able to find one that works.
+            // fetch the image from the layer.
 
-            bool autoFallback = _key.getLevelOfDetail() <= 1;
+            //bool autoFallback = _key.getLevelOfDetail() <= 1;
+            bool autoFallback = false;
 
             TileKey imageKey( _key );
             TileSource*    tileSource   = _layer->getTileSource();
@@ -131,12 +131,12 @@ namespace
                     _order,
                     geoImage.getImage(),
                     locator,
-                    _key.getLevelOfDetail(),
                     _key,
                     isFallbackData );
 
                 return true;
             }
+
             else
             {
                 return false;
@@ -165,7 +165,6 @@ namespace
             _opt   = &opt;
             _model = model;
             _hfCache = hfCache;
-            //_repo = &repo;
         }
 
         void execute()
@@ -179,17 +178,15 @@ namespace
 
             //if ( _mapf->getHeightField( _key, true, hf, &isFallback ) )
             if (_hfCache->getOrCreateHeightField( *_mapf, _key, true, hf, &isFallback) )
-            {                
-
-                // Put it in the repo
-                osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf.get() );
-
-                // Generate a locator.
-                hfLayer->setLocator( GeoLocator::createForKey( _key, mapInfo ) );
+            {
+                _model->_elevationData = TileModel::ElevationData(
+                    hf,
+                    GeoLocator::createForKey( _key, mapInfo ),
+                    isFallback );
 
-                _model->_elevationData = TileModel::ElevationData(hfLayer, isFallback);
-                
+#if 1
                 if ( *_opt->normalizeEdges() )
+#endif
                 {
                     // next, query the neighboring tiles to get adjacency information.
                     for( int x=-1; x<=1; x++ )
@@ -202,8 +199,7 @@ namespace
                                 if ( nk.valid() )
                                 {
                                     if (_hfCache->getOrCreateHeightField( *_mapf, nk, true, hf, &isFallback) )
-                                    //if ( _mapf->getHeightField(nk, true, hf, &isFallback) )
-                                    {               
+                                    {
                                         if ( mapInfo.isPlateCarre() )
                                         {
                                             HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() );
@@ -215,6 +211,20 @@ namespace
                             }
                         }
                     }
+
+                    // parent too.
+                    if ( _key.getLOD() > 0 )
+                    {
+                        if ( _hfCache->getOrCreateHeightField( *_mapf, _key.createParentKey(), true, hf, &isFallback) )
+                        {
+                            if ( mapInfo.isPlateCarre() )
+                            {
+                                HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() );
+                            }
+
+                            _model->_elevationData.setParent( hf.get() );
+                        }
+                    }
                 }
             }
         }
@@ -229,10 +239,10 @@ namespace
 
 //------------------------------------------------------------------------
 
-TileModelFactory::TileModelFactory(const Map*                          map, 
+TileModelFactory::TileModelFactory(//const Map*                          map, 
                                    TileNodeRegistry*                   liveTiles,
                                    const MPTerrainEngineOptions& terrainOptions ) :
-_map           ( map ),
+//_map           ( map ),
 _liveTiles     ( liveTiles ),
 _terrainOptions( terrainOptions )
 {
@@ -248,44 +258,29 @@ TileModelFactory::getHeightFieldCache() const
 
 void
 TileModelFactory::createTileModel(const TileKey&           key, 
-                                  osg::ref_ptr<TileModel>& out_model,
-                                  bool&                    out_hasRealData,
-                                  bool&                    out_hasLodBlendedLayers )
+                                  const MapFrame&          frame,
+                                  osg::ref_ptr<TileModel>& out_model) //,
+                                  //bool&                    out_hasRealData)
 {
-    MapFrame mapf( _map, Map::MASKED_TERRAIN_LAYERS );
-    
-    const MapInfo& mapInfo = mapf.getMapInfo();
-
-    osg::ref_ptr<TileModel> model = new TileModel();
-    model->_map         = _map;
-    model->_tileKey     = key;
-    model->_tileLocator = GeoLocator::createForKey(key, mapInfo);
-
-    // init this to false, then search for real data. "Real data" is data corresponding
-    // directly to the key, as opposed to fallback data, which is derived from a lower
-    // LOD key.
-    out_hasRealData = false;
-    out_hasLodBlendedLayers = false;
+
+    osg::ref_ptr<TileModel> model = new TileModel( frame.getRevision(), frame.getMapInfo() );
+    model->_tileKey = key;
+    model->_tileLocator = GeoLocator::createForKey(key, frame.getMapInfo());
     
     // Fetch the image data and make color layers.
     unsigned order = 0;
-    for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i )
+    for( ImageLayerVector::const_iterator i = frame.imageLayers().begin(); i != frame.imageLayers().end(); ++i )
     {
         ImageLayer* layer = i->get();
 
         if ( layer->getEnabled() )
         {
             BuildColorData build;
-            build.init( key, layer, order, mapInfo, _terrainOptions, model.get() );
+            build.init( key, layer, order, frame.getMapInfo(), _terrainOptions, model.get() );
             
             bool addedToModel = build.execute();
             if ( addedToModel )
             {
-                if ( layer->getImageLayerOptions().lodBlending() == true )
-                {
-                    out_hasLodBlendedLayers = true;
-                }
-
                 // only bump the order if we added something to the data model.
                 order++;
             }
@@ -294,52 +289,31 @@ TileModelFactory::createTileModel(const TileKey&           key,
 
     // make an elevation layer.
     BuildElevationData build;
-    build.init( key, mapf, _terrainOptions, model.get(), _hfCache );
+    build.init( key, frame, _terrainOptions, model.get(), _hfCache );
     build.execute();
 
 
     // Bail out now if there's no data to be had.
-    if ( model->_colorData.size() == 0 && !model->_elevationData.getHFLayer() )
+    if ( model->_colorData.size() == 0 && !model->_elevationData.getHeightField() )
     {
         return;
     }
 
-    // OK we are making a tile, so if there's no heightfield yet, make an empty one.
-    if ( !model->_elevationData.getHFLayer() )
-    {
-        osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 8, 8 );
-        osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf );
-        hfLayer->setLocator( GeoLocator::createForKey(key, mapInfo) );
-        model->_elevationData = TileModel::ElevationData( hfLayer, true );
-    }
-
-    // if we're using LOD blending, find and add the parent's state set.
-    if ( out_hasLodBlendedLayers && key.getLevelOfDetail() > 0 && _liveTiles.valid() )
-    {
-        osg::ref_ptr<TileNode> parent;
-        if ( _liveTiles->get( key.createParentKey(), parent ) )
-        {
-            model->_parentStateSet = parent->getPublicStateSet();
-        }
-    }
-
-    if (!out_hasRealData)
+    // OK we are making a tile, so if there's no heightfield yet, make an empty one (and mark it
+    // as fallback data of course)
+    if ( !model->_elevationData.getHeightField() )
     {
-        // Check the results and see if we have any real data.
-        for( TileModel::ColorDataByUID::const_iterator i = model->_colorData.begin(); i != model->_colorData.end(); ++i )
-        {
-            if ( !i->second.isFallbackData() ) 
-            {
-                out_hasRealData = true;
-                break;
-            }
-        }
+        osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 15, 15 );
+        model->_elevationData = TileModel::ElevationData(
+            hf,
+            GeoLocator::createForKey(key, frame.getMapInfo()),
+            true );
     }
 
-    if ( !out_hasRealData && !model->_elevationData.isFallbackData() )
-    {
-        out_hasRealData = true;
-    }
+    // look up the parent model and cache it.
+    osg::ref_ptr<TileNode> parentTile;
+    if ( _liveTiles->get(key.createParentKey(), parentTile) )
+        model->_parentModel = parentTile->getTileModel();
 
     out_model = model.release();
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileNode b/src/osgEarthDrivers/engine_mp/TileNode
index 55517a3..c9a592c 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode
+++ b/src/osgEarthDrivers/engine_mp/TileNode
@@ -21,28 +21,34 @@
 
 #include "Common"
 #include "TileModel"
-#include "TileModelCompiler"
-#include <osgEarth/Locators>
-#include <osg/Group>
-#include <vector>
+#include <osg/MatrixTransform>
 
 namespace osgEarth_engine_mp
 {
     using namespace osgEarth;
+    class TileNode;
 
-    //------------------------------------------------------------------------
+    /**
+     * Marker that gives access to a TileNode.
+     */
+    class TileNodeContainer
+    {
+    public:
+        virtual TileNode* getTileNode() = 0;
+    };
 
     /**
-     * Node that represents a single mp terrain tile corresponding to 
-     * unique TileKey.
+     * Node that represents a single terrain tile that was compiled from
+     * a TileModel (and corresponds to one TileKey). The matrixtransform
+     * localizes the TileNode within the terrain.
      */
-    class TileNode : public osg::Group
+    class TileNode : public osg::MatrixTransform, public TileNodeContainer
     {
     public:
         /**
          * Constructs a new tile node
          */
-        TileNode( const TileKey& key, GeoLocator* keyLocator );
+        TileNode( const TileKey& key, const TileModel* model );
 
         /**
          * The tilekey associated with this tile
@@ -50,45 +56,64 @@ namespace osgEarth_engine_mp
         const TileKey& getKey() const { return _key; }
 
         /**
-         * The data model used to create this node's geometry.
+         * True if this is a valid tile node.
+         * Subclass may override (see InvalidTileNode)
+         */
+        virtual bool isValid() const { return true; }
+
+        /**
+         * Access the source data model that built this tile.
          */
-        void setTileModel( TileModel* model );
-        TileModel* getTileModel() { return _model.get(); }
+        const TileModel* getTileModel() { return _model.get(); }
 
         /**
-         * Compiles this node's tile model into geometry.
-         * @param[in ] compiler     Compiler that will compiler the model
-         * @param[in ] releaseModel Whether to deallocate the tile model after compilation
-         * @return True upon success
+         * Sets the last traversal frame manually. A parent TileGroup
+         * will call this to prevent the born-time from resetting 
+         * when traversing the tile's children.
          */
-        bool compile( TileModelCompiler* compiler, bool releaseModel =true );
+        void setLastTraversalFrame(unsigned frame);
 
         /**
-         * The locator for geometry in this tile
+         * Tells the tile node the current map revision, which is turn
+         * will determine whether the tile is dirty and needs updating.
          */
-        GeoLocator* getLocator() const { return _locator.get(); }
+        void setMapRevision( const Revision& value ) { _maprevision = value; }
 
         /**
-         * The public state set associated with the compiled tile model.
+         * Flags this Tile as dirty, regardless of whether the revisions are in sync.
          */
-        osg::StateSet* getPublicStateSet() const { return _publicStateSet; }
+        void setDirty() { _dirty = true; }
+
+        /**
+         * Whether the tile is dirty and was traversed (and if therefore ready for
+         * a dynamic update)
+         */
+        bool isOutOfDate() const { return _outOfDate; }
+
+
+    public: // TileNodeContainer
+
+        TileNode* getTileNode() { return this; }
 
 
     public: // OVERRIDES
 
         virtual void traverse( class osg::NodeVisitor& nv );
 
-        //virtual osg::BoundingSphere computeBound() const;
+        virtual void resizeGLObjectBuffers(unsigned maxSize);
+        virtual void releaseGLObjects(osg::State* state) const;
 
     protected:
 
-        virtual ~TileNode();
+        virtual ~TileNode() { }
 
-        bool                      _cullTraversed;
-        TileKey                   _key;
-        osg::ref_ptr<GeoLocator>  _locator;
-        osg::ref_ptr<TileModel>   _model;
-        osg::StateSet*            _publicStateSet;
+        TileKey                            _key;
+        osg::ref_ptr<const TileModel>      _model;
+        osg::ref_ptr<osg::Uniform>         _tileParentMatrixUniform;
+        unsigned                           _lastTraversalFrame;
+        Revision                           _maprevision;
+        bool                               _outOfDate;
+        bool                               _dirty;
     };
 
 
@@ -96,14 +121,19 @@ namespace osgEarth_engine_mp
 
 
     /**
-     * Marker class. 
+     * Marker class - the engine will return one of these when a TileNode
+     * load fails permanently. It will also blacklist the TileKey.
      */
-    class TileNodeGroup : public osg::Group
+    class InvalidTileNode : public TileNode
     {
     public:
-        TileNodeGroup() : osg::Group() { }
+        InvalidTileNode(const TileKey& key) : TileNode(key, 0L) { }
+        bool isValid() const { return false; }
+    protected:
+        virtual ~InvalidTileNode() { }
     };
 
+
 } // namespace osgEarth_engine_mp
 
 #endif // OSGEARTH_ENGINE_MP_TILE_NODE
diff --git a/src/osgEarthDrivers/engine_mp/TileNode.cpp b/src/osgEarthDrivers/engine_mp/TileNode.cpp
index 1beb15f..e221203 100644
--- a/src/osgEarthDrivers/engine_mp/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNode.cpp
@@ -17,11 +17,11 @@
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
 #include "TileNode"
-#include "TerrainNode"
 
 #include <osg/ClusterCullingCallback>
 #include <osg/NodeCallback>
 #include <osg/NodeVisitor>
+#include <osg/Uniform>
 
 using namespace osgEarth_engine_mp;
 using namespace osgEarth;
@@ -32,63 +32,64 @@ using namespace OpenThreads;
 
 //----------------------------------------------------------------------------
 
-TileNode::TileNode( const TileKey& key, GeoLocator* keyLocator ) :
-_key              ( key ),
-_locator          ( keyLocator ),
-_publicStateSet   ( 0L )
+TileNode::TileNode( const TileKey& key, const TileModel* model ) :
+_key               ( key ),
+_model             ( model ),
+_lastTraversalFrame( 0 ),
+_dirty             ( false ),
+_outOfDate         ( false )
 {
     this->setName( key.str() );
-}
-
 
-TileNode::~TileNode()
-{
-    //nop
+    // revisions are initially in sync:
+    if ( model )
+        _maprevision = model->_revision;
 }
 
 
 void
-TileNode::setTileModel( TileModel* model )
+TileNode::setLastTraversalFrame(unsigned frame)
 {
-    _model = model;
-    _publicStateSet = 0L;
+    _lastTraversalFrame = frame;
 }
 
 
-bool
-TileNode::compile( TileModelCompiler* compiler, bool releaseModel )
-{
-    if ( !_model.valid() )
-        return false;
-
-    osg::Node* node = 0L;
-    _publicStateSet = 0L;
-
-    if ( !compiler->compile( _model.get(), node, _publicStateSet ) )
-        return false;
-
-    this->removeChildren( 0, this->getNumChildren() );
-    this->addChild( node );
-
-    // release the memory associated with the tile model.
-    if ( releaseModel )
-        _model = 0L;
-
-    return true;
-}
-
 void
 TileNode::traverse( osg::NodeVisitor& nv )
 {
-    // TODO: not sure we need this.
-    if ( nv.getVisitorType()==osg::NodeVisitor::CULL_VISITOR )
+    if ( _model.valid() && nv.getVisitorType() == nv.CULL_VISITOR )
     {
         osg::ClusterCullingCallback* ccc = dynamic_cast<osg::ClusterCullingCallback*>(getCullCallback());
         if (ccc)
         {
             if (ccc->cull(&nv,0,static_cast<osg::State *>(0))) return;
         }
+
+        // if this tile is marked dirty, bump the marker so the engine knows it
+        // needs replacing.
+        if ( _dirty || _model->_revision != _maprevision )
+        {
+            _outOfDate = true;
+        }
     }
 
-    osg::Group::traverse( nv );
+    osg::MatrixTransform::traverse( nv );
+}
+
+void
+TileNode::releaseGLObjects(osg::State* state) const
+{
+    osg::MatrixTransform::releaseGLObjects( state );
+
+    if ( _model.valid() )
+        _model->releaseGLObjects( state );
+}
+
+void
+TileNode::resizeGLObjectBuffers(unsigned maxSize)
+{
+    osg::MatrixTransform::resizeGLObjectBuffers( maxSize );
+
+    if ( _model.valid() )
+        const_cast<TileModel*>(_model.get())->resizeGLObjectBuffers( maxSize );
 }
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
index 4353d26..3d31723 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry
@@ -21,6 +21,7 @@
 
 #include "Common"
 #include "TileNode"
+#include <osgEarth/Revisioning>
 #include <osgEarth/ThreadingUtils>
 #include <map>
 
@@ -48,6 +49,23 @@ namespace osgEarth_engine_mp
     public:
         TileNodeRegistry( const std::string& name );
 
+        /* Enabled revisioning on TileNodes, to support incremental update. */
+        void setRevisioningEnabled(bool value);
+
+        /**
+         * Sets the revision of the map model - the registry will assign this
+         * to TileNodes added with add().
+         *
+         * @param rev        Revision of map
+         * @param setToDirty In addition to update the revision, immediately set
+         *                   all tiles to dirty as well, effectively forcing an
+         *                   update.
+         */
+        void setMapRevision( const Revision& rev, bool setToDirty =false );
+
+        /** Map revision that the reg will assign to new tiles. */
+        const Revision& getMapRevision() const { return _maprev; }
+
         virtual ~TileNodeRegistry() { }
 
         /** Adds a tile to the registry */
@@ -56,9 +74,12 @@ namespace osgEarth_engine_mp
         /** Adds several tiles to the registry */
         void add( const TileNodeVector& tiles );
 
-        /** Moves a tile to the "removed" list */
+        /** Removes a tile */
         void remove( TileNode* tile );
 
+        /** Moves a tile from this registry to another registry */
+        void move( TileNode* tile, TileNodeRegistry* destination );
+
         /** Finds a tile in the registry */
         bool get( const TileKey& key, osg::ref_ptr<TileNode>& out_tile );
 
@@ -74,8 +95,13 @@ namespace osgEarth_engine_mp
         /** Runs an operation against the read-locked tile set. */
         void run( const ConstOperation& op ) const;
 
+        /** Number of tiles in the registry. */
+        unsigned size() const { return _tiles.size(); }
+
     protected:
 
+        bool                              _revisioningEnabled;
+        Revision                          _maprev;
         std::string                       _name;
         TileNodeMap                       _tiles;
         mutable Threading::ReadWriteMutex _tilesMutex;
diff --git a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
index 1759ecf..3f93d3c 100644
--- a/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
+++ b/src/osgEarthDrivers/engine_mp/TileNodeRegistry.cpp
@@ -30,19 +30,55 @@ using namespace osgEarth;
 //----------------------------------------------------------------------------
 
 TileNodeRegistry::TileNodeRegistry(const std::string& name) :
-_name( name )
+_name              ( name ),
+_revisioningEnabled( false )
 {
     //nop
 }
 
 
 void
+TileNodeRegistry::setRevisioningEnabled(bool value)
+{
+    _revisioningEnabled = value;
+}
+
+
+void
+TileNodeRegistry::setMapRevision(const Revision& rev,
+                                 bool            setToDirty)
+{
+    if ( _revisioningEnabled )
+    {
+        if ( _maprev != rev || setToDirty )
+        {
+            Threading::ScopedWriteLock exclusive( _tilesMutex );
+
+            if ( _maprev != rev || setToDirty )
+            {
+                _maprev = rev;
+
+                for( TileNodeMap::iterator i = _tiles.begin(); i != _tiles.end(); ++i )
+                {
+                    i->second->setMapRevision( _maprev );
+                    if ( setToDirty )
+                        i->second->setDirty();
+                }
+            }
+        }
+    }
+}
+
+
+void
 TileNodeRegistry::add( TileNode* tile )
 {
     if ( tile )
     {
         Threading::ScopedWriteLock exclusive( _tilesMutex );
         _tiles[ tile->getKey() ] = tile;
+        if ( _revisioningEnabled )
+            tile->setMapRevision( _maprev );
         OE_TEST << LC << _name << ": tiles=" << _tiles.size() << std::endl;
     }
 }
@@ -75,6 +111,20 @@ TileNodeRegistry::remove( TileNode* tile )
 }
 
 
+void
+TileNodeRegistry::move(TileNode* tile, TileNodeRegistry* destination)
+{
+    if ( tile )
+    {
+        // ref just in case remove() is the last reference
+        osg::ref_ptr<TileNode> tileSafe = tile;
+        remove( tile );
+        if ( destination )
+            destination->add( tileSafe.get() );
+    }
+}
+
+
 bool
 TileNodeRegistry::get( const TileKey& key, osg::ref_ptr<TileNode>& out_tile )
 {
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD b/src/osgEarthDrivers/engine_mp/TilePagedLOD
new file mode 100644
index 0000000..c0c7b3c
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD
@@ -0,0 +1,68 @@
+/* -*-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_TILE_PAGED_LOD
+#define OSGEARTH_ENGINE_MP_TILE_PAGED_LOD 1
+
+#include "Common"
+#include "TileNodeRegistry"
+#include <osg/PagedLOD>
+#include <osgEarth/ThreadingUtils>
+
+using namespace osgEarth;
+
+namespace osgEarth_engine_mp
+{
+    /**
+     * TilePagedLOD is an extension to osg::PagedLOD that supports the tile registry.
+     */
+    class TilePagedLOD : public osg::PagedLOD
+    {
+    public:
+        TilePagedLOD(
+            const UID&        engineUID,
+            TileNodeRegistry* liveTiles,
+            TileNodeRegistry* deadTiles);
+
+        /** The tilenode in this group */
+        TileNode* getTileNode();
+        void setTileNode(TileNode* tilenode);
+
+    public: // osg::Group
+
+        /** called by the OSG DatabasePager when a paging result is ready. */
+        bool addChild( osg::Node* node );
+
+    public: // osg::PagedLOD
+
+        /** override to manage the tile node registries. */
+        bool removeExpiredChildren(double expiryTime, unsigned expiryFrame, osg::NodeList& removedChildren);
+
+    protected:
+        virtual ~TilePagedLOD();
+
+    private:
+        osg::ref_ptr<TileNodeRegistry> _live;
+        osg::ref_ptr<TileNodeRegistry> _dead;
+        UID                            _engineUID;
+        Threading::Mutex               _updateMutex;
+    };
+
+} // namespace osgEarth_engine_mp
+
+#endif // OSGEARTH_ENGINE_MP_TILE_PAGED_LOD
diff --git a/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
new file mode 100644
index 0000000..cfd3f03
--- /dev/null
+++ b/src/osgEarthDrivers/engine_mp/TilePagedLOD.cpp
@@ -0,0 +1,171 @@
+/* -*-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 "TilePagedLOD"
+#include "TileNodeRegistry"
+#include <osg/Version>
+#include <cassert>
+
+using namespace osgEarth_engine_mp;
+using namespace osgEarth;
+
+#define LC "[TilePagedLOD] "
+
+//#define OE_TEST OE_INFO
+#define OE_TEST OE_NULL
+
+namespace
+{
+    // traverses a node graph and moves any TileNodes from the LIVE
+    // registry to the DEAD registry.
+    struct ExpirationCollector : public osg::NodeVisitor
+    {
+        TileNodeRegistry* _live;
+        TileNodeRegistry* _dead;
+        unsigned          _count;
+
+        ExpirationCollector(TileNodeRegistry* live, TileNodeRegistry* dead)
+            : _live(live), _dead(dead), _count(0)
+        {
+            // set up to traverse the entire subgraph, ignoring node masks.
+            setTraversalMode( TRAVERSE_ALL_CHILDREN );
+            setNodeMaskOverride( ~0 );
+        }
+
+        void apply(osg::Node& node)
+        {
+            TileNode* tn = dynamic_cast<TileNode*>( &node );
+            if ( tn && _live )
+            {
+                _live->move( tn, _dead );
+                _count++;
+                //OE_NOTICE << "Expired " << tn->getKey().str() << std::endl;
+            }
+            traverse(node);
+        }
+    };
+}
+
+
+TilePagedLOD::TilePagedLOD(const UID&        engineUID,
+                           TileNodeRegistry* live,
+                           TileNodeRegistry* dead) :
+osg::PagedLOD(),
+_engineUID( engineUID ),
+_live     ( live ),
+_dead     ( dead )
+{
+    //nop
+}
+
+TilePagedLOD::~TilePagedLOD()
+{
+    // need this here b/c it's possible for addChild() to get called from
+    // a pager dispatch even after the PLOD in question has been "expired"
+    // so we still need to process the live/dead list.
+    ExpirationCollector collector( _live.get(), _dead.get() );
+    this->accept( collector );
+}
+
+TileNode*
+TilePagedLOD::getTileNode()
+{
+    return _children.size() > 0 ? static_cast<TileNode*>(_children[0].get()) : 0L;
+}
+
+void
+TilePagedLOD::setTileNode(TileNode* tilenode)
+{
+    // if the new tile has a culling callback, remove it and put it on the
+    // PagedLOD itself as nature intended.
+    if ( tilenode->getCullCallback() )
+    {
+        this->setCullCallback( tilenode->getCullCallback() );
+        tilenode->setCullCallback( 0L );
+    }
+    setChild( 0, tilenode );
+}
+
+// The osgDB::DatabasePager will call this method when merging a new child
+// into the scene graph.
+bool
+TilePagedLOD::addChild(osg::Node* node)
+{
+    if ( node )
+    {
+        // if we see an invalid tile marker, disable the paged lod.
+        if ( dynamic_cast<InvalidTileNode*>(node) )
+        {
+            this->setFileName( 1, "" );
+            this->setRange( 1, 0, 0 );
+            this->setRange( 0, 0.0f, FLT_MAX );
+            return true;
+        }
+
+        // If it's a TileNode, this is the simple first addition of the 
+        // static TileNode child (not from the pager).
+        TileNode* tilenode = dynamic_cast<TileNode*>( node );
+        if ( tilenode && _live.get() )
+        {
+            _live->add( tilenode );
+        }
+
+        return osg::PagedLOD::addChild( node );
+    }
+
+    return false;
+}
+
+
+// The osgDB::DatabasePager will call this automatically to purge expired
+// tiles from the scene graph.
+bool
+TilePagedLOD::removeExpiredChildren(double         expiryTime, 
+                                    unsigned       expiryFrame, 
+                                    osg::NodeList& removedChildren)
+{
+    if (_children.size()>_numChildrenThatCannotBeExpired)
+    {
+        unsigned cindex = _children.size() - 1;
+
+        double   minExpiryTime   = 0.0;
+        unsigned minExpiryFrames = 0;
+
+        // these were added in osg 3.1.0+
+#if OSG_VERSION_GREATER_OR_EQUAL(3,1,0)
+        minExpiryTime   = _perRangeDataList[cindex]._minExpiryTime;
+        minExpiryFrames = _perRangeDataList[cindex]._minExpiryFrames;
+#endif
+
+        if (!_perRangeDataList[cindex]._filename.empty() &&
+            _perRangeDataList[cindex]._timeStamp   + minExpiryTime   < expiryTime &&
+            _perRangeDataList[cindex]._frameNumber + minExpiryFrames < expiryFrame)
+        {
+            osg::Node* nodeToRemove = _children[cindex].get();
+            removedChildren.push_back(nodeToRemove);
+
+            ExpirationCollector collector( _live.get(), _dead.get() );
+            nodeToRemove->accept( collector );
+
+            OE_DEBUG << LC << "Expired " << collector._count << std::endl;
+
+            return Group::removeChildren(cindex,1);
+        }
+    }
+    return false;
+}
diff --git a/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp
index 4f1a886..e7d511a 100644
--- a/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp
+++ b/src/osgEarthDrivers/engine_osgterrain/OSGTerrainEngineNode.cpp
@@ -833,7 +833,7 @@ OSGTerrainEngineNode::traverse( osg::NodeVisitor& nv )
         if ( nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR )
         {
             // update the cull-thread map frame if necessary. (We don't need to sync the
-            // update_mapf becuase that happens in response to a map callback.)
+            // update_mapf because that happens in response to a map callback.)
 
             // TODO: address the fact that this can happen from multiple threads.
             // Really we need a _cull_mapf PER view. -gw
diff --git a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions b/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions
index 1f99d79..c478491 100644
--- a/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions
+++ b/src/osgEarthDrivers/engine_quadtree/QuadTreeTerrainEngineOptions
@@ -34,6 +34,7 @@ namespace osgEarth { namespace Drivers
             _quickRelease( true ),
             _lodFallOff  ( 0.0 ),
             _normalizeEdges( false ),
+            _morphLODs     ( false ),
             _rangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT ),
             _tilePixelSize( 256 )
         {
@@ -57,6 +58,9 @@ namespace osgEarth { namespace Drivers
         optional<bool>& normalizeEdges() { return _normalizeEdges; }
         const optional<bool>& normalizeEdges() const { return _normalizeEdges; }
 
+        optional<bool>& morphLODs() { return _morphLODs; }
+        const optional<bool>& morphLODs() const { return _morphLODs; }
+
         optional<osg::LOD::RangeMode>& rangeMode() { return _rangeMode;}
         const optional<osg::LOD::RangeMode>& rangeMode() const { return _rangeMode;}
 
@@ -70,6 +74,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "quick_release_gl_objects", _quickRelease );
             conf.updateIfSet( "lod_fall_off", _lodFallOff );
             conf.updateIfSet( "normalize_edges", _normalizeEdges);
+            conf.updateIfSet( "morph_lods", _morphLODs );
             conf.updateIfSet( "tile_pixel_size", _tilePixelSize );
             conf.updateIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
             conf.updateIfSet( "range_mode", "DISTANCE_FROM_EYE_POINT", _rangeMode, osg::LOD::DISTANCE_FROM_EYE_POINT);
@@ -88,6 +93,7 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "quick_release_gl_objects", _quickRelease );
             conf.getIfSet( "lod_fall_off", _lodFallOff );
             conf.getIfSet( "normalize_edges", _normalizeEdges );
+            conf.getIfSet( "morph_lods", _morphLODs );
             conf.getIfSet( "tile_pixel_size", _tilePixelSize );
 
             conf.getIfSet( "range_mode", "PIXEL_SIZE_ON_SCREEN", _rangeMode, osg::LOD::PIXEL_SIZE_ON_SCREEN );
@@ -98,6 +104,7 @@ namespace osgEarth { namespace Drivers
         optional<bool>  _quickRelease;
         optional<float> _lodFallOff;
         optional<bool> _normalizeEdges;
+        optional<bool> _morphLODs;
         optional<osg::LOD::RangeMode> _rangeMode;
         optional<float> _tilePixelSize;
     };
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModel b/src/osgEarthDrivers/engine_quadtree/TileModel
index ec44306..3081f0f 100644
--- a/src/osgEarthDrivers/engine_quadtree/TileModel
+++ b/src/osgEarthDrivers/engine_quadtree/TileModel
@@ -73,6 +73,16 @@ namespace osgEarth_engine_quadtree
                 _neighbors[index] = hf;
             }
 
+            osg::HeightField* getParent() const
+            {
+                return _parent;
+            }
+
+            void setParent(osg::HeightField* hf)
+            {
+                _parent = hf;
+            }
+
         private:
             static int getNeighborIndex(int xoffset, int yoffset)
             {
@@ -83,6 +93,7 @@ namespace osgEarth_engine_quadtree
             osg::ref_ptr<osgTerrain::HeightFieldLayer> _hfLayer;
             bool _fallbackData;
             osg::ref_ptr<osg::HeightField> _neighbors[8];
+            osg::ref_ptr<osg::HeightField> _parent;
         };
 
 
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp b/src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp
index 828d513..47610ac 100644
--- a/src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp
+++ b/src/osgEarthDrivers/engine_quadtree/TileModelCompiler.cpp
@@ -21,6 +21,7 @@
 #include <osgEarth/Locators>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/HeightFieldUtils>
 #include <osgEarth/TextureCompositor>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/MeshConsolidator>
@@ -115,7 +116,6 @@ namespace
             skirt            = 0L;
             stitching_skirts = 0L;
             ss_verts         = 0L;
-            //skirtHeight      = 0.0f;
             scaleHeight      = 1.0f;
             createSkirt      = false;
             i_sampleFactor   = 1.0f;
@@ -141,6 +141,7 @@ namespace
         osg::Vec3Array*               surfaceVerts;
         osg::Vec3Array*               normals;
         osg::Vec4Array*               surfaceElevData;
+        osg::FloatArray*              surfaceMorphData;
         unsigned                      numVerticesInSurface;
         osg::Vec2Array*               unifiedSurfaceTexCoords;
         osg::ref_ptr<osg::FloatArray> elevations;
@@ -323,7 +324,13 @@ namespace
         d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_6, d.surfaceElevData );
         d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX );
         d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_6, false );
-        
+
+        d.surfaceMorphData = new osg::FloatArray();
+        d.surfaceMorphData->reserve( d.numVerticesInSurface );
+        d.surface->setVertexAttribArray( osg::Drawable::ATTRIBUTE_7, d.surfaceMorphData );
+        d.surface->setVertexAttribBinding( osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX );
+        d.surface->setVertexAttribNormalize( osg::Drawable::ATTRIBUTE_7, false );
+
         // temporary data structures for triangulation support
         d.elevations = new osg::FloatArray();
         d.elevations->reserve( d.numVerticesInSurface );
@@ -610,6 +617,22 @@ namespace
 
                     // store the unit extrusion vector and the raw height value.
                     (*d.surfaceElevData).push_back( osg::Vec4f(model_one.x(), model_one.y(), model_one.z(), heightValue) );
+
+                    float oldHeightValue = heightValue;
+                    if ( i>0 && j>0 && i<d.numCols-1 && j<d.numRows-1 && (i%2 == 1 || j%2 == 1) )
+                    {
+                        float h;
+                        if ( HeightFieldUtils::getInterpolatedHeight(
+                            elevationLayer->getHeightField(),
+                            i_equiv,
+                            j_equiv,
+                            h ) )
+                        {
+                            oldHeightValue = h;
+                        }
+                    }
+
+                    d.surfaceMorphData->push_back( oldHeightValue );
                 }
             }
         }
@@ -990,10 +1013,12 @@ namespace
         osg::Vec3Array* skirtVerts = new osg::Vec3Array();
         osg::Vec3Array* skirtNormals = new osg::Vec3Array();
         osg::Vec4Array* skirtElevData = new osg::Vec4Array();
+        osg::FloatArray* skirtMorphData = new osg::FloatArray();
 
         skirtVerts->reserve( d.numVerticesInSkirt );
         skirtNormals->reserve( d.numVerticesInSkirt );
         skirtElevData->reserve( d.numVerticesInSkirt );
+        skirtMorphData->reserve( d.numVerticesInSkirt );
 
         Indices skirtBreaks;
         skirtBreaks.reserve( d.numVerticesInSkirt );
@@ -1022,6 +1047,9 @@ namespace
                 const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
                 skirtElevData->push_back( elevData );
                 skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const float& morphData = (*d.surfaceMorphData)[orig_i];
+                skirtMorphData->push_back( morphData );
+                skirtMorphData->push_back( morphData - skirtHeight );
 
                 if ( compositor->requiresUnitTextureSpace() )
                 {
@@ -1065,6 +1093,9 @@ namespace
                 const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
                 skirtElevData->push_back( elevData );
                 skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const float& morphData = (*d.surfaceMorphData)[orig_i];
+                skirtMorphData->push_back( morphData );
+                skirtMorphData->push_back( morphData - skirtHeight );
 
                 if ( compositor->requiresUnitTextureSpace() )
                 {
@@ -1108,6 +1139,9 @@ namespace
                 const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
                 skirtElevData->push_back( elevData );
                 skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const float& morphData = (*d.surfaceMorphData)[orig_i];
+                skirtMorphData->push_back( morphData );
+                skirtMorphData->push_back( morphData - skirtHeight );
 
                 if ( compositor->requiresUnitTextureSpace() )
                 {
@@ -1151,6 +1185,9 @@ namespace
                 const osg::Vec4f& elevData = (*d.surfaceElevData)[orig_i];
                 skirtElevData->push_back( elevData );
                 skirtElevData->push_back( elevData - osg::Vec4f(0,0,0,skirtHeight) );
+                const float& morphData = (*d.surfaceMorphData)[orig_i];
+                skirtMorphData->push_back( morphData );
+                skirtMorphData->push_back( morphData - skirtHeight );
 
                 if ( compositor->requiresUnitTextureSpace() )
                 {
@@ -1183,6 +1220,10 @@ namespace
         d.skirt->setVertexAttribBinding  (osg::Drawable::ATTRIBUTE_6, osg::Geometry::BIND_PER_VERTEX);
         d.skirt->setVertexAttribNormalize(osg::Drawable::ATTRIBUTE_6, false);
 
+        d.skirt->setVertexAttribArray    (osg::Drawable::ATTRIBUTE_7, skirtMorphData );
+        d.skirt->setVertexAttribBinding  (osg::Drawable::ATTRIBUTE_7, osg::Geometry::BIND_PER_VERTEX);
+        d.skirt->setVertexAttribNormalize(osg::Drawable::ATTRIBUTE_7, false);
+
 
         // GW: not sure why this break stuff is here...?
 #if 0
diff --git a/src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp b/src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp
index 0e6fcb4..e6aac4a 100644
--- a/src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp
+++ b/src/osgEarthDrivers/engine_quadtree/TileModelFactory.cpp
@@ -206,6 +206,23 @@ namespace
                         }
                     }
                 }
+
+#if 0
+                if ( *_opt->morphLODs() )
+                {
+                    // find the parent tile as well, for LOD morphing!!
+                    if ( _key.getLevelOfDetail() > 0 )
+                    {
+                        if (_hfCache->getOrCreateHeightField( *_mapf, _key.createParentKey(), true, hf, &isFallback ))
+                        {       
+                            if ( mapInfo.isPlateCarre() )
+                                HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() );
+
+                            _model->_elevationData.setParent( hf.get() );
+                        }
+                    }
+                }
+#endif
             }
         }
 
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNode b/src/osgEarthDrivers/engine_quadtree/TileNode
index b0fbb36..c457066 100644
--- a/src/osgEarthDrivers/engine_quadtree/TileNode
+++ b/src/osgEarthDrivers/engine_quadtree/TileNode
@@ -89,6 +89,7 @@ namespace osgEarth_engine_quadtree
         osg::ref_ptr<GeoLocator>  _locator;
         osg::ref_ptr<TileModel>   _model;
         osg::StateSet*            _publicStateSet;
+        osg::Uniform*             _born;
     };
 
 
diff --git a/src/osgEarthDrivers/engine_quadtree/TileNode.cpp b/src/osgEarthDrivers/engine_quadtree/TileNode.cpp
index 5816264..44f928f 100644
--- a/src/osgEarthDrivers/engine_quadtree/TileNode.cpp
+++ b/src/osgEarthDrivers/engine_quadtree/TileNode.cpp
@@ -38,6 +38,10 @@ _locator          ( keyLocator ),
 _publicStateSet   ( 0L )
 {
     this->setName( key.str() );
+
+    _born = new osg::Uniform(osg::Uniform::FLOAT, "oe_birthTime");
+    _born->set( -1.0f );
+    this->getOrCreateStateSet()->addUniform( _born );
 }
 
 
@@ -88,6 +92,11 @@ TileNode::traverse( osg::NodeVisitor& nv )
         {
             if (ccc->cull(&nv,0,static_cast<osg::State *>(0))) return;
         }
+
+        float bt;
+        _born->get( bt );
+        if ( bt < 0.0f )
+            _born->set( nv.getFrameStamp() ? (float)nv.getFrameStamp()->getReferenceTime() : 0.0f );
     }
 
     osg::Group::traverse( nv );
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
index 3a5e13c..a051eaa 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureCursorOGR.cpp
@@ -53,15 +53,20 @@ _filters          ( filters )
         std::string expr;
         std::string from = OGR_FD_GetName( OGR_L_GetLayerDefn( _layerHandle ));        
         
-        //Quote the layer name.
-        std::string driverName = OGR_Dr_GetName( OGR_DS_GetDriver( dsHandle ) );
-        std::string delim = "'";  //Use single quotes by default
-        if (driverName.compare("PostgreSQL") == 0)
-        {
-            //PostgreSQL uses double quotes as identifier delimeters
-            delim = "\"";
-        }            
-        from = delim + from + delim;                    
+        
+        std::string driverName = OGR_Dr_GetName( OGR_DS_GetDriver( dsHandle ) );             
+        // Quote the layer name if it is a shapefile, so we can handle any weird filenames like those with spaces or hyphens.
+        // Or quote any layers containing spaces for PostgreSQL
+        if (driverName == "ESRI Shapefile" || from.find(" ") != std::string::npos)
+        {                        
+            std::string delim = "'";  //Use single quotes by default
+            if (driverName.compare("PostgreSQL") == 0)
+            {
+                //PostgreSQL uses double quotes as identifier delimeters
+                delim = "\"";
+            }            
+            from = delim + from + delim;                    
+        }
 
         if ( query.expression().isSet() )
         {
diff --git a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
index 82eeb34..718c1a4 100644
--- a/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
+++ b/src/osgEarthDrivers/feature_ogr/FeatureSourceOGR.cpp
@@ -32,6 +32,7 @@
 #include <osgDB/FileUtils>
 #include <list>
 #include <ogr_api.h>
+#include <cpl_error.h>
 
 #define LC "[OGR FeatureSource] "
 
@@ -255,7 +256,7 @@ public:
             }
             else
             {
-                OE_INFO << LC << "failed to open dataset \"" << _source << "\"" << std::endl;
+                OE_INFO << LC << "failed to open dataset at \"" << _source << "\" error " << CPLGetLastErrorMsg() << std::endl;
             }
         }
         else
diff --git a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
index a9ce0b6..973a73a 100644
--- a/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
+++ b/src/osgEarthDrivers/feature_wfs/FeatureSourceWFS.cpp
@@ -275,21 +275,11 @@ public:
     {
         //OGR is particular sometimes about the extension of files when it's reading them so it's good to have
         //the temp file have an appropriate extension
-        if ((mime.compare("text/xml") == 0) ||
-            (mime.compare("text/xml; subtype=gml/2.1.2") == 0) ||
-            (mime.compare("text/xml; subtype=gml/3.1.1") == 0)
-            )
+        if (isGML(mime))
         {
             return ".xml";
         }        
-        else if ((mime.compare("application/json") == 0) ||
-                 (mime.compare("json") == 0) ||            
-
-                 (mime.compare("application/x-javascript") == 0) ||
-                 (mime.compare("text/javascript") == 0) ||
-                 (mime.compare("text/x-javascript") == 0) ||
-                 (mime.compare("text/x-json") == 0)                 
-                )
+		else if (isJSON(mime))
         {
             return ".json";
         }        
@@ -299,20 +289,19 @@ public:
     bool isGML( const std::string& mime ) const
     {        
         return
-            startsWith(mime, "text/xml");            
+            startsWith(mime, "text/xml");
     }
 
 
     bool isJSON( const std::string& mime ) const
     {
         return
-            (mime.compare("application/json") == 0)         ||
-            (mime.compare("json") == 0)                     ||            
-
-            (mime.compare("application/x-javascript") == 0) ||
-            (mime.compare("text/javascript") == 0)          ||
-            (mime.compare("text/x-javascript") == 0)        ||
-            (mime.compare("text/x-json") == 0);
+            startsWith(mime, "application/json") ||
+            startsWith(mime, "json") ||            
+            startsWith(mime, "application/x-javascript") ||
+            startsWith(mime, "text/javascript") ||
+            startsWith(mime, "text/x-javascript") ||
+            startsWith(mime, "text/x-json");
     }
 
     std::string createURL(const Symbology::Query& query)
diff --git a/src/osgEarthDrivers/gdal/GDALOptions b/src/osgEarthDrivers/gdal/GDALOptions
index 982e6cd..a316238 100644
--- a/src/osgEarthDrivers/gdal/GDALOptions
+++ b/src/osgEarthDrivers/gdal/GDALOptions
@@ -88,11 +88,12 @@ namespace osgEarth { namespace Drivers
         optional<ElevationInterpolation>& interpolation() { return _interpolation; }
         const optional<ElevationInterpolation>& interpolation() const { return _interpolation; }
 
-        /*
-         * Maximum level of detail of available data
+        /**
+         * Tells the driver to always generate data to this level even if it means
+         * upsampling
          */
-        optional<unsigned int>& maxDataLevel() { return _maxDataLevel;}
-        const optional<unsigned int>& maxDataLevel() const { return _maxDataLevel;}
+        optional<unsigned int>& maxDataLevelOverride() { return _maxDataLevelOverride;}
+        const optional<unsigned int>& maxDataLevelOverride() const { return _maxDataLevelOverride;}
 
         /*
          * Some GDAL-supported formats support sub-datasets; use this property
@@ -157,7 +158,7 @@ namespace osgEarth { namespace Drivers
                 else if ( _interpolation.value() == osgEarth::INTERP_BILINEAR ) conf.update( "interpolation", "bilinear" );
             }
 
-            conf.updateIfSet( "max_data_level", _maxDataLevel);
+            conf.updateIfSet( "max_data_level_override", _maxDataLevelOverride);
             conf.updateIfSet( "subdataset", _subDataSet);
 
             conf.updateIfSet( "interp_imagery", _interpolateImagery);
@@ -183,7 +184,7 @@ namespace osgEarth { namespace Drivers
             if ( in == "nearest" ) _interpolation = osgEarth::INTERP_NEAREST;
             else if ( in == "average" ) _interpolation = osgEarth::INTERP_AVERAGE;
             else if ( in == "bilinear" ) _interpolation = osgEarth::INTERP_BILINEAR;
-            conf.getIfSet( "max_data_level", _maxDataLevel);
+            conf.getIfSet( "max_data_level_override", _maxDataLevelOverride);
             conf.getIfSet( "subdataset", _subDataSet);
 
             conf.getIfSet("interp_imagery", _interpolateImagery);
@@ -196,10 +197,10 @@ namespace osgEarth { namespace Drivers
         optional<URI>                    _url;
         optional<std::string>            _connection;
         optional<std::string>            _extensions;
-        optional<std::string>			 _blackExtensions;
+        optional<std::string>            _blackExtensions;
         optional<ElevationInterpolation> _interpolation;
         optional<bool>                   _interpolateImagery;
-        optional<unsigned int>           _maxDataLevel;
+        optional<unsigned int>           _maxDataLevelOverride;
         optional<unsigned int>           _subDataSet;
         optional<ProfileOptions>         _warpProfile;
         osg::ref_ptr<ExternalDataset>    _externalDataset;
diff --git a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
index f70c060..f7bbd30 100644
--- a/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
+++ b/src/osgEarthDrivers/gdal/ReaderWriterGDAL.cpp
@@ -776,10 +776,10 @@ public:
                 //Try to load the VRT file from the cache so we don't have to build it each time.
                 if (_cacheBin.valid())
                 {                
-                    ReadResult result = _cacheBin->readString( vrtKey );                    
+                    ReadResult result = _cacheBin->readString( vrtKey, 0 );
                     if (result.succeeded())
                     {                        
-                        _srcDS = (GDALDataset*)GDALOpen(result.getString().c_str(), GA_ReadOnly );                                                
+                        _srcDS = (GDALDataset*)GDALOpen(result.getString().c_str(), GA_ReadOnly );
                         if (_srcDS)
                         {
                             OE_INFO << LC << "Read VRT from cache!" << std::endl;
@@ -1073,9 +1073,9 @@ public:
 
         OE_INFO << LC << "Resolution= " << resolutionX << "x" << resolutionY << " max=" << maxResolution << std::endl;
 
-        if (_options.maxDataLevel().isSet())
+        if (_options.maxDataLevelOverride().isSet())
         {
-            _maxDataLevel = _options.maxDataLevel().value();
+            _maxDataLevel = _options.maxDataLevelOverride().value();
             OE_INFO << _options.url().value().full() << " using override max data level " << _maxDataLevel << std::endl;
         }
         else
@@ -1095,7 +1095,7 @@ public:
                 }
             }
 
-            OE_NOTICE << LC << _options.url().value().full() << " max Data Level: " << _maxDataLevel << std::endl;
+            OE_INFO << LC << _options.url().value().full() << " max Data Level: " << _maxDataLevel << std::endl;
         }
 
         osg::ref_ptr< SpatialReference > srs = SpatialReference::create( warpedSRSWKT );
@@ -1221,6 +1221,20 @@ public:
         geoY = _geotransform[3] + _geotransform[4] * x + _geotransform[5] * y;
     }
 
+    void geoToPixel(double geoX, double geoY, double &x, double &y)
+    {                
+        x = _invtransform[0] + _invtransform[1] * geoX + _invtransform[2] * geoY;
+        y = _invtransform[3] + _invtransform[4] * geoX + _invtransform[5] * geoY;                
+
+         //Account for slight rounding errors.  If we are right on the edge of the dataset, clamp to the edge
+        double eps = 0.0001;
+        if (osg::equivalent(x, 0, eps)) x = 0;
+        if (osg::equivalent(y, 0, eps)) y = 0;
+        if (osg::equivalent(x, (double)_warpedDS->GetRasterXSize(), eps)) x = _warpedDS->GetRasterXSize();
+        if (osg::equivalent(y, (double)_warpedDS->GetRasterYSize(), eps)) y = _warpedDS->GetRasterYSize();
+
+    }
+
     osg::Image* createImage( const TileKey&        key,
                              ProgressCallback*     progress)
     {
@@ -1242,60 +1256,59 @@ public:
             double xmin, ymin, xmax, ymax;
             key.getExtent().getBounds(xmin, ymin, xmax, ymax);
 
-            double dx       = (xmax - xmin) / (tileSize-1); 
-            double dy       = (ymax - ymin) / (tileSize-1); 
-
-
-            int target_width = tileSize;
-            int target_height = tileSize;
-            int tile_offset_left = 0;
-            int tile_offset_top = 0;
-
-            int off_x = int((xmin - _geotransform[0]) / _geotransform[1]);
-            int off_y = int((ymax - _geotransform[3]) / _geotransform[5]);
-            int width = int(((xmax - _geotransform[0]) / _geotransform[1]) - off_x);
-            int height = int(((ymin - _geotransform[3]) / _geotransform[5]) - off_y);
-
-            if (off_x + width > _warpedDS->GetRasterXSize())
+            // Compute the intersection of the incoming key with the data extents of the dataset
+            osgEarth::GeoExtent intersection = key.getExtent().intersectionSameSRS( _extents );
+            
+            // Determine the read window
+            double src_min_x, src_min_y, src_max_x, src_max_y;
+            // Get the pixel coordiantes of the intersection
+            geoToPixel( intersection.xMin(), intersection.yMax(), src_min_x, src_min_y);
+            geoToPixel( intersection.xMax(), intersection.yMin(), src_max_x, src_max_y);   
+
+            // Convert the doubles to integers.  We floor the mins and ceil the maximums to give the widest window possible.
+            src_min_x = floor(src_min_x);
+            src_min_y = floor(src_min_y);
+            src_max_x = ceil(src_max_x);
+            src_max_y = ceil(src_max_y);
+
+            int off_x = (int)( src_min_x );
+            int off_y = (int)( src_min_y );
+            int width  = (int)(src_max_x - src_min_x);
+            int height = (int)(src_max_y - src_min_y);      
+
+
+            int rasterWidth = _warpedDS->GetRasterXSize();
+            int rasterHeight = _warpedDS->GetRasterYSize();
+            if (off_x + width > rasterWidth || off_y + height > rasterHeight)
             {
-                int oversize_right = off_x + width - _warpedDS->GetRasterXSize();
-                target_width = target_width - int(float(oversize_right) / width * target_width);
-                width = _warpedDS->GetRasterXSize() - off_x;
+                OE_WARN << LC << "Read window outside of bounds of dataset.  Source Dimensions=" << rasterWidth << "x" << rasterHeight << " Read Window=" << off_x << ", " << off_y << " " << width << "x" << height << std::endl;
             }
 
-            if (off_x < 0)
-            {
-                int oversize_left = -off_x;
-                tile_offset_left = int(float(oversize_left) / width * target_width);
-                target_width = target_width - int(float(oversize_left) / width * target_width);
-                width = width + off_x;
-                off_x = 0;
-            }
+            // Determine the destination window            
 
-            if (off_y + height > _warpedDS->GetRasterYSize())
-            {
-                int oversize_bottom = off_y + height - _warpedDS->GetRasterYSize();
-                target_height = target_height - (int)osg::round(float(oversize_bottom) / height * target_height);
-                height = _warpedDS->GetRasterYSize() - off_y;
-            }
+            // Compute the offsets in geo coordinates of the intersection from the TileKey
+            double offset_left = intersection.xMin() - xmin;            
+            double offset_top = ymax - intersection.yMax();
 
 
-            if (off_y < 0)
-            {
-                int oversize_top = -off_y;
-                tile_offset_top = int(float(oversize_top) / height * target_height);
-                target_height = target_height - int(float(oversize_top) / height * target_height);
-                height = height + off_y;
-                off_y = 0;
-            }
+            int target_width = (int)ceil((intersection.width() / key.getExtent().width())*(double)tileSize);
+            int target_height = (int)ceil((intersection.height() / key.getExtent().height())*(double)tileSize);
+            int tile_offset_left = (int)floor((offset_left / key.getExtent().width()) * (double)tileSize);
+            int tile_offset_top = (int)floor((offset_top / key.getExtent().height()) * (double)tileSize);
+            
+            // Compute spacing
+            double dx       = (xmax - xmin) / (tileSize-1); 
+            double dy       = (ymax - ymin) / (tileSize-1); 
+
+            OE_DEBUG << LC << "ReadWindow " << off_x << "," << off_y << " " << width << "x" << height << std::endl;
+            OE_DEBUG << LC << "DestWindow " << tile_offset_left << "," << tile_offset_top << " " << target_width << "x" << target_height << std::endl;                        
 
-            OE_DEBUG << LC << "ReadWindow " << width << "x" << height << " DestWindow " << target_width << "x" << target_height << std::endl;
 
             //Return if parameters are out of range.
             if (width <= 0 || height <= 0 || target_width <= 0 || target_height <= 0)
             {
                 return 0;
-            }
+            }            
 
 
 
@@ -1595,14 +1608,8 @@ public:
     float getInterpolatedValue(GDALRasterBand *band, double x, double y, bool applyOffset=true)
     {
         double r, c;
-        GDALApplyGeoTransform(_invtransform, x, y, &c, &r);
-
-        //Account for slight rounding errors.  If we are right on the edge of the dataset, clamp to the edge
-        double eps = 0.0001;
-        if (osg::equivalent(c, 0, eps)) c = 0;
-        if (osg::equivalent(r, 0, eps)) r = 0;
-        if (osg::equivalent(c, (double)_warpedDS->GetRasterXSize(), eps)) c = _warpedDS->GetRasterXSize();
-        if (osg::equivalent(r, (double)_warpedDS->GetRasterYSize(), eps)) r = _warpedDS->GetRasterYSize();
+        geoToPixel( x, y, c, r );        
+       
 
         if (applyOffset)
         {
diff --git a/src/osgEarthDrivers/kml/KMLReader.cpp b/src/osgEarthDrivers/kml/KMLReader.cpp
index afef868..fb6a052 100644
--- a/src/osgEarthDrivers/kml/KMLReader.cpp
+++ b/src/osgEarthDrivers/kml/KMLReader.cpp
@@ -21,9 +21,8 @@
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/XmlUtils>
-#include <osgEarth/ShaderGenerator>
 #include <osgEarth/VirtualProgram>
-#include <osgEarthAnnotation/Decluttering>
+#include <osgEarth/Decluttering>
 #include <stack>
 #include <iterator>
 
diff --git a/src/osgEarthDrivers/kml/KML_Geometry.cpp b/src/osgEarthDrivers/kml/KML_Geometry.cpp
index 538e0c9..1eb3508 100644
--- a/src/osgEarthDrivers/kml/KML_Geometry.cpp
+++ b/src/osgEarthDrivers/kml/KML_Geometry.cpp
@@ -43,36 +43,37 @@ KML_Geometry::buildChild( const Config& conf, KMLContext& cx, Style& style)
     if ( conf.key() == "point" )
     {
         KML_Point g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
         _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
     }
     else if ( conf.key() == "linestring" )
     {
         KML_LineString g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
         _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
     }
     else if ( conf.key() == "linearring" || conf.key() == "gx:latlonquad" )
     {
         KML_LinearRing g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
         _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
     }
     else if ( conf.key() == "polygon" )
     {
         KML_Polygon g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
         _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
     }
     else if ( conf.key() == "multigeometry" )
     {
         KML_MultiGeometry g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
+        _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
         const ConfigSet& mgChildren = conf.children();
         
         for( ConfigSet::const_iterator i = mgChildren.begin(); i != mgChildren.end(); ++i )
@@ -85,15 +86,13 @@ KML_Geometry::buildChild( const Config& conf, KMLContext& cx, Style& style)
             if ( subGeom._geom.valid() )
                 dynamic_cast<MultiGeometry*>(g._geom.get())->getComponents().push_back( subGeom._geom.get() );
         }
-        //g.parseCoords(conf.child("multigeometry"), cx);
-        _geom = g._geom.get();
     }
     else if ( conf.key() == "model" )
     {
         KML_Model g;
-        g.parseStyle(conf, cx, style);
         g.parseCoords(conf, cx);
         _geom = g._geom.get();
+        g.parseStyle(conf, cx, style);
     }
 }
 
@@ -127,13 +126,33 @@ KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
     _tessellate = conf.value("tessellate") == "1";
 
     std::string am = conf.value("altitudemode");
+    if ( am.empty() )
+        am = "clampToGround"; // default.
+
+    bool isPoly = _geom->getComponentType() == Geometry::TYPE_POLYGON;
 
-    // clampToGround is the default. We will be draping the geometry UNLESS tessellate is
-    // set to true.
-    if ( (am.empty() || am == "clampToGround") && _tessellate )
+    // Resolve the correct altitude symbol. CLAMP_TO_TERRAIN is the default, but the
+    // technique will depend on the geometry's type and setup.
+    AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
+    alt->clamping() = alt->CLAMP_TO_TERRAIN;
+
+    // clamp to ground mode:
+    if ( am == "clampToGround" )
     {
-        AltitudeSymbol* af = style.getOrCreate<AltitudeSymbol>();
-        af->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
+        if ( _extrude )
+        {
+            alt->technique() = alt->TECHNIQUE_MAP;
+        }
+        else if ( isPoly )
+        {
+            alt->technique() = alt->TECHNIQUE_DRAPE;
+        }
+        else // line or point
+        {
+            alt->technique() = alt->TECHNIQUE_SCENE;
+        }
+
+        // extrusion is not compatible with clampToGround.
         _extrude = false;
     }
 
@@ -142,15 +161,50 @@ KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
     // which seems wrong..
     else if ( am == "relativeToGround" )
     {
-        AltitudeSymbol* af = style.getOrCreate<AltitudeSymbol>();
-        af->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
+        alt->clamping() = alt->CLAMP_RELATIVE_TO_TERRAIN;
+
+        if ( _extrude )
+        {
+            alt->technique() = alt->TECHNIQUE_MAP;
+        }
+        else
+        {
+            alt->technique() = alt->TECHNIQUE_SCENE;
+
+            if ( isPoly )
+            {
+                bool zeroElev = true;
+                ConstGeometryIterator gi( _geom.get(), false );
+                while( zeroElev == true && gi.hasMore() )
+                {
+                    const Geometry* g = gi.next();
+                    for( Geometry::const_iterator ji = g->begin(); ji != g->end() && zeroElev == true; ++ji )
+                    {
+                        if ( !osg::equivalent(ji->z(), 0.0) )
+                            zeroElev = false;
+                    }
+                }
+                if ( zeroElev )
+                {
+                    alt->clamping()  = alt->CLAMP_TO_TERRAIN;
+                    alt->technique() = alt->TECHNIQUE_DRAPE;
+                }
+            }
+        }
     }
 
     // "absolute" means to treat the Z values as-is
     else if ( am == "absolute" )
     {
-        AltitudeSymbol* af = style.getOrCreate<AltitudeSymbol>();
-        af->clamping() = AltitudeSymbol::CLAMP_ABSOLUTE;
+        if ( _extrude )
+        {
+            alt->clamping() = alt->CLAMP_ABSOLUTE;
+            alt->technique() = alt->TECHNIQUE_MAP;
+        }
+        else
+        {
+            alt->clamping() = AltitudeSymbol::CLAMP_NONE;
+        }
     }
 
     if ( _extrude )
@@ -158,4 +212,12 @@ KML_Geometry::parseStyle( const Config& conf, KMLContext& cx, Style& style )
         ExtrusionSymbol* es = style.getOrCreate<ExtrusionSymbol>();
         es->flatten() = false;
     }
+    else
+    {
+        // remove polystyle since it doesn't apply to non-extruded lines and points
+        if ( !isPoly )
+        {
+            style.remove<PolygonSymbol>();
+        }
+    }
 }
diff --git a/src/osgEarthDrivers/kml/KML_LineString.cpp b/src/osgEarthDrivers/kml/KML_LineString.cpp
index d624961..632a3db 100644
--- a/src/osgEarthDrivers/kml/KML_LineString.cpp
+++ b/src/osgEarthDrivers/kml/KML_LineString.cpp
@@ -24,8 +24,19 @@ void
 KML_LineString::parseStyle( const Config& conf, KMLContext& cs, Style& style )
 {
     KML_Geometry::parseStyle(conf, cs, style);
+
+    // need a line symbol minimally
+    LineSymbol* line = style.get<LineSymbol>();
+    if ( !line )
+    {
+        line = style.getOrCreate<LineSymbol>();
+        line->stroke()->color() = osg::Vec4f(1,1,1,1);
+    }
+
     if ( conf.value("tessellate") == "1" )
-        style.getOrCreate<LineSymbol>()->tessellation() = 20;
+    {
+        line->tessellation() = 20; // KML default
+    }
 }
 
 void
diff --git a/src/osgEarthDrivers/kml/KML_LinearRing.cpp b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
index 0efde1f..756448c 100644
--- a/src/osgEarthDrivers/kml/KML_LinearRing.cpp
+++ b/src/osgEarthDrivers/kml/KML_LinearRing.cpp
@@ -24,8 +24,19 @@ void
 KML_LinearRing::parseStyle( const Config& conf, KMLContext& cs, Style& style )
 {
     KML_Geometry::parseStyle(conf, cs, style);
+
+    // need a line symbol minimally
+    LineSymbol* line = style.get<LineSymbol>();
+    if ( !line )
+    {
+        line = style.getOrCreate<LineSymbol>();
+        line->stroke()->color() = osg::Vec4f(1,1,1,1);
+    }
+
     if ( conf.value("tessellate") == "1" )
-        style.getOrCreate<LineSymbol>()->tessellation() = 20;
+    {
+        line->tessellation() = 20;
+    }
 }
 
 void
diff --git a/src/osgEarthDrivers/kml/KML_Placemark.cpp b/src/osgEarthDrivers/kml/KML_Placemark.cpp
index 60a05db..71bf4bc 100644
--- a/src/osgEarthDrivers/kml/KML_Placemark.cpp
+++ b/src/osgEarthDrivers/kml/KML_Placemark.cpp
@@ -24,8 +24,8 @@
 #include <osgEarthAnnotation/PlaceNode>
 #include <osgEarthAnnotation/LabelNode>
 #include <osgEarthAnnotation/ModelNode>
-#include <osgEarthAnnotation/Decluttering>
 #include <osgEarthAnnotation/LocalGeometryNode>
+#include <osgEarth/Decluttering>
 
 #include <osg/Depth>
 #include <osgDB/WriteFile>
@@ -68,22 +68,19 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
             Geometry* geom = giter.next();
             Style style = masterStyle;
 
-            // KML's default altitude mode is clampToGround.
-            AltitudeMode altMode = ALTMODE_RELATIVE;
-
-            AltitudeSymbol* altSym = style.get<AltitudeSymbol>();
-            if ( !altSym )
-            {
-                altSym = style.getOrCreate<AltitudeSymbol>();
-                altSym->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
-            }
-            else if ( !altSym->clamping().isSetTo(AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN) )
-            {
-                altMode = ALTMODE_ABSOLUTE;
-            }
+            AltitudeSymbol* alt = style.get<AltitudeSymbol>();
             
             if ( geom && geom->getTotalPointCount() > 0 )
             {
+                // resolve the proper altitude mode for the anchor point
+                AltitudeMode altMode = ALTMODE_RELATIVE;
+                if (alt && 
+                    !alt->clamping().isSetTo( alt->CLAMP_TO_TERRAIN ) &&
+                    !alt->clamping().isSetTo( alt->CLAMP_RELATIVE_TO_TERRAIN ) )
+                {
+                    altMode = ALTMODE_ABSOLUTE;
+                }
+
                 GeoPoint position(cx._srs.get(), geom->getBounds().center(), altMode);
 
                 bool isPoly = geom->getComponentType() == Geometry::TYPE_POLYGON;
@@ -125,12 +122,14 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                         ModelNode* node = new ModelNode( cx._mapNode, style, cx._dbOptions );
                         node->setPosition( position );
 
+                        // model scale:
                         if ( cx._options->modelScale() != 1.0f )
                         {
                             float s = *cx._options->modelScale();
                             node->setScale( osg::Vec3f(s,s,s) );
                         }
 
+                        // model local tangent plane rotation:
                         if ( !cx._options->modelRotation()->zeroRotation() )
                         {
                             node->setLocalRotation( *cx._options->modelRotation() );
@@ -139,12 +138,14 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                         modelNode = node;
                     }
 
+                    // is there a label?
                     else if ( !text && !name.empty() )
                     {
                         text = style.getOrCreate<TextSymbol>();
                         text->content()->setLiteral( name );
                     }
 
+                    // is there an icon?
                     if ( icon )
                     {
                         iconNode = new PlaceNode( cx._mapNode, position, style, cx._dbOptions );
@@ -161,7 +162,6 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                 if ( geom->getTotalPointCount() > 1 )
                 {
                     ExtrusionSymbol* extruded = style.get<ExtrusionSymbol>();
-                    AltitudeSymbol*  altitude = style.get<AltitudeSymbol>();
 
                     // Remove symbols that we have already processed so the geometry
                     // compiler doesn't get confused.
@@ -172,46 +172,8 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                     if ( text )
                         style.removeSymbol( text );
 
-                    // analyze the data; if the Z coords are all 0.0, enable draping.
-                    if ( /*isPoly &&*/ !extruded && altitude && altitude->clamping() != AltitudeSymbol::CLAMP_TO_TERRAIN )
-                    {
-                        bool zeroElev = true;
-                        ConstGeometryIterator gi( geom, false );
-                        while( zeroElev == true && gi.hasMore() )
-                        {
-                            const Geometry* g = gi.next();
-                            for( Geometry::const_iterator ji = g->begin(); ji != g->end() && zeroElev == true; ++ji )
-                            {
-                                if ( !osg::equivalent(ji->z(), 0.0) )
-                                    zeroElev = false;
-                            }
-                        }
-                        if ( zeroElev )
-                        {
-                            altitude->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
-                        }
-                    }
-
-                    // Make a feature node; drape if we're not extruding.
-                    bool draped =
-                        isPoly    && 
-                        !extruded &&
-                        (!altitude || altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN);
-
-                    // turn off the clamping if we're draping.
-                    if ( draped && altitude )
-                        altitude->clamping() = AltitudeSymbol::CLAMP_NONE;
-
-                    GeometryCompilerOptions compilerOptions;
-
-                    // Check for point-model substitution:
-                    if ( style.has<ModelSymbol>() )
-                    {
-                        compilerOptions.instancing() = true;
-                    }
-
                     Feature* feature = new Feature(geom, cx._srs.get(), style);
-                    featureNode = new FeatureNode( cx._mapNode, feature, draped, compilerOptions );
+                    featureNode = new FeatureNode( cx._mapNode, feature );
                 }
 
 
@@ -228,7 +190,9 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                     cx._groupStack.top()->addChild( group );
 
                     if ( iconNode && cx._options->declutter() == true )
+                    {
                         Decluttering::setEnabled( iconNode->getOrCreateStateSet(), true );
+                    }
 
                     if ( iconNode )
                         KML_Feature::build( conf, cx, iconNode );
@@ -250,7 +214,9 @@ KML_Placemark::build( const Config& conf, KMLContext& cx )
                         {
                             cx._groupStack.top()->addChild( iconNode );
                             if ( cx._options->declutter() == true )
+                            {
                                 Decluttering::setEnabled( iconNode->getOrCreateStateSet(), true );
+                            }
                         }
                         KML_Feature::build( conf, cx, iconNode );
                     }
diff --git a/src/osgEarthDrivers/kml/KML_Polygon.cpp b/src/osgEarthDrivers/kml/KML_Polygon.cpp
index 9273db2..e8ea29c 100644
--- a/src/osgEarthDrivers/kml/KML_Polygon.cpp
+++ b/src/osgEarthDrivers/kml/KML_Polygon.cpp
@@ -26,6 +26,12 @@ void
 KML_Polygon::parseStyle(const Config& conf, KMLContext& cx, Style& style)
 {
     KML_Geometry::parseStyle(conf, cx, style);
+
+    // need at minimum a poly symbol.
+    if ( !style.has<PolygonSymbol>() )
+    {
+        style.getOrCreate<PolygonSymbol>()->fill()->color() = osg::Vec4f(1,1,1,1);
+    }
 }
 
 void
diff --git a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
index fddeb41..02c869e 100644
--- a/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
+++ b/src/osgEarthDrivers/label_annotation/AnnotationLabelSource.cpp
@@ -17,12 +17,15 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/LabelSource>
-#include <osgEarth/DepthOffset>
+#include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthAnnotation/LabelNode>
-#include <osgEarthAnnotation/Decluttering>
+#include <osgEarthAnnotation/PlaceNode>
+#include <osgEarth/DepthOffset>
 #include <osgDB/FileNameUtils>
 #include <osgUtil/Optimizer>
 
+#define LC "[AnnoLabelSource] "
+
 using namespace osgEarth;
 using namespace osgEarth::Annotation;
 using namespace osgEarth::Features;
@@ -37,16 +40,6 @@ public:
     }
 
     /**
-     * Creates a simple label. The caller is responsible for placing it in the scene.
-     */
-    osg::Node* createNode(
-        const std::string& text,
-        const Style&       style )
-    {
-        return 0L; // no support
-    }
-
-    /**
      * Creates a complete set of positioned label nodes from a feature list.
      */
     osg::Node* createNode(
@@ -54,131 +47,95 @@ public:
         const Style&         style,
         const FilterContext& context )
     {
-        if ( style.get<TextSymbol>() == 0L )
+        if ( style.get<TextSymbol>() == 0L && style.get<IconSymbol>() == 0L )
             return 0L;
 
         // copy the style so we can (potentially) modify the text symbol.
         Style styleCopy = style;
         TextSymbol* text = styleCopy.get<TextSymbol>();
+        IconSymbol* icon = styleCopy.get<IconSymbol>();
 
         osg::Group* group = new osg::Group();
-
-#if 0
-        //TODO: revise; decluttering is enabled by the LabelNode now -gw
-
-        // check for decluttering
-        if ( text->declutter().isSet() )
-        {
-            Decluttering::setEnabled( group->getOrCreateStateSet(), *text->declutter() );
-        }
-#endif
-
-#if 0
-        if ( text->priority().isSet() )
-        {
-            DeclutteringOptions dco = Decluttering::getOptions();
-            dco.sortByPriority() = text->priority().isSet();
-            Decluttering::setOptions( dco );
-        }
-#endif
         
-        StringExpression  contentExpr ( *text->content() );
-        NumericExpression priorityExpr( *text->priority() );
+        StringExpression  textContentExpr ( text ? *text->content()  : StringExpression() );
+        NumericExpression textPriorityExpr( text ? *text->priority() : NumericExpression() );
+        StringExpression  iconUrlExpr     ( icon ? *icon->url()      : StringExpression() );
+        NumericExpression iconScaleExpr   ( icon ? *icon->scale()    : NumericExpression() );
+        NumericExpression iconHeadingExpr ( icon ? *icon->heading()  : NumericExpression() );
 
-        if ( text->removeDuplicateLabels() == true )
+        for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
         {
-            // in remove-duplicates mode, make a list of unique features, selecting
-            // the one with the largest area as the one we'll use for labeling.
+            const Feature* feature = i->get();
+            if ( !feature )
+                continue;
 
-            typedef std::pair<double, osg::ref_ptr<const Feature> > Entry;
-            typedef std::map<std::string, Entry>                    EntryMap;
+            const Geometry* geom = feature->getGeometry();
+            if ( !geom )
+                continue;
 
-            EntryMap used;
-    
-            for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
-            {
-                Feature* feature = i->get();
-                if ( feature && feature->getGeometry() )
-                {
-                    const std::string& value = feature->eval( contentExpr, &context );
-                    if ( !value.empty() )
-                    {
-                        double area = feature->getGeometry()->getBounds().area2d();
-                        if ( used.find(value) == used.end() )
-                        {
-                            used[value] = Entry(area, feature);
-                        }
-                        else 
-                        {
-                            Entry& biggest = used[value];
-                            if ( area > biggest.first )
-                            {
-                                biggest.first = area;
-                                biggest.second = feature;
-                            }
-                        }
-                    }
-                }
-            }
+            Style tempStyle = styleCopy;
+
+            // evaluate expressions into literals.
+            // TODO: Later we could replace this with a generate "expression evaluator" type
+            // that we could pass to PlaceNode in the DB options. -gw
 
-            for( EntryMap::iterator i = used.begin(); i != used.end(); ++i )
+            if ( text )
             {
-                const std::string& value = i->first;
-                const Feature* feature = i->second.second.get();
-                group->addChild( makeLabelNode(context, feature, value, text, priorityExpr) );
+                if ( text->content().isSet() )
+                    tempStyle.get<TextSymbol>()->content()->setLiteral( feature->eval( textContentExpr, &context ) );
             }
-        }
 
-        else
-        {
-            for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i )
+            if ( icon )
             {
-                const Feature* feature = i->get();
-                if ( !feature )
-                    continue;
+                if ( icon->url().isSet() )
+                    tempStyle.get<IconSymbol>()->url()->setLiteral( feature->eval(iconUrlExpr, &context) );
 
-                const Geometry* geom = feature->getGeometry();
-                if ( !geom )
-                    continue;
+                if ( icon->scale().isSet() )
+                    tempStyle.get<IconSymbol>()->scale()->setLiteral( feature->eval(iconScaleExpr, &context) );
 
-                const std::string& value = feature->eval( contentExpr, &context );
-                if ( value.empty() )
-                    continue;
+                if ( icon->heading().isSet() )
+                    tempStyle.get<IconSymbol>()->heading()->setLiteral( feature->eval(iconHeadingExpr, &context) );
+            }
+            
+            osg::Node* node = makePlaceNode(
+                context,
+                feature,
+                tempStyle,
+                textPriorityExpr);
+
+            if ( node )
+            {
+                if ( context.featureIndex() )
+                {
+                    context.featureIndex()->tagNode(node, const_cast<Feature*>(feature));
+                }
 
-                group->addChild( makeLabelNode(context, feature, value, text, priorityExpr) );
+                group->addChild( node );
             }
         }
 
-#if 0 // good idea but needs work.
-        DepthOffsetGroup* dog = new DepthOffsetGroup();
-        dog->setMinimumOffset( 500.0 );
-        dog->addChild( group );
-        return dog;
-#endif
         return group;
     }
 
-        
-    osg::Node* makeLabelNode(const FilterContext& context, 
+
+    osg::Node* makePlaceNode(const FilterContext& context,
                              const Feature*       feature, 
-                             const std::string&   value, 
-                             const TextSymbol*    text, 
+                             const Style&         style, 
                              NumericExpression&   priorityExpr )
     {
-        LabelNode* labelNode = new LabelNode(
-            0L,
-            GeoPoint(feature->getSRS(), feature->getGeometry()->getBounds().center(), ALTMODE_ABSOLUTE),
-            value,
-            text );
+        osg::Vec3d center = feature->getGeometry()->getBounds().center();
+        GeoPoint point(feature->getSRS(), center.x(), center.y());
+
+        PlaceNode* placeNode = new PlaceNode(0L, point, style, context.getDBOptions());
 
-        if ( text->priority().isSet() )
+        if ( !priorityExpr.empty() )
         {
             AnnotationData* data = new AnnotationData();
             data->setPriority( feature->eval(priorityExpr, &context) );
-            labelNode->setAnnotationData( data );
+            placeNode->setAnnotationData( data );
         }
 
-        return labelNode;
+        return placeNode;
     }
 
 };
diff --git a/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp b/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp
index 168923d..b169e93 100644
--- a/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp
+++ b/src/osgEarthDrivers/mbtiles/ReaderWriterMBTiles.cpp
@@ -55,42 +55,55 @@ public:
     }
 
     // override
-      void initialize( const osgDB::Options* dbOptions, const Profile* overrideProfile)
+    Status initialize(const osgDB::Options* dbOptions)
     {
-        //Set the profile
-        setProfile( osgEarth::Registry::instance()->getGlobalMercatorProfile() );
+        // no caching of source tiles
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
+        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
 
-#if 0
-        //Open the database
-        std::string filename = _options.filename().value();
-
-        //Get the absolute filename
-        if (!osgDB::containsServerAddress(filename))
-        {
-            filename = osgEarth::getFullPath(referenceURI, filename);
-        }
-#endif
+                   
 
         int flags = SQLITE_OPEN_READONLY;
         int rc = sqlite3_open_v2( _options.filename()->c_str(), &_database, flags, 0L );
         if ( rc != 0 )
-        {
-            OE_WARN << LC << "Failed to open database \"" << *_options.filename() << "\": " << sqlite3_errmsg(_database) << std::endl;
-            return;
+        {                        
+            std::stringstream buf;
+            buf << "Failed to open database \"" << *_options.filename() << "\": " << sqlite3_errmsg(_database);
+            return Status::Error(buf.str());
         }
 
         //Print out some metadata
-        std::string name, type, version, description, format;
+        std::string name, type, version, description, format, profileStr;
         getMetaData( "name", name );
         getMetaData( "type", type);
         getMetaData( "version", version );
         getMetaData( "description", description );
         getMetaData( "format", format );
+        getMetaData( "profile", profileStr );
         OE_NOTICE << "name=" << name << std::endl
                   << "type=" << type << std::endl
                   << "version=" << version << std::endl
                   << "description=" << description << std::endl
-                  << "format=" << format << std::endl;
+                  << "format=" << format << std::endl
+                  << "profile=" << profileStr << std::endl;
+
+
+
+         //Set the profile
+        const Profile* profile = getProfile();        
+        if (!profile)
+        {
+            if (!profileStr.empty())
+            {
+                profile = Profile::create(profileStr);
+            }
+            else
+            {
+                profile = osgEarth::Registry::instance()->getSphericalMercatorProfile();
+            }
+            setProfile( profile );                    
+        }
+        
 
         //Determine the tile format and get a reader writer for it.        
         if (_options.format().isSet())
@@ -112,9 +125,13 @@ public:
         OE_DEBUG << LC <<  "_tileFormat = " << _tileFormat << std::endl;
 
         //Get the ReaderWriter
-        _rw = osgDB::Registry::instance()->getReaderWriterForExtension( _tileFormat );
+        _rw = osgDB::Registry::instance()->getReaderWriterForExtension( _tileFormat );                
 
         computeLevels();
+
+        _emptyImage = ImageUtils::createEmptyImage( 256, 256 );
+        
+        return STATUS_OK;
     }    
 
     // override
@@ -127,8 +144,7 @@ public:
 
         if (z < (int)_minLevel)
         {
-            //Return an empty image to make it continue subdividing
-            return ImageUtils::createEmptyImage();
+            return _emptyImage.get();            
         }
 
         if (z > (int)_maxLevel)
@@ -171,8 +187,8 @@ public:
             osgDB::ReaderWriter::ReadResult rr = _rw->readImage( imageBufStream );
             if (rr.validImage())
             {
-                result = rr.takeImage();            
-            }
+                result = rr.takeImage();                
+            }            
         }
         else
         {
@@ -237,7 +253,7 @@ public:
         {                     
             _minLevel = sqlite3_column_int( select, 0 );
             _maxLevel = sqlite3_column_int( select, 1 );
-            //OE_NOTICE << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
+            OE_NOTICE << "Min=" << _minLevel << " Max=" << _maxLevel << std::endl;
         }
         else
         {
@@ -258,8 +274,10 @@ private:
     sqlite3* _database;
     unsigned int _minLevel;
     unsigned int _maxLevel;
+    osg::ref_ptr< osg::Image> _emptyImage;
 
     osg::ref_ptr<osgDB::ReaderWriter> _rw;
+    osg::ref_ptr<osgDB::Options> _dbOptions;
     std::string _tileFormat;
 
 };
diff --git a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
index 7bd035a..7af03a6 100644
--- a/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
+++ b/src/osgEarthDrivers/model_feature_stencil/FeatureStencilModelSource.cpp
@@ -186,19 +186,7 @@ namespace
             densificationThreshold = *_options.densificationThreshold();
 
             // Scan the geometry to see if it includes line data, since that will require buffering:
-            bool hasLines = false;
-            for( FeatureList::const_iterator i = featureList.begin(); i != featureList.end(); ++i )
-            {
-                Feature* feature = (*i).get();
-                Geometry* geom = feature->getGeometry();
-                if ( geom && 
-                     ( geom->getComponentType() == Geometry::TYPE_LINESTRING ||
-                       geom->getComponentType() == Geometry::TYPE_RING ) )
-                {
-                    hasLines = true;
-                    break;
-                }
-            }
+            bool hasLines = style.has<LineSymbol>() && !style.has<PolygonSymbol>();
 
             // If the geometry is lines, we need to buffer them before they will work with stenciling
             if ( hasLines )
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelOptions b/src/osgEarthDrivers/model_simple/SimpleModelOptions
index c3232e5..66380fd 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelOptions
+++ b/src/osgEarthDrivers/model_simple/SimpleModelOptions
@@ -46,6 +46,12 @@ namespace osgEarth { namespace Drivers
         optional<ShaderPolicy>& shaderPolicy() { return _shaderPolicy; }
         const optional<ShaderPolicy>& shaderPolicy() const { return _shaderPolicy; }
         
+        optional<float>& loadingPriorityScale() { return _loadingPriorityScale; }
+        const optional<float>& loadingPriorityScale() const { return _loadingPriorityScale; }
+
+        optional<float>& loadingPriorityOffset() { return _loadingPriorityOffset; }
+        const optional<float>& loadingPriorityOffset() const { return _loadingPriorityOffset; }
+        
         /**
          If specified, use this node instead try to load from url
         */
@@ -57,7 +63,10 @@ namespace osgEarth { namespace Drivers
     public:
         SimpleModelOptions( const ConfigOptions& options=ConfigOptions() )
             : ModelSourceOptions( options ),
-              _shaderPolicy( SHADERPOLICY_GENERATE )
+              _lod_scale(1.0f),
+              _shaderPolicy( SHADERPOLICY_GENERATE ),
+              _loadingPriorityScale(1.0f),
+              _loadingPriorityOffset(0.0f)
         {
             setDriver( "simple" );
             fromConfig( _conf );
@@ -72,6 +81,8 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet( "lod_scale", _lod_scale );
             conf.updateIfSet( "location", _location );
             conf.updateIfSet( "orientation", _orientation);
+            conf.updateIfSet( "loading_priority_scale", _loadingPriorityScale );
+            conf.updateIfSet( "loading_priority_offset", _loadingPriorityOffset );
 
             conf.addIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
             conf.addIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -93,6 +104,8 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet( "lod_scale", _lod_scale );
             conf.getIfSet( "location", _location);
             conf.getIfSet( "orientation", _orientation);
+            conf.getIfSet( "loading_priority_scale", _loadingPriorityScale );
+            conf.getIfSet( "loading_priority_offset", _loadingPriorityOffset );
 
             conf.getIfSet( "shader_policy", "disable",  _shaderPolicy, SHADERPOLICY_DISABLE );
             conf.getIfSet( "shader_policy", "inherit",  _shaderPolicy, SHADERPOLICY_INHERIT );
@@ -106,6 +119,8 @@ namespace osgEarth { namespace Drivers
         optional<osg::Vec3> _location;
         optional<osg::Vec3> _orientation;
         optional<ShaderPolicy> _shaderPolicy;
+        optional<float> _loadingPriorityScale;
+        optional<float> _loadingPriorityOffset;
         osg::ref_ptr<osg::Node> _node;
     };
 
diff --git a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
index d22eb2d..2ba06c2 100644
--- a/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
+++ b/src/osgEarthDrivers/model_simple/SimpleModelSource.cpp
@@ -23,7 +23,9 @@
 #include <osgEarth/Map>
 #include <osgEarth/ShaderGenerator>
 #include <osgEarth/FileUtils>
+#include <osgEarth/AutoScale>
 #include <osg/LOD>
+#include <osg/ProxyNode>
 #include <osg/Notify>
 #include <osg/MatrixTransform>
 #include <osg/io_utils>
@@ -67,6 +69,33 @@ namespace
     private:
         float m_lodScale;
     };
+
+    class SetLoadPriorityVisitor : public osg::NodeVisitor
+    {
+    public:
+        SetLoadPriorityVisitor(float scale=1.0f, float offset=0.0f)
+            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+            , m_scale(scale)
+            , m_offset(offset)
+        {}
+
+        virtual void apply(osg::PagedLOD& node)
+        {
+            for(unsigned n = 0; n < node.getNumFileNames(); n++)
+            {
+                float old;
+                old = node.getPriorityScale(n);
+                node.setPriorityScale(n, old * m_scale);
+                old = node.getPriorityOffset(n);
+                node.setPriorityOffset(n, old + m_offset);
+            }
+            traverse(node);
+        }
+
+    private:
+        float m_scale;
+        float m_offset;
+    };
 }
 
 //--------------------------------------------------------------------------
@@ -111,8 +140,6 @@ public:
                 (*_options.location()).y(), 
                 (*_options.location()).z(),
                 ALTMODE_ABSOLUTE );
-
-            OE_NOTICE << "Read location " << geoPoint.vec3d() << std::endl;
             
             osg::Matrixd matrix;
             geoPoint.createLocalToWorld( matrix );
@@ -144,21 +171,28 @@ public:
             }
         }
 
-        if(_options.lodScale().isSet())
-        {
-            LODScaleOverrideNode * node = new LODScaleOverrideNode;
-            node->setLODScale(_options.lodScale().value());
-            node->addChild(result.release());
-            result = node;
-        }
-
         // generate a shader program to render the model.
         if ( result.valid() )
         {
+            if(_options.loadingPriorityScale().isSet() || _options.loadingPriorityOffset().isSet())
+            {
+                SetLoadPriorityVisitor slpv(_options.loadingPriorityScale().value(), _options.loadingPriorityOffset().value());
+                result->accept(slpv);
+            }
+    
+            if(_options.lodScale().isSet())
+            {
+                LODScaleOverrideNode * node = new LODScaleOverrideNode;
+                node->setLODScale(_options.lodScale().value());
+                node->addChild(result.release());
+                result = node;
+            }
+
             if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE )
             {
                 ShaderGenerator gen;
-                result->accept( gen );
+                gen.setProgramName( "osgEarth.SimpleModelSource" );
+                gen.run( result );
             }
             else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE )
             {
@@ -168,6 +202,7 @@ public:
             }
         }
 
+
         return result.release();
     }
 
diff --git a/src/osgEarthDrivers/noise/CMakeLists.txt b/src/osgEarthDrivers/noise/CMakeLists.txt
new file mode 100644
index 0000000..e7a6129
--- /dev/null
+++ b/src/osgEarthDrivers/noise/CMakeLists.txt
@@ -0,0 +1,14 @@
+INCLUDE_DIRECTORIES( ${LIBNOISE_INCLUDE_DIR} )
+
+SET(TARGET_SRC ReaderWriterNoise.cpp)
+SET(TARGET_H NoiseOptions)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} ${LIBNOISE_LIBRARY})
+
+SETUP_PLUGIN(osgearth_noise)
+
+# to install public driver includes:
+SET(LIB_NAME noise)
+SET(LIB_PUBLIC_HEADERS NoiseOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/noise/NoiseOptions b/src/osgEarthDrivers/noise/NoiseOptions
new file mode 100644
index 0000000..addca2e
--- /dev/null
+++ b/src/osgEarthDrivers/noise/NoiseOptions
@@ -0,0 +1,220 @@
+/* -*-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_NOISE_DRIVEROPTIONS
+#define OSGEARTH_DRIVER_NOISE_DRIVEROPTIONS 1
+
+#include <osgEarth/Common>
+#include <osgEarth/TileSource>
+
+namespace osgEarth { namespace Drivers
+{
+    using namespace osgEarth;
+
+    /**
+     * Options for the Noise driver
+     * See http://libnoise.sourceforge.net/docs/classnoise_1_1module_1_1Perlin.html for documentation
+     * on specific noise settings.
+     */
+    class NoiseOptions : public TileSourceOptions // NO EXPORT; header only
+    {
+    public:
+
+        /**
+         * Resolution is the geogrphic range over which to generate one
+         * complete cycle the noise. At each end of the cycle, the noise
+         * value will be zero.
+         *
+         * Resolution is simply the reciprocal of the frequency property,
+         * but the concept is a little more intuitive in the context of maps
+         * since it refers to a real-world distance. Therefore, you only 
+         * need to specify one or the other.
+         * 
+         * Within this resoltuion range, you can control the extent of the 
+         * noise values by using the "scale" property.
+         */
+        optional<double>& resolution() { return _resolution; }
+        const optional<double>& resolution() const { return _resolution; }
+
+        /**
+         * Scale factor for elevation noise. For heightfields or normal maps,
+         * use this to map noise values [-1..1] to elevations [-1*scale..1*scale]
+         * across your resolution span.
+         *
+         * For example, if your resolution is 250m, a scale of 20 means:
+         * "vary the elevation values by +/-20m across that 250m span, with a
+         * an offset of zero at each endpoint."
+         */
+        optional<double>& scale() { return _scale; }
+        const optional<double>& scale() const { return _scale; } 
+
+        /**
+         * Bias (offset) for elevation noise. For heightfields or normal maps,
+         * use this to offset scaled elevation values by a fixed amount.
+         * (elevation = bias + scale * [-1..1]).
+         */
+        optional<double>& bias() { return _bias; }
+        const optional<double>& bias() const { return _bias; }
+
+        /**
+         * Levels of detail in the output of the noise function. Over a
+         * span of [resolution], the noise function will recurse this many
+         * times within that span. Increase this value to get more detail
+         * the further you zoom in. Use the persistence and lacunarity
+         * properties to tweak how that detail gets added.
+         *
+         * See: http://libnoise.sourceforge.net/glossary/index.html#octave
+         */
+        optional<int>& octaves() { return _octaves; }
+        const optional<int>& octaves() const { return _octaves; }
+
+        /**
+         * The base frequency of the noise generator; i.e. how often the 
+         * noise pattern resets across the map. A frequency of 1.0/map_width
+         * will cause the noise function to reset once across the entire
+         * width of the map. So in general, a frequency of "1.0/x" will
+         * cause to function to reset every X meters.
+         *
+         * Frequency is the reciprocal of the resolution property. You only
+         * need to set one or the other.
+         */
+        optional<double>& frequency() { return _frequency; }
+        const optional<double>& frequency() const { return _frequency; }
+
+        /**
+         * When using multiple octaves, the Persistence is the factor by
+         * which the "scale" of the noise function decreases with each successive
+         * octave.
+         * I.e.: Amp(Oct2) = Amp(Oct1) * Persistance.
+         * Default = 0.5.
+         */
+        optional<double>& persistence() { return _persistence; }
+        const optional<double>& persistence() const { return _persistence; }
+
+        /**
+         * When using multiple octaves, Lacunarity is the factor by which
+         * frequency increases with each successive octave:
+         * I.e.: Freq(Oct2) = Freq(Oct1) * Lacunarity.
+         * Default = 2.0.
+         */
+        optional<double>& lacunarity() { return _lacunarity; }
+        const optional<double>& lacunarity() const { return _lacunarity; } 
+
+        /**
+         * Seed value for the noise function's random number generator.
+         * Set this to ensure the exact same noise data each time you run.
+         */
+        optional<int>& seed() { return _seed; }
+        const optional<int>& seed() const { return _seed; }
+
+        /**
+         * Whether to convert the image into a normal map (images only)
+         * Default = false.
+         */
+        optional<bool>& normalMap() { return _normalMap; }
+        const optional<bool>& normalMap() const { return _normalMap; }
+
+        /**
+         * For height fields, clamp the minimum elevation to this value.
+         */
+        optional<float>& minElevation() { return _minElevation; }
+        const optional<float>& minElevation() const { return _minElevation; }
+
+        /**
+         * For height fields, clamp the maximum elevation to this value.
+         */
+        optional<float>& maxElevation() { return _maxElevation; }
+        const optional<float>& maxElevation() const { return _maxElevation; }
+
+
+    public:
+        NoiseOptions( const TileSourceOptions& opt =TileSourceOptions() ) :
+            TileSourceOptions( opt ),
+            _minElevation(-FLT_MAX),
+            _maxElevation( FLT_MAX),
+            _normalMap   (false),
+            _scale       (1.0),
+            _bias        (0.0),
+            _octaves     (3),
+            _resolution  (1.0),
+            _frequency   (1.0),
+            _persistence (0.5),
+            _lacunarity  (2.0)
+        {
+            setDriver( "noise" );
+            fromConfig( _conf );
+        }
+
+        /** dtor */
+        virtual ~NoiseOptions() { }
+
+    public:
+        Config getConfig() const {
+            Config conf = TileSourceOptions::getConfig();
+            conf.updateIfSet("min_elevation", _minElevation);
+            conf.updateIfSet("max_elevation", _maxElevation);
+            conf.updateIfSet("octaves", _octaves);
+            conf.updateIfSet("resolution", _resolution);
+            conf.updateIfSet("frequency", _frequency);
+            conf.updateIfSet("persistence", _persistence);
+            conf.updateIfSet("lacunarity", _lacunarity);
+            conf.updateIfSet("seed", _seed);
+            conf.updateIfSet("normal_map", _normalMap);
+            conf.updateIfSet("scale", _scale );
+            conf.updateIfSet("bias", _bias );
+            return conf;
+        }
+
+    protected:
+        void mergeConfig( const Config& conf ) {
+            TileSourceOptions::mergeConfig( conf );
+            fromConfig( conf );
+        }
+
+    private:
+        void fromConfig( const Config& conf ) {
+            conf.getIfSet( "min_elevation", _minElevation );
+            conf.getIfSet( "max_elevation", _maxElevation );
+            conf.getIfSet( "octaves", _octaves );
+            conf.getIfSet( "resolution", _resolution);
+            conf.getIfSet( "frequency", _frequency );
+            conf.getIfSet( "persistence", _persistence );
+            conf.getIfSet( "lacunarity", _lacunarity );
+            conf.getIfSet( "seed", _seed );
+            conf.getIfSet( "normal_map", _normalMap );
+            conf.getIfSet( "scale", _scale );
+            conf.getIfSet( "bias", _bias );
+        }
+
+        optional<float>  _minElevation;
+        optional<float>  _maxElevation;
+        optional<int>    _octaves;
+        optional<double> _resolution;
+        optional<double> _frequency;
+        optional<double> _persistence;
+        optional<double> _lacunarity;
+        optional<int>    _seed;
+        optional<bool>   _normalMap;
+        optional<double> _scale;
+        optional<double> _bias;
+    };
+
+} } // namespace osgEarth::Drivers
+
+#endif // OSGEARTH_DRIVER_NOISE_DRIVEROPTIONS
+
diff --git a/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp b/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp
new file mode 100644
index 0000000..d25857b
--- /dev/null
+++ b/src/osgEarthDrivers/noise/ReaderWriterNoise.cpp
@@ -0,0 +1,318 @@
+/* -*-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/TileSource>
+#include <osgEarth/Registry>
+#include <osgEarth/URI>
+#include <osgEarth/ImageUtils>
+
+#include <osg/Notify>
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
+#include <sstream>
+
+#include <noise/noise.h>
+
+using namespace noise;
+
+#include "NoiseOptions"
+
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+
+class NoiseSource : public TileSource
+{
+public:
+    NoiseSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
+    {
+        //nop
+    }
+
+    // Yahoo! uses spherical mercator, but the top LOD is a 2x2 tile set.
+    Status initialize(const osgDB::Options* dbOptions)
+    {
+        // no caching of source tiles (there are none..)
+        _dbOptions = Registry::instance()->cloneOrCreateOptions( dbOptions );
+        CachePolicy::NO_CACHE.apply( _dbOptions.get() );
+        setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
+
+        // resolve frequency if the user set resolution
+        if (_options.resolution().isSet() && !_options.resolution().isSetTo(0.0))
+        {
+            _options.frequency().init( 1.0 / *_options.resolution() );
+        }
+
+        return STATUS_OK;
+    }
+    
+    /** Tell the terrain engine not to cache tiles form this source by default. */
+    CachePolicy getCachePolicyHint(const Profile*) const
+    {
+        return CachePolicy::NO_CACHE;
+    }
+
+    inline double sample(module::Perlin& noise, double x, double y, double z)
+    {
+        return noise.GetValue(x, y, z);
+    }
+
+    inline double sample(module::Perlin& noise, const osg::Vec3d& v)
+    {
+        return noise.GetValue(v.x(), v.y(), v.z());
+    }
+
+    inline double turbulence(module::Perlin& noise, const osg::Vec3d& v, double f )
+    {
+        double t = -0.5;
+        for( ; f<getPixelsPerTile()/2; f *= 2 ) 
+            t += abs(noise.GetValue(v.x(), v.y(), v.z())/f);
+        return t;
+    }
+
+    inline double stripes(double x, double f)
+    {
+        double t = 0.5 + 0.5 * asin(f * 2*osg::PI * x);
+        return t * t - 0.5;
+    }
+
+
+    osg::Image* createImage(const TileKey&        key,
+                            ProgressCallback*     progress )
+    {
+        if ( _options.normalMap() == true )
+        {
+            return createNormalMap(key, progress);
+        }
+        else
+        {
+            module::Perlin noise;
+            noise.SetFrequency  ( _options.frequency().get() );
+            noise.SetPersistence( _options.persistence().get() );
+            noise.SetLacunarity ( _options.lacunarity().get() );
+            noise.SetOctaveCount( _options.octaves().get() );
+
+            const SpatialReference* srs = key.getProfile()->getSRS();
+
+            osg::Image* image = new osg::Image();
+            image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
+
+            double dx = key.getExtent().width()  / (double)(image->s()-1);
+            double dy = key.getExtent().height() / (double)(image->t()-1);
+
+            ImageUtils::PixelWriter write(image);
+            for(int s=0; s<image->s(); ++s)
+            {
+                for(int t=0; t<image->t(); ++t)
+                {
+                    double lon = key.getExtent().xMin() + (double)s * dx;
+                    double lat = key.getExtent().yMin() + (double)t * dy;
+
+                    osg::Vec3d world(lon, lat, 0.0);
+                    if ( srs->isGeographic() )
+                        srs->transform(world, srs->getECEF(), world);
+
+                    double n = noise.GetValue(world.x(), world.y(), world.z());
+                    //world.normalize();
+                    //double n = 0.1 * stripes(world.x() + 2.0*turbulence(noise, world, 1.0), 1.6);
+                    //double n = -.10 * turbulence(noise, world, 0.2);
+
+                    // scale and bias from[-1..1] to [0..1] for coloring. It should be noted that
+                    // the Perlin noise function can generate values outside this range, hence
+                    // the clamp!
+                    n = osg::clampBetween( (n+1.0)*0.5, 0.0, 1.0 );
+
+                    write(osg::Vec4f(n,n,n,1), s, t);
+                }
+            }
+
+            return image;
+        }
+    }
+
+
+    osg::HeightField* createHeightField(const TileKey&        key,
+                                        ProgressCallback*     progress )
+    {
+        module::Perlin noise;
+        noise.SetFrequency  ( _options.frequency().get() );
+        noise.SetPersistence( _options.persistence().get() );
+        noise.SetLacunarity ( _options.lacunarity().get() );
+        noise.SetOctaveCount( _options.octaves().get() );
+
+        const SpatialReference* srs = key.getProfile()->getSRS();
+
+        osg::HeightField* hf = new osg::HeightField();
+        hf->allocate( getPixelsPerTile(), getPixelsPerTile() );
+
+        double dx = key.getExtent().width() / (double)(hf->getNumColumns()-1);
+        double dy = key.getExtent().height() / (double)(hf->getNumRows()-1);
+
+        double bias  = _options.bias().get();
+        double scale = _options.scale().get();
+
+        //Initialize the heightfield
+        for (unsigned int c = 0; c < hf->getNumColumns(); c++) 
+        {
+            for (unsigned int r = 0; r < hf->getNumRows(); r++)
+            {                
+                double lon = key.getExtent().xMin() + (double)c * dx;
+                double lat = key.getExtent().yMin() + (double)r * dy;
+
+                osg::Vec3d world(lon, lat, 0.0);
+                if ( srs->isGeographic() )
+                    srs->transform(world, srs->getECEF(), world);
+
+                double n = noise.GetValue(world.x(), world.y(), world.z());
+
+                // Scale the noise value which is between -1 and 1...ish
+                double h = osg::clampBetween(
+                    (float)(bias + scale * n),
+                    *_options.minElevation(),
+                    *_options.maxElevation() );
+
+                hf->setHeight( c, r, h );
+
+                // NOTE! The elevation engine treats extreme values (>32000, etc)
+                // as "no data" so be careful with your scale.
+            }
+        }     
+
+        return hf;
+    }
+
+
+    osg::Image* createNormalMap(const TileKey& key, ProgressCallback* progress)
+    {
+        module::Perlin noise;
+        noise.SetFrequency  ( _options.frequency().get() );
+        noise.SetPersistence( _options.persistence().get() );
+        noise.SetLacunarity ( _options.lacunarity().get() );
+        noise.SetOctaveCount( _options.octaves().get() );
+
+        // set up the image and prepare to write to it.
+        osg::Image* image = new osg::Image();
+        image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGB, GL_UNSIGNED_BYTE );
+        ImageUtils::PixelWriter write(image);
+
+        const GeoExtent&        ex     = key.getExtent();
+        const SpatialReference* srs    = ex.getSRS();
+        bool                    isGeo  = srs->isGeographic();
+        const SpatialReference* ecef   = srs->getECEF();
+
+        double dx = ex.width()  / (double)(image->s()-1);
+        double dy = ex.height() / (double)(image->t()-1);
+
+        double scale  = _options.scale().get();
+        double bias   = _options.bias().get();
+
+        // figure out the spacing between pixels in the same units as the height value:
+        double udx = dx;
+        double udy = dy;
+        if ( isGeo )
+        {
+            udx = srs->transformUnits(dx, ecef, ex.south()+0.5*dy);
+            udy = srs->transformUnits(dy, ecef, ex.south()+0.5*dy);
+        }
+
+        double z = 0.0;
+        std::vector<osg::Vec3d> v(4);
+        double samples[4];
+
+        for(int s=0; s<image->s(); ++s)
+        {
+            for(int t=0; t<image->t(); ++t)
+            {
+                double x = ex.xMin() + (double)s * dx;
+                double y = ex.yMin() + (double)t * dy;
+
+                if ( isGeo )
+                {
+                    v[0].set(x-dx, y, z);
+                    v[1].set(x+dx, y, z);
+                    v[2].set(x, y+dy, z);
+                    v[3].set(x, y-dy, z);
+                    srs->transform(v, ecef);
+                    for(int i=0; i<4; ++i )
+                        samples[i] = bias + scale * sample(noise, v[i]);
+                }
+                else
+                {
+                    samples[0] = bias + scale * sample(noise, osg::Vec3d(x-dx, y, z));
+                    samples[1] = bias + scale * sample(noise, osg::Vec3d(x+dx, y, z));
+                    samples[2] = bias + scale * sample(noise, osg::Vec3d(x, y+dy, z));
+                    samples[3] = bias + scale * sample(noise, osg::Vec3d(x, y-dy, z));
+                }
+
+                osg::Vec3d west (-udx,    0, samples[0]);
+                osg::Vec3d east ( udx,    0, samples[1]);
+                osg::Vec3d north(   0,  udy, samples[2]);
+                osg::Vec3d south(   0, -udy, samples[3]);
+
+                // calculate the normal at the center point.
+                osg::Vec3 normal = (east-west) ^ (north-south);
+                normal.normalize();
+
+                // encode as: xyz[-1..1]=>r[0..255]. (In reality Z will always fall 
+                // between [0..1] but a uniform encoding makes the shader code simpler.)
+                normal.x() = normal.x()*0.5f + 0.5f;
+                normal.y() = normal.y()*0.5f + 0.5f;
+                normal.z() = normal.z()*0.5f + 0.5f;
+                normal.normalize();
+
+                write(osg::Vec4f(normal,1), s, t);
+            }
+        }
+
+        return image;
+    }
+
+
+private:
+    NoiseOptions                 _options;
+    osg::ref_ptr<osgDB::Options> _dbOptions;
+};
+
+
+class ReaderWriterNoise : public TileSourceDriver
+{
+    public:
+        ReaderWriterNoise()
+        {
+            supportsExtension( "osgearth_noise", "Procedurally generated terrain" );
+        }
+
+        virtual const char* className()
+        {
+            return "Noise ReaderWriter";
+        }
+
+        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
+        {
+            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
+                return ReadResult::FILE_NOT_HANDLED;
+
+            return new NoiseSource( getTileSourceOptions(options) );
+        }
+};
+
+REGISTER_OSGPLUGIN(osgearth_noise, ReaderWriterNoise)
+
diff --git a/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp b/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp
index 792cb35..1bcd43d 100644
--- a/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp
+++ b/src/osgEarthDrivers/ocean_surface/OceanCompositor.cpp
@@ -42,15 +42,10 @@ void
 OceanCompositor::updateMasterStateSet(osg::StateSet*       stateSet, 
                                       const TextureLayout& layout ) const
 {
-    VirtualProgram* vp = static_cast<VirtualProgram*>( stateSet->getAttribute(VirtualProgram::SA_TYPE) );
-    if ( !vp )
-    {
-        vp = new VirtualProgram();
-        vp->setName("osgEarth OceanCompositor");
-        stateSet->setAttributeAndModes( vp, 1 );
-    }
-    
-    //vp->installDefaultLightingShaders();
+    VirtualProgram* vp = VirtualProgram::getOrCreate( stateSet );
+    vp->setName( "osgEarth.OceanCompositor" );
+
+    // install a default lighting shader
     Registry::shaderFactory()->installLightingShaders( vp );
 
     // use the appropriate shader for the active technique:
@@ -58,19 +53,8 @@ OceanCompositor::updateMasterStateSet(osg::StateSet*       stateSet,
     std::string fragSource = _options.maskLayer().isSet() ? source_fragMask : source_fragProxy;
 
     vp->setFunction( "oe_ocean_vertex",   vertSource, ShaderComp::LOCATION_VERTEX_VIEW );
-
     vp->setFunction( "oe_ocean_fragment", fragSource, ShaderComp::LOCATION_FRAGMENT_COLORING );
 
-    //vp->setShader( 
-    //    "osgearth_vert_setupColoring", 
-    //    new osg::Shader(osg::Shader::VERTEX, vertSource),
-    //    osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-
-    //vp->setShader( 
-    //    "osgearth_frag_applyColoring", 
-    //    new osg::Shader(osg::Shader::FRAGMENT, fragSource),
-    //    osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
-
     // install the slot attribute(s)
     stateSet->getOrCreateUniform( OCEAN_DATA, osg::Uniform::SAMPLER_2D )->set( 0 );
     stateSet->getOrCreateUniform( OCEAN_TEX,  osg::Uniform::SAMPLER_2D )->set( 1 );
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt b/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
new file mode 100644
index 0000000..a7b8cd0
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/CMakeLists.txt
@@ -0,0 +1,23 @@
+
+INCLUDE_DIRECTORIES( ${JAVASCRIPTCORE_INCLUDE_DIR} )
+
+SET(TARGET_SRC
+    JavaScriptCoreEngineFactory.cpp
+    JavaScriptCoreEngine.cpp
+    JSWrappers.cpp
+)
+
+SET(TARGET_H
+    JavaScriptCoreEngine
+    JSWrappers
+)
+
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} ${JAVASCRIPTCORE_LIBRARY} osgEarthFeatures osgEarthSymbology)
+
+SETUP_PLUGIN(osgearth_scriptengine_javascriptcore)
+
+# to install public driver includes:
+SET(LIB_NAME scriptengine_javascriptcore)
+SET(LIB_PUBLIC_HEADERS ${TARGET_H} )
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarth/MaskNode.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers
similarity index 70%
copy from src/osgEarth/MaskNode.cpp
copy to src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers
index f67b644..f3dbeae 100644
--- a/src/osgEarth/MaskNode.cpp
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers
@@ -16,21 +16,18 @@
  * You 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/MaskNode>
 
-using namespace osgEarth;
+#ifndef OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H
+#define OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H 1
 
-MaskNode::MaskNode()
-{
-    //nop
-}
+#import <JavaScriptCore/JavaScriptCore.h>
 
-MaskNode::MaskNode( const MaskNode& rhs, const osg::CopyOp& op )
-: osg::Group( rhs, op )
+class JSUtils
 {
-    //nop
-}
-
-
+public:
+    static char* JSStringRef_to_CharArray(JSStringRef jsString);
+};
 
+JSClassRef JSFeature_class(JSContextRef ctx);
 
+#endif // OSGEARTHDRIVERS_JAVASCRIPTCORE_JSWRAPPERS_H
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp
new file mode 100644
index 0000000..76899c3
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/JSWrappers.cpp
@@ -0,0 +1,87 @@
+//
+//  JSWrappers.cpp
+//  OSGEARTH
+//
+//  Created by Jeff Smith on 5/22/13.
+//
+//
+
+#include "JSWrappers"
+#include <osgEarthFeatures/Feature>
+#include <osgEarth/Notify>
+
+
+char* JSUtils::JSStringRef_to_CharArray(JSStringRef jsString)
+{
+    char* buf = 0L;
+    int len = JSStringGetLength(jsString) + 1;
+    buf = (char*) malloc(len*sizeof(char));
+    JSStringGetUTF8CString(jsString, buf, len);
+    
+    return buf;
+}
+
+// --------------------------------------------------------------------------
+// JSFeature
+
+static void JSFeature_initialize(JSContextRef ctx, JSObjectRef object)
+{
+    //NOP
+}
+
+static void JSFeature_finalize(JSObjectRef object)
+{
+    //NOP
+}
+
+static JSValueRef JSFeature_getProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
+{
+    osgEarth::Features::Feature *feature = static_cast<osgEarth::Features::Feature*>(JSObjectGetPrivate(object));
+
+    char* attrBuf = JSUtils::JSStringRef_to_CharArray(propertyName);
+    if (attrBuf)
+    {
+        std::string attr(attrBuf);
+        delete [] attrBuf;
+        
+        if (attr == "attributes" || attr == "attrs")
+        {
+            return object;
+        }
+        
+        osgEarth::Features::AttributeTable::const_iterator it = feature->getAttrs().find(attr);
+        if (it != feature->getAttrs().end())
+        {
+            osgEarth::Features::AttributeType atype = (*it).second.first;
+            switch (atype)
+            {
+                case osgEarth::Features::ATTRTYPE_BOOL:
+                    return JSValueMakeBoolean(ctx, (*it).second.getBool());
+                case osgEarth::Features::ATTRTYPE_DOUBLE:
+                    return JSValueMakeNumber(ctx, (*it).second.getDouble());
+                case osgEarth::Features::ATTRTYPE_INT:
+                    return JSValueMakeNumber(ctx, (*it).second.getInt());
+                default:
+                    return JSValueMakeString(ctx, JSStringCreateWithUTF8CString((*it).second.getString().c_str()));
+            }
+        }
+    }
+
+    return JSValueMakeNull(ctx);
+}
+
+//Caches and returns the JSShape class.
+JSClassRef JSFeature_class(JSContextRef ctx)
+{
+    static JSClassRef jsClass;
+    if (!jsClass) {
+        JSClassDefinition classDefinition = kJSClassDefinitionEmpty;
+        classDefinition.initialize = JSFeature_initialize;
+        classDefinition.finalize = JSFeature_finalize;
+        classDefinition.getProperty = JSFeature_getProperty;
+        
+        jsClass = JSClassCreate(&classDefinition);
+    }
+    
+    return jsClass;
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8 b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine
similarity index 56%
copy from src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
copy to src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine
index efdaff2..02d1678 100644
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine
@@ -17,26 +17,26 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 
-#ifndef OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H
-#define OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H 1
+#ifndef OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H
+#define OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H 1
 
 #include <osgEarth/StringUtils>
 #include <osgEarthFeatures/Feature>
 #include <osgEarthFeatures/Script>
 #include <osgEarthFeatures/ScriptEngine>
 
-#include <v8.h>
+#include <JavaScriptCore/JavaScriptCore.h>
 
-//namespace osgEarth { namespace Drivers { namespace JavascriptV8
+//namespace osgEarth { namespace Drivers { namespace JavaScriptCore
 //{
 
   using namespace osgEarth::Features;
 
-  class JavascriptEngineV8 : public ScriptEngine
+  class JavaScriptCoreEngine : public ScriptEngine
   {
   public:
-    JavascriptEngineV8(const ScriptEngineOptions& options =ScriptEngineOptions());
-    virtual ~JavascriptEngineV8();
+    JavaScriptCoreEngine(const ScriptEngineOptions& options =ScriptEngineOptions());
+    virtual ~JavaScriptCoreEngine();
 
     bool supported(std::string lang) { return osgEarth::toLower(lang).compare("javascript") == 0; }
     bool supported(Script* script) { return script && supported(script->getLanguage()); }
@@ -47,26 +47,13 @@
     ScriptResult call(const std::string& function, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
 
   protected:
-    static v8::Handle<v8::Value> logCallback(const v8::Arguments& args);
-    //static v8::Handle<v8::Value> constructFeatureCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructBoundsCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructVec3dCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructGeoExtentCallback(const v8::Arguments &args);
-
-    static v8::Handle<v8::Value> constructSpatialReferenceCallback(const v8::Arguments &args);
-    //static v8::Handle<v8::Value> constructSymbologyGeometryCallback(const v8::Arguments &args);
-
-    v8::Local<v8::ObjectTemplate> createGlobalObjectTemplate();
-
     /** Compiles and runs javascript in the current context. */
-    ScriptResult executeScript(v8::Handle<v8::String> script);
+    ScriptResult executeScript(const std::string& script);
 
   protected:
-    v8::Persistent<v8::ObjectTemplate> _globalTemplate;
-    v8::Persistent<v8::Context> _globalContext;
-    v8::Isolate* _isolate;
+    JSGlobalContextRef _ctx;
   };
 
-//} } } // namespace osgEarth::Drivers::JavascriptV8
+//} } } // namespace osgEarth::Drivers::JavaScriptCore
 
-#endif // OSGEARTHDRIVERS_JAVASCRIPT_ENGINE_V8_H
+#endif // OSGEARTHDRIVERS_JAVASCRIPTCORE_ENGINE_H
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp
new file mode 100644
index 0000000..1ce5a33
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngine.cpp
@@ -0,0 +1,126 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2013 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "JavaScriptCoreEngine"
+#include "JSWrappers"
+
+#include <osgEarthFeatures/Script>
+#include <osgEarthFeatures/ScriptEngine>
+#include <osgEarth/Notify>
+#include <osgEarth/StringUtils>
+
+#include <JavaScriptCore/JavaScriptCore.h>
+
+using namespace osgEarth;
+using namespace osgEarth::Features;
+
+#define LC "[JavaScriptCoreEngine] "
+
+
+//----------------------------------------------------------------------------
+
+JavaScriptCoreEngine::JavaScriptCoreEngine(const ScriptEngineOptions& options)
+: ScriptEngine(options)
+{
+    // Create JavaScript execution context.
+    _ctx = JSGlobalContextCreate(NULL);
+    
+  if (options.script().isSet() && !options.script()->getCode().empty())
+  {
+    // Compile and run the script
+    ScriptResult result = executeScript(options.script()->getCode());
+    if (!result.success())
+      OE_WARN << LC << "Error reading javascript: " << result.message() << std::endl;
+  }
+}
+
+JavaScriptCoreEngine::~JavaScriptCoreEngine()
+{
+    // Release JavaScript execution context.
+    JSGlobalContextRelease(_ctx);
+}
+
+ScriptResult
+JavaScriptCoreEngine::executeScript(const std::string& script)
+ {   
+    // Evaluate script.
+    JSStringRef scriptJS = JSStringCreateWithUTF8CString(script.c_str());
+    JSValueRef result = JSEvaluateScript(_ctx, scriptJS, NULL, NULL, 0, NULL);
+    JSStringRelease(scriptJS);
+    
+    // Convert result to string, unless result is NULL.
+    char* buf = 0L;
+    if (result) {
+        JSStringRef resultStringJS = JSValueToStringCopy(_ctx, result, NULL);
+        buf = JSUtils::JSStringRef_to_CharArray(resultStringJS);
+        JSStringRelease(resultStringJS);
+    }
+    
+    if (buf)
+    {
+        std::string resultStr(buf);
+        delete [] buf;
+
+        return ScriptResult(resultStr);
+    }
+
+  return ScriptResult("");
+}
+
+ScriptResult
+JavaScriptCoreEngine::run(Script* script, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
+{
+  if (!script)
+    return ScriptResult(EMPTY_STRING, false, "Script is null.");
+
+  return run(script->getCode(), feature, context);
+}
+
+ScriptResult
+JavaScriptCoreEngine::run(const std::string& code, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
+{
+  if (code.empty())
+    return ScriptResult(EMPTY_STRING, false, "Script is empty.");
+
+
+  if (feature)
+  {
+      JSStringRef featureStr = JSStringCreateWithUTF8CString("feature");
+      JSObjectRef jsFeature = JSObjectMake(_ctx, JSFeature_class(_ctx), const_cast<osgEarth::Features::Feature*>(feature));
+      JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), featureStr, jsFeature, kJSPropertyAttributeNone, NULL);
+  }
+
+    
+  //TODO: Wrap FilterContext and set as global property
+  //if (context)
+  //{
+  //}
+
+    
+  // Compile and run the script
+  ScriptResult result = executeScript(code);
+
+  return result;
+}
+
+ScriptResult
+JavaScriptCoreEngine::call(const std::string& function, osgEarth::Features::Feature const* feature, osgEarth::Features::FilterContext const* context)
+{
+    return ScriptResult("");
+}
diff --git a/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp
new file mode 100644
index 0000000..1368b0d
--- /dev/null
+++ b/src/osgEarthDrivers/script_engine_javascriptcore/JavaScriptCoreEngineFactory.cpp
@@ -0,0 +1,50 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+ * Copyright 2008-2013 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+#include <osgDB/ReaderWriter>
+#include "JavaScriptCoreEngine"
+#include <osgEarthFeatures/ScriptEngine>
+#include <osgEarth/Common>
+#include <osgEarth/Config>
+#include <osgDB/FileNameUtils>
+
+
+class JavaScriptCoreEngineFactory : public osgEarth::Features::ScriptEngineDriver
+{
+public:
+    JavaScriptCoreEngineFactory()
+    {
+        supportsExtension( "osgearth_scriptengine_javascript", "osgEarth scriptengine javascript plugin" );
+        supportsExtension( "osgearth_scriptengine_javascript_javascriptcore", "osgEarth scriptengine javascript JavaScriptCore plugin" );
+    }
+
+    virtual const char* className()
+    {
+        return "osgEarth ScriptEngine Javascript JavaScriptCore Plugin";
+    }
+
+    virtual ReadResult readObject(const std::string& file_name, const osgDB::ReaderWriter::Options* options) const
+    {
+      if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )) )
+            return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
+
+        return osgDB::ReaderWriter::ReadResult( new JavaScriptCoreEngine( getScriptEngineOptions(options) ) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_scriptengine_javascriptcore, JavaScriptCoreEngineFactory)
diff --git a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
index 7c1ab93..ac095af 100644
--- a/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
+++ b/src/osgEarthDrivers/script_engine_v8/CMakeLists.txt
@@ -13,7 +13,9 @@ SET(TARGET_H
     V8Util
 )
 
-SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} ${V8_LIBRARY} osgEarthFeatures)
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures)
+
+SET(TARGET_LIBRARIES_VARS ${TARGET_LIBRARIES_VARS} V8_BASE_LIBRARY V8_SNAPSHOT_LIBRARY V8_ICUUC_LIBRARY V8_ICUI18N_LIBRARY)
 
 SETUP_PLUGIN(osgearth_scriptengine_javascript)
 
diff --git a/src/osgEarthDrivers/script_engine_v8/JSWrappers b/src/osgEarthDrivers/script_engine_v8/JSWrappers
index cee4554..a8597bd 100644
--- a/src/osgEarthDrivers/script_engine_v8/JSWrappers
+++ b/src/osgEarthDrivers/script_engine_v8/JSWrappers
@@ -32,20 +32,20 @@
 class JSFeature
 {
 public:
-  static v8::Handle<v8::Object> WrapFeature(osgEarth::Features::Feature* feature, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapFeature(v8::Isolate* isolate, osgEarth::Features::Feature* feature, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::ObjectTemplate> GetAttributesObjectTemplate();
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static v8::Handle<v8::ObjectTemplate> GetAttributesObjectTemplate(v8::Isolate* isolate);
 
   static v8::Handle<v8::Value> GetFeatureAttr(const std::string& attr, osgEarth::Features::Feature const* feature);
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
-  static v8::Handle<v8::Value> AttrPropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
+  static void AttrPropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static void FreeFeatureCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeFeatureCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Feature* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -53,17 +53,17 @@ protected:
 class JSSymbologyGeometry
 {
 public:
-  static v8::Handle<v8::Object> WrapGeometry(osgEarth::Symbology::Geometry* geometry, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapGeometry(v8::Isolate* isolate, osgEarth::Symbology::Geometry* geometry, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
-  static v8::Handle<v8::Value> IndexedPropertyCallback(uint32_t index, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
+  static void IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static void FreeGeometryCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeGeometryCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Symbology::Geometry* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -71,20 +71,20 @@ protected:
 class JSBounds
 {
 public:
-  static v8::Handle<v8::Object> WrapBounds(osgEarth::Bounds* bounds, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapBounds(v8::Isolate* isolate, osgEarth::Bounds* bounds, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
   
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static v8::Handle<v8::Value> ContainsCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> UnionCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> IntersectionCallback(const v8::Arguments& args);
+  static void ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void UnionCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void IntersectionCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 
-  static void FreeBoundsCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeBoundsCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Bounds* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -92,17 +92,17 @@ protected:
 class JSVec3d
 {
 public:
-  static v8::Handle<v8::Object> WrapVec3d(osg::Vec3d* vec, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapVec3d(v8::Isolate* isolate, osg::Vec3d* vec, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
-  static v8::Handle<v8::Value> IndexedPropertyCallback(uint32_t index, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
+  static void IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static void FreeVecCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeVecCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osg::Vec3d* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -110,21 +110,21 @@ protected:
 class JSFilterContext
 {
 public:
-  static v8::Handle<v8::Object> WrapFilterContext(osgEarth::Features::FilterContext* context, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapFilterContext(v8::Isolate* isolate, osgEarth::Features::FilterContext* context, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static v8::Handle<v8::Value> ToLocalCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> ToWorldCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> ToMapCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> FromMapCallback(const v8::Arguments& args);
+  static void ToLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void ToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void FromMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 
-  static void FreeContextCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeContextCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FilterContext* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -132,20 +132,20 @@ protected:
 class JSSession
 {
 public:
-  static v8::Handle<v8::Object> WrapSession(osgEarth::Features::Session* session, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapSession(v8::Isolate* isolate, osgEarth::Features::Session* session, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
 #if 0
-  static v8::Handle<v8::Value> ResolveUriCallback(const v8::Arguments& args);
+  static void ResolveUriCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 #endif
 
-  static void FreeSessionCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeSessionCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Session* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -153,22 +153,22 @@ protected:
 class JSMapInfo
 {
 public:
-  static v8::Handle<v8::Object> WrapMapInfo(osgEarth::MapInfo* mapInfo, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapMapInfo(v8::Isolate* isolate, osgEarth::MapInfo* mapInfo, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
 #if 0
-  static v8::Handle<v8::Value> ToMapCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> MapToWorldCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> WorldToMapCallback(const v8::Arguments& args);
+  static void ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void MapToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void WorldToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 #endif
 
-  static void FreeMapInfoCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeMapInfoCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::MapInfo* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -176,16 +176,16 @@ protected:
 class JSFeatureProfile
 {
 public:
-  static v8::Handle<v8::Object> WrapFeatureProfile(osgEarth::Features::FeatureProfile* context, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapFeatureProfile(v8::Isolate* isolate, osgEarth::Features::FeatureProfile* context, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static void FreeProfileCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeProfileCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FeatureProfile* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -193,19 +193,19 @@ protected:
 class JSGeoExtent
 {
 public:
-  static v8::Handle<v8::Object> WrapGeoExtent(osgEarth::GeoExtent* extent, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapGeoExtent(v8::Isolate* isolate, osgEarth::GeoExtent* extent, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static v8::Handle<v8::Value> ContainsCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> IntersectsCallback(const v8::Arguments& args);
+  static void ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void IntersectsCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 
-  static void FreeGeoExtentCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeGeoExtentCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::GeoExtent* parameter);
 };
 
 // ---------------------------------------------------------------------------
@@ -213,19 +213,19 @@ protected:
 class JSSpatialReference
 {
 public:
-  static v8::Handle<v8::Object> WrapSpatialReference(osgEarth::SpatialReference* srs, bool freeObject=false);
+  static v8::Handle<v8::Object> WrapSpatialReference(v8::Isolate* isolate, osgEarth::SpatialReference* srs, bool freeObject=false);
   static const std::string& GetObjectType() { return _objectType; }
 
 protected:
   static const std::string _objectType;
 
-  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate();
-  static v8::Handle<v8::Value> PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info);
+  static v8::Handle<v8::ObjectTemplate> GetObjectTemplate(v8::Isolate* isolate);
+  static void PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info);
 
-  static v8::Handle<v8::Value> EquivalenceCallback(const v8::Arguments& args);
-  static v8::Handle<v8::Value> TangentPlaneCallback(const v8::Arguments& args);
+  static void EquivalenceCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+  static void TangentPlaneCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
 
-  static void FreeSpatialReferenceCallback(v8::Persistent<v8::Value> object, void *parameter);
+  static void FreeSpatialReferenceCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::SpatialReference* parameter);
 };
 
 // ---------------------------------------------------------------------------
diff --git a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp b/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
index 1858e98..fb6201e 100644
--- a/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
+++ b/src/osgEarthDrivers/script_engine_v8/JSWrappers.cpp
@@ -29,9 +29,9 @@ using namespace osgEarth::Features;
 const std::string JSFeature::_objectType = "JSFeature";
 
 v8::Handle<v8::Object>
-JSFeature::WrapFeature(osgEarth::Features::Feature* feature, bool freeObject)
+JSFeature::WrapFeature(v8::Isolate* isolate, osgEarth::Features::Feature* feature, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   if (!feature)
   {
@@ -39,21 +39,22 @@ JSFeature::WrapFeature(osgEarth::Features::Feature* feature, bool freeObject)
     return handle_scope.Close(obj);
   }
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(feature, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, feature, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(feature, FreeFeatureCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(feature, &FreeFeatureCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSFeature::GetObjectTemplate()
+JSFeature::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> feat_instance = v8::ObjectTemplate::New();
 
@@ -65,9 +66,9 @@ JSFeature::GetObjectTemplate()
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSFeature::GetAttributesObjectTemplate()
+JSFeature::GetAttributesObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> attr_instance = v8::ObjectTemplate::New();
 
@@ -92,19 +93,25 @@ JSFeature::GetFeatureAttr(const std::string& attr, Feature const* feature)
   switch (atype)
   {
     case osgEarth::Features::ATTRTYPE_BOOL:
-      return v8::Boolean::New((*it).second.getBool());
+      return v8::Boolean::New((*it).second.second.set ? (*it).second.getBool() : false);
     case osgEarth::Features::ATTRTYPE_DOUBLE:
-      return v8::Number::New((*it).second.getDouble());
+      if ((*it).second.second.set)
+        return v8::Number::New((*it).second.getDouble());
+      else
+        return v8::Undefined();
     case osgEarth::Features::ATTRTYPE_INT:
-      return v8::Integer::New((*it).second.getInt());
+      if ((*it).second.second.set)
+        return v8::Integer::New((*it).second.getInt());
+      else
+        return v8::Undefined();
     default:
-      std::string val = (*it).second.getString();
+      std::string val = (*it).second.second.set ? (*it).second.getString() : "";
       return v8::String::New(val.c_str(), val.length());
   }
 }
 
-v8::Handle<v8::Value>
-JSFeature::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSFeature::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   Feature* feature = V8Util::UnwrapObject<Feature>(info.Holder());
 
@@ -112,21 +119,22 @@ JSFeature::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo&
   std::string prop(*utf8_value);
 
   if (!feature || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
 
+  v8::Local<v8::Value> value;
   if (prop == "fid")
-    return v8::Uint32::New(feature->getFID());
+    value = v8::Uint32::New(feature->getFID());
   else if (prop == "attrs" || prop == "attributes")
-    return V8Util::WrapObject(feature, GetAttributesObjectTemplate());
+    value = V8Util::WrapObject(v8::Isolate::GetCurrent(), feature, GetAttributesObjectTemplate(v8::Isolate::GetCurrent()));
   else if (prop == "geometry")
-    return JSSymbologyGeometry::WrapGeometry(feature->getGeometry());
+    value = JSSymbologyGeometry::WrapGeometry(v8::Isolate::GetCurrent(), feature->getGeometry());
 
-  //return GetFeatureAttr(prop, feature);
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSFeature::AttrPropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSFeature::AttrPropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   Feature* feature = V8Util::UnwrapObject<Feature>(info.Holder());
 
@@ -134,19 +142,18 @@ JSFeature::AttrPropertyCallback(v8::Local<v8::String> name, const v8::AccessorIn
   std::string attr(*utf8_value);
 
   if (!feature || attr.empty())
-    return v8::Handle<v8::Value>();
+    return;
 
-  return GetFeatureAttr(attr, feature);
+  v8::Local<v8::Value> value = GetFeatureAttr(attr, feature);
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSFeature::FreeFeatureCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSFeature::FreeFeatureCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Feature* parameter)
 {
-  Feature* feature = static_cast<Feature*>(parameter);
-  delete feature;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -154,25 +161,26 @@ JSFeature::FreeFeatureCallback(v8::Persistent<v8::Value> object, void *parameter
 const std::string JSSymbologyGeometry::_objectType = "JSSymbologyGeometry";
 
 v8::Handle<v8::Object>
-JSSymbologyGeometry::WrapGeometry(osgEarth::Symbology::Geometry* geometry, bool freeObject)
+JSSymbologyGeometry::WrapGeometry(v8::Isolate* isolate, osgEarth::Symbology::Geometry* geometry, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(geometry, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, geometry, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(geometry, FreeGeometryCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(geometry, &FreeGeometryCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSSymbologyGeometry::GetObjectTemplate()
+JSSymbologyGeometry::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -184,8 +192,8 @@ JSSymbologyGeometry::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSSymbologyGeometry::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSSymbologyGeometry::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::Symbology::Geometry* geom = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(info.Holder());
 
@@ -193,46 +201,49 @@ JSSymbologyGeometry::PropertyCallback(v8::Local<v8::String> name, const v8::Acce
   std::string prop(*utf8_value);
 
   if (!geom || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "totalPointCount")
-    return v8::Integer::New(geom->getTotalPointCount());
+    value = v8::Integer::New(geom->getTotalPointCount());
   else if (prop == "numComponents")
-    return v8::Uint32::New(geom->getNumComponents());
+    value = v8::Uint32::New(geom->getNumComponents());
   else if (prop == "bounds")
   {
     osgEarth::Bounds bounds = geom->getBounds();
     osgEarth::Bounds* newBounds = new osgEarth::Bounds();
     newBounds->set(bounds.xMin(), bounds.yMin(), bounds.zMin(), bounds.xMax(), bounds.yMax(), bounds.zMax());
-    return JSBounds::WrapBounds(newBounds, true);
+    value = JSBounds::WrapBounds(v8::Isolate::GetCurrent(), newBounds, true);
   }
   else if (prop == "type")
-    return v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getType()).c_str());
+    value = v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getType()).c_str());
   else if (prop == "componentType")
-    return v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getComponentType()).c_str());
+    value = v8::String::New(osgEarth::Symbology::Geometry::toString(geom->getComponentType()).c_str());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSSymbologyGeometry::IndexedPropertyCallback(uint32_t index, const v8::AccessorInfo& info)
+void
+JSSymbologyGeometry::IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::Symbology::Geometry* geom = V8Util::UnwrapObject<osgEarth::Symbology::Geometry>(info.Holder());
 
-  if (!geom)
-    return v8::Handle<v8::Value>();
+  v8::Local<v8::Value> value;
+  
+  if (geom)
+    value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), &((*geom)[index]));
 
-  return JSVec3d::WrapVec3d(&((*geom)[index]));
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSSymbologyGeometry::FreeGeometryCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSSymbologyGeometry::FreeGeometryCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Symbology::Geometry* parameter)
 {
-  osgEarth::Symbology::Geometry* geometry = static_cast<osgEarth::Symbology::Geometry*>(parameter);
-  delete geometry;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -240,25 +251,26 @@ JSSymbologyGeometry::FreeGeometryCallback(v8::Persistent<v8::Value> object, void
 const std::string JSBounds::_objectType = "JSBounds";
 
 v8::Handle<v8::Object>
-JSBounds::WrapBounds(osgEarth::Bounds* bounds, bool freeObject)
+JSBounds::WrapBounds(v8::Isolate* isolate, osgEarth::Bounds* bounds, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(bounds, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, bounds, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(bounds, FreeBoundsCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(bounds, &FreeBoundsCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSBounds::GetObjectTemplate()
+JSBounds::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -273,8 +285,8 @@ JSBounds::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSBounds::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSBounds::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
 
@@ -282,119 +294,127 @@ JSBounds::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& i
   std::string prop(*utf8_value);
 
   if (!bounds || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "valid")
-    return v8::Boolean::New(bounds->valid());
+    value = v8::Boolean::New(bounds->valid());
   else if (prop == "xMin")
-    return v8::Number::New(bounds->xMin());
+    value = v8::Number::New(bounds->xMin());
   else if (prop == "xMax")
-    return v8::Number::New(bounds->xMax());
+    value = v8::Number::New(bounds->xMax());
   else if (prop == "yMin")
-    return v8::Number::New(bounds->yMin());
+    value = v8::Number::New(bounds->yMin());
   else if (prop == "yMax")
-    return v8::Number::New(bounds->yMax());
+    value = v8::Number::New(bounds->yMax());
   else if (prop == "zMin")
-    return v8::Number::New(bounds->zMin());
+    value = v8::Number::New(bounds->zMin());
   else if (prop == "zMax")
-    return v8::Number::New(bounds->zMax());
+    value = v8::Number::New(bounds->zMax());
   else if (prop == "center")
   {
     osg::Vec3d* vec = new osg::Vec3d(bounds->center());
-    return JSVec3d::WrapVec3d(vec, true);
+    value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), vec, true);
   }
   else if (prop == "radius")
-    return v8::Number::New(bounds->radius());
+    value = v8::Number::New(bounds->radius());
   else if (prop == "width")
-    return v8::Number::New(bounds->width());
+    value = v8::Number::New(bounds->width());
   else if (prop == "height")
-    return v8::Number::New(bounds->height());
+    value = v8::Number::New(bounds->height());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSBounds::ContainsCallback(const v8::Arguments& args)
+void
+JSBounds::ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(args.Holder());
+  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
 
   if (bounds)
   {
-    if (args.Length() == 1 && args[0]->IsObject())  // Bounds
-    {
-      v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Value> value;
 
+    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
+    {
+      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
       if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
       {
         osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        return v8::Boolean::New(bounds->contains(*rhs));
+        value = v8::Boolean::New(bounds->contains(*rhs));
       }
     }
-    else if (args.Length() == 2)
+    else if (info.Length() == 2)
     {
-      return v8::Boolean::New(bounds->contains(args[0]->NumberValue(), args[1]->NumberValue()));
+      value = v8::Boolean::New(bounds->contains(info[0]->NumberValue(), info[1]->NumberValue()));
     }
-  }
 
-  return v8::Undefined();
+    if (!value.IsEmpty())
+      info.GetReturnValue().Set(value);
+  }
 }
 
-v8::Handle<v8::Value>
-JSBounds::UnionCallback(const v8::Arguments& args)
+void
+JSBounds::UnionCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(args.Holder());
+  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
 
   if (bounds)
   {
-    if (args.Length() == 1 && args[0]->IsObject())  // Bounds
+    v8::Local<v8::Value> value;
+
+    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
     {
-      v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
       if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
       {
         osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
         osgEarth::Bounds* outBounds = new osgEarth::Bounds();
         outBounds->expandBy(bounds->unionWith(*rhs));
-        return WrapBounds(outBounds, true);
+        value = WrapBounds(v8::Isolate::GetCurrent(), outBounds, true);
       }
     }
-  }
 
-  return v8::Undefined();
+    if (!value.IsEmpty())
+      info.GetReturnValue().Set(value);
+  }
 }
 
-v8::Handle<v8::Value>
-JSBounds::IntersectionCallback(const v8::Arguments& args)
+void
+JSBounds::IntersectionCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(args.Holder());
+  osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(info.Holder());
 
   if (bounds)
   {
-    if (args.Length() == 1 && args[0]->IsObject())  // Bounds
+    v8::Local<v8::Value> value;
+
+    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
     {
-      v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
       if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
       {
         osgEarth::Bounds* rhs = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
         osgEarth::Bounds* outBounds = new osgEarth::Bounds();
         outBounds->expandBy(bounds->intersectionWith(*rhs));
-        return WrapBounds(outBounds, true);
+        value = WrapBounds(v8::Isolate::GetCurrent(), outBounds, true);
       }
     }
-  }
 
-  return v8::Undefined();
+    if (!value.IsEmpty())
+      info.GetReturnValue().Set(value);
+  }
 }
 
 void
-JSBounds::FreeBoundsCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSBounds::FreeBoundsCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Bounds* parameter)
 {
-  Bounds* bounds = static_cast<Bounds*>(parameter);
-  delete bounds;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -402,25 +422,26 @@ JSBounds::FreeBoundsCallback(v8::Persistent<v8::Value> object, void *parameter)
 const std::string JSVec3d::_objectType = "JSVec3d";
 
 v8::Handle<v8::Object>
-JSVec3d::WrapVec3d(osg::Vec3d* vec, bool freeObject)
+JSVec3d::WrapVec3d(v8::Isolate* isolate, osg::Vec3d* vec, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(vec, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, vec, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(vec, FreeVecCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(vec, &FreeVecCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSVec3d::GetObjectTemplate()
+JSVec3d::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -432,8 +453,8 @@ JSVec3d::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSVec3d::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSVec3d::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osg::Vec3d* v = V8Util::UnwrapObject<osg::Vec3d>(info.Holder());
 
@@ -441,37 +462,36 @@ JSVec3d::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& in
   std::string prop(*utf8_value);
 
   if (!v || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "x")
-    return v8::Number::New(v->x());
+    value = v8::Number::New(v->x());
   if (prop == "y")
-    return v8::Number::New(v->y());
+    value = v8::Number::New(v->y());
   if (prop == "z")
-    return v8::Number::New(v->z());
+    value = v8::Number::New(v->z());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSVec3d::IndexedPropertyCallback(uint32_t index, const v8::AccessorInfo& info)
+void
+JSVec3d::IndexedPropertyCallback(uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osg::Vec3d* v = V8Util::UnwrapObject<osg::Vec3d>(info.Holder());
 
   if (!v || index > 2)
-    return v8::Handle<v8::Value>();
+    return;
 
-  return v8::Number::New((*v)[index]);
+  info.GetReturnValue().Set(v8::Number::New((*v)[index]));
 }
 
-void
-JSVec3d::FreeVecCallback(v8::Persistent<v8::Value> object, void *parameter)
+void JSVec3d::FreeVecCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osg::Vec3d* parameter)
 {
-  osg::Vec3d* v = static_cast<osg::Vec3d*>(parameter);
-  delete v;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -479,9 +499,9 @@ JSVec3d::FreeVecCallback(v8::Persistent<v8::Value> object, void *parameter)
 const std::string JSFilterContext::_objectType = "JSFilterContext";
 
 v8::Handle<v8::Object>
-JSFilterContext::WrapFilterContext(osgEarth::Features::FilterContext* context, bool freeObject)
+JSFilterContext::WrapFilterContext(v8::Isolate* isolate, osgEarth::Features::FilterContext* context, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   if (!context)
   {
@@ -489,21 +509,22 @@ JSFilterContext::WrapFilterContext(osgEarth::Features::FilterContext* context, b
     return handle_scope.Close(obj);
   }
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(context, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, context, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(context, FreeContextCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(context, &FreeContextCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSFilterContext::GetObjectTemplate()
+JSFilterContext::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -521,8 +542,8 @@ JSFilterContext::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
 
@@ -530,28 +551,33 @@ JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::Accessor
   std::string prop(*utf8_value);
 
   if (!context || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "session")
-    return JSSession::WrapSession(const_cast<Session*>(context->getSession()));
+    value = JSSession::WrapSession(v8::Isolate::GetCurrent(), const_cast<Session*>(context->getSession()));
   if (prop == "profile")
-    return JSFeatureProfile::WrapFeatureProfile(const_cast<FeatureProfile*>(context->profile().get()));
+    value = JSFeatureProfile::WrapFeatureProfile(v8::Isolate::GetCurrent(), const_cast<FeatureProfile*>(context->profile().get()));
   if (prop == "extent" && context->extent().isSet())
-    return JSGeoExtent::WrapGeoExtent(const_cast<osgEarth::GeoExtent*>(&context->extent().get()));
+    value = JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), const_cast<osgEarth::GeoExtent*>(&context->extent().get()));
   //if (prop == "geocentric")
-  //  return v8::Boolean::New(context->isGeocentric());
+  //  value = v8::Boolean::New(context->isGeocentric());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSFilterContext::ToLocalCallback(const v8::Arguments& args)
+void
+JSFilterContext::ToLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(args.Holder());
+  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
 
-  if (context && args.Length() == 1 && args[0]->IsObject())
+  v8::Local<v8::Value> value;
+
+  if (context && info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     /*if (V8Util::CheckObjectType(obj, JSGeometry::GetObjectType()))  // Geometry
     {
@@ -564,21 +590,24 @@ JSFilterContext::ToLocalCallback(const v8::Arguments& args)
     {
       osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
       osg::Vec3d* local = new osg::Vec3d(context->toLocal(*vec));
-      return JSVec3d::WrapVec3d(local, true);
+      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), local, true);
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSFilterContext::ToWorldCallback(const v8::Arguments& args)
+void
+JSFilterContext::ToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(args.Holder());
+  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
 
-  if (context && args.Length() == 1 && args[0]->IsObject())
+  v8::Local<v8::Value> value;
+
+  if (context && info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     /*if (V8Util::CheckObjectType(obj, JSGeometry::GetObjectType()))  // Geometry
     {
@@ -591,61 +620,65 @@ JSFilterContext::ToWorldCallback(const v8::Arguments& args)
     {
       osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
       osg::Vec3d* world = new osg::Vec3d(context->toWorld(*vec));
-      return JSVec3d::WrapVec3d(world, true);
+      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), world, true);
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSFilterContext::ToMapCallback(const v8::Arguments& args)
+void
+JSFilterContext::ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(args.Holder());
+  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
+
+  v8::Local<v8::Value> value;
 
-  if (context && args.Length() == 1 && args[0]->IsObject())
+  if (context && info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
     
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
     {
       osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
       osg::Vec3d* map = new osg::Vec3d(context->toMap(*vec));
-      return JSVec3d::WrapVec3d(map, true);
+      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), map, true);
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSFilterContext::FromMapCallback(const v8::Arguments& args)
+void
+JSFilterContext::FromMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  FilterContext* context = V8Util::UnwrapObject<FilterContext>(args.Holder());
+  FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder());
+
+  v8::Local<v8::Value> value;
 
-  if (context && args.Length() == 1 && args[0]->IsObject())
+  if (context && info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
     
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))  // Vec3d
     {
       osg::Vec3d* map = V8Util::UnwrapObject<osg::Vec3d>(obj);
       osg::Vec3d* local = new osg::Vec3d(context->fromMap(*map));
-      return JSVec3d::WrapVec3d(local, true);
+      value = JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), local, true);
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSFilterContext::FreeContextCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSFilterContext::FreeContextCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FilterContext* parameter)
 {
-  FilterContext* context = static_cast<FilterContext*>(parameter);
-  delete context;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -653,25 +686,26 @@ JSFilterContext::FreeContextCallback(v8::Persistent<v8::Value> object, void *par
 const std::string JSSession::_objectType = "JSSession";
 
 v8::Handle<v8::Object>
-JSSession::WrapSession(osgEarth::Features::Session* session, bool freeObject)
+JSSession::WrapSession(v8::Isolate* isolate, osgEarth::Features::Session* session, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(session, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, session, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(session, FreeSessionCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(session, &FreeSessionCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSSession::GetObjectTemplate()
+JSSession::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -686,8 +720,8 @@ JSSession::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSSession::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSSession::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   Session* session = V8Util::UnwrapObject<Session>(info.Holder());
 
@@ -695,39 +729,37 @@ JSSession::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo&
   std::string prop(*utf8_value);
 
   if (!session || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
 
   if (prop == "mapInfo")
-    return JSMapInfo::WrapMapInfo(const_cast<osgEarth::MapInfo*>(&session->getMapInfo()));
-
-  return v8::Handle<v8::Value>();
+    info.GetReturnValue().Set(JSMapInfo::WrapMapInfo(v8::Isolate::GetCurrent(), const_cast<osgEarth::MapInfo*>(&session->getMapInfo())));
 }
 
 #if 0
-v8::Handle<v8::Value>
-JSSession::ResolveUriCallback(const v8::Arguments& args)
+void
+JSSession::ResolveUriCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  Session* session = V8Util::UnwrapObject<Session>(args.Holder());
+  Session* session = V8Util::UnwrapObject<Session>(info.Holder());
 
-  if (session && args.Length() == 1 && args[0]->IsString())
+  v8::Local<v8::Value> value;
+
+  if (session && info.Length() == 1 && info[0]->IsString())
   {
-    v8::String::Utf8Value utf8_value(args[0]->ToString());
+    v8::String::Utf8Value utf8_value(info[0]->ToString());
     std::string uri(*utf8_value);
-    return v8::String::New(session->resolveURI(uri).c_str());
+    value = v8::String::New(session->resolveURI(uri).c_str());
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 #endif
 
 void
-JSSession::FreeSessionCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSSession::FreeSessionCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::Session* parameter)
 {
-  Session* session = static_cast<Session*>(parameter);
-  delete session;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -735,25 +767,26 @@ JSSession::FreeSessionCallback(v8::Persistent<v8::Value> object, void *parameter
 const std::string JSMapInfo::_objectType = "JSMapInfo";
 
 v8::Handle<v8::Object>
-JSMapInfo::WrapMapInfo(osgEarth::MapInfo* mapInfo, bool freeObject)
+JSMapInfo::WrapMapInfo(v8::Isolate* isolate, osgEarth::MapInfo* mapInfo, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(mapInfo, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, mapInfo, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(mapInfo, FreeMapInfoCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(mapInfo, &FreeMapInfoCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSMapInfo::GetObjectTemplate()
+JSMapInfo::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -770,8 +803,8 @@ JSMapInfo::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSMapInfo::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSMapInfo::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
 
@@ -779,32 +812,37 @@ JSMapInfo::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo&
   std::string prop(*utf8_value);
 
   if (!mapInfo || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "geocentric")
-    return v8::Boolean::New(mapInfo->isGeocentric());
+    value = v8::Boolean::New(mapInfo->isGeocentric());
   if (prop == "cube")
-    return v8::Boolean::New(mapInfo->isCube());
+    value = v8::Boolean::New(mapInfo->isCube());
   if (prop == "plateCarre" || prop == "platecarre")
-    return v8::Boolean::New(mapInfo->isPlateCarre());
+    value = v8::Boolean::New(mapInfo->isPlateCarre());
   if (prop == "projectedSRS")
-    return v8::Boolean::New(mapInfo->isProjectedSRS());
+    value = v8::Boolean::New(mapInfo->isProjectedSRS());
   if (prop == "geographicSRS")
-    return v8::Boolean::New(mapInfo->isGeographicSRS());
+    value = v8::Boolean::New(mapInfo->isGeographicSRS());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 #if 0
-v8::Handle<v8::Value>
-JSMapInfo::ToMapCallback(const v8::Arguments& args)
+void
+JSMapInfo::ToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(args.Holder());
+  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
+
+  v8::Local<v8::Value> value;
 
-  if (mapInfo && args.Length() == 2 && args[0]->IsObject() && args[1]->IsObject()) // Vec3d & SpatialReference
+  if (mapInfo && info.Length() == 2 && info[0]->IsObject() && info[1]->IsObject()) // Vec3d & SpatialReference
   {
-    v8::Local<v8::Object> obj0( v8::Object::Cast(*args[0]) );
-    v8::Local<v8::Object> obj1( v8::Object::Cast(*args[1]) );
+    v8::Local<v8::Object> obj0( v8::Object::Cast(*info[0]) );
+    v8::Local<v8::Object> obj1( v8::Object::Cast(*info[1]) );
 
     if (V8Util::CheckObjectType(obj0, JSVec3d::GetObjectType()) && V8Util::CheckObjectType(obj1, JSSpatialReference::GetObjectType()))
     {
@@ -817,73 +855,77 @@ JSMapInfo::ToMapCallback(const v8::Arguments& args)
       mapPoint.fromWorld( srs, *input );
       out->set( mapPoint.x(), mapPoint.y(), mapPoint.z() );
 
-      return JSVec3d::WrapVec3d(out, true);
+      value = JSVec3d::WrapVec3d(out, true);
 
       //if (mapInfo->toMapPoint(*input, srs, *out))
-      //  return JSVec3d::WrapVec3d(out, true);
+      //  value = JSVec3d::WrapVec3d(out, true);
 
       delete out;
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSMapInfo::MapToWorldCallback(const v8::Arguments& args)
+void
+JSMapInfo::MapToWorldCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(args.Holder());
+  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
+  
+  v8::Local<v8::Value> value;
 
-  if (mapInfo && args.Length() == 1 && args[0]->IsObject()) // Vec3d
+  if (mapInfo && info.Length() == 1 && info[0]->IsObject()) // Vec3d
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
     {
       osg::Vec3d* input = V8Util::UnwrapObject<osg::Vec3d>(obj);
 
       osg::Vec3d* out = new osg::Vec3d();
       if (mapInfo->mapPointToWorldPoint(*input, *out))
-        return JSVec3d::WrapVec3d(out, true);
+        value = JSVec3d::WrapVec3d(out, true);
 
       delete out;
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSMapInfo::WorldToMapCallback(const v8::Arguments& args)
+void
+JSMapInfo::WorldToMapCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(args.Holder());
+  osgEarth::MapInfo* mapInfo = V8Util::UnwrapObject<osgEarth::MapInfo>(info.Holder());
+
+  v8::Local<v8::Value> value;
 
-  if (mapInfo && args.Length() == 1 && args[0]->IsObject()) // Vec3d
+  if (mapInfo && info.Length() == 1 && info[0]->IsObject()) // Vec3d
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
     {
       osg::Vec3d* input = V8Util::UnwrapObject<osg::Vec3d>(obj);
 
       osg::Vec3d* out = new osg::Vec3d();
       if (mapInfo->worldPointToMapPoint(*input, *out))
-        return JSVec3d::WrapVec3d(out, true);
+        value = JSVec3d::WrapVec3d(out, true);
 
       delete out;
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 #endif
 
 void
-JSMapInfo::FreeMapInfoCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSMapInfo::FreeMapInfoCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::MapInfo* parameter)
 {
-  Session* session = static_cast<Session*>(parameter);
-  delete session;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -891,25 +933,26 @@ JSMapInfo::FreeMapInfoCallback(v8::Persistent<v8::Value> object, void *parameter
 const std::string JSFeatureProfile::_objectType = "JSFeatureProfile";
 
 v8::Handle<v8::Object>
-JSFeatureProfile::WrapFeatureProfile(osgEarth::Features::FeatureProfile* profile, bool freeObject)
+JSFeatureProfile::WrapFeatureProfile(v8::Isolate* isolate, osgEarth::Features::FeatureProfile* profile, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(profile, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, profile, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(profile, FreeProfileCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(profile, &FreeProfileCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSFeatureProfile::GetObjectTemplate()
+JSFeatureProfile::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -922,8 +965,8 @@ JSFeatureProfile::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSFeatureProfile::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSFeatureProfile::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   FeatureProfile* profile = V8Util::UnwrapObject<FeatureProfile>(info.Holder());
 
@@ -931,24 +974,24 @@ JSFeatureProfile::PropertyCallback(v8::Local<v8::String> name, const v8::Accesso
   std::string prop(*utf8_value);
 
   if (!profile || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "extent")
-    return JSGeoExtent::WrapGeoExtent(const_cast<osgEarth::GeoExtent*>(&profile->getExtent()));
+    value = JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), const_cast<osgEarth::GeoExtent*>(&profile->getExtent()));
   if (prop == "srs")
-    return JSSpatialReference::WrapSpatialReference(const_cast<osgEarth::SpatialReference*>(profile->getSRS()));
+    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(profile->getSRS()));
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSFeatureProfile::FreeProfileCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSFeatureProfile::FreeProfileCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::Features::FeatureProfile* parameter)
 {
-  FeatureProfile* profile = static_cast<FeatureProfile*>(parameter);
-  delete profile;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -956,25 +999,26 @@ JSFeatureProfile::FreeProfileCallback(v8::Persistent<v8::Value> object, void *pa
 const std::string JSGeoExtent::_objectType = "JSGeoExtent";
 
 v8::Handle<v8::Object>
-JSGeoExtent::WrapGeoExtent(osgEarth::GeoExtent* extent, bool freeObject)
+JSGeoExtent::WrapGeoExtent(v8::Isolate* isolate, osgEarth::GeoExtent* extent, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(extent, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, extent, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(extent, FreeGeoExtentCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(extent, &FreeGeoExtentCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSGeoExtent::GetObjectTemplate()
+JSGeoExtent::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -988,8 +1032,8 @@ JSGeoExtent::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSGeoExtent::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSGeoExtent::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
 
@@ -997,102 +1041,107 @@ JSGeoExtent::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo
   std::string prop(*utf8_value);
 
   if (!extent || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "srs")
-    return JSSpatialReference::WrapSpatialReference(const_cast<osgEarth::SpatialReference*>(extent->getSRS()));
+    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(extent->getSRS()));
   if (prop == "xMin")
-    return v8::Number::New(extent->xMin());
+    value = v8::Number::New(extent->xMin());
   if (prop == "xMax")
-    return v8::Number::New(extent->xMax());
+    value = v8::Number::New(extent->xMax());
   if (prop == "yMin")
-    return v8::Number::New(extent->yMin());
+    value = v8::Number::New(extent->yMin());
   if (prop == "yMax")
-    return v8::Number::New(extent->yMax());
+    value = v8::Number::New(extent->yMax());
   if (prop == "width")
-    return v8::Number::New(extent->width());
+    value = v8::Number::New(extent->width());
   if (prop == "height")
-    return v8::Number::New(extent->height());
+    value = v8::Number::New(extent->height());
   if (prop == "crossesAntimeridian")
-    return v8::Boolean::New(extent->crossesAntimeridian());
+    value = v8::Boolean::New(extent->crossesAntimeridian());
   if (prop == "valid")
-    return v8::Boolean::New(extent->isValid());
+    value = v8::Boolean::New(extent->isValid());
   if (prop == "defined")
-    return v8::Boolean::New(extent->defined());
+    value = v8::Boolean::New(extent->defined());
   if (prop == "area")
-    return v8::Number::New(extent->area());
+    value = v8::Number::New(extent->area());
   //if (prop == "toString")
-  //  return v8::String::New(extent->toString().c_str());
+  //  value = v8::String::New(extent->toString().c_str());
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSGeoExtent::ContainsCallback(const v8::Arguments& args)
+void
+JSGeoExtent::ContainsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(args.Holder());
+  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
+  v8::Local<v8::Value> value;
 
   if (extent)
   {
-    if (args.Length() == 1 && args[0]->IsObject())  // Bounds
+    if (info.Length() == 1 && info[0]->IsObject())  // Bounds
     {
-      v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
       if (V8Util::CheckObjectType(obj, JSBounds::GetObjectType()))
       {
         osgEarth::Bounds* bounds = V8Util::UnwrapObject<osgEarth::Bounds>(obj);
-        return v8::Boolean::New(extent->contains(*bounds));
+        value = v8::Boolean::New(extent->contains(*bounds));
       }
     }
-    else if (args.Length() == 2 /*&& args[0]->IsNumber() && args[1]->IsNumber()*/)  // x and y
+    else if (info.Length() == 2 /*&& info[0]->IsNumber() && info[1]->IsNumber()*/)  // x and y
     {
-      return v8::Boolean::New(extent->contains(args[0]->NumberValue(), args[1]->NumberValue()));
+      value = v8::Boolean::New(extent->contains(info[0]->NumberValue(), info[1]->NumberValue()));
     }
-    else if (args.Length() == 3 && /*args[0]->IsNumber() && args[1]->IsNumber() &&*/ args[2]->IsObject())  // x, y, and SpatialReference
+    else if (info.Length() == 3 && /*info[0]->IsNumber() && info[1]->IsNumber() &&*/ info[2]->IsObject())  // x, y, and SpatialReference
     {
-      v8::Local<v8::Object> obj( v8::Object::Cast(*args[2]) );
+      v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[2]) );
 
       if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
       {
         osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-        return v8::Boolean::New(extent->contains(args[0]->NumberValue(), args[1]->NumberValue(), srs));
+        value = v8::Boolean::New(extent->contains(info[0]->NumberValue(), info[1]->NumberValue(), srs));
       }
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSGeoExtent::IntersectsCallback(const v8::Arguments& args)
+void
+JSGeoExtent::IntersectsCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(args.Holder());
+  osgEarth::GeoExtent* extent = V8Util::UnwrapObject<osgEarth::GeoExtent>(info.Holder());
 
   if (!extent)
-    return v8::Undefined();
+    return;
 
-  if (args.Length() == 1 && args[0]->IsObject())  // GeoExtent
+  v8::Local<v8::Value> value;
+
+  if (info.Length() == 1 && info[0]->IsObject())  // GeoExtent
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     if (V8Util::CheckObjectType(obj, JSGeoExtent::GetObjectType()))
     {
       osgEarth::GeoExtent* rhs = V8Util::UnwrapObject<osgEarth::GeoExtent>(obj);
-      return v8::Boolean::New(extent->intersects(*rhs));
+      value = v8::Boolean::New(extent->intersects(*rhs));
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSGeoExtent::FreeGeoExtentCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSGeoExtent::FreeGeoExtentCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::GeoExtent* parameter)
 {
-  osgEarth::GeoExtent* extent = static_cast<osgEarth::GeoExtent*>(parameter);
-  delete extent;
-
-  object.Dispose();
-  object.Clear();
+  delete parameter;
+  handle->Dispose();
 }
 
 // ---------------------------------------------------------------------------
@@ -1100,25 +1149,26 @@ JSGeoExtent::FreeGeoExtentCallback(v8::Persistent<v8::Value> object, void *param
 const std::string JSSpatialReference::_objectType = "JSSpatialReference";
 
 v8::Handle<v8::Object>
-JSSpatialReference::WrapSpatialReference(osgEarth::SpatialReference* srs, bool freeObject)
+JSSpatialReference::WrapSpatialReference(v8::Isolate* isolate, osgEarth::SpatialReference* srs, bool freeObject)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
-  v8::Handle<v8::Object> obj = V8Util::WrapObject(srs, GetObjectTemplate());
+  v8::Handle<v8::Object> obj = V8Util::WrapObject(isolate, srs, GetObjectTemplate(isolate));
 
   if (freeObject)
   {
-    v8::Persistent<v8::Object> weakRef = v8::Persistent<v8::Object>::New(obj);
-    weakRef.MakeWeak(srs, FreeSpatialReferenceCallback);
+    v8::Persistent<v8::Object> weakRef;
+    weakRef.Reset(v8::Isolate::GetCurrent(), obj);
+    weakRef.MakeWeak(srs, &FreeSpatialReferenceCallback);
   }
 
   return handle_scope.Close(obj);
 }
 
 v8::Handle<v8::ObjectTemplate>
-JSSpatialReference::GetObjectTemplate()
+JSSpatialReference::GetObjectTemplate(v8::Isolate* isolate)
 {
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(isolate);
 
   v8::Handle<v8::ObjectTemplate> template_instance = v8::ObjectTemplate::New();
 
@@ -1134,8 +1184,8 @@ JSSpatialReference::GetObjectTemplate()
   return handle_scope.Close(template_instance);
 }
 
-v8::Handle<v8::Value>
-JSSpatialReference::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info)
+void
+JSSpatialReference::PropertyCallback(v8::Local<v8::String> name, const v8::PropertyCallbackInfo<v8::Value>& info)
 {
   osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
 
@@ -1143,97 +1193,102 @@ JSSpatialReference::PropertyCallback(v8::Local<v8::String> name, const v8::Acces
   std::string prop(*utf8_value);
   
   if (!srs || prop.empty())
-    return v8::Handle<v8::Value>();
+    return;
+
+  v8::Local<v8::Value> value;
 
   if (prop == "geographic")
-    return v8::Boolean::New(srs->isGeographic());
+    value = v8::Boolean::New(srs->isGeographic());
   if (prop == "projected")
-    return v8::Boolean::New(srs->isProjected());
+    value = v8::Boolean::New(srs->isProjected());
   if (prop == "mercator")
-    return v8::Boolean::New(srs->isMercator());
+    value = v8::Boolean::New(srs->isMercator());
   if (prop == "sphericalMercator")
-    return v8::Boolean::New(srs->isSphericalMercator());
+    value = v8::Boolean::New(srs->isSphericalMercator());
   if (prop == "northPolar")
-    return v8::Boolean::New(srs->isNorthPolar());
+    value = v8::Boolean::New(srs->isNorthPolar());
   if (prop == "southPolar")
-    return v8::Boolean::New(srs->isSouthPolar());
+    value = v8::Boolean::New(srs->isSouthPolar());
   if (prop == "userDefined")
-    return v8::Boolean::New(srs->isUserDefined());
+    value = v8::Boolean::New(srs->isUserDefined());
   if (prop == "contiguous")
-    return v8::Boolean::New(srs->isContiguous());
+    value = v8::Boolean::New(srs->isContiguous());
   if (prop == "cube")
-    return v8::Boolean::New(srs->isCube());
+    value = v8::Boolean::New(srs->isCube());
   if (prop == "LTP" || prop == "ltp")
-    return v8::Boolean::New(srs->isLTP());
+    value = v8::Boolean::New(srs->isLTP());
   if (prop == "name")
-    return v8::String::New(srs->getName().c_str());
+    value = v8::String::New(srs->getName().c_str());
   if (prop == "WKT" || prop == "wkt")
-    return v8::String::New(srs->getWKT().c_str());
+    value = v8::String::New(srs->getWKT().c_str());
   if (prop == "initType")
-    return v8::String::New(srs->getInitType().c_str());
+    value = v8::String::New(srs->getInitType().c_str());
   if (prop == "horizInitString")
-    return v8::String::New(srs->getHorizInitString().c_str());
+    value = v8::String::New(srs->getHorizInitString().c_str());
   if (prop == "vertInitString")
-    return v8::String::New(srs->getVertInitString().c_str());
+    value = v8::String::New(srs->getVertInitString().c_str());
   if (prop == "datumName")
-    return v8::String::New(srs->getDatumName().c_str());
+    value = v8::String::New(srs->getDatumName().c_str());
   if (prop == "geographicSRS")
-    return JSSpatialReference::WrapSpatialReference(const_cast<osgEarth::SpatialReference*>(srs->getGeographicSRS()));
+    value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<osgEarth::SpatialReference*>(srs->getGeographicSRS()));
 
-  return v8::Handle<v8::Value>();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSSpatialReference::EquivalenceCallback(const v8::Arguments& args)
+void
+JSSpatialReference::EquivalenceCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(args.Holder());
+  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
 
   if (!srs)
-    return v8::Undefined();
+    return;
+
+  v8::Local<v8::Value> value;
 
-  if (args.Length() == 1 && args[0]->IsObject())  // SpatialReference
+  if (info.Length() == 1 && info[0]->IsObject())  // SpatialReference
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
     {
       osgEarth::SpatialReference* rhs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-      return v8::Boolean::New(srs->isEquivalentTo(rhs));
+      value = v8::Boolean::New(srs->isEquivalentTo(rhs));
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
-v8::Handle<v8::Value>
-JSSpatialReference::TangentPlaneCallback(const v8::Arguments& args)
+void
+JSSpatialReference::TangentPlaneCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(args.Holder());
+  osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(info.Holder());
 
   if (!srs)
-    return v8::Undefined();
+    return;
+
+  v8::Local<v8::Value> value;
 
-  if (args.Length() == 1 && args[0]->IsObject())  // Vec3d
+  if (info.Length() == 1 && info[0]->IsObject())  // Vec3d
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
     {
       osg::Vec3d* vec = V8Util::UnwrapObject<osg::Vec3d>(obj);
-      return JSSpatialReference::WrapSpatialReference(const_cast<SpatialReference*>(srs->createTangentPlaneSRS(*vec)), true);
+      value = JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), const_cast<SpatialReference*>(srs->createTangentPlaneSRS(*vec)), true);
     }
   }
 
-  return v8::Undefined();
+  if (!value.IsEmpty())
+    info.GetReturnValue().Set(value);
 }
 
 void
-JSSpatialReference::FreeSpatialReferenceCallback(v8::Persistent<v8::Value> object, void *parameter)
+JSSpatialReference::FreeSpatialReferenceCallback(v8::Isolate* isolate, v8::Persistent<v8::Object>* handle, osgEarth::SpatialReference* parameter)
 {
-  //osgEarth::SpatialReference* srs = static_cast<osgEarth::SpatialReference*>(parameter);
-  //delete srs;
-  osg::ref_ptr<osgEarth::SpatialReference> srs = static_cast<osgEarth::SpatialReference*>(parameter);
-
-  object.Dispose();
-  object.Clear();
+  osg::ref_ptr<osgEarth::SpatialReference> srs = parameter;
+  handle->Dispose();
 }
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8 b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
index efdaff2..05dfa0e 100644
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
+++ b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8
@@ -47,22 +47,21 @@
     ScriptResult call(const std::string& function, osgEarth::Features::Feature const* feature=0L, osgEarth::Features::FilterContext const* context=0L);
 
   protected:
-    static v8::Handle<v8::Value> logCallback(const v8::Arguments& args);
-    //static v8::Handle<v8::Value> constructFeatureCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructBoundsCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructVec3dCallback(const v8::Arguments &args);
-    static v8::Handle<v8::Value> constructGeoExtentCallback(const v8::Arguments &args);
+    static void logCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
+    //static void constructFeatureCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
+    static void constructBoundsCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
+    static void constructVec3dCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
+    static void constructGeoExtentCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
 
-    static v8::Handle<v8::Value> constructSpatialReferenceCallback(const v8::Arguments &args);
-    //static v8::Handle<v8::Value> constructSymbologyGeometryCallback(const v8::Arguments &args);
+    static void constructSpatialReferenceCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
+    //static void constructSymbologyGeometryCallback(const v8::FunctionCallbackInfo<v8::Value> &info);
 
-    v8::Local<v8::ObjectTemplate> createGlobalObjectTemplate();
+    v8::Handle<v8::Context> createGlobalContext();
 
     /** Compiles and runs javascript in the current context. */
     ScriptResult executeScript(v8::Handle<v8::String> script);
 
   protected:
-    v8::Persistent<v8::ObjectTemplate> _globalTemplate;
     v8::Persistent<v8::Context> _globalContext;
     v8::Isolate* _isolate;
   };
diff --git a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
index dba0abe..5842567 100644
--- a/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
+++ b/src/osgEarthDrivers/script_engine_v8/JavascriptEngineV8.cpp
@@ -44,26 +44,23 @@ JavascriptEngineV8::JavascriptEngineV8(const ScriptEngineOptions& options)
   v8::Locker locker(_isolate);
   v8::Isolate::Scope isolate_scope(_isolate);
 
-  v8:: HandleScope handle_scope;
-
-  v8::Handle<v8::ObjectTemplate> global = createGlobalObjectTemplate();
-  _globalContext = v8::Context::New(NULL, global);
+  v8::HandleScope handle_scope(_isolate);
 
+  _globalContext.Reset(_isolate, createGlobalContext());
   if (options.script().isSet() && !options.script()->getCode().empty())
   {
     // Create a nested handle scope
-    v8::HandleScope local_handle_scope;
+    v8::HandleScope local_handle_scope(_isolate);
 
     // Enter the global context
-    v8::Context::Scope context_scope(_globalContext);
+    v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
+    v8::Context::Scope context_scope(globalContext);
 
     // Compile and run the script
     ScriptResult result = executeScript(v8::String::New(options.script()->getCode().c_str(), options.script()->getCode().length()));
     if (!result.success())
       OE_WARN << LC << "Error reading javascript: " << result.message() << std::endl;
   }
-
-  _globalTemplate = v8::Persistent<v8::ObjectTemplate>::New(global);
 }
 
 JavascriptEngineV8::~JavascriptEngineV8()
@@ -72,18 +69,15 @@ JavascriptEngineV8::~JavascriptEngineV8()
     v8::Locker locker(_isolate);
     v8::Isolate::Scope isolate_scope(_isolate);
 
-    _globalTemplate.Dispose();
     _globalContext.Dispose();
   }
 
   _isolate->Dispose();
 }
 
-v8::Local<v8::ObjectTemplate>
-JavascriptEngineV8::createGlobalObjectTemplate()
+v8::Handle<v8::Context>
+JavascriptEngineV8::createGlobalContext()
 {
-  v8:: HandleScope handle_scope;
-
   v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
 
   // add callback for global logging
@@ -97,27 +91,28 @@ JavascriptEngineV8::createGlobalObjectTemplate()
   global->Set(v8::String::New("SpatialReference"), v8::FunctionTemplate::New(constructSpatialReferenceCallback));
   //global->Set(v8::String::New("Geometry"), v8::FunctionTemplate::New(constructSymbologyGeometryCallback));
   
-  return handle_scope.Close(global);
+  return v8::Context::New(_isolate, NULL, global);
 }
 
-v8::Handle<v8::Value>
-JavascriptEngineV8::logCallback(const v8::Arguments& args)
+void
+JavascriptEngineV8::logCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
 {
-  if (args.Length() < 1) return v8::Undefined();
-  v8::HandleScope scope;
-  v8::Handle<v8::Value> arg = args[0];
+  if (info.Length() < 1) return;
+  v8::HandleScope scope(v8::Isolate::GetCurrent());
+  v8::Handle<v8::Value> arg = info[0];
   v8::String::AsciiValue value(arg);
   
   OE_WARN << LC << "javascript message: " << (*value) << std::endl;
-
-  return v8::Undefined();
 }
 
 ScriptResult
 JavascriptEngineV8::executeScript(v8::Handle<v8::String> script)
 {
+  v8::String::Utf8Value utf8_value(script);
+  std::string scriptStr(*utf8_value);
+
   // Handle scope for temporary handles.
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(_isolate);
 
   // TryCatch for any script errors
   v8::TryCatch try_catch;
@@ -156,6 +151,9 @@ JavascriptEngineV8::executeScript(v8::Handle<v8::String> script)
     return ScriptResult(EMPTY_STRING, false, std::string("Script result was empty: ") + std::string(*error));
   }
 
+  if (result->IsUndefined())
+    return ScriptResult(EMPTY_STRING, false, "Script result was undefined");
+
   v8::String::AsciiValue ascii(result);
   return ScriptResult(std::string(*ascii));
 }
@@ -178,25 +176,24 @@ JavascriptEngineV8::run(const std::string& code, osgEarth::Features::Feature con
   v8::Locker locker(_isolate);
   v8::Isolate::Scope isolate_scope(_isolate);
 
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(_isolate);
+
+  v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
 
-  //Create a separate context
-  //v8::Persistent<v8::Context> context = v8::Context::New(NULL, _globalTemplate);
-  //v8::Context::Scope context_scope(context);
-  v8::Context::Scope context_scope(_globalContext);
+  v8::Context::Scope context_scope(globalContext);
 
   if (feature)
   {
-    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(const_cast<Feature*>(feature));
+    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(_isolate, const_cast<Feature*>(feature));
     if (!fObj.IsEmpty())
-      _globalContext->Global()->Set(v8::String::New("feature"), fObj);
+      globalContext->Global()->Set(v8::String::New("feature"), fObj);
   }
 
   if (context)
   {
-    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(const_cast<FilterContext*>(context));
+    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(_isolate, const_cast<FilterContext*>(context));
     if (!cObj.IsEmpty())
-      _globalContext->Global()->Set(v8::String::New("context"), cObj);
+      globalContext->Global()->Set(v8::String::New("context"), cObj);
   }
 
   // Compile and run the script
@@ -217,13 +214,15 @@ JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Featur
   v8::Locker locker(_isolate);
   v8::Isolate::Scope isolate_scope(_isolate);
 
-  v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(_isolate);
 
-  v8::Context::Scope context_scope(_globalContext);
+  v8::Local<v8::Context> globalContext = *reinterpret_cast<v8::Local<v8::Context>*>(&_globalContext);
+
+  v8::Context::Scope context_scope(globalContext);
 
   // Attempt to fetch the function from the global object.
   v8::Handle<v8::String> func_name = v8::String::New(function.c_str(), function.length());
-  v8::Handle<v8::Value> func_val = _globalContext->Global()->Get(func_name);
+  v8::Handle<v8::Value> func_val = globalContext->Global()->Get(func_name);
 
   // If there is no function, or if it is not a function, bail out
   if (!func_val->IsFunction())
@@ -233,16 +232,16 @@ JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Featur
 
   if (feature)
   {
-    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(const_cast<Feature*>(feature));
+    v8::Handle<v8::Object> fObj = JSFeature::WrapFeature(_isolate, const_cast<Feature*>(feature));
     if (!fObj.IsEmpty())
-      _globalContext->Global()->Set(v8::String::New("feature"), fObj);
+      globalContext->Global()->Set(v8::String::New("feature"), fObj);
   }
 
   if (context)
   {
-    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(const_cast<FilterContext*>(context));
+    v8::Handle<v8::Object> cObj = JSFilterContext::WrapFilterContext(_isolate, const_cast<FilterContext*>(context));
     if (!cObj.IsEmpty())
-      _globalContext->Global()->Set(v8::String::New("context"), cObj);
+      globalContext->Global()->Set(v8::String::New("context"), cObj);
   }
 
   // Set up an exception handler before calling the Eval function
@@ -252,7 +251,7 @@ JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Featur
   //const int argc = 1;
   //v8::Handle<v8::Value> argv[argc] = { fObj };
   //v8::Handle<v8::Value> result = func_func->Call(_globalContext->Global(), argc, argv);
-  v8::Handle<v8::Value> result = func_func->Call(_globalContext->Global(), 0, NULL);
+  v8::Handle<v8::Value> result = func_func->Call(globalContext->Global(), 0, NULL);
 
   if (result.IsEmpty())
   {
@@ -268,22 +267,22 @@ JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Featur
 //----------------------------------------------------------------------------
 // Constructor callbacks for constructing native objects in javascript
 
-//v8::Handle<v8::Value>
-//JavascriptEngineV8::constructFeatureCallback(const v8::Arguments &args)
+//void
+//JavascriptEngineV8::constructFeatureCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 //{
-//  if (!args.IsConstructCall()) 
+//  if (!info.IsConstructCall()) 
 //    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
 // 
-//	v8::HandleScope handle_scope;
+//	v8::HandleScope handle_scope(_isolate);
 // 
 //  Feature* feature;
-//  if (args.Length() == 0)
+//  if (info.Length() == 0)
 //  {
 //    feature = new Feature();
 //  }
-//  else if (args.Length() == 1 && args[0]->IsUint32())
+//  else if (info.Length() == 1 && info[0]->IsUint32())
 //  {
-//    feature = new Feature(args[0]->Uint32Value());
+//    feature = new Feature(info[0]->Uint32Value());
 //  }
 //  else
 //  {
@@ -296,41 +295,50 @@ JavascriptEngineV8::call(const std::string& function, osgEarth::Features::Featur
 //  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
 //}
 
-v8::Handle<v8::Value>
-JavascriptEngineV8::constructBoundsCallback(const v8::Arguments &args)
+void
+JavascriptEngineV8::constructBoundsCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 {
-  if (!args.IsConstructCall()) 
-    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+  if (!info.IsConstructCall())
+  {
+    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+    return;
+  }
  
-	v8::HandleScope handle_scope;
+	v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
  
   osgEarth::Bounds* bounds;
-  //if (args.Length() == 0)
+  //if (info.Length() == 0)
   //  bounds = new osgEarth::Bounds();
   //else
-  if (args.Length() == 4)
-    bounds = new osgEarth::Bounds(args[0]->NumberValue(), args[1]->NumberValue(), args[2]->NumberValue(), args[3]->NumberValue());
+  if (info.Length() == 4)
+    bounds = new osgEarth::Bounds(info[0]->NumberValue(), info[1]->NumberValue(), info[2]->NumberValue(), info[3]->NumberValue());
 
-  if (bounds)
-    return JSBounds::WrapBounds(bounds, true);
+  if (!bounds)
+  {
+    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+    return;
+  }
 
-  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+  info.GetReturnValue().Set(JSBounds::WrapBounds(v8::Isolate::GetCurrent(), bounds, true));
 }
 
-v8::Handle<v8::Value>
-JavascriptEngineV8::constructVec3dCallback(const v8::Arguments &args)
+void
+JavascriptEngineV8::constructVec3dCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 {
-  if (!args.IsConstructCall()) 
-    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+  if (!info.IsConstructCall()) 
+  {
+    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+    return;
+  }
  
-	v8::HandleScope handle_scope;
+  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
 
   osg::Vec3d* vec;
-  if (args.Length() == 0)
+  if (info.Length() == 0)
     vec = new osg::Vec3d();
-  else if (args.Length() == 1 && args[0]->IsObject())
+  else if (info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     if (V8Util::CheckObjectType(obj, JSVec3d::GetObjectType()))
     {
@@ -338,30 +346,37 @@ JavascriptEngineV8::constructVec3dCallback(const v8::Arguments &args)
       vec = new osg::Vec3d(*rhs);
     }
   }
-  else if (args.Length() == 3)
-    vec = new osg::Vec3d(args[0]->NumberValue(), args[1]->NumberValue(), args[2]->NumberValue());
+  else if (info.Length() == 3)
+    vec = new osg::Vec3d(info[0]->NumberValue(), info[1]->NumberValue(), info[2]->NumberValue());
 
-  if (vec)
-    return JSVec3d::WrapVec3d(vec, true);
+  if (!vec)
+  {
+    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+    return;
+  }
+  
+  info.GetReturnValue().Set(JSVec3d::WrapVec3d(v8::Isolate::GetCurrent(), vec, true));
 
-  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
 }
 
-v8::Handle<v8::Value>
-JavascriptEngineV8::constructGeoExtentCallback(const v8::Arguments &args)
+void
+JavascriptEngineV8::constructGeoExtentCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 {
-  if (!args.IsConstructCall()) 
-    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
- 
-	v8::HandleScope handle_scope;
+  if (!info.IsConstructCall())
+  {
+    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+    return;
+  }
+
+  v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+  osgEarth::GeoExtent* extent = 0L;// = new osgEarth::GeoExtent(
 
-  osgEarth::GeoExtent* extent;// = new osgEarth::GeoExtent(
-  //if (args.Length() == 0)
+  //if (info.Length() == 0)
   //  extent = new osgEarth::GeoExtent();
   //else
-  if (args.Length() == 1 && args[0]->IsObject())
+  if (info.Length() == 1 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     //if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
     //{
@@ -375,10 +390,10 @@ JavascriptEngineV8::constructGeoExtentCallback(const v8::Arguments &args)
       extent = new osgEarth::GeoExtent(*rhs);
     }
   }
-  else if (args.Length() == 2 && args[0]->IsObject() && args[1]->IsObject())
+  else if (info.Length() == 2 && info[0]->IsObject() && info[1]->IsObject())
   {
-    v8::Local<v8::Object> obj0( v8::Object::Cast(*args[0]) );
-    v8::Local<v8::Object> obj1( v8::Object::Cast(*args[1]) );
+    v8::Local<v8::Object> obj0( v8::Local<v8::Object>::Cast(info[0]) );
+    v8::Local<v8::Object> obj1( v8::Local<v8::Object>::Cast(info[1]) );
 
     if (V8Util::CheckObjectType(obj0, JSSpatialReference::GetObjectType()) && V8Util::CheckObjectType(obj1, JSBounds::GetObjectType()))
     {
@@ -387,57 +402,65 @@ JavascriptEngineV8::constructGeoExtentCallback(const v8::Arguments &args)
       extent = new osgEarth::GeoExtent(srs, *bounds);
     }
   }
-  else if (args.Length() == 5 && args[0]->IsObject())
+  else if (info.Length() == 5 && info[0]->IsObject())
   {
-    v8::Local<v8::Object> obj( v8::Object::Cast(*args[0]) );
+    v8::Local<v8::Object> obj( v8::Local<v8::Object>::Cast(info[0]) );
 
     if (V8Util::CheckObjectType(obj, JSSpatialReference::GetObjectType()))
     {
       osgEarth::SpatialReference* srs = V8Util::UnwrapObject<osgEarth::SpatialReference>(obj);
-      extent = new osgEarth::GeoExtent(srs, args[1]->NumberValue(), args[2]->NumberValue(), args[3]->NumberValue(), args[4]->NumberValue());
+      extent = new osgEarth::GeoExtent(srs, info[1]->NumberValue(), info[2]->NumberValue(), info[3]->NumberValue(), info[4]->NumberValue());
     }
   }
 
-  if (extent)
-    return JSGeoExtent::WrapGeoExtent(extent, true);
+  if (!extent)
+  {
+    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+    return;
+  }
 
-  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+  info.GetReturnValue().Set(JSGeoExtent::WrapGeoExtent(v8::Isolate::GetCurrent(), extent, true));
 }
 
-v8::Handle<v8::Value>
-JavascriptEngineV8::constructSpatialReferenceCallback(const v8::Arguments &args)
+void
+JavascriptEngineV8::constructSpatialReferenceCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 {
-  if (!args.IsConstructCall()) 
-    return v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+  if (!info.IsConstructCall())
+  {
+    v8::ThrowException(v8::String::New("Cannot call constructor as function"));
+    return;
+  }
 
-	v8::HandleScope handle_scope;
+	v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
  
   osgEarth::SpatialReference* srs;
-  if (args.Length() == 1 && args[0]->IsString())
+  if (info.Length() == 1 && info[0]->IsString())
   {
-    v8::String::Utf8Value utf8_value(args[0]->ToString());
+    v8::String::Utf8Value utf8_value(info[0]->ToString());
     std::string init(*utf8_value);
     srs = osgEarth::SpatialReference::create(init);
   }
 
-  if (srs)
-    return JSSpatialReference::WrapSpatialReference(srs, true);
+  if (!srs)
+  {
+    v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+    return;
+  }
 
-  return v8::ThrowException(v8::String::New("Unsupported arguments in constructor call"));
+  info.GetReturnValue().Set(JSSpatialReference::WrapSpatialReference(v8::Isolate::GetCurrent(), srs, true));
 }
 
-//v8::Handle<v8::Value>
-//JavascriptEngineV8::constructSymbologyGeometryCallback(const v8::Arguments &args)
+//void
+//JavascriptEngineV8::constructSymbologyGeometryCallback(const v8::FunctionCallbackInfo<v8::Value> &info)
 //{
-//	v8::HandleScope handle_scope;
+//	v8::HandleScope handle_scope(_isolate);
 // 
 //  osgEarth::Symbology::Geometry* geom;
-//  if (args.Length() == 2)
+//  if (info.Length() == 2)
 //    geom = new osgEarth::Symbology::Geometry::create(
 //
 //  if (geom)
-//    return JSBounds::WrapBounds(bounds, true);
+//    info.GetReturnValue().Set(JSBounds::WrapBounds(bounds, true));
 //
 //  //return v8::ThrowException(v8::String::New("Unsupported arguments"));
-//  return v8::Handle<v8::Value>();
 //}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/script_engine_v8/V8Util b/src/osgEarthDrivers/script_engine_v8/V8Util
index fbf388a..4a1f633 100644
--- a/src/osgEarthDrivers/script_engine_v8/V8Util
+++ b/src/osgEarthDrivers/script_engine_v8/V8Util
@@ -31,15 +31,15 @@ public:
   typedef std::map< std::string, v8::Handle<v8::Value> > V8PropertyMap;
 
   template<typename T>
-  static v8::Handle<v8::Object> WrapObject(T* obj,  v8::Handle<v8::ObjectTemplate> templ)
+  static v8::Handle<v8::Object> WrapObject(v8::Isolate* isolate, T* obj,  v8::Handle<v8::ObjectTemplate> templ)
   {
-    return WrapObject(obj, templ, V8PropertyMap());
+    return WrapObject(isolate, obj, templ, V8PropertyMap());
   }
 
   template<typename T>
-  static v8::Handle<v8::Object> WrapObject(T* obj,  v8::Handle<v8::ObjectTemplate> templ, const V8PropertyMap& props)
+  static v8::Handle<v8::Object> WrapObject(v8::Isolate* isolate, T* obj,  v8::Handle<v8::ObjectTemplate> templ, const V8PropertyMap& props)
   {
-    v8::HandleScope handle_scope;
+    v8::HandleScope handle_scope(isolate);
 
     v8::Handle<v8::Object> result = templ->NewInstance();
 
diff --git a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
index 6c4208a..541066f 100644
--- a/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
+++ b/src/osgEarthDrivers/tilecache/ReaderWriterTileCache.cpp
@@ -63,7 +63,7 @@ public:
     osg::Image* createImage( const TileKey& key, ProgressCallback* progress)
     {
         unsigned int level, tile_x, tile_y;
-        level = key.getLevelOfDetail() +1;
+        level = key.getLevelOfDetail();
         key.getTileXY( tile_x, tile_y );
 
         unsigned int numCols, numRows;
diff --git a/src/osgEarthDrivers/tileindex/CMakeLists.txt b/src/osgEarthDrivers/tileindex/CMakeLists.txt
new file mode 100644
index 0000000..7d44613
--- /dev/null
+++ b/src/osgEarthDrivers/tileindex/CMakeLists.txt
@@ -0,0 +1,17 @@
+SET(TARGET_COMMON_LIBRARIES ${TARGET_COMMON_LIBRARIES} osgEarthFeatures osgEarthSymbology osgEarthUtil)
+
+SET(TARGET_SRC
+    ReaderWriterTileIndex.cpp
+)
+SET(TARGET_H
+    TileIndexOptions
+)
+
+SETUP_PLUGIN(osgearth_tileindex)
+
+
+# to install public driver includes:
+SET(LIB_NAME tileindex)
+SET(LIB_PUBLIC_HEADERS TileIndexOptions)
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
+
diff --git a/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
new file mode 100644
index 0000000..5976494
--- /dev/null
+++ b/src/osgEarthDrivers/tileindex/ReaderWriterTileIndex.cpp
@@ -0,0 +1,186 @@
+/* -*-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/TileSource>
+#include <osgEarth/FileUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/ImageUtils>
+#include <osgEarth/URI>
+
+#include <osgEarthUtil/TileIndex>
+
+
+#include <osgDB/FileNameUtils>
+#include <osgDB/FileUtils>
+#include <osgDB/Registry>
+#include <osgDB/ReadFile>
+#include <osgDB/ImageOptions>
+
+#include <sstream>
+#include <stdlib.h>
+#include <memory.h>
+
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include "TileIndexOptions"
+
+
+#define LC "[TileIndex driver] "
+
+using namespace std;
+using namespace osgEarth;
+using namespace osgEarth::Drivers;
+using namespace osgEarth::Util;
+
+class TileIndexSource : public TileSource
+{
+public:
+    TileIndexSource( const TileSourceOptions& options ):
+      TileSource( options ),
+      _options( options ),
+	  _tileSourceCache( true )
+    {
+    }
+
+    Status initialize( const osgDB::Options* dbOptions )
+    {
+        _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
+        if ( _options.url().isSet() )
+        {
+            _index = TileIndex::load( _options.url()->full() );        
+            if (_index.valid() )
+            {
+                setProfile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() );
+                return STATUS_OK;
+            }
+        }
+        return Status::Error("Failed to load TileIndex");
+    }
+
+    osg::Image* createImage( const TileKey&        key,
+                             ProgressCallback*     progress)
+    {        
+        osg::Timer_t start = osg::Timer::instance()->tick();
+        std::vector< std::string > files;
+        _index->getFiles( key.getExtent(), files );        
+        osg::Timer_t end = osg::Timer::instance()->tick();
+        OE_DEBUG << "Got " << files.size() << " files in " << osg::Timer::instance()->delta_m( start, end) << " ms" << std::endl;
+
+        // The result image
+        osg::Image* result = 0;
+        
+        for (unsigned int i = 0; i < files.size(); i++)
+        {            
+            osg::ref_ptr< TileSource> source;
+            {
+                //Try to get the TileSource from the cache
+                TileSourceCache::Record record;
+                if (_tileSourceCache.get( files[i], record ))
+                {
+                    source = record.value().get();                    
+                }
+                else
+                {
+                    // Couldn't get it from the cache so open it.                    
+                    GDALOptions opt;
+                    opt.url() = files[i];
+                    //Just force it to render so we don't have to worry about falling back
+                    opt.maxDataLevelOverride() = 23;           
+                    //Disable the l2 cache so that we don't run out of RAM so easily.
+                    opt.L2CacheSize() = 0;
+
+                    start = osg::Timer::instance()->tick();
+                    source = osgEarth::TileSourceFactory::create( opt );                               
+                    TileSource::Status compStatus = source->startup( 0 );
+                    if (compStatus.isOK())
+                    {
+                        _tileSourceCache.insert( files[i], source.get() );                                                
+                    }
+                    else
+                    {
+                        OE_WARN << "Failed to open " << files[i] << std::endl;
+                    }
+                    end = osg::Timer::instance()->tick();
+                    //OE_NOTICE << "init took " << osg::Timer::instance()->delta_m( start, end) << "ms" << std::endl;
+                }               
+            }
+
+            
+            
+            start = osg::Timer::instance()->tick();
+            osg::ref_ptr< osg::Image > image = source->createImage( key);
+            end = osg::Timer::instance()->tick();
+            OE_DEBUG << "createImage " << osg::Timer::instance()->delta_m( start, end) << "ms" << std::endl;
+            if (image)
+            {                                
+                if (!result)
+                {
+                    // Initialize the result
+                     result = new osg::Image( *image.get() );
+                }
+                else
+                {
+                    // Composite the new image with the result
+                     ImageUtils::mix( result, image.get(), 1.0);
+                }                
+            }
+            else
+            {
+                OE_DEBUG << "Failed to create image for " << files[i] << std::endl;
+            }
+        }
+
+        return result;
+    }
+
+    //std::map< std::string, osg::ref_ptr< TileSource> > _tileSourceCache;
+    typedef LRUCache< std::string, osg::ref_ptr< TileSource> > TileSourceCache;
+    TileSourceCache _tileSourceCache;
+
+    osg::ref_ptr< TileIndex > _index;
+    TileIndexOptions _options;
+    osg::ref_ptr<osgDB::Options> _dbOptions;
+};
+
+
+class ReaderWriterTileIndex : public TileSourceDriver
+{
+public:
+    ReaderWriterTileIndex() {}
+
+    virtual const char* className()
+    {
+        return "TileIndex Reader";
+    }
+
+    virtual bool acceptsExtension(const std::string& extension) const
+    {
+        return osgDB::equalCaseInsensitive( extension, "osgearth_tileindex" );
+    }
+
+    virtual ReadResult readObject(const std::string& file_name, const Options* options ) const
+    {        
+        if ( !acceptsExtension( osgDB::getFileExtension( file_name ) ) )
+        {
+            return ReadResult::FILE_NOT_HANDLED;
+        }
+        
+        return new TileIndexSource( getTileSourceOptions(options) );
+    }
+};
+
+REGISTER_OSGPLUGIN(osgearth_tileindex, ReaderWriterTileIndex)
diff --git a/src/osgEarthDrivers/arcgis/ArcGISOptions b/src/osgEarthDrivers/tileindex/TileIndexOptions
similarity index 64%
copy from src/osgEarthDrivers/arcgis/ArcGISOptions
copy to src/osgEarthDrivers/tileindex/TileIndexOptions
index fe27a64..3cd0d57 100644
--- a/src/osgEarthDrivers/arcgis/ArcGISOptions
+++ b/src/osgEarthDrivers/tileindex/TileIndexOptions
@@ -16,41 +16,42 @@
  * You 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_ARCGIS_DRIVEROPTIONS
-#define OSGEARTH_DRIVER_ARCGIS_DRIVEROPTIONS 1
+#ifndef OSGEARTH_DRIVER_TILEINDEX_DRIVEROPTIONS
+#define OSGEARTH_DRIVER_TILEINDEX_DRIVEROPTIONS 1
 
 #include <osgEarth/Common>
 #include <osgEarth/TileSource>
+#include <osgEarth/GeoCommon>
 #include <osgEarth/URI>
 
 namespace osgEarth { namespace Drivers
 {
     using namespace osgEarth;
 
-    class ArcGISOptions : public TileSourceOptions // NO EXPORT; header only
-    {
-    public:
+    class TileIndexOptions : public TileSourceOptions // NO EXPORT; header only
+    {      
+    public: // properties
+
         optional<URI>& url() { return _url; }
         const optional<URI>& url() const { return _url; }
 
-        optional<std::string>& token() { return _token; }
-        const optional<std::string>& token() const { return _token; }
+    public: // ctors
 
-    public:
-        ArcGISOptions( const TileSourceOptions& opt =TileSourceOptions() ) : TileSourceOptions( opt )
+        TileIndexOptions( const TileSourceOptions& options =TileSourceOptions() ) :
+            TileSourceOptions( options )            
         {
-            setDriver( "arcgis" );
+            setDriver( "tileindex" );
             fromConfig( _conf );
         }
 
-        /** dtor */
-        virtual ~ArcGISOptions() { }
+        virtual ~TileIndexOptions() { }
 
     public:
-        Config getConfig() const {
+
+        Config getConfig() const
+        {
             Config conf = TileSourceOptions::getConfig();
-            conf.updateIfSet("url", _url );
-            conf.updateIfSet("token", _token );
+            conf.updateIfSet( "url", _url );
             return conf;
         }
 
@@ -61,15 +62,11 @@ namespace osgEarth { namespace Drivers
 
         void fromConfig( const Config& conf ) {
             conf.getIfSet( "url", _url );
-            conf.getIfSet( "token", _token);
         }
 
-    private:
-        optional<URI>         _url;
-        optional<std::string> _token;
+        optional<URI>                    _url;        
     };
 
 } } // namespace osgEarth::Drivers
 
-#endif // OSGEARTH_DRIVER_ARCGIS_DRIVEROPTIONS
-
+#endif // OSGEARTH_DRIVER_TILEINDEX_DRIVEROPTIONS
diff --git a/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp b/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp
index 5a743a6..b8d44b1 100644
--- a/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp
+++ b/src/osgEarthDrivers/tms/ReaderWriterTMS.cpp
@@ -51,7 +51,7 @@ public:
         _invertY = _options.tmsType() == "google";
     }
 
-
+    // one-time initialization (per source)
     Status initialize(const osgDB::Options* dbOptions)
     {
         _dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
@@ -88,6 +88,11 @@ public:
             {
                 return Status::Error( Stringify() << "Failed to read tilemap from " << tmsURI.full() );
             }
+
+            OE_INFO << LC
+                << "TMS tile map datestamp = "
+                << DateTime(_tileMap->getTimeStamp()).asRFC1123()
+                << std::endl;
             
             profile = _tileMap->createProfile();
             if ( profile )
@@ -125,7 +130,33 @@ public:
         return STATUS_OK;
     }
 
+    // TileSource timestamp is the time of the time map metadata.
+    TimeStamp getLastModifiedTime() const
+    {
+        if ( _tileMap.valid() )
+            return _tileMap->getTimeStamp();
+        else
+            return TileSource::getLastModifiedTime();
+    }
+
+    // reflect a default cache policy based on whether this TMS repo is
+    // local or remote.
+    CachePolicy getCachePolicyHint(const Profile* targetProfile) const
+    {
+        // if the source is local and the profiles line up, don't cache (by default).
+        if (!_options.url()->isRemote() &&
+            targetProfile && 
+            targetProfile->isEquivalentTo(getProfile()) )
+        {
+            return CachePolicy::NO_CACHE;
+        }
+        else
+        {
+            return CachePolicy::DEFAULT;
+        }
+    }
 
+    // creates an image from the TMS repo.
     osg::Image* createImage(const TileKey&        key,
                             ProgressCallback*     progress )
     {
diff --git a/src/osgEarthDrivers/vpb/Copy of ReaderWriterVPB.cpp b/src/osgEarthDrivers/vpb/Copy of ReaderWriterVPB.cpp
deleted file mode 100644
index 4845729..0000000
--- a/src/osgEarthDrivers/vpb/Copy of ReaderWriterVPB.cpp	
+++ /dev/null
@@ -1,669 +0,0 @@
-/* -*-c++-*- */
-/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
- * Copyright 2008-2009 Pelican Ventures, Inc.
- * http://osgearth.org
- *
- * osgEarth is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- */
-
-#include <osgEarth/Registry>
-#include <osgEarth/TileSource>
-#include <osgEarth/HTTPClient>
-
-#include <osg/Notify>
-#include <osg/io_utils>
-#include <osg/Version>
-#include <osgTerrain/Terrain>
-
-#include <osgDB/FileNameUtils>
-#include <osgDB/FileUtils>
-#include <osgDB/Registry>
-#include <osgDB/ReadFile>
-#include <osgDB/WriteFile>
-
-#include <OpenThreads/Mutex>
-#include <OpenThreads/ScopedLock>
-
-#include <sstream>
-
-using namespace osgEarth;
-using namespace OpenThreads;
-
-#define PROPERTY_URL                    "url"
-#define PROPERTY_PRIMARY_SPLIT_LEVEL    "primary_split_level"
-#define PROPERTY_SECONDARY_SPLIT_LEVEL  "secondary_split_level"
-#define PROPERTY_DIRECTORY_STRUCTURE    "directory_structure"
-#define PROPERTY_LAYER_NUM              "layer"
-#define PROPERTY_NUM_TILES_WIDE_AT_LOD0 "num_tiles_wide_at_lod0"
-#define PROPERTY_NUM_TILES_HIGH_AT_LOD0 "num_tiles_high_at_lod0"
-#define PROPERTY_BASE_NAME              "base_name"
-
-
-class CollectTiles : public osg::NodeVisitor
-{
-public:
-
-    CollectTiles(): 
-        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
-    {
-    }
-    
-    void reset()
-    {
-        _terrainTiles.clear();
-    }
-    
-    void apply(osg::Group& group)
-    {
-        osgTerrain::TerrainTile* terrainTile = dynamic_cast<osgTerrain::TerrainTile*>(&group);
-        if (terrainTile)
-        {
-            osg::notify(osg::INFO)<<"Found terrain tile TileID("<<
-				TileKey::getLOD(terrainTile->getTileID())<<", "<<
-                terrainTile->getTileID().x<<", "<<
-                terrainTile->getTileID().y<<")"<<std::endl;
-            
-            _terrainTiles.push_back(terrainTile);
-        }
-        else
-        {
-            traverse(group);
-        }
-    }
-    
-    osgTerrain::Locator* getLocator()
-    {
-        for(unsigned int i=0; i<_terrainTiles.size(); ++i)
-        {
-            osgTerrain::TerrainTile* tile = _terrainTiles[i].get();
-            osgTerrain::Locator* locator = tile->getLocator();
-            if (locator) return locator;
-        }
-        return 0;
-    }
-
-    bool getRange(double& min_x, double& min_y, double& max_x, double& max_y) const
-    {
-        min_x = DBL_MAX;
-        max_x = -DBL_MAX;
-        min_y = DBL_MAX;
-        max_y = -DBL_MAX;
-        
-        typedef std::vector<osg::Vec3d> Corners;
-        Corners corners;
-        corners.push_back(osg::Vec3d(0.0f,0.0f,0.0f));
-        corners.push_back(osg::Vec3d(1.0f,0.0f,0.0f));
-        corners.push_back(osg::Vec3d(1.0f,1.0f,0.0f));
-        corners.push_back(osg::Vec3d(1.0f,1.0f,0.0f));
-        
-        for(unsigned int i=0; i<_terrainTiles.size(); ++i)
-        {
-            osgTerrain::TerrainTile* tile = _terrainTiles[i].get();
-            osgTerrain::Locator* locator = tile->getLocator();
-            if (locator)
-            {
-                for(Corners::iterator itr = corners.begin();
-                    itr != corners.end();
-                    ++itr)
-                {
-                    osg::Vec3d& local = *itr;
-                    osg::Vec3d projected = local * locator->getTransform();
-
-                    if (projected.x()<min_x) min_x = projected.x();
-                    if (projected.x()>max_x) max_x = projected.x();
-
-                    if (projected.y()<min_y) min_y = projected.y();
-                    if (projected.y()>max_y) max_y = projected.y();
-                }
-            }
-        }
-        
-        return min_x <= max_x;
-    }
-    
-    typedef std::vector< osg::ref_ptr<osgTerrain::TerrainTile> > TerrainTiles;
-    TerrainTiles _terrainTiles;
-};
-
-
-class VPBDatabase : public osg::Referenced
-{
-public:
-
-    enum DirectoryStructure
-    {
-        FLAT,
-        FLAT_TASK_DIRECTORIES,
-        NESTED_TASK_DIRECTORIES
-    };
-
-    VPBDatabase(const PluginOptions* in_options ) :
-        options( in_options),
-        primary_split_level(-1),
-        secondary_split_level(-1),
-        directory_structure(FLAT_TASK_DIRECTORIES),
-        maxNumTilesInCache(128),
-        profile( osgEarth::Registry::instance()->getGlobalGeodeticProfile() ),
-        initialized( false )
-    {
-        //NOP
-    }
-
-    void init()
-    {
-        if ( initialized ) return;
-        initialized = true;
-
-        unsigned int numTilesWideAtLod0, numTilesHighAtLod0;
-        profile->getNumTiles(0, numTilesWideAtLod0, numTilesHighAtLod0);
-
-        if ( options.valid() )
-        {
-            const Config& conf = options->config();
-
-            url = conf.value( PROPERTY_URL );
-            primary_split_level = conf.value<int>( PROPERTY_PRIMARY_SPLIT_LEVEL, -1 );
-            secondary_split_level = conf.value<int>( PROPERTY_SECONDARY_SPLIT_LEVEL, -1 );
-
-            std::string dir_string = conf.value( PROPERTY_DIRECTORY_STRUCTURE );
-            if (dir_string=="nested" || dir_string=="nested_task_directories" ) directory_structure = NESTED_TASK_DIRECTORIES;
-            if (dir_string=="task" || dir_string=="flat_task_directories") directory_structure = FLAT_TASK_DIRECTORIES;
-            if (dir_string=="flat") directory_structure = FLAT;
-            
-            base_name = conf.value( PROPERTY_BASE_NAME );
-        }
-
-        // validate dataset
-        if (!url.empty())
-        {
-            osg::ref_ptr<osgDB::ReaderWriter::Options> localOptions = new osgDB::ReaderWriter::Options;
-            localOptions->setPluginData("osgearth_vpb Plugin",(void*)(1));
-            root_node = osgDB::readNodeFile(url, localOptions.get());
-
-
-            if (root_node.valid())
-            {
-                path = osgDB::getFilePath(url);
-                if ( base_name.empty() )
-                    base_name = osgDB::getStrippedName(url);
-                extension = osgDB::getFileExtension(url);
-                
-                osg::notify(osg::INFO)<<"VPBasebase constructor: Loaded root "<<url<<", path="<<path<<" base_name="<<base_name<<" extension="<<extension<<std::endl;
-                
-                std::string srs = profile->getSRS()->getInitString(); //.srs();
-                
-                osg::CoordinateSystemNode* csn = dynamic_cast<osg::CoordinateSystemNode*>(root_node.get());
-                if (csn)
-                {
-                    osg::notify(osg::INFO)<<"CordinateSystemNode found, coordinate system is : "<<csn->getCoordinateSystem()<<std::endl;
-                    
-                    srs = csn->getCoordinateSystem();
-                }
-
-                CollectTiles ct;
-                root_node->accept(ct);
-
-                    
-                osgTerrain::Locator* locator = ct.getLocator();
-                if (locator)
-                {
-                    double min_x, max_x, min_y, max_y;
-                    ct.getRange(min_x, min_y, max_x, max_y);
-
-                    osg::notify(osg::INFO)<<"range("<<min_x<<", "<<min_y<<", "<<max_x<<", "<<max_y<<std::endl;
-
-                    srs = locator->getCoordinateSystem();
-                
-                    //osgEarth::Profile::ProfileType ptype = osgEarth::Profile::TYPE_UNKNOWN;
-
-                    //switch(locator->getCoordinateSystemType())
-                    //{
-                    //    case(osgTerrain::Locator::GEOCENTRIC):
-                    //        ptype = Profile::TYPE_GEODETIC; //profile.setProfileType(osgEarth::Profile::GLOBAL_GEODETIC);
-                    //        break;
-                    //    case(osgTerrain::Locator::GEOGRAPHIC):
-                    //        ptype = Profile::TYPE_LOCAL; //profile.setProfileType(osgEarth::Profile::PROJECTED);
-                    //        break;
-                    //    case(osgTerrain::Locator::PROJECTED):
-                    //        ptype = Profile::TYPE_LOCAL; //profile.setProfileType(osgEarth::Profile::PROJECTED);
-                    //        break;
-                    //}
-
-                    double aspectRatio = (max_x-min_x)/(max_y-min_y);
-                    
-                    osg::notify(osg::INFO)<<"aspectRatio = "<<aspectRatio<<std::endl;
-
-                    if (aspectRatio>1.0)
-                    {
-                        numTilesWideAtLod0 = static_cast<unsigned int>(floor(aspectRatio+0.499999));
-                        numTilesHighAtLod0 = 1;
-                    }
-                    else
-                    {
-                        numTilesWideAtLod0 = 1;
-                        numTilesHighAtLod0 = static_cast<unsigned int>(floor(1.0/aspectRatio+0.499999));
-                    }
-                    
-                    osg::notify(osg::INFO)<<"computed numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
-                    osg::notify(osg::INFO)<<"computed numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
-                    
-                    if ( options.valid() )
-                    {
-                        if ( options->getPluginData( PROPERTY_NUM_TILES_WIDE_AT_LOD0 ) )
-                            numTilesWideAtLod0 = atoi( (const char*)options->getPluginData( PROPERTY_NUM_TILES_WIDE_AT_LOD0 ) );
-
-                        if ( options->getPluginData( PROPERTY_NUM_TILES_HIGH_AT_LOD0 ) )
-                            numTilesHighAtLod0 = atoi( (const char*)options->getPluginData( PROPERTY_NUM_TILES_HIGH_AT_LOD0 ) );
-                    }
-
-                    osg::notify(osg::INFO)<<"final numTilesWideAtLod0 = "<<numTilesWideAtLod0<<std::endl;
-                    osg::notify(osg::INFO)<<"final numTilesHightAtLod0 = "<<numTilesHighAtLod0<<std::endl;
-                   
-                    profile = osgEarth::Profile::create( 
-                        srs,
-                        osg::RadiansToDegrees(min_x), 
-                        osg::RadiansToDegrees(min_y), 
-                        osg::RadiansToDegrees(max_x), 
-                        osg::RadiansToDegrees(max_y),
-                        numTilesWideAtLod0,
-                        numTilesHighAtLod0 );
-                }
-                
-            }
-            else
-            {
-                osg::notify(osg::NOTICE)<<"Unable to read file "<<url<<std::endl;
-                url = "";
-            }
-        }
-        else 
-        {
-            osg::notify(osg::NOTICE)<<"No data referenced "<<std::endl;
-        }
-
-    }
-    
-    std::string createTileName( int level, int tile_x, int tile_y )
-    {
-        init();
-        std::stringstream buf;
-        if (directory_structure==FLAT)
-        {
-             buf<<path<<"/"<<base_name<<"_L"<<level<<"_X"<<tile_x/2<<"_Y"<<tile_y/2<<"_subtile."<<extension;
-        }
-        else
-        {
-            if (level<primary_split_level)
-            {
-                buf<<path<<"/"<<base_name<<"_root_L0_X0_Y0/"<<
-                     base_name<<"_L"<<level<<"_X"<<tile_x/2<<"_Y"<<tile_y/2<<"_subtile."<<extension;
-
-            }
-            else if (level<secondary_split_level)
-            {
-                tile_x /= 2;
-                tile_y /= 2;
-
-                int split_x = tile_x >> (level - primary_split_level);
-                int split_y = tile_y >> (level - primary_split_level);
-
-                buf<<path<<"/"<<base_name<<"_subtile_L"<<primary_split_level<<"_X"<<split_x<<"_Y"<<split_y<<"/"<<
-                     base_name<<"_L"<<level<<"_X"<<tile_x<<"_Y"<<tile_y<<"_subtile."<<extension;
-            }
-            else if (directory_structure==NESTED_TASK_DIRECTORIES)
-            {
-                tile_x /= 2;
-                tile_y /= 2;
-
-                int split_x = tile_x >> (level - primary_split_level);
-                int split_y = tile_y >> (level - primary_split_level);
-
-                int secondary_split_x = tile_x >> (level - secondary_split_level);
-                int secondary_split_y = tile_y >> (level - secondary_split_level);
-
-                buf<<path<<"/"<<base_name<<"_subtile_L"<<primary_split_level<<"_X"<<split_x<<"_Y"<<split_y<<"/"<<
-                     base_name<<"_subtile_L"<<secondary_split_level<<"_X"<<secondary_split_x<<"_Y"<<secondary_split_y<<"/"<< 
-                     base_name<<"_L"<<level<<"_X"<<tile_x<<"_Y"<<tile_y<<"_subtile."<<extension;
-            }
-            else
-            {
-                tile_x /= 2;
-                tile_y /= 2;
-
-                int split_x = tile_x >> (level - secondary_split_level);
-                int split_y = tile_y >> (level - secondary_split_level);
-
-                buf<<path<<"/"<<base_name<<"_subtile_L"<<secondary_split_level<<"_X"<<split_x<<"_Y"<<split_y<<"/"<<
-                     base_name<<"_L"<<level<<"_X"<<tile_x<<"_Y"<<tile_y<<"_subtile."<<extension;
-            }
-        }
-        
-        osg::notify(osg::INFO)<<"VPBDatabase::createTileName(), buf.str()=="<<buf.str()<<std::endl;
-        
-        return buf.str();
-    }
-    
-    osgTerrain::TerrainTile* getTerrainTile( const TileKey* key, ProgressCallback* progress )
-    {
-        init();
-        int level = key->getLevelOfDetail();
-        unsigned int tile_x, tile_y;
-        key->getTileXY( tile_x, tile_y );
-        
-        int max_x = (2 << level) - 1;
-        int max_y = (1 << level) - 1;
-        
-        tile_y = max_y - tile_y;
-
-        osgTerrain::TileID tileID(level, tile_x, tile_y);
-
-        osg::ref_ptr<osgTerrain::TerrainTile> tile = findTile(tileID, false);
-        if (tile.valid()) return tile.get();
-        
-        osg::notify(osg::INFO)<<"Max_x = "<<max_x<<std::endl;
-        osg::notify(osg::INFO)<<"Max_y = "<<max_y<<std::endl;
-
-        osg::notify(osg::INFO)<<"base_name = "<<base_name<<" psl="<<primary_split_level<<" ssl="<<secondary_split_level<<std::endl;
-        osg::notify(osg::INFO)<<"level = "<<level<<", x = "<<tile_x<<", tile_y = "<<tile_y<<std::endl;
-        osg::notify(osg::INFO)<<"tile_name "<<createTileName(level, tile_x, tile_y)<<std::endl;
-        osg::notify(osg::INFO)<<"thread "<<OpenThreads::Thread::CurrentThread()<<std::endl;
-
-        std::string filename = createTileName(level, tile_x, tile_y);
-        
-        {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(blacklistMutex);
-            if (blacklistedFilenames.count(filename)==1)
-            {
-                osg::notify(osg::INFO)<<"file has been found in black list : "<<filename<<std::endl;
-
-                insertTile(tileID, 0);
-                return 0;
-            }
-        }
-        
-
-        osg::ref_ptr<osgDB::ReaderWriter::Options> localOptions = new osgDB::ReaderWriter::Options;
-        localOptions->setPluginData("osgearth_vpb Plugin",(void*)(1));
-
-        //osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(filename, localOptions.get());
-        osg::ref_ptr<osg::Node> node;
-        if (osgDB::containsServerAddress( filename ) )
-        {
-            node = HTTPClient::readNodeFile( filename, localOptions.get(), progress );
-        }
-        else
-        {
-            node = osgDB::readNodeFile( filename, localOptions.get() );
-        }
-
-        if (node.valid())
-        {
-            osg::notify(osg::INFO)<<"Loaded model "<<filename<<std::endl;
-            CollectTiles ct;
-            node->accept(ct);
-
-            int base_x = (tile_x / 2) * 2;
-            int base_y = (tile_y / 2) * 2;
-            
-            double min_x, max_x, min_y, max_y;
-            ct.getRange(min_x, min_y, max_x, max_y);
-
-            double center_x = (min_x + max_x)*0.5;
-            double center_y = (min_y + max_y)*0.5;
-
-            osg::Vec3d local(0.5,0.5,0.0);
-            for(unsigned int i=0; i<ct._terrainTiles.size(); ++i)
-            {
-                osgTerrain::TerrainTile* tile = ct._terrainTiles[i].get();
-                osgTerrain::Locator* locator = tile->getLocator();
-                if (locator)
-                {
-                    osg::Vec3d projected = local * locator->getTransform();
-                    
-                    int local_x = base_x + ((projected.x() > center_x) ? 1 : 0);
-                    int local_y = base_y + ((projected.y() > center_y) ? 1 : 0);
-                    osgTerrain::TileID local_tileID(level, local_x, local_y);
-                    
-                    tile->setTileID(local_tileID);
-                    insertTile(local_tileID, tile);
-                }
-
-            }
-
-        }
-        else
-        {
-            //Only blacklist if the request wasn't cancelled by the callback.
-            if (!progress || (!progress->isCanceled()))
-            {
-                osg::notify(osg::INFO)<<"Black listing : "<<filename<<std::endl;
-                OpenThreads::ScopedLock<OpenThreads::Mutex> lock(blacklistMutex);
-                blacklistedFilenames.insert(filename);
-            }
-        }
-        
-        return findTile(tileID, true);
-    }
-    
-    void insertTile(const osgTerrain::TileID& tileID, osgTerrain::TerrainTile* tile)
-    {
-        init();
-        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(tileMapMutex);
-
-        if ( tileMap.find(tileID) == tileMap.end() )
-        {
-            tileMap[tileID] = tile;
-
-            tileFIFO.push_back(tileID);
-
-            if (tileFIFO.size()>maxNumTilesInCache)
-            {
-                osgTerrain::TileID tileToRemove = tileFIFO.front();
-                tileFIFO.pop_front();
-                tileMap.erase(tileToRemove);
-
-			    osg::notify(osg::INFO)<<"Pruned tileID ("<<TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<")"<<std::endl;
-            }
-
-            osg::notify(osg::INFO)<<"insertTile ("
-                << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
-                << " tileFIFO.size()=="<<tileFIFO.size()<<std::endl;
-        }
-        else
-        {
-            osg::notify(osg::INFO)<<"insertTile ("
-                << TileKey::getLOD(tileID)<<", "<<tileID.x<<", "<<tileID.y<<") " 
-                << " ...already in cache!"<<std::endl;
-        }
-    }
-
-    osgTerrain::TerrainTile* findTile(const osgTerrain::TileID& tileID, bool insertBlankTileIfNotFound = false)
-    {
-        init();
-        {
-            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(tileMapMutex);
-            TileMap::iterator itr = tileMap.find(tileID);
-            if (itr!=tileMap.end()) return itr->second.get();
-        }
-
-        if (insertBlankTileIfNotFound) insertTile(tileID, 0);
-
-        return 0;
-    }
-
-    const Profile* getProfile() 
-    {
-        init();
-        return profile.get();
-    }
-
-    const std::string& getExtension()
-    {
-        init();
-        return extension;
-    }
-
-    bool initialized;
-
-    osg::ref_ptr<const PluginOptions> options;
-    std::string url;
-    std::string path;
-    std::string base_name;
-    std::string extension;
-    int primary_split_level;
-    int secondary_split_level;
-    DirectoryStructure directory_structure;
-
-    osg::ref_ptr<const Profile> profile;
-    osg::ref_ptr<osg::Node> root_node;
-    
-    unsigned int maxNumTilesInCache;
-    
-    typedef std::map<osgTerrain::TileID, osg::ref_ptr<osgTerrain::TerrainTile> > TileMap;
-    TileMap tileMap;
-    OpenThreads::Mutex tileMapMutex;
-    
-    typedef std::list<osgTerrain::TileID> TileIDList;
-    TileIDList tileFIFO;
-    
-    typedef std::set<std::string> StringSet;
-    StringSet blacklistedFilenames;
-    OpenThreads::Mutex blacklistMutex;
-    
-};
-
-class VPBSource : public TileSource
-{
-public:
-    VPBSource( VPBDatabase* vpbDatabase, const PluginOptions* in_options) :  
-      TileSource(in_options),
-      _vpbDatabase(vpbDatabase),
-      _layerNum(0)
-    {
-        if ( in_options )
-        {
-            _layerNum = in_options->config().value<int>( PROPERTY_LAYER_NUM, _layerNum );
-        }
-    }
-
-    void initialize( const std::string& referenceURI, const Profile* overrideProfile)
-    {
-		if ( overrideProfile)
-		{
-			setProfile( overrideProfile );
-		}
-		else
-		{
-			setProfile( _vpbDatabase->getProfile() ); //profile.get() );
-		}
-    }
-    
-    osg::Image* createImage( const TileKey* key,
-                             ProgressCallback* progress)
-    {
-        //TODO:  Make VPB driver use progress callback
-        osg::ref_ptr<osgTerrain::TerrainTile> tile = _vpbDatabase->getTerrainTile(key, progress);                
-        if (tile.valid())
-        {        
-            if (_layerNum < tile->getNumColorLayers())
-            {
-                osgTerrain::Layer* layer = tile->getColorLayer(_layerNum);
-                osgTerrain::ImageLayer* imageLayer = dynamic_cast<osgTerrain::ImageLayer*>(layer);
-                if (imageLayer)
-                {
-                    return imageLayer->getImage();
-                }
-            }
-        }
-        
-        return 0;
-    }
-
-    osg::HeightField* createHeightField( const TileKey* key,
-                                         ProgressCallback* progress
-                                         )
-    {
-        osg::ref_ptr<osgTerrain::TerrainTile> tile = _vpbDatabase->getTerrainTile(key, progress);                
-        if (tile.valid())
-        {        
-            osgTerrain::Layer* elevationLayer = tile->getElevationLayer();
-            osgTerrain::HeightFieldLayer* hfLayer = dynamic_cast<osgTerrain::HeightFieldLayer*>(elevationLayer);
-            if (hfLayer) 
-            {
-                return hfLayer->getHeightField();
-            }
-        }
-
-        return 0;
-    }
-
-    virtual std::string getExtension()  const 
-    {
-        //All VPB tiles are in IVE format
-        return _vpbDatabase->getExtension();
-    }
-
-private:
-    osg::ref_ptr<VPBDatabase>   _vpbDatabase;
-    unsigned int                _layerNum;
-    osg::ref_ptr<PluginOptions> _options;
-};
-
-
-class ReaderWriterVPB : public osgDB::ReaderWriter
-{
-    public:
-        ReaderWriterVPB()
-        {
-            supportsExtension( "osgearth_vpb", "VirtualPlanetBuilder data" );
-        }
-
-        virtual const char* className()
-        {
-            return "VirtualPlanetBuilder ReaderWriter";
-        }
-
-        virtual ReadResult readObject(const std::string& file_name, const Options* options) const
-        {
-            if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name )))
-                return ReadResult::FILE_NOT_HANDLED;
-
-            const PluginOptions* pluginOpts = static_cast<const PluginOptions*>( options );
-
-            //osg::notify(osg::NOTICE) << pluginOpts->config().toString() << std::endl;
-
-            std::string url = pluginOpts->config().value( PROPERTY_URL );
-            if ( !url.empty() )
-            {                
-                OpenThreads::ScopedLock<OpenThreads::Mutex> lock(vpbDatabaseMapMutex);
-                osg::observer_ptr<VPBDatabase>& db_ptr = vpbDatabaseMap[url];
-                
-                if (!db_ptr) db_ptr = new VPBDatabase( pluginOpts );
-                
-                if (db_ptr.valid()) return new VPBSource(db_ptr.get(), pluginOpts );
-                else return ReadResult::FILE_NOT_FOUND;               
-            }
-            else
-            {
-                return ReadResult::FILE_NOT_HANDLED;
-            }
-        }
-        
-        typedef std::map<std::string, osg::observer_ptr<VPBDatabase> > VPBDatabaseMap;
-        mutable OpenThreads::Mutex vpbDatabaseMapMutex;
-        mutable VPBDatabaseMap vpbDatabaseMap;
-};
-
-REGISTER_OSGPLUGIN(osgearth_vpb, ReaderWriterVPB)
-
diff --git a/src/osgEarthDrivers/vpb/VPBOptions b/src/osgEarthDrivers/vpb/VPBOptions
index 4aa8529..21ae457 100644
--- a/src/osgEarthDrivers/vpb/VPBOptions
+++ b/src/osgEarthDrivers/vpb/VPBOptions
@@ -54,8 +54,8 @@ namespace osgEarth { namespace Drivers
         optional<int>& layer() { return _layer; }
         const optional<int>& layer() const { return _layer; }
 
-		optional<std::string>& layerSetName() { return _layerSetName; }
-		const optional<std::string>& layerSetName() const { return _layerSetName; }
+        optional<std::string>& layerSetName() { return _layerSetName; }
+        const optional<std::string>& layerSetName() const { return _layerSetName; }
 
         optional<int>& numTilesWideAtLod0() { return _widthLod0; }
         const optional<int>& numTilesWideAtLod0() const { return _widthLod0; }
@@ -93,7 +93,7 @@ namespace osgEarth { namespace Drivers
             conf.updateIfSet("primary_split_level", _primarySplitLevel);
             conf.updateIfSet("secondary_split_level", _secondarySplitLevel);
             conf.updateIfSet("layer", _layer);
-			conf.updateIfSet("layer_setname", _layerSetName);
+            conf.updateIfSet("layer_setname", _layerSetName);
             conf.updateIfSet("num_tiles_wide_at_lod_0", _widthLod0 );
             conf.updateIfSet("num_tiles_high_at_lod_0", _heightLod0 );
             conf.updateIfSet("base_name", _baseName );
@@ -118,9 +118,9 @@ namespace osgEarth { namespace Drivers
             conf.getIfSet("primary_split_level", _primarySplitLevel);
             conf.getIfSet("secondary_split_level", _secondarySplitLevel);
             conf.getIfSet("layer", _layer);
-			conf.getIfSet("layer_setname", _layerSetName);
-            conf.getIfSet("numTilesWideAtLod0", _widthLod0 );
-            conf.getIfSet("numTilesHighAtLod0", _heightLod0 );
+            conf.getIfSet("layer_setname", _layerSetName);
+            conf.getIfSet("num_tiles_wide_at_lod_0", _widthLod0 );
+            conf.getIfSet("num_tiles_high_at_lod_0", _heightLod0 );
             conf.getIfSet("base_name", _baseName);
             conf.getIfSet("terrain_tile_cache_size", _terrainTileCacheSize);
             
diff --git a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
index c8c7fc5..68a230b 100644
--- a/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
+++ b/src/osgEarthDrivers/wms/ReaderWriterWMS.cpp
@@ -290,14 +290,14 @@ public:
 
 public:
 
-    // fetch a tile from the WMS service and report any exceptions.
-    osgDB::ReaderWriter* fetchTileAndReader( 
+    // fetch a tile image from the WMS service and report any exceptions.
+    osg::Image* fetchTileImage(
         const TileKey&     key, 
         const std::string& extraAttrs,
         ProgressCallback*  progress, 
         ReadResult&        out_response )
     {
-        osgDB::ReaderWriter* result = 0L;
+        osg::ref_ptr<osg::Image> image;
 
         std::string uri = createURI(key);
         if ( !extraAttrs.empty() )
@@ -306,14 +306,15 @@ public:
             uri = uri + delim + extraAttrs;
         }
 
-
-        out_response = URI( uri ).readString( _dbOptions.get(), progress );
-
-        //...
-        //out_response = HTTPClient::get( uri, 0L, progress ); //getOptions(), progress );
-
-        if ( out_response.succeeded() )
+        // Try to get the image first
+        out_response = URI( uri ).readImage( _dbOptions.get(), progress);
+        
+        
+        if ( !out_response.succeeded() )
         {
+            // If it failed, try to read it again as a string to get the exception.
+            out_response = URI( uri ).readString( _dbOptions.get(), progress );      
+
             // get the mime type:
             std::string mt = out_response.metadata().value( IOMetadata::CONTENT_TYPE );
 
@@ -339,19 +340,13 @@ public:
                 {
                     OE_NOTICE << "WMS: unknown error." << std::endl;
                 }
-            }
-            else
-            {
-                // really ought to use mime-type support here -GW
-                std::string typeExt = mt.substr( mt.find_last_of("/")+1 );
-                result = osgDB::Registry::instance()->getReaderWriterForExtension( typeExt );
-                if ( !result )
-                {
-                    OE_NOTICE << "WMS: no reader registered; URI=" << createURI(key) << std::endl;
-                }
-            }
+            }            
         }
-        return result;
+        else
+        {
+            image = out_response.getImage();
+        }
+        return image.release();
     }
 
 
@@ -371,20 +366,7 @@ public:
                 extras = std::string("TIME=") + _timesVec[0];
 
             ReadResult response;
-            osgDB::ReaderWriter* reader = fetchTileAndReader( key, extras, progress, response );
-            if ( reader )
-            {
-                std::istringstream buf( response.getString() );
-                osgDB::ReaderWriter::ReadResult readResult = reader->readImage( buf, _dbOptions.get() );
-                if ( readResult.error() )
-                {
-                    OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
-                }
-                else
-                {
-                    image = readResult.getImage();
-                }
-            }
+            image = fetchTileImage( key, extras, progress, response );
         }
 
         return image.release();
@@ -399,37 +381,23 @@ public:
         {
             std::string extraAttrs = std::string("TIME=") + _timesVec[r];
             ReadResult response;
-            osgDB::ReaderWriter* reader = fetchTileAndReader( key, extraAttrs, progress, response );
-            if ( reader )
-            {
-                std::istringstream buf( response.getString() );
-
-                osgDB::ReaderWriter::ReadResult readResult = reader->readImage( buf, _dbOptions.get() );
-                if ( readResult.error() )
-                {
-                    OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
-                }
-                else
-                {
-                    osg::ref_ptr<osg::Image> timeImage = readResult.getImage();
-
-                    if ( !image.valid() )
-                    {
-                        image = new osg::Image();
-                        image->allocateImage(
-                            timeImage->s(), timeImage->t(), _timesVec.size(),
-                            timeImage->getPixelFormat(),
-                            timeImage->getDataType(),
-                            timeImage->getPacking() );
-                        image->setInternalTextureFormat( timeImage->getInternalTextureFormat() );
-                    }
+            
+            osg::ref_ptr<osg::Image> timeImage = fetchTileImage( key, extraAttrs, progress, response );
 
-                    memcpy( 
-                        image->data(0,0,r), 
-                        timeImage->data(), 
-                        osg::minimum(image->getImageSizeInBytes(), timeImage->getImageSizeInBytes()) );
-                }
+            if ( !image.valid() )
+            {
+                image = new osg::Image();
+                image->allocateImage(
+                    timeImage->s(), timeImage->t(), _timesVec.size(),
+                    timeImage->getPixelFormat(),
+                    timeImage->getDataType(),
+                    timeImage->getPacking() );
+                image->setInternalTextureFormat( timeImage->getInternalTextureFormat() );
             }
+
+            memcpy( image->data(0,0,r), 
+                    timeImage->data(), 
+                    osg::minimum(image->getImageSizeInBytes(), timeImage->getImageSizeInBytes()) );
         }
 
         return image.release();
@@ -450,19 +418,10 @@ public:
             std::string extraAttrs = std::string("TIME=") + _timesVec[r];
 
             ReadResult response;
-            osgDB::ReaderWriter* reader = fetchTileAndReader( key, extraAttrs, progress, response );
-            if ( reader )
+            osg::ref_ptr<osg::Image> image = fetchTileImage( key, extraAttrs, progress, response );
+            if ( image.get() )
             {
-                std::istringstream buf(response.getString());
-                osgDB::ReaderWriter::ReadResult readResult = reader->readImage( buf, _dbOptions.get() );
-                if ( !readResult.error() )
-                {
-                    seq->addImage( readResult.getImage() );
-                }
-                else
-                {
-                    OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
-                }
+                seq->addImage( image );
             }
         }
 
diff --git a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
index 5a36498..4272c9a 100644
--- a/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
+++ b/src/osgEarthDrivers/yahoo/ReaderWriterYahoo.cpp
@@ -120,7 +120,7 @@ public:
     }
     
     /** Tell the terrain engine not to cache tiles form this source. */
-    CachePolicy getCachePolicyHint() const
+    CachePolicy getCachePolicyHint(const Profile*) const
     {
         return CachePolicy::NO_CACHE;
     }
diff --git a/src/osgEarthFeatures/BuildGeometryFilter b/src/osgEarthFeatures/BuildGeometryFilter
index 7c6a338..908a543 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter
+++ b/src/osgEarthFeatures/BuildGeometryFilter
@@ -67,42 +67,19 @@ namespace osgEarth { namespace Features
         const optional<GeoInterpolation>& geoInterp() const { return _geoInterp; }
 
         /**
-         * Whether to merge the geometries from mutliple features together. Doing this can
-         * help performance by batching geometries together. The downside will be that individual
-         * geometries are no longer addressable in the scene graph. Default is FALSE.
-         */
-        optional<bool>& mergeGeometry() { return _mergeGeometry; }
-        const optional<bool>& mergeGeometry() const { return _mergeGeometry; }
-
-        /**
-         * Sets an expression to evaluate for setting the name of a Geometry. This only
-         * takes effect if mergeGeometry is false.
+         * Sets an expression to evaluate for setting the name of a Geometry.
+         * Warning: this will disable some performance optimizations since the filter
+         * can no longer merge geometries.
          */
         optional<StringExpression>& featureName() { return _featureNameExpr; }
         const optional<StringExpression>& featureName() const { return _featureNameExpr; }
 
-        /**
-         * Whether or not to use vertex buffer objects
-         */
-        optional<bool>& useVertexBufferObjects() { return _useVertexBufferObjects;}
-        const optional<bool>& useVertexBufferObjects() const { return _useVertexBufferObjects;}
-
     protected:
-        osg::ref_ptr<osg::Node> _result;
-        osg::ref_ptr<osg::Geode> _geode;
-        Style _style;
-        optional<double> _maxAngle_deg;
+        Style                      _style;
+
+        optional<double>           _maxAngle_deg;
         optional<GeoInterpolation> _geoInterp;
-        optional<bool> _mergeGeometry;
         optional<StringExpression> _featureNameExpr;
-        optional<bool> _useVertexBufferObjects;
-        bool _hasPoints;
-        bool _hasLines;
-        bool _hasPolygons;
-
-        void reset();
-        
-        bool process( FeatureList& input, const FilterContext& context );
         
         void buildPolygon(
             Geometry*               input,
@@ -111,6 +88,11 @@ namespace osgEarth { namespace Features
             bool                    makeECEF,
             bool                    tessellate,
             osg::Geometry*          osgGeom);
+
+        osg::Geode* processPolygons        (FeatureList& input, const FilterContext& cx);
+        osg::Geode* processLines           (FeatureList& input, const FilterContext& cx);
+        osg::Geode* processPolygonizedLines(FeatureList& input, bool twosided, const FilterContext& cx);
+        osg::Geode* processPoints          (FeatureList& input, const FilterContext& cx);
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/BuildGeometryFilter.cpp b/src/osgEarthFeatures/BuildGeometryFilter.cpp
index 4f3575e..b3c127a 100644
--- a/src/osgEarthFeatures/BuildGeometryFilter.cpp
+++ b/src/osgEarthFeatures/BuildGeometryFilter.cpp
@@ -18,13 +18,14 @@
  */
 #include <osgEarthFeatures/BuildGeometryFilter>
 #include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthFeatures/PolygonizeLines>
 #include <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/PointSymbol>
 #include <osgEarthSymbology/LineSymbol>
 #include <osgEarthSymbology/PolygonSymbol>
 #include <osgEarthSymbology/MeshSubdivider>
 #include <osgEarthSymbology/MeshConsolidator>
-#include <osgEarth/ECEF>
+#include <osgEarth/Utils>
 #include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/LineWidth>
@@ -42,62 +43,30 @@
 
 #define LC "[BuildGeometryFilter] "
 
+#define OE_TEST OE_NULL
+
 using namespace osgEarth;
 using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 
-#define USE_SINGLE_COLOR 0
-
-namespace
-{
-    void applyLineAndPointSymbology( osg::StateSet* stateSet, const LineSymbol* line, const PointSymbol* point )
-    {
-        if ( line )
-        {
-            float width = std::max( 1.0f, *line->stroke()->width() );
-            stateSet->setAttributeAndModes(new osg::LineWidth(width), 1);
-            if ( line->stroke()->stipple().isSet() )
-            {
-                stateSet->setAttributeAndModes( new osg::LineStipple(1, *line->stroke()->stipple()) );
-            }
-        }
-
-        if ( point )
-        {
-            float size = std::max( 0.1f, *point->size() );
-            stateSet->setAttributeAndModes(new osg::Point(size), 1);
-        }
-    }
-}
-
-
 BuildGeometryFilter::BuildGeometryFilter( const Style& style ) :
 _style        ( style ),
 _maxAngle_deg ( 1.0 ),
-_geoInterp    ( GEOINTERP_RHUMB_LINE ),
-_mergeGeometry( false ),
-_useVertexBufferObjects( true )
+_geoInterp    ( GEOINTERP_RHUMB_LINE )
 {
-    reset();
+    //nop
 }
 
-void
-BuildGeometryFilter::reset()
+osg::Geode*
+BuildGeometryFilter::processPolygons(FeatureList& features, const FilterContext& context)
 {
-    _result = 0L;
-    _geode = new osg::Geode();
-    _hasLines = false;
-    _hasPoints = false;
-    _hasPolygons = false;
-}
+    osg::Geode* geode = new osg::Geode();
 
-bool
-BuildGeometryFilter::process( FeatureList& features, const FilterContext& context )
-{
     bool makeECEF = false;
     const SpatialReference* featureSRS = 0L;
     const SpatialReference* mapSRS = 0L;
 
+    // set up the reference system info:
     if ( context.isGeoreferenced() )
     {
         makeECEF   = context.getSession()->getMapInfo().isGeocentric();
@@ -114,76 +83,193 @@ BuildGeometryFilter::process( FeatureList& features, const FilterContext& contex
         {
             Geometry* part = parts.next();
 
-            // skip empty geometry
-            if ( part->size() == 0 )
+            // skip geometry that is invalid for a polygon
+            if ( part->size() < 3 )
                 continue;
 
-            const Style& myStyle = input->style().isSet() ? *input->style() : _style;
-
-            bool  setLinePropsHere   = input->style().isSet(); // otherwise it will be set globally, we assume
-            float width              = 1.0f;
-            bool  hasPolyOutline     = false;
-
-            const PointSymbol*   pointSymbol = myStyle.get<PointSymbol>();
-            const LineSymbol*    lineSymbol  = myStyle.get<LineSymbol>();
-            const PolygonSymbol* polySymbol  = myStyle.get<PolygonSymbol>();
+            // access the polygon symbol, and bail out if there isn't one
+            const PolygonSymbol* poly =
+                input->style().isSet() && input->style()->has<PolygonSymbol>() ? input->style()->get<PolygonSymbol>() :
+                _style.get<PolygonSymbol>();
+            if ( !poly )
+                continue;
 
-            // resolve the geometry type from the component type and the symbology:
-            Geometry::Type renderType = Geometry::TYPE_UNKNOWN;
+            // resolve the color:
+            osg::Vec4f primaryColor = poly->fill()->color();
+            
+            osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
+            osgGeom->setUseVertexBufferObjects( true );
+            osgGeom->setUseDisplayList( false );
 
-            // First priority is a matching part type and symbol:
-            if ( polySymbol != 0L && part->getType() == Geometry::TYPE_POLYGON )
-            {
-                renderType = Geometry::TYPE_POLYGON;
-            }
-            else if ( lineSymbol != 0L && part->isLinear() )
-            {
-                renderType = part->getType();
-            }
-            else if ( pointSymbol != 0L && part->getType() == Geometry::TYPE_POINTSET )
+            // are we embedding a feature name?
+            if ( _featureNameExpr.isSet() )
             {
-                renderType = Geometry::TYPE_POINTSET;
+                const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context );
+                osgGeom->setName( name );
             }
 
-            // Second priority is the symbol:
-            else if ( polySymbol != 0L )
-            {
-                renderType = Geometry::TYPE_POLYGON;
-            }
-            else if ( lineSymbol != 0L )
+            // build the geometry:
+            buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom);
+
+            osg::Vec3Array* allPoints = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray());
+            
+            // subdivide the mesh if necessary to conform to an ECEF globe:
+            if ( makeECEF )
             {
-                if ( part->getType() == Geometry::TYPE_POLYGON )
-                    renderType = Geometry::TYPE_RING;
+                double threshold = osg::DegreesToRadians( *_maxAngle_deg );
+                OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
+
+                MeshSubdivider ms( _world2local, _local2world );
+                //ms.setMaxElementsPerEBO( INT_MAX );
+                if ( input->geoInterp().isSet() )
+                    ms.run( *osgGeom, threshold, *input->geoInterp() );
                 else
-                    renderType = Geometry::TYPE_LINESTRING;
-            }
-            else if ( pointSymbol != 0L )
-            {
-                renderType = Geometry::TYPE_POINTSET;
+                    ms.run( *osgGeom, threshold, *_geoInterp );
             }
 
-            // No symbol? just use the geometry type.
-            else
+            // assign the primary color array. PER_VERTEX required in order to support
+            // vertex optimization later
+            osg::Vec4Array* colors = new osg::Vec4Array;
+            colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
+            osgGeom->setColorArray( colors );
+            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+            geode->addDrawable( osgGeom );
+
+            // record the geometry's primitive set(s) in the index:
+            if ( context.featureIndex() )
+                context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+        }
+    }
+    
+    return geode;
+}
+
+
+osg::Geode*
+BuildGeometryFilter::processPolygonizedLines(FeatureList&         features, 
+                                             bool                 twosided,
+                                             const FilterContext& context)
+{
+    osg::Geode* geode = new osg::Geode();
+
+    // establish some referencing
+    bool                    makeECEF   = false;
+    const SpatialReference* featureSRS = 0L;
+    const SpatialReference* mapSRS     = 0L;
+
+    if ( context.isGeoreferenced() )
+    {
+        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
+        featureSRS = context.extent()->getSRS();
+        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+    }
+
+    // iterate over all features.
+    for( FeatureList::iterator i = features.begin(); i != features.end(); ++i )
+    {
+        Feature* input = i->get();
+
+        // extract the required line symbol; bail out if not found.
+        const LineSymbol* line =
+            input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
+            _style.get<LineSymbol>();
+        if ( !line )
+            continue;
+
+        // The operator we'll use to make lines into polygons.
+        PolygonizeLinesOperator polygonizer( *line->stroke() );
+
+        // iterate over all the feature's geometry parts. We will treat
+        // them as lines strings.
+        GeometryIterator parts( input->getGeometry(), true );
+        while( parts.hasMore() )
+        {
+            Geometry* part = parts.next();
+
+            // if the underlying geometry is a ring (or a polygon), close it so the
+            // polygonizer will generate a closed loop.
+            Ring* ring = dynamic_cast<Ring*>(part);
+            if ( ring )
+                ring->close();
+
+            // skip invalid geometry
+            if ( part->size() < 2 )
+                continue;
+
+            // transform the geometry into the target SRS and localize it about 
+            // a local reference point.
+            osg::ref_ptr<osg::Vec3Array> verts   = new osg::Vec3Array();
+            osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
+            transformAndLocalize( part->asVector(), featureSRS, verts.get(), normals.get(), mapSRS, _world2local, makeECEF );
+
+            // turn the lines into polygons.
+            osg::Geometry* geom = polygonizer( verts.get(), normals.get(), twosided );
+            if ( geom )
             {
-                renderType = part->getType();
+                geode->addDrawable( geom );
             }
 
-            // validate the geometry:
-            if ( renderType == Geometry::TYPE_POLYGON && part->size() < 3 )
+            // record the geometry's primitive set(s) in the index:
+            if ( context.featureIndex() )
+                context.featureIndex()->tagPrimitiveSets( geom, input );
+        }
+
+        polygonizer.installShaders( geode->getOrCreateStateSet() );
+    }
+    return geode;
+}
+
+
+osg::Geode*
+BuildGeometryFilter::processLines(FeatureList& features, const FilterContext& context)
+{
+    osg::Geode* geode = new osg::Geode();
+
+    bool makeECEF = false;
+    const SpatialReference* featureSRS = 0L;
+    const SpatialReference* mapSRS = 0L;
+
+    // set up referencing information:
+    if ( context.isGeoreferenced() )
+    {
+        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
+        featureSRS = context.extent()->getSRS();
+        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+    }
+
+    for( FeatureList::iterator f = features.begin(); f != features.end(); ++f )
+    {
+        Feature* input = f->get();
+
+        GeometryIterator parts( input->getGeometry(), true );
+        while( parts.hasMore() )
+        {
+            Geometry* part = parts.next();
+
+            // skip invalid geometry for lines.
+            if ( part->size() < 2 )
                 continue;
-            else if ( (renderType == Geometry::TYPE_LINESTRING || renderType == Geometry::TYPE_RING) && part->size() < 2 )
+
+            // extract the required line symbol; bail out if not found.
+            const LineSymbol* line = 
+                input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() :
+                _style.get<LineSymbol>();
+            if ( !line )
                 continue;
 
+            // if the underlying geometry is a ring (or a polygon), use a line loop; otherwise
+            // use a line strip.
+            GLenum primMode = dynamic_cast<Ring*>(part) ? GL_LINE_LOOP : GL_LINE_STRIP;
+
             // resolve the color:
-            osg::Vec4f primaryColor =
-                polySymbol ? osg::Vec4f(polySymbol->fill()->color()) :
-                lineSymbol ? osg::Vec4f(lineSymbol->stroke()->color()) :
-                pointSymbol ? osg::Vec4f(pointSymbol->fill()->color()) :
-                osg::Vec4f(1,1,1,1);
+            osg::Vec4f primaryColor = line->stroke()->color();
             
-            osg::Geometry* osgGeom = new osg::Geometry();
-            osgGeom->setUseVertexBufferObjects( _useVertexBufferObjects.value() );
+            osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
+            osgGeom->setUseVertexBufferObjects( true );
+            osgGeom->setUseDisplayList( false );
 
+            // embed the feature name if requested. Warning: blocks geometry merge optimization!
             if ( _featureNameExpr.isSet() )
             {
                 const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context );
@@ -191,146 +277,131 @@ BuildGeometryFilter::process( FeatureList& features, const FilterContext& contex
             }
 
             // build the geometry:
-            osg::Vec3Array* allPoints = 0L;
+            osg::Vec3Array* allPoints = new osg::Vec3Array();
+
+            transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
 
-            if ( renderType == Geometry::TYPE_POLYGON )
+            osgGeom->addPrimitiveSet( new osg::DrawArrays(primMode, 0, allPoints->getNumElements()) );
+            osgGeom->setVertexArray( allPoints );
+
+            if ( input->style().isSet() )
             {
-                buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom);
-                allPoints = static_cast<osg::Vec3Array*>( osgGeom->getVertexArray() );
+                //TODO: re-evaluate this. does it hinder geometry merging?
+                applyLineSymbology( osgGeom->getOrCreateStateSet(), line );
             }
-            else
+            
+            // subdivide the mesh if necessary to conform to an ECEF globe:
+            if ( makeECEF && !line->tessellation().isSetTo(0) )
             {
-                // line or point geometry
-                GLenum primMode = 
-                    renderType == Geometry::TYPE_LINESTRING ? GL_LINE_STRIP :
-                    renderType == Geometry::TYPE_RING       ? GL_LINE_LOOP :
-                    GL_POINTS;
-                allPoints = new osg::Vec3Array();
-                transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
-                osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, 0, part->size() ) );
-                osgGeom->setVertexArray( allPoints );
-
-                applyLineAndPointSymbology( osgGeom->getOrCreateStateSet(), lineSymbol, pointSymbol );
-
-                if ( primMode == GL_POINTS && allPoints->size() == 1 )
-                {
-                    const osg::Vec3d& center = (*allPoints)[0];
-                    osgGeom->setInitialBound( osg::BoundingBox(center-osg::Vec3(.5,.5,.5), center+osg::Vec3(.5,.5,.5)) );
-                }
+                double threshold = osg::DegreesToRadians( *_maxAngle_deg );
+                OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
+
+                MeshSubdivider ms( _world2local, _local2world );
+                //ms.setMaxElementsPerEBO( INT_MAX );
+                if ( input->geoInterp().isSet() )
+                    ms.run( *osgGeom, threshold, *input->geoInterp() );
+                else
+                    ms.run( *osgGeom, threshold, *_geoInterp );
             }
 
-            if (allPoints->getVertexBufferObject())
-                allPoints->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);
+            // assign the primary color (PER_VERTEX required for later optimization)
+            osg::Vec4Array* colors = new osg::Vec4Array;
+            colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
+            osgGeom->setColorArray( colors );
+            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
+
+            geode->addDrawable( osgGeom );
+
+            // record the geometry's primitive set(s) in the index:
+            if ( context.featureIndex() )
+                context.featureIndex()->tagPrimitiveSets( osgGeom, input );
+        }
+    }
+    
+    return geode;
+}
+
+
+osg::Geode*
+BuildGeometryFilter::processPoints(FeatureList& features, const FilterContext& context)
+{
+    osg::Geode* geode = new osg::Geode();
+
+    bool makeECEF = false;
+    const SpatialReference* featureSRS = 0L;
+    const SpatialReference* mapSRS = 0L;
+
+    // set up referencing information:
+    if ( context.isGeoreferenced() )
+    {
+        makeECEF   = context.getSession()->getMapInfo().isGeocentric();
+        featureSRS = context.extent()->getSRS();
+        mapSRS     = context.getSession()->getMapInfo().getProfile()->getSRS();
+    }
+
+    for( FeatureList::iterator f = features.begin(); f != features.end(); ++f )
+    {
+        Feature* input = f->get();
+
+        GeometryIterator parts( input->getGeometry(), true );
+        while( parts.hasMore() )
+        {
+            Geometry* part = parts.next();
+
+            // skip invalid geometry for lines.
+            if ( part->size() < 2 )
+                continue;
+
+            // extract the required line symbol; bail out if not found.
+            const PointSymbol* point =
+                input->style().isSet() && input->style()->has<PointSymbol>() ? input->style()->get<PointSymbol>() :
+                _style.get<PointSymbol>();
+            if ( !point )
+                continue;
+
+            // resolve the color:
+            osg::Vec4f primaryColor = point->fill()->color();
             
-            // subdivide the mesh if necessary to conform to an ECEF globe:
-            if ( makeECEF && renderType != Geometry::TYPE_POINTSET )
+            osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry();
+            osgGeom->setUseVertexBufferObjects( true );
+            osgGeom->setUseDisplayList( false );
+
+            // embed the feature name if requested. Warning: blocks geometry merge optimization!
+            if ( _featureNameExpr.isSet() )
             {
-                // check for explicit tessellation disable:
-                const LineSymbol* line = _style.get<LineSymbol>();
-                bool disableTess = line && line->tessellation().isSetTo(0);
-
-                if ( makeECEF && !disableTess )
-                {                    
-                    double threshold = osg::DegreesToRadians( *_maxAngle_deg );
-                    OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl;
-
-                    MeshSubdivider ms( _world2local, _local2world );
-                    //ms.setMaxElementsPerEBO( INT_MAX );
-                    if ( input->geoInterp().isSet() )
-                        ms.run( *osgGeom, threshold, *input->geoInterp() );
-                    else
-                        ms.run( *osgGeom, threshold, *_geoInterp );
-                }
+                const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context );
+                osgGeom->setName( name );
             }
 
+            // build the geometry:
+            osg::Vec3Array* allPoints = new osg::Vec3Array();
 
-            // assign the primary color:
-#if USE_SINGLE_COLOR            
-            osg::Vec4Array* colors = new osg::Vec4Array( 1 );
-            (*colors)[0] = primaryColor;
-            osgGeom->setColorBinding( osg::Geometry::BIND_OVERALL );
-#else
+            transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF );
 
-            osg::Vec4Array* colors = new osg::Vec4Array( osgGeom->getVertexArray()->getNumElements() ); //allPoints->size() );
-            for(unsigned c=0; c<colors->size(); ++c)
-                (*colors)[c] = primaryColor;
-            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-#endif
+            osgGeom->addPrimitiveSet( new osg::DrawArrays(GL_POINTS, 0, allPoints->getNumElements()) );
+            osgGeom->setVertexArray( allPoints );
 
+            if ( input->style().isSet() )
+            {
+                //TODO: re-evaluate this. does it hinder geometry merging?
+                applyPointSymbology( osgGeom->getOrCreateStateSet(), point );
+            }
 
+            // assign the primary color (PER_VERTEX required for later optimization)
+            osg::Vec4Array* colors = new osg::Vec4Array;
+            colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor );
             osgGeom->setColorArray( colors );
-            
+            osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
 
-            _geode->addDrawable( osgGeom );
+            geode->addDrawable( osgGeom );
 
             // record the geometry's primitive set(s) in the index:
             if ( context.featureIndex() )
                 context.featureIndex()->tagPrimitiveSets( osgGeom, input );
-
-            // build secondary geometry, if necessary (polygon outlines)
-            if ( renderType == Geometry::TYPE_POLYGON && lineSymbol )
-            {
-                // polygon offset on the poly so the outline doesn't z-fight
-                osgGeom->getOrCreateStateSet()->setAttributeAndModes( new osg::PolygonOffset(1,1), 1 );
-
-                osg::Geometry* outline = new osg::Geometry();
-                outline->setUseVertexBufferObjects( _useVertexBufferObjects.value() );
-
-                buildPolygon(part, featureSRS, mapSRS, makeECEF, false, outline);
-
-                if ( outline->getVertexArray()->getVertexBufferObject() )
-                    outline->getVertexArray()->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB);                
-                
-                osg::Vec4f outlineColor = lineSymbol->stroke()->color();                
-
-                osg::Vec4Array* outlineColors = new osg::Vec4Array();                
-#if USE_SINGLE_COLOR
-                outlineColors->reserve(1);
-                outlineColors->push_back( outlineColor );
-                outline->setColorBinding( osg::Geometry::BIND_OVERALL );
-#else
-                unsigned pcount = part->getTotalPointCount();                
-                outlineColors->reserve( pcount );
-                for( unsigned c=0; c < pcount; ++c )
-                    outlineColors->push_back( outlineColor );
-                outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-#endif
-                outline->setColorArray(outlineColors);
-
-                // check for explicit tessellation disable:                
-                bool disableTess = lineSymbol && lineSymbol->tessellation().isSetTo(0);
-
-                // subdivide if necessary.                
-                if ( makeECEF && !disableTess )
-                {
-                    double threshold = osg::DegreesToRadians( *_maxAngle_deg );
-                    OE_DEBUG << "Running mesh subdivider for outlines with threshold " << *_maxAngle_deg << std::endl;
-                    MeshSubdivider ms( _world2local, _local2world );
-                    if ( input->geoInterp().isSet() )
-                        ms.run( *outline, threshold, *input->geoInterp() );
-                    else
-                        ms.run( *outline, threshold, *_geoInterp );
-                }
-
-                applyLineAndPointSymbology( outline->getOrCreateStateSet(), lineSymbol, 0L );
-
-                // make normals before adding an outline
-                osgUtil::SmoothingVisitor sv;
-                _geode->accept( sv );
-
-                _geode->addDrawable( outline );
-
-                //_featureNode->addDrawable( outline, input->getFID() );
-
-                // Mark each primitive set with its feature ID.
-                if ( context.featureIndex() )
-                    context.featureIndex()->tagPrimitiveSets( outline, input );
-            }
-
         }
     }
     
-    return true;
+    return geode;
 }
 
 // builds and tessellates a polygon (with or without holes)
@@ -376,7 +447,6 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
         osgUtil::Tessellator tess;
         tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY );
         tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE );
-        //tess.setBoundaryOnly( true );
         tess.retessellatePolygons( *osgGeom );
     }
 
@@ -409,55 +479,157 @@ BuildGeometryFilter::buildPolygon(Geometry*               ring,
 osg::Node*
 BuildGeometryFilter::push( FeatureList& input, FilterContext& context )
 {
-    reset();
+    osg::ref_ptr<osg::Group> result = new osg::Group();
 
     computeLocalizers( context );
 
-    bool ok = process( input, context );
+    const LineSymbol*    line  = _style.get<LineSymbol>();
+    const PolygonSymbol* poly  = _style.get<PolygonSymbol>();
+    const PointSymbol*   point = _style.get<PointSymbol>();
+
+    // bin the feautres into polygons, lines, polygonized lines, and points.
+    FeatureList polygons;
+    FeatureList lines;
+    FeatureList polygonizedLines;
+    FeatureList points;
 
-    // convert all geom to triangles and consolidate into minimal set of Geometries
-    if ( !_featureNameExpr.isSet() )
+    for(FeatureList::iterator i = input.begin(); i != input.end(); ++i)
     {
-#if 1
-        MeshConsolidator::run( *_geode.get() );
-#else
-        osgUtil::Optimizer opt;
-        opt.optimize( _geode.get(),
-            osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-            osgUtil::Optimizer::INDEX_MESH |
-            osgUtil::Optimizer::VERTEX_POSTTRANSFORM |
-            osgUtil::Optimizer::MERGE_GEOMETRY );
-#endif
+        Feature* f = i->get();
+
+        // first consider the overall style:
+        bool has_polysymbol     = poly != 0L;
+        bool has_linesymbol     = line != 0L && line->stroke()->widthUnits() == Units::PIXELS;
+        bool has_polylinesymbol = line != 0L && line->stroke()->widthUnits() != Units::PIXELS;
+        bool has_pointsymbol    = point != 0L;
+
+        // if the featue has a style set, that overrides:
+        if ( f->style().isSet() )
+        {
+            has_polysymbol     = has_polysymbol     || (f->style()->has<PolygonSymbol>());
+            has_linesymbol     = has_linesymbol     || (f->style()->has<LineSymbol>() && f->style()->get<LineSymbol>()->stroke()->widthUnits() == Units::PIXELS);
+            has_polylinesymbol = has_polylinesymbol || (f->style()->has<LineSymbol>() && f->style()->get<LineSymbol>()->stroke()->widthUnits() != Units::PIXELS);
+            has_pointsymbol    = has_pointsymbol    || (f->style()->has<PointSymbol>());
+        }
+
+        // if no style is set, use the geometry type:
+        if ( !has_polysymbol && !has_linesymbol && !has_polylinesymbol && !has_pointsymbol && f->getGeometry() )
+        {
+            switch( f->getGeometry()->getComponentType() )
+            {
+            case Geometry::TYPE_LINESTRING:
+            case Geometry::TYPE_RING:
+                f->style()->add( new LineSymbol() );
+                has_linesymbol = true;
+                break;
+
+            case Geometry::TYPE_POINTSET:
+                f->style()->add( new PointSymbol() );
+                has_pointsymbol = true;
+                break;
+
+            case Geometry::TYPE_POLYGON:
+                f->style()->add( new PolygonSymbol() );
+                has_polysymbol = true;
+                break;
+            }
+        }
+
+        if ( has_polysymbol )
+            polygons.push_back( f );
+
+        if ( has_linesymbol )
+            lines.push_back( f );
+
+        if ( has_polylinesymbol )
+            polygonizedLines.push_back( f );
+
+        if ( has_pointsymbol )
+            points.push_back( f );
     }
 
-    osg::Node* result = 0L;
+    // process them separately.
 
-    if ( ok )
+    if ( polygons.size() > 0 )
     {
-        if ( !_style.empty() && _geode.valid() )
+        OE_TEST << LC << "Building " << polygons.size() << " polygons." << std::endl;
+        osg::ref_ptr<osg::Geode> geode = processPolygons(polygons, context);
+        if ( geode->getNumDrawables() > 0 )
         {
-            // could optimize this to only happen is lines or points were created ..
-            const LineSymbol* lineSymbol = _style.getSymbol<LineSymbol>();
-            float size = 1.0;
-            if (lineSymbol)
-                size = std::max(1.0f, lineSymbol->stroke()->width().value());
-
-            _geode->getOrCreateStateSet()->setAttribute( new osg::Point(size), osg::StateAttribute::ON );
-            _geode->getOrCreateStateSet()->setAttribute( new osg::LineWidth(size), osg::StateAttribute::ON );
-
-            const PointSymbol* pointSymbol = _style.getSymbol<PointSymbol>();
-            if ( pointSymbol && pointSymbol->size().isSet() )
-                _geode->getOrCreateStateSet()->setAttribute( 
-                    new osg::Point( *pointSymbol->size() ), osg::StateAttribute::ON );
+            if ( !context.featureIndex() )
+            {
+                osgUtil::Optimizer o;
+                o.optimize( geode.get(), 
+                    osgUtil::Optimizer::MERGE_GEOMETRY |
+                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                    osgUtil::Optimizer::INDEX_MESH |
+                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+            }
+            result->addChild( geode.get() );
         }
+    }
 
+    if ( polygonizedLines.size() > 0 )
+    {
+        OE_TEST << LC << "Building " << polygonizedLines.size() << " polygonized lines." << std::endl;
+        bool twosided = polygons.size() > 0 ? false : true;
+        osg::ref_ptr<osg::Geode> geode = processPolygonizedLines(polygonizedLines, twosided, context);
+        if ( geode->getNumDrawables() > 0 )
+        {
+            if ( !context.featureIndex() )
+            {
+                osgUtil::Optimizer o;
+                o.optimize( geode.get(), 
+                    osgUtil::Optimizer::MERGE_GEOMETRY |
+                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                    osgUtil::Optimizer::INDEX_MESH |
+                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+            }
+            result->addChild( geode.get() );
+        }
+    }
+
+    if ( lines.size() > 0 )
+    {
+        OE_TEST << LC << "Building " << lines.size() << " lines." << std::endl;
+        osg::ref_ptr<osg::Geode> geode = processLines(lines, context);
+        if ( geode->getNumDrawables() > 0 )
+        {
+            if ( !context.featureIndex() )
+            {
+                osgUtil::Optimizer o;
+                o.optimize( geode.get(), 
+                    osgUtil::Optimizer::MERGE_GEOMETRY );
+            }
+            applyLineSymbology( geode->getOrCreateStateSet(), line );
+            result->addChild( geode.get() );
+        }
+    }
+
+    if ( points.size() > 0 )
+    {
+        OE_TEST << LC << "Building " << points.size() << " points." << std::endl;
+        osg::ref_ptr<osg::Geode> geode = processPoints(points, context);
+        if ( geode->getNumDrawables() > 0 )
+        {
+            if ( !context.featureIndex() )
+            {
+                osgUtil::Optimizer o;
+                o.optimize( geode.get(), 
+                    osgUtil::Optimizer::MERGE_GEOMETRY );
+            }
+            applyPointSymbology( geode->getOrCreateStateSet(), point );
+            result->addChild( geode.get() );
+        }
+    }
+
+    if ( result->getNumChildren() > 0 )
+    {
         // apply the delocalization matrix for no-jitter
-        result = delocalize( _geode.release() );
+        return delocalize( result.release() );
     }
     else
     {
-        result = 0L;
+        return 0L;
     }
-
-    return result;
 }
diff --git a/src/osgEarthFeatures/BuildTextFilter b/src/osgEarthFeatures/BuildTextFilter
index c8380eb..638dfbc 100644
--- a/src/osgEarthFeatures/BuildTextFilter
+++ b/src/osgEarthFeatures/BuildTextFilter
@@ -49,8 +49,7 @@ namespace osgEarth { namespace Features
         osg::Node* push( FeatureList& input, FilterContext& context );
 
     protected:
-        osg::ref_ptr<osg::Geode> _geode;
-        Style                    _style;
+        Style _style;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/BuildTextFilter.cpp b/src/osgEarthFeatures/BuildTextFilter.cpp
index 64d1392..c93fae4 100644
--- a/src/osgEarthFeatures/BuildTextFilter.cpp
+++ b/src/osgEarthFeatures/BuildTextFilter.cpp
@@ -40,36 +40,28 @@ BuildTextFilter::push( FeatureList& input, FilterContext& context )
     osg::Node* result = 0L;
 
     const TextSymbol* text = _style.get<TextSymbol>();
-    if ( !text )
+    const IconSymbol* icon = _style.get<IconSymbol>();
+
+    if ( !text && !icon )
     {
-        OE_WARN << LC << "Insufficient symbology (no TextSymbol)" << std::endl;
+        OE_WARN << LC << "Insufficient symbology (no TextSymbol/IconSymbol)" << std::endl;
         return 0L;
     }
 
-    // if a provider is set, load the plugin and create the node.
-    if ( true ) //!text->provider()->empty() && !text->provider().isSetTo("legacy") )
+    LabelSourceOptions options;
+    options.setDriver( "annotation" );
+    //options.setDriver( text ? (*text->provider()) : (*icon->provider()) );
+    osg::ref_ptr<LabelSource> source = LabelSourceFactory::create( options );
+    if ( source.valid() )
     {
-        LabelSourceOptions options;
-        options.setDriver( *text->provider() );
-        osg::ref_ptr<LabelSource> source = LabelSourceFactory::create( options );
-        if ( source.valid() )
-        {
-            result = source->createNode( input, _style, context );
-        }
-        else
-        {
-            OE_WARN << LC << "FAIL, unable to load label provider \"" << (*text->provider()) << "\"" << std::endl;
-            return 0L;
-        }
+        result = source->createNode( input, _style, context );
     }
-
-#if 0
-    else // legacy behavior... will be deprecated.
+    else
     {
-        BuildTextOperator op;
-        result = op( input, text, context );
+        OE_WARN << LC << "FAIL, unable to load provider" << std::endl;
+        return 0L;
     }
-#endif
+
 
     return result;
 }
diff --git a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
index 0e00083..3bb2644 100644
--- a/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
+++ b/src/osgEarthFeatures/ExtrudeGeometryFilter.cpp
@@ -24,17 +24,15 @@
 #include <osgEarth/ImageUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
-#include <osgEarth/ShaderGenerator>
+#include <osgEarth/Utils>
 #include <osg/Geode>
 #include <osg/Geometry>
 #include <osg/MatrixTransform>
 #include <osgUtil/Tessellator>
 #include <osgUtil/Optimizer>
 #include <osgUtil/SmoothingVisitor>
-#include <osg/Version>
 #include <osg/LineWidth>
 #include <osg/PolygonOffset>
-#include <osgEarth/Version>
 
 #define LC "[ExtrudeGeometryFilter] "
 
@@ -248,10 +246,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
         colors->assign( numWallVerts, wallColor );
         walls->setColorArray( colors );
         walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-        //osg::Vec4Array* colors = new osg::Vec4Array( 1 );
-        //(*colors)[0] = wallColor;
-        //walls->setColorArray( colors );
-        //walls->setColorBinding( osg::Geometry::BIND_OVERALL );
     }
 
     // set up rooftop tessellation and texturing, if necessary:
@@ -276,11 +270,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
             roofColors->assign( pointCount, roofColor );
             roof->setColorArray( roofColors );
             roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
-
-            //osg::Vec4Array* roofColors = new osg::Vec4Array( 1 );
-            //(*roofColors)[0] = roofColor;
-            //roof->setColorArray( roofColors );
-            //roof->setColorBinding( osg::Geometry::BIND_OVERALL );
         }
 
         if ( roofSkin )
@@ -288,11 +277,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
             roofTexcoords = new osg::Vec2Array( pointCount );
             roof->setTexCoordArray( 0, roofTexcoords );
 
-            // Get the orientation of the geometry. This is a hueristic that will help 
-            // us align the roof skin texture properly. TODO: make this optional? It makes
-            // sense for buildings and such, but perhaps not for all extruded shapes.
-            roofRotation = getApparentRotation( input );
-
             roofBounds = input->getBounds();
 
             // if our data is lat/long, we need to reproject the geometry and the bounds into a projected
@@ -301,10 +285,13 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
             {
                 osg::Vec2d geogCenter = roofBounds.center2d();
                 roofProjSRS = srs->createUTMFromLonLat( Angular(geogCenter.x()), Angular(geogCenter.y()) );
-                roofBounds.transform( srs, roofProjSRS.get() );
-                osg::ref_ptr<Geometry> projectedInput = input->clone();
-                srs->transform( projectedInput->asVector(), roofProjSRS.get() );
-                roofRotation = getApparentRotation( projectedInput.get() );
+                if ( roofProjSRS.valid() )
+                {
+                    roofBounds.transform( srs, roofProjSRS.get() );
+                    osg::ref_ptr<Geometry> projectedInput = input->clone();
+                    srs->transform( projectedInput->asVector(), roofProjSRS.get() );
+                    roofRotation = getApparentRotation( projectedInput.get() );
+                }
             }
             else
             {
@@ -443,11 +430,11 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
 
             // figure out the rooftop texture coordinates before doing any
             // transformations:
-            if ( roofSkin )
+            if ( roofSkin && srs )
             {
                 double xr, yr;
 
-                if ( srs && srs->isGeographic() )
+                if ( srs && srs->isGeographic() && roofProjSRS )
                 {
                     osg::Vec3d projRoofPt;
                     srs->transform( roofPt, roofProjSRS.get(), projRoofPt );
@@ -464,13 +451,8 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
                 float v = (sinR*xr + cosR*yr) / roofTexSpanY;
 
                 (*roofTexcoords)[roofVertPtr].set( u, v );
-            }            
+            }
 
-            //if ( makeECEF )
-            //{
-            //    ECEF::transformAndLocalize( basePt, srs, basePt, _world2local );
-            //    ECEF::transformAndLocalize( roofPt, srs, roofPt, _world2local );
-            //}
             transformAndLocalize( basePt, srs, basePt, mapSRS, _world2local, makeECEF );
             transformAndLocalize( roofPt, srs, roofPt, mapSRS, _world2local, makeECEF );
 
@@ -601,7 +583,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
             unsigned len = baseVertPtr - basePartPtr;
 
             GLenum roofLineMode = isPolygon ? GL_LINE_LOOP : GL_LINE_STRIP;
-            //osg::DrawElementsUShort* roofLine = new osg::DrawElementsUShort( roofLineMode );
             osg::DrawElementsUInt* roofLine = new osg::DrawElementsUInt( roofLineMode );
             roofLine->reserveElements( len );
             for( unsigned i=0; i<len; ++i )
@@ -613,7 +594,6 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
             unsigned step = std::max( 1u, 
                 _outlineSymbol->tessellation().isSet() ? *_outlineSymbol->tessellation() : 1u );
 
-            //osg::DrawElementsUShort* wallLines = new osg::DrawElementsUShort( GL_LINES );
             osg::DrawElementsUInt* wallLines = new osg::DrawElementsUInt( GL_LINES );
             wallLines->reserve( len*2 );
             for( unsigned i=0; i<len; i+=step )
@@ -622,6 +602,8 @@ ExtrudeGeometryFilter::extrudeGeometry(const Geometry*         input,
                 wallLines->push_back( basePartPtr + i*2 + 1 );
             }
             outline->addPrimitiveSet( wallLines );
+
+            applyLineSymbology( outline->getOrCreateStateSet(), _outlineSymbol.get() );
         }
     }
 
@@ -727,8 +709,8 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                 offset = input->eval( _heightOffsetExpr.mutable_value(), &context );
             }
 
-            osg::StateSet* wallStateSet = 0L;
-            osg::StateSet* roofStateSet = 0L;
+            osg::ref_ptr<osg::StateSet> wallStateSet;
+            osg::ref_ptr<osg::StateSet> roofStateSet;
 
             // calculate the wall texturing:
             SkinResource* wallSkin = 0L;
@@ -764,7 +746,7 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             }
 
             // calculate the colors:
-            osg::Vec4f wallColor(1,1,1,1), wallBaseColor(1,1,1,1), roofColor(1,1,1,1), outlineColor(1,1,1,1);
+            osg::Vec4f wallColor(1,1,1,0), wallBaseColor(1,1,1,0), roofColor(1,1,1,0), outlineColor(1,1,1,1);
 
             if ( _wallPolygonSymbol.valid() )
             {
@@ -798,19 +780,14 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
             {      
                 if ( wallSkin )
                 {
-                    wallStateSet = context.resourceCache()->getStateSet( wallSkin );
+                    context.resourceCache()->getStateSet( wallSkin, wallStateSet );
                 }
 
                 // generate per-vertex normals, altering the geometry as necessary to avoid
                 // smoothing around sharp corners
-    #if OSG_MIN_VERSION_REQUIRED(2,9,9)
-                //Crease angle threshold wasn't added until
                 osgUtil::SmoothingVisitor::smooth(
                     *walls.get(), 
-                    osg::DegreesToRadians(_wallAngleThresh_deg) );            
-    #else
-                osgUtil::SmoothingVisitor::smooth(*walls.get());            
-    #endif
+                    osg::DegreesToRadians(_wallAngleThresh_deg) );
 
                 // tessellate and add the roofs if necessary:
                 if ( rooflines.valid() )
@@ -825,16 +802,9 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
                     if ( !_makeStencilVolume )
                         osgUtil::SmoothingVisitor::smooth( *rooflines.get() );
 
-                    // texture the rooflines if necessary
-                    //applyOverlayTexturing( rooflines.get(), input, env );
-
-                    // mark this geometry as DYNAMIC because otherwise the OSG optimizer will destroy it.
-                    // TODO: why??
-                    //rooflines->setDataVariance( osg::Object::DYNAMIC );
-
                     if ( roofSkin )
                     {
-                        roofStateSet = context.resourceCache()->getStateSet( roofSkin );
+                        context.resourceCache()->getStateSet( roofSkin, roofStateSet );
                     }
                 }
 
@@ -852,11 +822,11 @@ ExtrudeGeometryFilter::process( FeatureList& features, FilterContext& context )
 
                 FeatureSourceIndex* index = context.featureIndex();
 
-                addDrawable( walls.get(), wallStateSet, name, input, index );
+                addDrawable( walls.get(), wallStateSet.get(), name, input, index );
 
                 if ( rooflines.valid() )
                 {
-                    addDrawable( rooflines.get(), roofStateSet, name, input, index );
+                    addDrawable( rooflines.get(), roofStateSet.get(), name, input, index );
                 }
 
                 if ( baselines.valid() )
@@ -930,16 +900,27 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
     {
         for( SortedGeodeMap::iterator i = _geodes.begin(); i != _geodes.end(); ++i )
         {
-#if 1
-            MeshConsolidator::run( *i->second.get() );
-#else
-        osgUtil::Optimizer opt;
-        opt.optimize( i->second.get(),
-            osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-            osgUtil::Optimizer::INDEX_MESH |
-            osgUtil::Optimizer::VERTEX_POSTTRANSFORM |
-            osgUtil::Optimizer::MERGE_GEOMETRY );
-#endif
+            if ( context.featureIndex() )
+            {
+                // The MC will recognize the presence of feature indexing tags and
+                // preserve them. The Cache optimizer however will not, so it is
+                // out for now.
+                MeshConsolidator::run( *i->second.get() );
+
+                //VertexCacheOptimizer vco;
+                //i->second->accept( vco );
+            }
+            else
+            {
+                //TODO: try this -- issues: it won't work on lines, and will it screw up
+                // feature indexing?
+                osgUtil::Optimizer o;
+                o.optimize( i->second.get(),
+                    osgUtil::Optimizer::MERGE_GEOMETRY |
+                    osgUtil::Optimizer::VERTEX_PRETRANSFORM |
+                    osgUtil::Optimizer::INDEX_MESH |
+                    osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
+            }
         }
     }
 
@@ -962,16 +943,5 @@ ExtrudeGeometryFilter::push( FeatureList& input, FilterContext& context )
             groupStateSet->setAttributeAndModes( new osg::LineWidth(*_outlineSymbol->stroke()->width()), 1 );
     }
 
-#if 0 // now called from GeometryCompiler
-
-    // generate shaders to draw the geometry.
-    if ( Registry::capabilities().supportsGLSL() )
-    {
-        StateSetCache* cache = context.getSession() ? context.getSession()->getStateSetCache() : 0L;
-        ShaderGenerator gen( cache );
-        group->accept( gen );
-    }
-#endif
-
     return group;
 }
diff --git a/src/osgEarthFeatures/Feature.cpp b/src/osgEarthFeatures/Feature.cpp
index 5a590df..8fa5c06 100644
--- a/src/osgEarthFeatures/Feature.cpp
+++ b/src/osgEarthFeatures/Feature.cpp
@@ -320,7 +320,7 @@ Feature::eval( NumericExpression& expr, FilterContext const* context ) const
           if (result.success())
             val = result.asDouble();
           else
-              OE_WARN << LC << "Script error:" << result.message() << std::endl;
+              OE_WARN << LC << "Script error:" << result.message() << std::endl; 
         }
       }
 
diff --git a/src/osgEarthFeatures/FeatureDrawSet b/src/osgEarthFeatures/FeatureDrawSet
index 61a7897..d4e71d0 100644
--- a/src/osgEarthFeatures/FeatureDrawSet
+++ b/src/osgEarthFeatures/FeatureDrawSet
@@ -44,7 +44,7 @@ namespace osgEarth { namespace Features
 
     public:
         FeatureDrawSet();
-        virtual ~FeatureDrawSet() { }
+        virtual ~FeatureDrawSet();
 
         /** Nodes comprising this draw set */
         Nodes& nodes() { return _nodes; }
diff --git a/src/osgEarthFeatures/FeatureDrawSet.cpp b/src/osgEarthFeatures/FeatureDrawSet.cpp
index 340a248..458bada 100644
--- a/src/osgEarthFeatures/FeatureDrawSet.cpp
+++ b/src/osgEarthFeatures/FeatureDrawSet.cpp
@@ -55,6 +55,10 @@ _visible( true )
     //nop
 }
 
+FeatureDrawSet::~FeatureDrawSet()
+{
+}
+
 
 FeatureDrawSet::PrimitiveSets&
 FeatureDrawSet::getOrCreateSlice(osg::Drawable* d)
diff --git a/src/osgEarthFeatures/FeatureModelGraph b/src/osgEarthFeatures/FeatureModelGraph
index 79088a6..6fafc14 100644
--- a/src/osgEarthFeatures/FeatureModelGraph
+++ b/src/osgEarthFeatures/FeatureModelGraph
@@ -27,6 +27,7 @@
 #include <osgEarth/OverlayNode>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/ThreadingUtils>
+#include <osgEarth/DepthOffset>
 #include <osg/Node>
 #include <set>
 
@@ -186,6 +187,7 @@ namespace osgEarth { namespace Features
         osg::Group*                      _overlayPlaceholder;
         ClampableNode*                   _clampable;
         DrapeableNode*                   _drapeable;
+        DepthOffsetAdapter               _depthOffsetAdapter;
 
         enum OverlayChange {
             OVERLAY_NO_CHANGE,
@@ -198,7 +200,7 @@ namespace osgEarth { namespace Features
         osg::ref_ptr<RefNodeOperationVector> _postMergeOperations;
 
         void runPostMergeOperations(osg::Node* node);
-        void checkForGlobalAltitudeStyles(const Style& style);
+        void checkForGlobalStyles(const Style& style);
         void changeOverlay();
     };
 
diff --git a/src/osgEarthFeatures/FeatureModelGraph.cpp b/src/osgEarthFeatures/FeatureModelGraph.cpp
index c1e0385..791ce7c 100644
--- a/src/osgEarthFeatures/FeatureModelGraph.cpp
+++ b/src/osgEarthFeatures/FeatureModelGraph.cpp
@@ -361,7 +361,7 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
     if ( mapf )
     {
         // Use an appropriate resolution for this extents width
-        double resolution = workingExtent.width();             
+        double resolution = workingExtent.width();
         ElevationQuery query( *mapf );
         GeoPoint p( mapf->getProfile()->getSRS(), center, ALTMODE_ABSOLUTE );
         query.getElevation( p, center.z(), resolution );
@@ -381,6 +381,12 @@ FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
         //workingExtent.getSRS()->transformToECEF( corner, corner );
     }
 
+    if (workingExtent.getSRS()->isGeographic() &&
+        ( workingExtent.width() >= 90 || workingExtent.height() >= 90 ) )
+    {
+        return osg::BoundingSphered( osg::Vec3d(0,0,0), 2*center.length() );
+    }
+
     return osg::BoundingSphered( center, (center-corner).length() );
 }
 
@@ -863,7 +869,7 @@ FeatureModelGraph::build(const Style&        defaultStyle,
                 }
 
                 // otherwise, all feature returned by this query will have the same style:
-                else
+                else if ( !_useTiledSource )
                 {
                     // combine the selection style with the incoming base style:
                     Style selectedStyle = *styles->getStyle( sel.getSelectedStyleName() );
@@ -878,6 +884,16 @@ FeatureModelGraph::build(const Style&        defaultStyle,
                     if ( styleGroup && !group->containsNode(styleGroup) )
                         group->addChild( styleGroup );
                 }
+
+                // Tried to apply a selector query to a tiled source, which is illegal because
+                // you cannot run an SQL expression on pre-tiled data (like TFS).
+                else
+                {
+                    OE_WARN << LC 
+                        << "Illegal: you cannot use a selector SQL query with a tiled feature source. "
+                        << "Consider using a JavaScript style expression instead."
+                        << std::endl;
+                }
             }
         }
 
@@ -997,22 +1013,30 @@ FeatureModelGraph::queryAndSortIntoStyleGroups(const Query&            query,
         if ( styleString.length() > 0 && styleString.at(0) == '{' )
         {
             Config conf( "style", styleString );
+            conf.setReferrer( styleExpr.uriContext().referrer() );
             conf.set( "type", "text/css" );
             combinedStyle = Style(conf);
         }
 
-        // otherwise, look up the style in the stylesheet:
+        // otherwise, look up the style in the stylesheet. Do NOT fall back on a default
+        // style in this case: for style expressions, the user must be explicity about 
+        // default styling; this is because there is no other way to exclude unwanted
+        // features.
         else
         {
-            const Style* selectedStyle = _session->styles()->getStyle(styleString);
+            const Style* selectedStyle = _session->styles()->getStyle(styleString, false);
             if ( selectedStyle )
                 combinedStyle = *selectedStyle;
         }
 
-        // create the node and add it.
-        osg::Group* styleGroup = createStyleGroup(combinedStyle, workingSet, context);
-        if ( styleGroup )
-            parent->addChild( styleGroup );
+        // if there is a valid style, create the node and add it. (Otherwise we will skip
+        // the feature.)
+        if ( !combinedStyle.empty() )
+        {
+            osg::Group* styleGroup = createStyleGroup(combinedStyle, workingSet, context);
+            if ( styleGroup )
+                parent->addChild( styleGroup );
+        }
     }
 }
 
@@ -1103,7 +1127,7 @@ FeatureModelGraph::createStyleGroup(const Style&        style,
 
 
 void
-FeatureModelGraph::checkForGlobalAltitudeStyles( const Style& style )
+FeatureModelGraph::checkForGlobalStyles( const Style& style )
 {
     const AltitudeSymbol* alt = style.get<AltitudeSymbol>();
     if ( alt )
@@ -1131,7 +1155,9 @@ FeatureModelGraph::checkForGlobalAltitudeStyles( const Style& style )
         const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>();
         if ( extrusion )
         {
-            _clampable->depthOffset().enabled() = false;
+            DepthOffsetOptions d = _clampable->getDepthOffsetOptions();
+            d.enabled() = false;
+            _clampable->setDepthOffsetOptions( d );
         }
 
         // check for explicit depth offset render settings (note, this could
@@ -1140,7 +1166,17 @@ FeatureModelGraph::checkForGlobalAltitudeStyles( const Style& style )
         const RenderSymbol* render = style.get<RenderSymbol>();
         if ( render && render->depthOffset().isSet() )
         {
-            _clampable->depthOffset() = *render->depthOffset();
+            _clampable->setDepthOffsetOptions(*render->depthOffset());
+        }
+    }
+
+    else 
+    {
+        const RenderSymbol* render = style.get<RenderSymbol>();
+        if ( render && render->depthOffset().isSet() )
+        {
+            _depthOffsetAdapter.setGraph( this );
+            _depthOffsetAdapter.setDepthOffsetOptions( *render->depthOffset() );
         }
     }
 }
@@ -1153,7 +1189,7 @@ FeatureModelGraph::getOrCreateStyleGroupFromFactory(const Style& style)
 
     // Check the style and see if we need to active GPU clamping. GPU clamping
     // is currently all-or-nothing for a single FMG.
-    checkForGlobalAltitudeStyles( style );
+    checkForGlobalStyles( style );
 
     return styleGroup;
 }
diff --git a/src/osgEarthFeatures/FeatureModelSource.cpp b/src/osgEarthFeatures/FeatureModelSource.cpp
index 51b22ec..df3bd69 100644
--- a/src/osgEarthFeatures/FeatureModelSource.cpp
+++ b/src/osgEarthFeatures/FeatureModelSource.cpp
@@ -19,6 +19,9 @@
 #include <osgEarthFeatures/FeatureModelSource>
 #include <osgEarthFeatures/FeatureModelGraph>
 #include <osgEarth/SpatialReference>
+#include <osgEarth/ShaderFactory>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osg/Notify>
 
 using namespace osgEarth;
@@ -213,9 +216,24 @@ FeatureNodeFactory::getOrCreateStyleGroup(const Style& style,
 
         if ( render->lighting().isSet() )
         {
-            group->getOrCreateStateSet()->setMode(
+            osg::StateSet* stateset = group->getOrCreateStateSet();
+
+            stateset->setMode(
                 GL_LIGHTING,
                 (render->lighting() == true ? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
+
+            if ( Registry::capabilities().supportsGLSL() )
+            {
+                stateset->addUniform( Registry::shaderFactory()->createUniformForGLMode(
+                    GL_LIGHTING, render->lighting().value()));
+            }
+        }
+
+        if ( render->backfaceCulling().isSet() )
+        {
+            group->getOrCreateStateSet()->setMode(
+                GL_CULL_FACE,
+                (render->backfaceCulling() == true ? osg::StateAttribute::ON : osg::StateAttribute::OFF) | osg::StateAttribute::OVERRIDE );
         }
     }
 
diff --git a/src/osgEarthFeatures/FeatureSource b/src/osgEarthFeatures/FeatureSource
index 0d8833a..29833b2 100644
--- a/src/osgEarthFeatures/FeatureSource
+++ b/src/osgEarthFeatures/FeatureSource
@@ -68,7 +68,7 @@ namespace osgEarth { namespace Features
 
     public:
         FeatureSourceOptions( const ConfigOptions& options =ConfigOptions() );
-        virtual ~FeatureSourceOptions() { }
+        virtual ~FeatureSourceOptions();
         virtual Config getConfig() const;
 
     protected:
@@ -238,7 +238,7 @@ namespace osgEarth { namespace Features
         /**
          * DTOR is protected to prevent this object from being allocated on the stack.
          */
-        virtual ~FeatureSource() { }
+        virtual ~FeatureSource();
 
         /** Access the raw DB options that came in */
         const osgDB::Options* dbOptions() const { return _dbOptions.get(); }
diff --git a/src/osgEarthFeatures/FeatureSource.cpp b/src/osgEarthFeatures/FeatureSource.cpp
index 9fd8ee0..9e0c335 100644
--- a/src/osgEarthFeatures/FeatureSource.cpp
+++ b/src/osgEarthFeatures/FeatureSource.cpp
@@ -37,6 +37,10 @@ DriverConfigOptions( options )
     fromConfig( _conf );
 }
 
+FeatureSourceOptions::~FeatureSourceOptions()
+{
+}
+
 void
 FeatureSourceOptions::fromConfig( const Config& conf )
 {
@@ -111,6 +115,10 @@ _options( options )
     _cache      = Cache::get( dbOptions );
 }
 
+FeatureSource::~FeatureSource()
+{
+}
+
 const FeatureProfile*
 FeatureSource::getFeatureProfile() const
 {
diff --git a/src/osgEarthFeatures/FeatureTileSource.cpp b/src/osgEarthFeatures/FeatureTileSource.cpp
index 99ee5b1..c1deaa8 100644
--- a/src/osgEarthFeatures/FeatureTileSource.cpp
+++ b/src/osgEarthFeatures/FeatureTileSource.cpp
@@ -67,8 +67,6 @@ void
 FeatureTileSourceOptions::fromConfig( const Config& conf )
 {
     conf.getObjIfSet( "features", _featureOptions );
-    //if ( conf.hasChild("features") )
-    //    _featureOptions->merge( ConfigOptions(conf.child("features")) );
 
     conf.getObjIfSet( "styles", _styles );
     
@@ -113,16 +111,6 @@ FeatureTileSource::initialize(const osgDB::Options* dbOptions)
     if ( _features.valid() )
     {
         _features->initialize( dbOptions );
-
-#if 0 // removed this as it was screwing up the rasterizer (agglite plugin).. not sure there's any reason to do this anyway
-        if (_features->getFeatureProfile())
-        {
-            setProfile( Profile::create(_features->getFeatureProfile()->getSRS(),
-                                    _features->getFeatureProfile()->getExtent().xMin(), _features->getFeatureProfile()->getExtent().yMin(),
-                                    _features->getFeatureProfile()->getExtent().xMax(), _features->getFeatureProfile()->getExtent().yMax()));
-
-        }
-#endif
     }
     else
     {
@@ -158,9 +146,9 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
     // implementation-specific data
     osg::ref_ptr<osg::Referenced> buildData = createBuildData();
 
-	// allocate the image.
-	osg::ref_ptr<osg::Image> image = new osg::Image();
-	image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
+    // allocate the image.
+    osg::ref_ptr<osg::Image> image = new osg::Image();
+    image->allocateImage( getPixelsPerTile(), getPixelsPerTile(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
 
     preProcess( image.get(), buildData.get() );
 
@@ -207,7 +195,7 @@ FeatureTileSource::createImage( const TileKey& key, ProgressCallback* progress )
     // final tile processing after all styles are done
     postProcess( image.get(), buildData.get() );
 
-	return image.release();
+    return image.release();
 }
 
 
@@ -229,11 +217,11 @@ FeatureTileSource::queryAndRenderFeaturesForStyle(const Style&     style,
     {
         GeoExtent queryExtent = queryExtentWGS84.transform( featuresExtent.getSRS() );
 
-	    // incorporate the image extent into the feature query for this style:
+        // incorporate the image extent into the feature query for this style:
         Query localQuery = query;
-        localQuery.bounds() = query.bounds().isSet()?
-		    query.bounds()->unionWith( queryExtent.bounds() ) :
-		    queryExtent.bounds();
+        localQuery.bounds() = 
+            query.bounds().isSet() ? query.bounds()->unionWith( queryExtent.bounds() ) :
+            queryExtent.bounds();
 
         // query the feature source:
         osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor( localQuery );
@@ -269,7 +257,7 @@ FeatureTileSource::queryAndRenderFeaturesForStyle(const Style&     style,
         //    << queryExtent.toString() << ")"
         //    << std::endl;
 
-	    return renderFeaturesForStyle( style, cellFeatures, data, imageExtent, out_image );
+        return renderFeaturesForStyle( style, cellFeatures, data, imageExtent, out_image );
     }
     else
     {
diff --git a/src/osgEarthFeatures/Filter b/src/osgEarthFeatures/Filter
index b4dfd95..274281e 100644
--- a/src/osgEarthFeatures/Filter
+++ b/src/osgEarthFeatures/Filter
@@ -33,8 +33,10 @@ namespace osgEarth { namespace Features
     /**
      * Base class for a filter.
      */
-    class Filter : public osg::Referenced
+    class OSGEARTHFEATURES_EXPORT Filter : public osg::Referenced
     {
+    protected:
+        virtual ~Filter();
     };
 
     /**
@@ -50,7 +52,7 @@ namespace osgEarth { namespace Features
          */
         virtual Config getConfig() const { return Config(); }
 
-        virtual ~FeatureFilter() { }
+        virtual ~FeatureFilter();
     };
 
     typedef std::list< osg::ref_ptr<FeatureFilter> > FeatureFilterList;
@@ -143,11 +145,11 @@ namespace osgEarth { namespace Features
     public:
         const osg::Matrixd& local2world() const { return _local2world; }
         const osg::Matrixd& world2local() const { return _world2local; }
-        
-        virtual ~FeaturesToNodeFilter() { }
-        
+                        
     protected:
 
+        virtual ~FeaturesToNodeFilter();
+
         // computes the matricies required to localizer/delocalize double-precision coords
         void computeLocalizers( const FilterContext& context );
 
@@ -181,6 +183,9 @@ namespace osgEarth { namespace Features
             const osg::Matrixd&            world2local,
             bool                           toECEF );
 
+        void applyLineSymbology(osg::StateSet*, const class LineSymbol*);
+        void applyPointSymbology(osg::StateSet*, const class PointSymbol*);
+
         osg::Matrixd _world2local, _local2world;   // for coordinate localization
     };
 
diff --git a/src/osgEarthFeatures/Filter.cpp b/src/osgEarthFeatures/Filter.cpp
index a7587a2..efcddf2 100644
--- a/src/osgEarthFeatures/Filter.cpp
+++ b/src/osgEarthFeatures/Filter.cpp
@@ -17,13 +17,28 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/Filter>
+#include <osgEarthSymbology/LineSymbol>
+#include <osgEarthSymbology/PointSymbol>
 #include <osgEarth/ECEF>
 #include <osg/MatrixTransform>
+#include <osg/Point>
+#include <osg/LineWidth>
+#include <osg/LineStipple>
 
 using namespace osgEarth;
 using namespace osgEarth::Features;
 
 /********************************************************************************/
+Filter::~Filter()
+{
+}
+
+/********************************************************************************/
+FeatureFilter::~FeatureFilter()
+{
+}
+
+/********************************************************************************/
         
 FeatureFilterRegistry::FeatureFilterRegistry()
 {
@@ -67,6 +82,10 @@ FeatureFilterRegistry::create( const Config& conf )
 
 /********************************************************************************/
 
+FeaturesToNodeFilter::~FeaturesToNodeFilter()
+{
+}
+
 void
 FeaturesToNodeFilter::computeLocalizers( const FilterContext& context )
 {
@@ -236,3 +255,39 @@ FeaturesToNodeFilter::createDelocalizeGroup() const
 
     return group;
 }
+
+
+void 
+FeaturesToNodeFilter::applyLineSymbology(osg::StateSet*    stateset, 
+                                         const LineSymbol* line)
+{
+    if ( line && line->stroke().isSet() )
+    {
+        if ( line->stroke()->width().isSet() )
+        {
+            float width = std::max( 1.0f, *line->stroke()->width() );
+            if ( width != 1.0f )
+            {
+                stateset->setAttributeAndModes(new osg::LineWidth(width), 1);
+            }
+        }
+
+        if ( line->stroke()->stipplePattern().isSet() )
+        {
+            stateset->setAttributeAndModes( new osg::LineStipple(
+                line->stroke()->stippleFactor().value(),
+                line->stroke()->stipplePattern().value() ) );
+        }
+    }
+}
+
+void 
+FeaturesToNodeFilter::applyPointSymbology(osg::StateSet*     stateset, 
+                                          const PointSymbol* point)
+{
+    if ( point )
+    {
+        float size = std::max( 0.1f, *point->size() );
+        stateset->setAttributeAndModes(new osg::Point(size), 1);
+    }
+}
diff --git a/src/osgEarthFeatures/FilterContext b/src/osgEarthFeatures/FilterContext
index 564d012..8c6a1fc 100644
--- a/src/osgEarthFeatures/FilterContext
+++ b/src/osgEarthFeatures/FilterContext
@@ -21,11 +21,11 @@
 
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Feature>
-#include <osgEarthFeatures/OptimizerHints>
 #include <osgEarthFeatures/Session>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/GeoData>
+#include <osgEarth/ShaderUtils>
 #include <osg/Matrix>
 #include <list>
 
@@ -152,18 +152,25 @@ namespace osgEarth { namespace Features
         /**
          * Accesses the shared resource cache where filters can store data.
          */
-        ResourceCache* resourceCache() { return _resourceCache.get(); }
+        ResourceCache* resourceCache();
 
         /**
-         * Hints to the OSG optimizer. Filters that use this context can explicity
-         * ask to include or exclude optimizer options via this mechanism.
+         * Shader policy. Unset by default, but code using this context can expressly
+         * set it to affect shader generation. Typical use case it to set the policy
+         * to "inherit" to inhibit shader generation if you have already generated
+         * shaders yourself.
          */
-        OptimizerHints& optimizerHints() { return _optimizerHints; }
+        optional<ShaderPolicy>& shaderPolicy() { return _shaderPolicy; }
+        const optional<ShaderPolicy>& shaderPolicy() const { return _shaderPolicy; }
 
-        /** Dump as a string */
+        /**
+         * Dump as a string
+         */
         std::string toString() const;
 
-        /** Gets the DB Options associated with the context's session */
+        /**
+         * Gets the DB Options associated with the context's session
+         */
         const osgDB::Options* getDBOptions() const;
 
     protected:
@@ -173,9 +180,9 @@ namespace osgEarth { namespace Features
         optional<GeoExtent>                _extent;
         osg::Matrixd                       _referenceFrame;
         osg::Matrixd                       _inverseReferenceFrame;
-        OptimizerHints                     _optimizerHints;
         osg::ref_ptr<ResourceCache>        _resourceCache;
         FeatureSourceIndex*                _index;
+        optional<ShaderPolicy>             _shaderPolicy;
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/FilterContext.cpp b/src/osgEarthFeatures/FilterContext.cpp
index c9c943a..2a2426f 100644
--- a/src/osgEarthFeatures/FilterContext.cpp
+++ b/src/osgEarthFeatures/FilterContext.cpp
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/FilterContext>
+#include <osgEarthSymbology/ResourceCache>
 #include <osgEarth/Registry>
 
 using namespace osgEarth;
@@ -30,7 +31,8 @@ _session     ( session ),
 _profile     ( profile ),
 _extent      ( workingExtent, workingExtent ),
 _isGeocentric( false ),
-_index       ( index )
+_index       ( index ),
+_shaderPolicy( osgEarth::SHADERPOLICY_GENERATE )
 {
     _resourceCache = new ResourceCache( session ? session->getDBOptions() : 0L );
 
@@ -58,9 +60,9 @@ _isGeocentric         ( rhs._isGeocentric ),
 _extent               ( rhs._extent ),
 _referenceFrame       ( rhs._referenceFrame ),
 _inverseReferenceFrame( rhs._inverseReferenceFrame ),
-_optimizerHints       ( rhs._optimizerHints ),
 _resourceCache        ( rhs._resourceCache.get() ),
-_index                ( rhs._index )
+_index                ( rhs._index ),
+_shaderPolicy         ( rhs._shaderPolicy )
 {
     //nop
 }
@@ -115,11 +117,15 @@ FilterContext::fromMap( const osg::Vec3d& point ) const
 {
     osg::Vec3d world;
     _extent->getSRS()->transformToWorld( point, world );
-    //if ( _isGeocentric )
-    //    _extent->getSRS()->transformToECEF( point, world );
     return toLocal(world);
 }
 
+ResourceCache*
+FilterContext::resourceCache()
+{
+    return _resourceCache.get();
+}
+
 std::string
 FilterContext::toString() const
 {
diff --git a/src/osgEarthFeatures/GeometryCompiler.cpp b/src/osgEarthFeatures/GeometryCompiler.cpp
index f35865c..e0fbfee 100644
--- a/src/osgEarthFeatures/GeometryCompiler.cpp
+++ b/src/osgEarthFeatures/GeometryCompiler.cpp
@@ -26,6 +26,8 @@
 #include <osgEarthFeatures/ScatterFilter>
 #include <osgEarthFeatures/SubstituteModelFilter>
 #include <osgEarthFeatures/TessellateOperator>
+#include <osgEarth/AutoScale>
+#include <osgEarth/CullingUtils>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
 #include <osgEarth/ShaderGenerator>
@@ -336,7 +338,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     }
 
     // instance substitution (replaces marker)
-    else if ( model || icon )
+    else if ( model )
     {
         const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon;
 
@@ -363,9 +365,6 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             AltitudeFilter clamp;
             clamp.setPropertiesFromStyle( style );
             localCX = clamp.push( workingSet, localCX );
-
-            // don't set this; we changed the input data.
-            //altRequired = false;
         }
 
         SubstituteModelFilter sub( style );
@@ -384,6 +383,12 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         if ( node )
         {
             resultGroup->addChild( node );
+
+            // enable auto scaling on the group?
+            if ( model && model->autoScale() == true )
+            {
+                resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN );
+            }
         }
     }
 
@@ -414,25 +419,6 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
     }
 
-    // polygonized lines.
-    else if ( line != 0L && line->stroke()->widthUnits() != Units::PIXELS )
-    {
-        if ( altRequired )
-        {
-            AltitudeFilter clamp;
-            clamp.setPropertiesFromStyle( style );
-            sharedCX = clamp.push( workingSet, sharedCX );
-            altRequired = false;
-        }
-
-        PolygonizeLinesFilter filter( style );
-        osg::Node* node = filter.push( workingSet, sharedCX );
-        if ( node )
-        {
-            resultGroup->addChild( node );
-        }
-    }
-
     // simple geometry
     else if ( point || line || polygon )
     {
@@ -449,12 +435,8 @@ GeometryCompiler::compile(FeatureList&          workingSet,
             filter.maxGranularity() = *_options.maxGranularity();
         if ( _options.geoInterp().isSet() )
             filter.geoInterp() = *_options.geoInterp();
-        if ( _options.mergeGeometry().isSet() )
-            filter.mergeGeometry() = *_options.mergeGeometry();
         if ( _options.featureName().isSet() )
             filter.featureName() = *_options.featureName();
-        if ( _options.useVertexBufferObjects().isSet())
-            filter.useVertexBufferObjects() = *_options.useVertexBufferObjects();
 
         osg::Node* node = filter.push( workingSet, sharedCX );
         if ( node )
@@ -463,7 +445,7 @@ GeometryCompiler::compile(FeatureList&          workingSet,
         }
     }
 
-    if ( text )
+    if ( text || icon )
     {
         if ( altRequired )
         {
@@ -493,8 +475,9 @@ GeometryCompiler::compile(FeatureList&          workingSet,
     {
         if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE )
         {
-            ShaderGenerator gen( 0L );  // no ss cache because we will optimize later
-            resultGroup->accept( gen );
+            // no ss cache because we will optimize later.
+            ShaderGenerator gen;
+            gen.run( resultGroup.get() );
         }
         else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE )
         {
@@ -506,29 +489,6 @@ GeometryCompiler::compile(FeatureList&          workingSet,
 
     // Optimize stateset sharing.
     sscache->optimize( resultGroup.get() );
-    
-    // todo: this helps a lot, but is currently broken for non-triangle
-    // geometries. (gw, 12-17-2012)
-#if 0
-        osgUtil::Optimizer optimizer;
-        optimizer.optimize(
-            resultGroup.get(),
-            osgUtil::Optimizer::VERTEX_PRETRANSFORM );
-            osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-#endif
-
-#if 0
-    // if necessary, modify the bounding boxes of the underlying Geometry
-    // drawables so they will work with clamping.
-    if (altitude &&
-        (altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN || altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN) &&
-        altitude->technique() == AltitudeSymbol::TECHNIQUE_GPU)
-    {
-        OverlayGeometryAdjuster adjuster( -10000.0f, 10000.0f );
-        resultGroup->accept( adjuster );
-    }
-#endif
-
 
     //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" );
 
diff --git a/src/osgEarthFeatures/LabelSource b/src/osgEarthFeatures/LabelSource
index dff2a3c..2c845c3 100644
--- a/src/osgEarthFeatures/LabelSource
+++ b/src/osgEarthFeatures/LabelSource
@@ -43,7 +43,7 @@ namespace osgEarth { namespace Features
             fromConfig(_conf);
         }
         
-        virtual ~LabelSourceOptions() { }
+        virtual ~LabelSourceOptions();
 
     public:
         virtual Config getConfig() const;
@@ -84,9 +84,9 @@ namespace osgEarth { namespace Features
          *
          * @return A scene graph node
          */
-        virtual osg::Node* createNode(
-            const std::string&  text,
-            const Style&        style ) =0;
+        //virtual osg::Node* createNode(
+        //    const std::string&  text,
+        //    const Style&        style ) =0;
 
 
     public:
@@ -99,7 +99,7 @@ namespace osgEarth { namespace Features
         virtual const char* libraryName() const { return "osgEarth::Features"; }
 
     protected:
-        virtual ~LabelSource() { }
+        virtual ~LabelSource();
     };
 
     //--------------------------------------------------------------------
diff --git a/src/osgEarthFeatures/LabelSource.cpp b/src/osgEarthFeatures/LabelSource.cpp
index 23ec187..d9b2cdc 100644
--- a/src/osgEarthFeatures/LabelSource.cpp
+++ b/src/osgEarthFeatures/LabelSource.cpp
@@ -28,6 +28,10 @@ using namespace osgEarth::Symbology;
 
 //------------------------------------------------------------------------
 
+LabelSourceOptions::~LabelSourceOptions()
+{
+}
+
 void
 LabelSourceOptions::fromConfig( const Config& conf )
 {
@@ -49,6 +53,11 @@ LabelSourceOptions::getConfig() const
 }
 
 //------------------------------------------------------------------------
+LabelSource::~LabelSource()
+{
+}
+
+//------------------------------------------------------------------------
 
 #undef  LC
 #define LC "[LabeSourceFactory] "
diff --git a/src/osgEarthFeatures/MeshClamper.cpp b/src/osgEarthFeatures/MeshClamper.cpp
index b5add00..1956385 100644
--- a/src/osgEarthFeatures/MeshClamper.cpp
+++ b/src/osgEarthFeatures/MeshClamper.cpp
@@ -81,6 +81,9 @@ MeshClamper::apply( osg::Geode& geode )
     osgUtil::IntersectionVisitor iv( lsi );
 
     double r = std::min( em->getRadiusEquator(), em->getRadiusPolar() );
+    //double r = 50000;
+
+    unsigned count = 0;
 
     for( unsigned i=0; i<geode.getNumDrawables(); ++i )
     {
@@ -182,6 +185,7 @@ MeshClamper::apply( osg::Geode& geode )
 
                     (*verts)[k] = (fw * world2local);
                     geomDirty = true;
+                    ++count;
                 }
             }
 
@@ -189,10 +193,15 @@ MeshClamper::apply( osg::Geode& geode )
             {
                 geom->dirtyBound();
                 if ( geom->getUseVertexBufferObjects() )
+                {
+                    verts->getVertexBufferObject()->setUsage( GL_DYNAMIC_DRAW_ARB );
                     verts->dirty();
+                }
                 else
                     geom->dirtyDisplayList();
             }
         }
+
+        //OE_NOTICE << LC << "clamped " << count << " verts." << std::endl;
     }
 }
diff --git a/src/osgEarthFeatures/PolygonizeLines b/src/osgEarthFeatures/PolygonizeLines
index d2c9ddc..d153984 100644
--- a/src/osgEarthFeatures/PolygonizeLines
+++ b/src/osgEarthFeatures/PolygonizeLines
@@ -49,15 +49,21 @@ namespace osgEarth { namespace Features
         /**
          * Run the polygonizer.
          *
-         * @param[in ] verts   Line string geometry to polygonize. The polygonizer
-         *                     will add this array to the resulting geometry.
-         * @param[in ] normals Localized normals associated with the input verts.
-         *                     Used to determine the plane in which to polygonize each
-         *                     line segment. Optional; can be NULL
+         * @param[in ] verts    Line string geometry to polygonize. The polygonizer
+         *                      will add this array to the resulting geometry.
+         * @param[in ] normals  Localized normals associated with the input verts.
+         *                      Used to determine the plane in which to polygonize each
+         *                      line segment. Optional; can be NULL
+         * @param[in ] twosided Generate polygons on both sides of the center line.
          *
          * @return Triangulated geometry, including primitive set
          */
-        osg::Geometry* operator()(osg::Vec3Array* verts, osg::Vec3Array* normals) const;
+        osg::Geometry* operator()(osg::Vec3Array* verts, osg::Vec3Array* normals, bool twosided =true) const;
+
+        /**
+         * Installs an auto-scaling shader on a stateset.
+         */
+        void installShaders(osg::StateSet* stateset) const;
 
     protected:
         Stroke _stroke;
diff --git a/src/osgEarthFeatures/PolygonizeLines.cpp b/src/osgEarthFeatures/PolygonizeLines.cpp
index 954425e..6fa4449 100644
--- a/src/osgEarthFeatures/PolygonizeLines.cpp
+++ b/src/osgEarthFeatures/PolygonizeLines.cpp
@@ -17,9 +17,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
 #include <osgEarthFeatures/PolygonizeLines>
+#include <osgEarthFeatures/FeatureSourceIndexNode>
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarth/VirtualProgram>
-#include <osgUtil/Optimizer>
+#include <osgEarth/Utils>
 
 #define LC "[PolygonizeLines] "
 
@@ -89,9 +90,9 @@ namespace
 
     // Add two triangles to an EBO vector; [side] controls the winding
     // direction.
-    inline void addTris(std::vector<unsigned>& ebo, unsigned i, unsigned prev_i, unsigned current, int side)
+    inline void addTris(std::vector<unsigned>& ebo, unsigned i, unsigned prev_i, unsigned current, float side)
     {
-        if ( side == 0 )
+        if ( side < 0.0f )
         {
             ebo.push_back( i-1 );
             ebo.push_back( i );
@@ -113,11 +114,11 @@ namespace
 
     // Add a triangle to an EBO vector; [side] control the winding
     // direction.
-    inline void addTri(std::vector<unsigned>& ebo, unsigned i0, unsigned i1, unsigned i2, int side)
+    inline void addTri(std::vector<unsigned>& ebo, unsigned i0, unsigned i1, unsigned i2, float side)
     {
         ebo.push_back( i0 );
-        ebo.push_back( side == 0 ? i1 : i2 );
-        ebo.push_back( side == 0 ? i2 : i1 );
+        ebo.push_back( side < 0.0f ? i1 : i2 );
+        ebo.push_back( side < 0.0f ? i2 : i1 );
     }
 }
 
@@ -131,7 +132,8 @@ _stroke( stroke )
 
 osg::Geometry*
 PolygonizeLinesOperator::operator()(osg::Vec3Array* verts, 
-                                    osg::Vec3Array* normals) const
+                                    osg::Vec3Array* normals,
+                                    bool            twosided) const
 {
     // number of verts on the original line.
     unsigned lineSize = verts->size();
@@ -148,6 +150,7 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
     osg::Geometry* geom  = new osg::Geometry();
     geom->setUseVertexBufferObjects( true );
+    geom->setUseDisplayList( false );
 
     // Add the input verts to the geometry. This forms the "spine" of the
     // polygonized line. We need the spine so we can affect proper clamping,
@@ -155,6 +158,11 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     geom->setVertexArray( verts );
 
     // Set up the normals array
+    if ( !normals )
+    {
+        normals = new osg::Vec3Array(verts->size());
+        normals->assign( normals->size(), osg::Vec3(0,0,1) );
+    }
     geom->setNormalArray( normals );
     geom->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
 
@@ -194,10 +202,12 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
     osg::Quat rot, unrot;
 
     // iterate over both "sides" of the center line:
-    for( int s=0; s<=1; ++s )
+    int lastside = twosided ? 1 : 0;
+
+    for( int ss=0; ss<=lastside; ++ss )
     {
-        // s==0 is the left side, s==1 is the right side.
-        float side = s == 0 ? -1.0f : 1.0f;
+        //float side = s == 0 ? -1.0f : 1.0f;
+        float side = ss == 0 ? 1.0f : -1.0f;
 
         // iterate over each line segment.
         for( i=0; i<lineSize-1; ++i )
@@ -213,7 +223,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
             // the buffering vector is orthogonal to the direction vector and the normal;
             // flip it depending on the current side.
-            osg::Vec3 bufVecUnit = (s==0) ? normal ^ dir : dir ^ normal;
+            //osg::Vec3 bufVecUnit = (s==0) ? normal ^ dir : dir ^ normal;
+            osg::Vec3 bufVecUnit = (side < 0.0f) ? normal ^ dir : dir ^ normal;
 
             // scale the buffering vector to half the stroke width.
             osg::Vec3 bufVec = bufVecUnit * halfWidth;
@@ -229,7 +240,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                 prevBufVertPtr = verts->size() - 1;
 
                 // first tex coord:
-                tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                // TODO: revisit. I believe we have them going x = [-1..1] instead of [0..1] -gw
+                tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
 
                 // first normal
                 normals->push_back( (*normals)[i] );
@@ -252,8 +264,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                         rotate( circlevec, -(side)*a, (*normals)[i], v );
 
                         verts->push_back( (*verts)[i] + v );
-                        addTri( ebo, i, verts->size()-2, verts->size()-1, s );
-                        tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                        addTri( ebo, i, verts->size()-2, verts->size()-1, side );
+                        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                         normals->push_back( (*normals)[i] );
                         if ( spine ) spine->push_back( (*verts)[i] );
                     }
@@ -263,14 +275,14 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                     float cornerWidth = sqrt(2.0*halfWidth*halfWidth);
 
                     verts->push_back( verts->back() - dir*halfWidth );
-                    addTri( ebo, i, verts->size()-2, verts->size()-1, s );
-                    tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                    addTri( ebo, i, verts->size()-2, verts->size()-1, side );
+                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                     normals->push_back( normals->back() );
                     if ( spine ) spine->push_back( (*verts)[i] );
 
                     verts->push_back( (*verts)[i] - dir*halfWidth );
-                    addTri( ebo, i, verts->size()-2, verts->size()-1, s );
-                    tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                    addTri( ebo, i, verts->size()-2, verts->size()-1, side );
+                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
                     if ( spine ) spine->push_back( (verts->back() - (*verts)[i]) * sqrt(2.0f) );
                 }
@@ -279,7 +291,7 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
             {
                 // does the new segment turn create a reflex angle (>180deg)?
                 float z = (prevDir ^ dir).z();
-                bool isOutside = s == 0 ? z <= 0.0 : z >= 0.0;
+                bool isOutside = side < 0.0f ? z <= 0.0 : z >= 0.0;
                 bool isInside = !isOutside;
 
                 // if this is an inside angle (or we're using mitered corners)
@@ -306,8 +318,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
                     // now that we have the current buffered point, build triangles
                     // for *previous* segment.
-                    addTris( ebo, i, prevBufVertPtr, verts->size()-1, s );
-                    tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                    addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
+                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
 
                     if ( spine ) spine->push_back( (*verts)[i] );
@@ -319,8 +331,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                     osg::Vec3 start = (*verts)[i] + prevBufVec;
 
                     verts->push_back( start );
-                    addTris( ebo, i, prevBufVertPtr, verts->size()-1, s );
-                    tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                    addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
+                    tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                     normals->push_back( (*normals)[i] );
                     if ( spine ) spine->push_back( (*verts)[i] );
 
@@ -336,8 +348,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                         rotate( circlevec, side*a, (*normals)[i], v );
 
                         verts->push_back( (*verts)[i] + v );
-                        addTri( ebo, i, verts->size()-1, verts->size()-2, s );
-                        tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                        addTri( ebo, i, verts->size()-1, verts->size()-2, side );
+                        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                         normals->push_back( (*normals)[i] );
 
                         if ( spine ) spine->push_back( (*verts)[i] );
@@ -356,8 +368,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 
         // record the final point data.
         verts->push_back( (*verts)[i] + prevBufVec );
-        addTris( ebo, i, prevBufVertPtr, verts->size()-1, s );
-        tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+        addTris( ebo, i, prevBufVertPtr, verts->size()-1, side );
+        tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
         normals->push_back( (*normals)[i] );
         if ( spine ) spine->push_back( (*verts)[i] );
 
@@ -375,8 +387,8 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
                 float a = step * (float)j;
                 rotate( circlevec, (side)*a, (*normals)[i], v );
                 verts->push_back( (*verts)[i] + v );
-                addTri( ebo, i, verts->size()-1, verts->size()-2, s );
-                tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+                addTri( ebo, i, verts->size()-1, verts->size()-2, side );
+                tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
                 normals->push_back( (*normals)[i] );
                 if ( spine ) spine->push_back( (*verts)[i] );
             }
@@ -386,14 +398,14 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
             float cornerWidth = sqrt(2.0*halfWidth*halfWidth);
 
             verts->push_back( verts->back() + prevDir*halfWidth );
-            addTri( ebo, i, verts->size()-1, verts->size()-2, s );
-            tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+            addTri( ebo, i, verts->size()-1, verts->size()-2, side );
+            tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
             normals->push_back( normals->back() );
             if ( spine ) spine->push_back( (*verts)[i] );
 
             verts->push_back( (*verts)[i] + prevDir*halfWidth );
-            addTri( ebo, i, verts->size()-1, verts->size()-2, s );
-            tverts->push_back( osg::Vec2f(1.0*(float)s, (*tverts)[i].y()) );
+            addTri( ebo, i, verts->size()-1, verts->size()-2, side );
+            tverts->push_back( osg::Vec2f(1.0*side, (*tverts)[i].y()) );
             normals->push_back( (*normals)[i] );
             if ( spine ) spine->push_back( (*verts)[i] );
         }
@@ -431,6 +443,75 @@ PolygonizeLinesOperator::operator()(osg::Vec3Array* verts,
 }
 
 
+#define SHADER_NAME "osgEarth::PolygonizeLinesAutoScale"
+
+
+void
+PolygonizeLinesOperator::installShaders(osg::StateSet* stateset) const
+{
+    float minPixels = _stroke.minPixels().getOrUse( 0.0f );
+    if ( minPixels <= 0.0f )
+        return;
+
+    VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+    // bail if already installed.
+    if ( vp->getName().compare( SHADER_NAME ) == 0 )
+        return;
+
+    vp->setName( SHADER_NAME );
+
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+        "attribute vec3   oe_polyline_center; \n"
+        "uniform   float  oe_polyline_scale;  \n"
+        "uniform   float  oe_polyline_min_pixels; \n"
+        "uniform   mat3   oe_WindowScaleMatrix; \n"
+
+        "void oe_polyline_scalelines(inout vec4 VertexMODEL) \n"
+        "{ \n"
+        //"   if ( oe_polyline_scale != 1.0 || oe_polyline_min_pixels > 0.0 ) \n"
+        "   { \n"
+        "       vec4  center_model = vec4(oe_polyline_center*VertexMODEL.w, VertexMODEL.w); \n"
+        "       vec4  vector_model = VertexMODEL - center_model; \n"
+        "       if ( length(vector_model.xyz) > 0.0 ) \n"
+        "       { \n"
+        "           float scale = oe_polyline_scale; \n"
+
+        "           vec4 vertex_clip = gl_ModelViewProjectionMatrix * VertexMODEL; \n"
+        "           vec4 center_clip = gl_ModelViewProjectionMatrix * center_model; \n"
+        "           vec4 vector_clip = vertex_clip - center_clip; \n"
+
+        "           if ( oe_polyline_min_pixels > 0.0 ) \n"
+        "           { \n"
+        "               vec3 vector_win = oe_WindowScaleMatrix * (vertex_clip.xyz/vertex_clip.w - center_clip.xyz/center_clip.w); \n"
+        "               float min_scale = max( (0.5*oe_polyline_min_pixels)/length(vector_win.xy), 1.0 ); \n"
+        "               scale = max( scale, min_scale ); \n"
+        "           } \n"
+
+        "           VertexMODEL = center_model + vector_model*scale; \n"
+        "        } \n"
+        "    } \n"
+        "} \n";
+
+    vp->setFunction( "oe_polyline_scalelines", vs, ShaderComp::LOCATION_VERTEX_MODEL );
+    vp->addBindAttribLocation( "oe_polyline_center", osg::Drawable::ATTRIBUTE_6 );
+
+    // add the default scaling uniform.
+    // good way to test:
+    //    osgearth_viewer earthfile --uniform oe_polyline_scale 1.0 10.0
+    osg::Uniform* scaleU = new osg::Uniform(osg::Uniform::FLOAT, "oe_polyline_scale");
+    scaleU->set( 1.0f );
+    stateset->addUniform( scaleU, 1 );
+
+    // the default "min pixels" uniform.
+    osg::Uniform* minPixelsU = new osg::Uniform(osg::Uniform::FLOAT, "oe_polyline_min_pixels");
+    minPixelsU->set( minPixels );
+    stateset->addUniform( minPixelsU, 1 );
+}
+
+
 //------------------------------------------------------------------------
 
 
@@ -491,6 +572,10 @@ PolygonizeLinesFilter::push(FeatureList& input, FilterContext& cx)
             // turn the lines into polygons.
             osg::Geometry* geom = polygonize( verts, normals );
             geode->addDrawable( geom );
+
+            // record the geometry's primitive set(s) in the index:
+            if ( cx.featureIndex() )
+                cx.featureIndex()->tagPrimitiveSets( geom, f );
         }
     }
 
@@ -498,73 +583,11 @@ PolygonizeLinesFilter::push(FeatureList& input, FilterContext& cx)
     MeshConsolidator::run( *geode );
 
     // GPU performance optimization:
-#if 0 // issue: ignores vertex attributes
-    osgUtil::Optimizer optimizer;
-    optimizer.optimize(
-        result,
-        osgUtil::Optimizer::VERTEX_PRETRANSFORM |
-        osgUtil::Optimizer::VERTEX_POSTTRANSFORM );
-#endif
+    VertexCacheOptimizer vco;
+    geode->accept( vco );
 
     // If we're auto-scaling, we need a shader
-    float minPixels = line ? line->stroke()->minPixels().getOrUse( 0.0f ) : 0.0f;
-    if ( minPixels )
-    {
-        osg::StateSet* stateSet = geode->getOrCreateStateSet();
-
-        VirtualProgram* vp = new VirtualProgram();
-        vp->setName( "osgEarth::PolygonizeLines" );
-
-        const char* vs =
-            "#version " GLSL_VERSION_STR "\n"
-            GLSL_DEFAULT_PRECISION_FLOAT "\n"
-            "attribute vec3   oe_polyline_center; \n"
-            "uniform   float  oe_polyline_scale;  \n"
-            "uniform   float  oe_polyline_min_pixels; \n"
-            "uniform   mat3   oe_WindowScaleMatrix; \n"
-
-            "void oe_polyline_scalelines(inout vec4 VertexMODEL) \n"
-            "{ \n"
-            "   if ( oe_polyline_scale != 1.0 || oe_polyline_min_pixels > 0.0 ) \n"
-            "   { \n"
-            "       vec4  center_model = vec4(oe_polyline_center*VertexMODEL.w, VertexMODEL.w); \n"
-            "       vec4  vector_model = VertexMODEL - center_model; \n"
-            "       if ( length(vector_model.xyz) > 0.0 ) \n"
-            "       { \n"
-            "           float scale = oe_polyline_scale; \n"
-
-            "           vec4 vertex_clip = gl_ModelViewProjectionMatrix * VertexMODEL; \n"
-            "           vec4 center_clip = gl_ModelViewProjectionMatrix * center_model; \n"
-            "           vec4 vector_clip = vertex_clip - center_clip; \n"
-
-            "           if ( oe_polyline_min_pixels > 0.0 ) \n"
-            "           { \n"
-            "               vec3 vector_win = oe_WindowScaleMatrix * (vertex_clip.xyz/vertex_clip.w - center_clip.xyz/center_clip.w); \n"
-            "               float min_scale = max( (0.5*oe_polyline_min_pixels)/length(vector_win.xy), 1.0 ); \n"
-            "               scale = max( scale, min_scale ); \n"
-            "           } \n"
-
-            "           VertexMODEL = center_model + vector_model*scale; \n"
-            "        } \n"
-            "    } \n"
-            "} \n";
-
-        vp->setFunction( "oe_polyline_scalelines", vs, ShaderComp::LOCATION_VERTEX_MODEL );
-        vp->addBindAttribLocation( "oe_polyline_center", osg::Drawable::ATTRIBUTE_6 );
-        stateSet->setAttributeAndModes( vp, 1 );
-
-        // add the default scaling uniform.
-        // good way to test:
-        //    osgearth_viewer earthfile --uniform oe_polyline_scale 1.0 10.0
-        osg::Uniform* scaleU = new osg::Uniform(osg::Uniform::FLOAT, "oe_polyline_scale");
-        scaleU->set( 1.0f );
-        stateSet->addUniform( scaleU, 1 );
-
-        // the default "min pixels" uniform.
-        osg::Uniform* minPixelsU = new osg::Uniform(osg::Uniform::FLOAT, "oe_polyline_min_pixels");
-        minPixelsU->set( minPixels );
-        stateSet->addUniform( minPixelsU, 1 );
-    }
+    polygonize.installShaders( geode->getOrCreateStateSet() );
 
     return delocalize( geode );
 }
diff --git a/src/osgEarthFeatures/ScriptEngine b/src/osgEarthFeatures/ScriptEngine
index 151a191..e40ac67 100644
--- a/src/osgEarthFeatures/ScriptEngine
+++ b/src/osgEarthFeatures/ScriptEngine
@@ -22,6 +22,7 @@
 #include <osgEarthFeatures/Common>
 #include <osgEarthFeatures/Script>
 #include <osgEarth/Config>
+#include <osgEarth/ThreadingUtils>
 
 namespace osgEarth { namespace Features
 {
@@ -102,9 +103,18 @@ namespace osgEarth { namespace Features
   class OSGEARTHFEATURES_EXPORT ScriptEngineFactory
   {   
 	public:
+    static ScriptEngineFactory* instance();
+
     static ScriptEngine* create( const std::string& language, const std::string& engineName="" );
     static ScriptEngine* create( const Script& script, const std::string& engineName="" );
     static ScriptEngine* create( const ScriptEngineOptions& options );
+
+  protected:
+    ScriptEngineFactory() { }
+
+    std::vector<std::string> _failedDrivers;
+    static ScriptEngineFactory* s_singleton;
+    static osgEarth::Threading::Mutex s_singletonMutex;
   };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/ScriptEngine.cpp b/src/osgEarthFeatures/ScriptEngine.cpp
index d4ddc9d..0c4966c 100644
--- a/src/osgEarthFeatures/ScriptEngine.cpp
+++ b/src/osgEarthFeatures/ScriptEngine.cpp
@@ -70,6 +70,23 @@ ScriptEngineOptions::getConfig() const
 #define LC "[ScriptEngineFactory] "
 #define SCRIPT_ENGINE_OPTIONS_TAG "__osgEarth::Features::ScriptEngineOptions"
 
+ScriptEngineFactory* ScriptEngineFactory::s_singleton = 0L;
+osgEarth::Threading::Mutex ScriptEngineFactory::s_singletonMutex;
+
+ScriptEngineFactory*
+ScriptEngineFactory::instance()
+{
+    if ( !s_singleton )
+    {
+        Threading::ScopedMutexLock lock(s_singletonMutex);
+        if ( !s_singleton )
+        {
+            s_singleton = new ScriptEngineFactory();
+        }
+    }
+    return s_singleton;
+}
+
 ScriptEngine*
 ScriptEngineFactory::create( const std::string& language, const std::string& engineName )
 {
@@ -96,19 +113,27 @@ ScriptEngineFactory::create( const ScriptEngineOptions& options )
 
     if ( !options.getDriver().empty() )
     {
-        std::string driverExt = std::string(".osgearth_scriptengine_") + options.getDriver();
-
-        osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
-        rwopts->setPluginData( SCRIPT_ENGINE_OPTIONS_TAG, (void*)&options );
-
-        scriptEngine = dynamic_cast<ScriptEngine*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
-        if ( scriptEngine )
+        if ( std::find(instance()->_failedDrivers.begin(), instance()->_failedDrivers.end(), options.getDriver()) == instance()->_failedDrivers.end() )
         {
-            OE_INFO << "Loaded ScriptEngine driver \"" << options.getDriver() << "\" OK" << std::endl;
+            std::string driverExt = std::string(".osgearth_scriptengine_") + options.getDriver();
+
+            osg::ref_ptr<osgDB::Options> rwopts = Registry::instance()->cloneOrCreateOptions();
+            rwopts->setPluginData( SCRIPT_ENGINE_OPTIONS_TAG, (void*)&options );
+
+            scriptEngine = dynamic_cast<ScriptEngine*>( osgDB::readObjectFile( driverExt, rwopts.get() ) );
+            if ( scriptEngine )
+            {
+                OE_DEBUG << "Loaded ScriptEngine driver \"" << options.getDriver() << "\" OK" << std::endl;
+            }
+            else
+            {
+                OE_WARN << "FAIL, unable to load ScriptEngine driver for \"" << options.getDriver() << "\"" << std::endl;
+                instance()->_failedDrivers.push_back(options.getDriver());
+            }
         }
         else
         {
-            OE_WARN << "FAIL, unable to load ScriptEngine driver for \"" << options.getDriver() << "\"" << std::endl;
+            //OE_WARN << "Skipping previously failed ScriptEngine driver \"" << options.getDriver() << "\"" << std::endl;
         }
     }
     else
diff --git a/src/osgEarthFeatures/Session.cpp b/src/osgEarthFeatures/Session.cpp
index ce398ea..79fe906 100644
--- a/src/osgEarthFeatures/Session.cpp
+++ b/src/osgEarthFeatures/Session.cpp
@@ -23,6 +23,7 @@
 #include <osgEarthFeatures/FeatureSource>
 #include <osgEarth/FileUtils>
 #include <osgEarth/StringUtils>
+#include <osgEarth/Registry>
 #include <osg/AutoTransform>
 #include <osg/Depth>
 #include <osg/TextureRectangle>
@@ -46,11 +47,17 @@ _dbOptions     ( dbOptions )
     else
         _styles = new StyleSheet();
 
+    // if no script engine was created when the style was set above, create a default javascript one
+    if (!_styleScriptEngine.valid())
+      _styleScriptEngine = ScriptEngineFactory::create("javascript");
+
     // if the caller did not provide a dbOptions, take it from the map.
     if ( map && !dbOptions )
         _dbOptions = map->getDBOptions();
 
-    // a new cache to optimize state changes.
+    // A new cache to optimize state changes. Since the cache lives in the Session, any
+    // geometry created under this session takes advantage of it. That's reasonable since
+    // tiles in a particular "layer" will tend to share state.
     _stateSetCache = new StateSetCache();
 }
 
diff --git a/src/osgEarthFeatures/SubstituteModelFilter b/src/osgEarthFeatures/SubstituteModelFilter
index 0d12726..f1af4ba 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter
+++ b/src/osgEarthFeatures/SubstituteModelFilter
@@ -93,7 +93,7 @@ namespace osgEarth { namespace Features
         bool process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* ap, FilterContext& context );
         bool cluster(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* ap, FilterContext& context );
         
-        InstanceResource* findResource( const URI& instanceURI, const InstanceSymbol* symbol, FilterContext& context, std::set<URI>& missing ) ;
+        bool findResource( const URI& instanceURI, const InstanceSymbol* symbol, FilterContext& context, std::set<URI>& missing, osg::ref_ptr<InstanceResource>& output );
     };
 
 } } // namespace osgEarth::Features
diff --git a/src/osgEarthFeatures/SubstituteModelFilter.cpp b/src/osgEarthFeatures/SubstituteModelFilter.cpp
index f31b75b..4306b7b 100644
--- a/src/osgEarthFeatures/SubstituteModelFilter.cpp
+++ b/src/osgEarthFeatures/SubstituteModelFilter.cpp
@@ -21,10 +21,11 @@
 #include <osgEarthSymbology/MeshConsolidator>
 #include <osgEarth/ECEF>
 #include <osgEarth/VirtualProgram>
-#include <osgEarth/ShaderGenerator>
 #include <osgEarth/DrawInstanced>
 #include <osgEarth/Registry>
 #include <osgEarth/Capabilities>
+#include <osgEarth/Decluttering>
+#include <osgEarth/CullingUtils>
 
 #include <osg/AutoTransform>
 #include <osg/Drawable>
@@ -53,6 +54,16 @@ using namespace osgEarth::Symbology;
 namespace
 {
     static osg::Node* s_defaultModel =0L;
+
+    struct SetSmallFeatureCulling : public osg::NodeCallback
+    {
+        bool _value;
+        SetSmallFeatureCulling(bool value) : _value(value) { }
+        void operator()(osg::Node* node, osg::NodeVisitor* nv) {
+            Culling::asCullVisitor(nv)->setSmallFeatureCullingPixelSize(-1.0f);
+            traverse(node, nv);
+        }
+    };
 }
 
 //------------------------------------------------------------------------
@@ -62,42 +73,42 @@ _style                ( style ),
 _cluster              ( false ),
 _useDrawInstanced     ( false ),
 _merge                ( true ),
-_normalScalingRequired( false )
+_normalScalingRequired( false ),
+_instanceCache        ( false )     // cache per object so MT not required
 {
     //NOP
 }
 
-InstanceResource*
+bool
 SubstituteModelFilter::findResource(const URI&            uri,
-                                    const InstanceSymbol* symbol, 
+                                    const InstanceSymbol* symbol,
                                     FilterContext&        context, 
-                                    std::set<URI>&        missing ) 
+                                    std::set<URI>&        missing,
+                                    osg::ref_ptr<InstanceResource>& output )
 {
-    // find the corresponding marker in the cache
-    InstanceResource* instance = 0L;
+    // be careful about refptrs here since _instanceCache is an LRU.
 
     InstanceCache::Record rec;
     if ( _instanceCache.get(uri, rec) )
     {
         // found it in the cache:
-        instance = rec.value();
+        output = rec.value().get();
     }
     else if ( _resourceLib.valid() )
     {
         // look it up in the resource library:
-        instance = _resourceLib->getInstance( uri.base(), context.getDBOptions() );
+        output = _resourceLib->getInstance( uri.base(), context.getDBOptions() );
     }
     else
     {
         // create it on the fly:
-        OE_DEBUG << "New resource (not in the cache!)" << std::endl;
-        instance = symbol->createResource();
-        instance->uri() = uri;
-        _instanceCache.insert( uri, instance );
+        output = symbol->createResource();
+        output->uri() = uri;
+        _instanceCache.insert( uri, output.get() );
     }
 
     // failed to find the instance.
-    if ( instance == 0L )
+    if ( !output.valid() )
     {
         if ( missing.find(uri) == missing.end() )
         {
@@ -106,7 +117,7 @@ SubstituteModelFilter::findResource(const URI&            uri,
         }
     }
 
-    return instance;
+    return output.valid();
 }
 
 bool
@@ -137,7 +148,6 @@ SubstituteModelFilter::process(const FeatureList&           features,
     if ( modelSymbol )
         headingEx = *modelSymbol->heading();
 
-
     for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f )
     {
         Feature* input = f->get();
@@ -147,8 +157,8 @@ SubstituteModelFilter::process(const FeatureList&           features,
         URI instanceURI( input->eval(uriEx, &context), uriEx.uriContext() );
 
         // find the corresponding marker in the cache
-        InstanceResource* instance = findResource( instanceURI, symbol, context, missing );
-        if ( !instance )
+        osg::ref_ptr<InstanceResource> instance;
+        if ( !findResource(instanceURI, symbol, context, missing, instance) )
             continue;
 
         // evalute the scale expression (if there is one)
@@ -166,18 +176,6 @@ SubstituteModelFilter::process(const FeatureList&           features,
         }
         
         osg::Matrixd rotationMatrix;
-#if 0
-        if ( symbol->orientation().isSet() )
-        {
-            osg::Vec3d hpr = *symbol->orientation();
-            //Rotation in HPR
-            //Apply the rotation            
-            rotationMatrix.makeRotate( 
-                osg::DegreesToRadians(hpr.y()), osg::Vec3(1,0,0),
-                osg::DegreesToRadians(hpr.x()), osg::Vec3(0,0,1),
-                osg::DegreesToRadians(hpr.z()), osg::Vec3(0,1,0) );
-        }
-#endif
 
         if ( modelSymbol && modelSymbol->heading().isSet() )
         {
@@ -188,24 +186,27 @@ SubstituteModelFilter::process(const FeatureList&           features,
         // how that we have a marker source, create a node for it
         std::pair<URI,float> key( instanceURI, scale );
 
+        // cache nodes per instance.
         osg::ref_ptr<osg::Node>& model = uniqueModels[key];
         if ( !model.valid() )
         {
-            model = context.resourceCache()->getInstanceNode( instance );
+            context.resourceCache()->getInstanceNode( instance.get(), model );
 
-            if ( scale != 1.0f && dynamic_cast<osg::AutoTransform*>( model.get() ) )
+            // if icon decluttering is off, install an AutoTransform.
+            if ( iconSymbol )
             {
-                // clone the old AutoTransform, set the new scale, and copy over its children.
-                osg::AutoTransform* oldAT = dynamic_cast<osg::AutoTransform*>(model.get());
-                osg::AutoTransform* newAT = osg::clone( oldAT );
-
-                // make a scaler and put it between the new AutoTransform and its kids
-                osg::MatrixTransform* scaler = new osg::MatrixTransform(osg::Matrix::scale(scale,scale,scale));
-                for( unsigned i=0; i<newAT->getNumChildren(); ++i )
-                    scaler->addChild( newAT->getChild(0) );
-                newAT->removeChildren(0, newAT->getNumChildren());
-                newAT->addChild( scaler );
-                model = newAT;
+                if ( iconSymbol->declutter() == true )
+                {
+                    Decluttering::setEnabled( model->getOrCreateStateSet(), true );
+                }
+                else if ( dynamic_cast<osg::AutoTransform*>(model.get()) == 0L )
+                {
+                    osg::AutoTransform* at = new osg::AutoTransform();
+                    at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
+                    at->setAutoScaleToScreen( true );
+                    at->addChild( model );
+                    model = at;
+                }
             }
         }
 
@@ -264,23 +265,30 @@ SubstituteModelFilter::process(const FeatureList&           features,
         }
     }
 
+    if ( iconSymbol )
+    {
+        // activate decluttering for icons if requested
+        if ( iconSymbol->declutter() == true )
+        {
+            Decluttering::setEnabled( attachPoint->getOrCreateStateSet(), true );
+        }
+
+        // activate horizon culling if we are in geocentric space
+        if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() )
+        {
+            HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() );
+        }
+    }
+
+    // active DrawInstanced if required:
     if ( _useDrawInstanced && Registry::capabilities().supportsDrawInstanced() )
     {
         DrawInstanced::convertGraphToUseDrawInstanced( attachPoint );
 
         // install a shader program to render draw-instanced.
-        VirtualProgram* p = DrawInstanced::createDrawInstancedProgram();
-        attachPoint->getOrCreateStateSet()->setAttributeAndModes( p, osg::StateAttribute::ON );
+        DrawInstanced::install( attachPoint->getOrCreateStateSet() );
     }
 
-#if 0 // now called from GeometryCompiler
-
-    // Generate shader code to render the models
-    StateSetCache* cache = context.getSession() ? context.getSession()->getStateSetCache() : 0L;
-    ShaderGenerator gen( cache );
-    attachPoint->accept( gen );
-
-#endif
     return true;
 }
 
@@ -317,12 +325,6 @@ struct ClusterVisitor : public osg::NodeVisitor
         // ..and clear out the drawables list.
         geode.removeDrawables( 0, geode.getNumDrawables() );
 
-#if 0
-        // ... and remove all drawables from the feature node
-        for( osg::Geode::DrawableList::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ )
-            _featureNode->removeDrawable(i->get());
-#endif
-
         // foreach each drawable that was originally in the geode...
         for( osg::Geode::DrawableList::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ )
         {
@@ -344,18 +346,6 @@ struct ClusterVisitor : public osg::NodeVisitor
                 }
 
                 osg::Matrixd rotationMatrix;
-#if 0
-                if ( _symbol->orientation().isSet() )
-                {
-                    osg::Vec3d hpr = *_symbol->orientation();
-                    //Rotation in HPR
-                    //Apply the rotation            
-                    rotationMatrix.makeRotate( 
-                        osg::DegreesToRadians(hpr.y()), osg::Vec3(1,0,0),
-                        osg::DegreesToRadians(hpr.x()), osg::Vec3(0,0,1),
-                        osg::DegreesToRadians(hpr.z()), osg::Vec3(0,1,0) );            
-                }
-#endif
                 if ( _modelSymbol && _modelSymbol->heading().isSet() )
                 {
                     float heading = feature->eval( _headingExpr, &_cx );
@@ -452,7 +442,6 @@ SubstituteModelFilter::cluster(const FeatureList&           features,
                                FilterContext&               context )
 {
     ModelBins modelBins;
-    //ModelToFeatures modelToFeatures;
 
     std::set<URI> missing;
 
@@ -472,8 +461,8 @@ SubstituteModelFilter::cluster(const FeatureList&           features,
         osg::ref_ptr<osg::Node> model = context.getSession()->getObject<osg::Node>( instanceURI.full() );
         if ( !model.valid() )
         {
-            InstanceResource* instance = findResource( instanceURI, symbol, context, missing );
-            if ( !instance )
+            osg::ref_ptr<InstanceResource> instance;
+            if ( !findResource( instanceURI, symbol, context, missing, instance) )
                 continue;
 
             model = instance->createNode( context.getSession()->getDBOptions() );
@@ -582,6 +571,10 @@ SubstituteModelFilter::push(FeatureList& features, FilterContext& context)
         process( features, symbol, context.getSession(), group, newContext );
     }
 
+    // return proper context
+    context = newContext;
+
+    // TODO: OBE due to shader pipeline
     // see if we need normalized normals
     if ( _normalScalingRequired )
     {
diff --git a/src/osgEarthFeatures/TextSymbolizer.cpp b/src/osgEarthFeatures/TextSymbolizer.cpp
index 0190a3e..fadd985 100644
--- a/src/osgEarthFeatures/TextSymbolizer.cpp
+++ b/src/osgEarthFeatures/TextSymbolizer.cpp
@@ -68,25 +68,8 @@ TextSymbolizer::create(Feature*             feature,
         t->setPosition( osg::Vec3(_symbol->pixelOffset()->x(), _symbol->pixelOffset()->y(), 0.0f) );
     }
 
-
-    //    //TODO: relacate in annotationutils...
-    //    t->setPosition( osg::Vec3(
-    //        positionOffset.x() + _symbol->pixelOffset()->x(),
-    //        positionOffset.y() + _symbol->pixelOffset()->y(),
-    //        positionOffset.z() ) );
-    //}
-// TODO: retian in annotationutils...
-    //else
-    //{
-    //    t->setPosition( positionOffset );
-    //}
-
     //TODO: resonsider defaults here
     t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
-#if 0
-    t->setAutoRotateToScreen( false );
-    t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
-#endif
 
     t->setCharacterSize( _symbol.valid() && _symbol->size().isSet() ? *_symbol->size() : 16.0f );
 
diff --git a/src/osgEarthFeatures/TransformFilter.cpp b/src/osgEarthFeatures/TransformFilter.cpp
index 5ceb4db..214bf14 100644
--- a/src/osgEarthFeatures/TransformFilter.cpp
+++ b/src/osgEarthFeatures/TransformFilter.cpp
@@ -29,49 +29,6 @@ using namespace osgEarth::Symbology;
 
 namespace 
 {
-#if 0
-    osg::Matrixd
-    createGeocentricInvRefFrame( const osg::Vec3d& input, const SpatialReference* inputSRS )
-    {
-        // convert to gencentric first:
-        double X = input.x(), Y = input.y(), Z = input.z();
-
-        osg::Matrixd localToWorld;
-        localToWorld.makeTranslate(X,Y,Z);
-
-        // normalize X,Y,Z
-        double inverse_length = 1.0/sqrt(X*X + Y*Y + Z*Z);
-        
-        X *= inverse_length;
-        Y *= inverse_length;
-        Z *= inverse_length;
-
-        double length_XY = sqrt(X*X + Y*Y);
-        double inverse_length_XY = 1.0/length_XY;
-
-        // Vx = |(-Y,X,0)|
-        localToWorld(0,0) = -Y*inverse_length_XY;
-        localToWorld(0,1) = X*inverse_length_XY;
-        localToWorld(0,2) = 0.0;
-
-        // Vy = /(-Z*X/(sqrt(X*X+Y*Y), -Z*Y/(sqrt(X*X+Y*Y),sqrt(X*X+Y*Y))| 
-        double Vy_x = -Z*X*inverse_length_XY;
-        double Vy_y = -Z*Y*inverse_length_XY;
-        double Vy_z = length_XY;
-        inverse_length = 1.0/sqrt(Vy_x*Vy_x + Vy_y*Vy_y + Vy_z*Vy_z);            
-        localToWorld(1,0) = Vy_x*inverse_length;
-        localToWorld(1,1) = Vy_y*inverse_length;
-        localToWorld(1,2) = Vy_z*inverse_length;
-
-        // Vz = (X,Y,Z)
-        localToWorld(2,0) = X;
-        localToWorld(2,1) = Y;
-        localToWorld(2,2) = Z;
-
-        return localToWorld;
-    }
-#endif
-
     void
     localizeGeometry( Feature* input, const osg::Matrixd& refFrame )
     {
diff --git a/src/osgEarthQt/AnnotationDialogs b/src/osgEarthQt/AnnotationDialogs
index 660d8f9..59bcd84 100644
--- a/src/osgEarthQt/AnnotationDialogs
+++ b/src/osgEarthQt/AnnotationDialogs
@@ -283,6 +283,7 @@ namespace osgEarth { namespace QtGui
         ls->stroke()->stipple() = 0x0F0F;
         ls->tessellation() = 20;
         _lineStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>()->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        _lineStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>()->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
       }
     }
 
diff --git a/src/osgEarthQt/CMakeLists.txt b/src/osgEarthQt/CMakeLists.txt
index 83b822c..cc3a3d6 100644
--- a/src/osgEarthQt/CMakeLists.txt
+++ b/src/osgEarthQt/CMakeLists.txt
@@ -59,7 +59,6 @@ SET(LIB_PUBLIC_HEADERS
     LOSControlWidget
     LOSCreationDialog
     MapCatalogWidget
-    MultiViewerWidget
     TerrainProfileGraph
     TerrainProfileWidget
     ViewWidget
@@ -84,7 +83,6 @@ ADD_LIBRARY(${LIB_NAME} SHARED
     LOSControlWidget.cpp
     LOSCreationDialog.cpp
     MapCatalogWidget.cpp
-    MultiViewerWidget.cpp
     TerrainProfileGraph.cpp
     TerrainProfileWidget.cpp
     ViewWidget.cpp
diff --git a/src/osgEarthQt/LayerManagerWidget b/src/osgEarthQt/LayerManagerWidget
index b562f18..8119c8a 100644
--- a/src/osgEarthQt/LayerManagerWidget
+++ b/src/osgEarthQt/LayerManagerWidget
@@ -30,6 +30,7 @@
 #include <QFrame>
 #include <QHBoxLayout>
 #include <QPoint>
+#include <QPushButton>
 #include <QScrollArea>
 #include <QSlider>
 #include <QVBoxLayout>
@@ -47,7 +48,7 @@ namespace osgEarth { namespace QtGui
     Q_OBJECT
 
     public:
-      LayerControlWidgetBase(LayerManagerWidget* parentManager, bool hasContent=true) : _parent(parentManager) { initUi(hasContent); }
+      LayerControlWidgetBase(LayerManagerWidget* parentManager, bool hasContent=true, bool showRemove=true) : _parent(parentManager) { initUi(hasContent, showRemove); }
 
       virtual Action* getDoubleClickAction(const ViewVector& views) { return 0L; }
 
@@ -58,10 +59,13 @@ namespace osgEarth { namespace QtGui
     signals:
       void doubleClicked();
 
+    private slots:
+      virtual void onRemoveClicked(bool checked);
+
     protected:
       virtual ~LayerControlWidgetBase();
 
-      virtual void initUi(bool hasContent);
+      virtual void initUi(bool hasContent, bool showRemove);
 
       void mouseDoubleClickEvent(QMouseEvent* event);
       void mousePressEvent(QMouseEvent* event);
@@ -75,9 +79,14 @@ namespace osgEarth { namespace QtGui
       QVBoxLayout*  _primaryLayout;
       QFrame*       _headerBox;
       QHBoxLayout*  _headerBoxLayout;
+      QFrame*       _headerTitleBox;
+      QHBoxLayout*  _headerTitleBoxLayout;
+      QFrame*       _headerButtonBox;
+      QHBoxLayout*  _headerButtonBoxLayout;
       QFrame*       _contentBox;
       QHBoxLayout*  _contentBoxLayout;
       QFrame*       _dropBox;
+      QPushButton*  _removeButton;
 
       QPoint _dragStartPosition;
     };
@@ -102,6 +111,9 @@ namespace osgEarth { namespace QtGui
     private slots:
       void onEnabledCheckStateChanged(int state);
 
+    protected slots:
+      void onRemoveClicked(bool checked);
+
     protected:
       virtual ~ElevationLayerControlWidget();
 
@@ -136,6 +148,9 @@ namespace osgEarth { namespace QtGui
       void onCheckStateChanged(int state);
       void onSliderValueChanged(int value);
 
+    protected slots:
+      void onRemoveClicked(bool checked);
+
     protected:
       virtual ~ImageLayerControlWidget();
 
@@ -168,7 +183,9 @@ namespace osgEarth { namespace QtGui
 
     private slots:
       void onEnabledCheckStateChanged(int state);
-      void onOverlayCheckStateChanged(int state);
+
+    protected slots:
+      void onRemoveClicked(bool checked);
 
     protected:
       virtual ~ModelLayerControlWidget();
@@ -180,7 +197,6 @@ namespace osgEarth { namespace QtGui
       osg::ref_ptr<ModelLayerCallback> _layerCallback;
       osg::ref_ptr<Action> _doubleClick;
       QCheckBox* _visibleCheckBox;
-      QCheckBox* _overlayCheckBox;
     };
 
 
@@ -244,6 +260,8 @@ namespace osgEarth { namespace QtGui
 
       void resetStyleSheet();
 
+      osgEarth::Map* getMap() { return _map.get(); }
+
     private slots:
       void onItemDoubleClicked();
 
diff --git a/src/osgEarthQt/LayerManagerWidget.cpp b/src/osgEarthQt/LayerManagerWidget.cpp
index 3f8c00e..8272742 100644
--- a/src/osgEarthQt/LayerManagerWidget.cpp
+++ b/src/osgEarthQt/LayerManagerWidget.cpp
@@ -95,12 +95,6 @@ namespace
         _widget->setLayerVisible(layer->getVisible());
     }
 
-    void onOverlayChanged(ModelLayer* layer)
-    {
-      if (_widget)
-        _widget->setLayerOverlay(layer->getOverlay());
-    }
-
   private:
     ModelLayerControlWidget* _widget;
   };
@@ -180,7 +174,7 @@ LayerControlWidgetBase::~LayerControlWidgetBase()
 {
 }
 
-void LayerControlWidgetBase::initUi(bool hasContent)
+void LayerControlWidgetBase::initUi(bool hasContent, bool showRemove)
 {
   // object name for custom stylesheets
   setObjectName("oeItem");
@@ -212,12 +206,40 @@ void LayerControlWidgetBase::initUi(bool hasContent)
   _primaryLayout->addWidget(_dropBox);
 
 
-  // create the header box and layout
+  // create the header boxes and layouts
   _headerBox = new QFrame;
   _headerBoxLayout = new QHBoxLayout;
-  _headerBoxLayout->setSpacing(4);
-  _headerBoxLayout->setContentsMargins(2, 2, 2, 2);
+  //_headerBoxLayout->setSpacing(4);
+  //_headerBoxLayout->setContentsMargins(2, 2, 2, 2);
   _headerBox->setLayout(_headerBoxLayout);
+
+  _headerTitleBox = new QFrame;
+  _headerTitleBoxLayout = new QHBoxLayout;
+  _headerTitleBoxLayout->setSpacing(4);
+  _headerTitleBoxLayout->setContentsMargins(2, 2, 2, 2);
+  _headerTitleBox->setLayout(_headerTitleBoxLayout);
+  _headerBoxLayout->addWidget(_headerTitleBox);
+
+  _headerBoxLayout->addStretch();
+
+  _headerButtonBox = new QFrame;
+  _headerButtonBoxLayout = new QHBoxLayout;
+  //_headerButtonBoxLayout->setSpacing(4);
+  _headerButtonBoxLayout->setContentsMargins(2, 2, 2, 2);
+  _headerButtonBox->setLayout(_headerButtonBoxLayout);
+  _headerBoxLayout->addWidget(_headerButtonBox);
+
+  if (showRemove)
+  {
+    // add remove button to the header button box
+    _removeButton = new QPushButton(QIcon(":/images/close.png"), tr(""));
+    _removeButton->setFlat(true);
+    _removeButton->setMaximumSize(16, 16);
+    _headerButtonBoxLayout->addWidget(_removeButton);
+
+    connect(_removeButton, SIGNAL(clicked(bool)), this, SLOT(onRemoveClicked(bool)));
+  }
+
   _primaryLayout->addWidget(_headerBox);
 
 
@@ -236,6 +258,11 @@ void LayerControlWidgetBase::initUi(bool hasContent)
   setAcceptDrops(true); 
 }
 
+void LayerControlWidgetBase::onRemoveClicked(bool checked)
+{
+  //NOP
+}
+
 void LayerControlWidgetBase::mouseDoubleClickEvent(QMouseEvent *event)
 {
   emit doubleClicked();
@@ -320,14 +347,17 @@ void ElevationLayerControlWidget::initUi()
     _visibleCheckBox = new QCheckBox();
     _visibleCheckBox->setCheckState(_layer->getVisible() ? Qt::Checked : Qt::Unchecked);
     connect(_visibleCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onEnabledCheckStateChanged(int)));
-    _headerBoxLayout->addWidget(_visibleCheckBox);
+    _headerTitleBoxLayout->addWidget(_visibleCheckBox);
 
     // create name label
     QLabel* label = new QLabel(tr(!_layer->getName().empty() ? _layer->getName().c_str() : "Elevation Layer"));
-    _headerBoxLayout->addWidget(label);
+    _headerTitleBoxLayout->addWidget(label);
 
-    // add stretch for spacing
-    _headerBoxLayout->addStretch();
+    if (!_layer->getProfile())
+    {
+      _headerBox->setStyleSheet("background-color: #FF6666; color: white;");
+      _visibleCheckBox->setEnabled(false);
+    }
   }
   else
   {
@@ -344,6 +374,12 @@ void ElevationLayerControlWidget::onEnabledCheckStateChanged(int state)
     _layer->setVisible(checked);
 }
 
+void ElevationLayerControlWidget::onRemoveClicked(bool checked)
+{
+  if (_parent && _parent->getMap())
+    _parent->getMap()->removeElevationLayer(_layer);
+}
+
 void ElevationLayerControlWidget::setLayerVisible(bool visible)
 {
   if ((_visibleCheckBox->checkState() == Qt::Checked) != visible)
@@ -360,7 +396,7 @@ osgEarth::UID ElevationLayerControlWidget::getUID()
 
 Action* ElevationLayerControlWidget::getDoubleClickAction(const ViewVector& views)
 {
-  if (!_doubleClick.valid() && _layer.valid())
+  if (!_doubleClick.valid() && _layer.valid() && _layer->getProfile())
   {
     const osgEarth::GeoExtent llExt = _layer->getProfile()->getLatLongExtent();
 
@@ -410,14 +446,11 @@ void ImageLayerControlWidget::initUi()
     _visibleCheckBox = new QCheckBox();
     _visibleCheckBox->setCheckState(_layer->getVisible() ? Qt::Checked : Qt::Unchecked);
     connect(_visibleCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onCheckStateChanged(int)));
-    _headerBoxLayout->addWidget(_visibleCheckBox);
+    _headerTitleBoxLayout->addWidget(_visibleCheckBox);
 
     // create name label
     QLabel* label = new QLabel(tr(!_layer->getName().empty() ? _layer->getName().c_str() : "Image Layer"));
-    _headerBoxLayout->addWidget(label);
-
-    // add stretch for spacing
-    _headerBoxLayout->addStretch();
+    _headerTitleBoxLayout->addWidget(label);
 
     // create opacity slider
     _opacitySlider = new QSlider(Qt::Horizontal);
@@ -427,6 +460,13 @@ void ImageLayerControlWidget::initUi()
     _opacitySlider->setTracking(true);
     connect(_opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int)));
     _contentBoxLayout->addWidget(_opacitySlider);
+
+    if (!_layer->getProfile())
+    {
+      _headerBox->setStyleSheet("background-color: #FF6666; color: white;");
+      _visibleCheckBox->setEnabled(false);
+      _opacitySlider->setEnabled(false);
+    }
   }
   else
   {
@@ -450,6 +490,12 @@ void ImageLayerControlWidget::onSliderValueChanged(int value)
     _layer->setOpacity(opacity);
 }
 
+void ImageLayerControlWidget::onRemoveClicked(bool checked)
+{
+  if (_parent && _parent->getMap())
+    _parent->getMap()->removeImageLayer(_layer);
+}
+
 void ImageLayerControlWidget::setLayerVisible(bool visible)
 {
   if ((_visibleCheckBox->checkState() == Qt::Checked) != visible)
@@ -473,7 +519,7 @@ osgEarth::UID ImageLayerControlWidget::getUID()
 
 Action* ImageLayerControlWidget::getDoubleClickAction(const ViewVector& views)
 {
-  if (!_doubleClick.valid() && _layer.valid())
+  if (!_doubleClick.valid() && _layer.valid() && _layer->getProfile())
   {
     const osgEarth::GeoExtent llExt = _layer->getProfile()->getLatLongExtent();
 
@@ -523,21 +569,11 @@ void ModelLayerControlWidget::initUi()
     _visibleCheckBox = new QCheckBox();
     _visibleCheckBox->setCheckState(_layer->getVisible() ? Qt::Checked : Qt::Unchecked);
     connect(_visibleCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onEnabledCheckStateChanged(int)));
-    _headerBoxLayout->addWidget(_visibleCheckBox);
+    _headerTitleBoxLayout->addWidget(_visibleCheckBox);
 
     // create name label
     QLabel* label = new QLabel(tr(!_layer->getName().empty() ? _layer->getName().c_str() : "Model Layer"));
-    _headerBoxLayout->addWidget(label);
-
-    // add stretch for spacing
-    _headerBoxLayout->addStretch();
-
-    // create overlay checkbox
-    _contentBoxLayout->addSpacing(16);
-    _overlayCheckBox = new QCheckBox("overlay");
-    _overlayCheckBox->setCheckState(_layer->getOverlay() ? Qt::Checked : Qt::Unchecked);
-    connect(_overlayCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onOverlayCheckStateChanged(int)));
-    _contentBoxLayout->addWidget(_overlayCheckBox);
+    _headerTitleBoxLayout->addWidget(label);
   }
   else
   {
@@ -554,11 +590,10 @@ void ModelLayerControlWidget::onEnabledCheckStateChanged(int state)
     _layer->setVisible(checked);
 }
 
-void ModelLayerControlWidget::onOverlayCheckStateChanged(int state)
+void ModelLayerControlWidget::onRemoveClicked(bool checked)
 {
-  bool checked = state == Qt::Checked;
-  if (_layer.valid() && _layer->getOverlay() != checked)
-    _layer->setOverlay(checked);
+  if (_parent && _parent->getMap())
+    _parent->getMap()->removeModelLayer(_layer);
 }
 
 void ModelLayerControlWidget::setLayerVisible(bool visible)
@@ -567,12 +602,6 @@ void ModelLayerControlWidget::setLayerVisible(bool visible)
     _visibleCheckBox->setCheckState(visible ? Qt::Checked : Qt::Unchecked);
 }
 
-void ModelLayerControlWidget::setLayerOverlay(bool overlay)
-{
-  if ((_overlayCheckBox->checkState() == Qt::Checked) != overlay)
-    _overlayCheckBox->setCheckState(overlay ? Qt::Checked : Qt::Unchecked);
-}
-
 
 osgEarth::UID ModelLayerControlWidget::getUID()
 {
diff --git a/src/osgEarthQt/MultiViewerWidget b/src/osgEarthQt/MultiViewerWidget
deleted file mode 100644
index 29164b3..0000000
--- a/src/osgEarthQt/MultiViewerWidget
+++ /dev/null
@@ -1,57 +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 OSGEARTHQT_COMPOSITEVIEWERWIDGET_H
-#define OSGEARTHQT_COMPOSITEVIEWERWIDGET_H 1
-
-#include <osgEarthQt/Common>
-
-#include <osgEarth/Map>
-
-#include <osgQt/GraphicsWindowQt>
-#include <osgViewer/CompositeViewer>
-
-#include <QtCore/QTimer>
-#include <QtGui/QWidget>
-
-namespace osgEarth { namespace QtGui 
-{
-    using namespace osgEarth;
-
-    /**
-     * A widget that uses a CompositeViewer and puts each View in its own panel of a layout 
-     */
-    class OSGEARTHQT_EXPORT MultiViewerWidget : public QWidget, public osgViewer::CompositeViewer
-    {
-    public:
-      MultiViewerWidget(osg::Node* scene=0L);
-      virtual ~MultiViewerWidget() { }
-
-      osgViewer::View* createViewWidget(osg::Node* scene=0L, osgViewer::View* shared=0L);
-      virtual void layoutWidgets();
-
-    protected:
-      QTimer _timer;
-
-      void initialize();
-      osg::Camera* createCamera(int x, int y, int width, int height, osg::GraphicsContext* shared=0L);
-      void paintEvent(QPaintEvent *);
-    };
-} }
-
-#endif // OSGEARTHQT_COMPOSITEVIEWERWIDGET_H
diff --git a/src/osgEarthQt/MultiViewerWidget.cpp b/src/osgEarthQt/MultiViewerWidget.cpp
deleted file mode 100644
index 49656ed..0000000
--- a/src/osgEarthQt/MultiViewerWidget.cpp
+++ /dev/null
@@ -1,159 +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 <osgEarthQt/MultiViewerWidget>
-
-#include <osgEarthUtil/EarthManipulator>
-
-#include <osgGA/StateSetManipulator>
-#include <osgQt/GraphicsWindowQt>
-#include <osgViewer/ViewerBase>
-#include <osgViewer/ViewerEventHandlers>
-
-#include <QtCore/QTimer>
-#include <QtGui/QGridLayout>
-#include <QtGui/QWidget>
-
-using namespace osgEarth;
-using namespace osgEarth::QtGui;
-
-
-MultiViewerWidget::MultiViewerWidget(osg::Node* scene)
-{
-  initialize();
-
-  connect(&_timer, SIGNAL(timeout()), this, SLOT(update()));
-  _timer.start(20);
-}
-
-void MultiViewerWidget::initialize()
-{
-  setThreadingModel(osgViewer::Viewer::SingleThreaded);
-}
-
-osgViewer::View* MultiViewerWidget::createViewWidget(osg::Node* scene, osgViewer::View* shared)
-{
-  osgViewer::View* view = new osgViewer::View();
-  view->setCamera(createCamera(0, 0, 100, 100, (shared ? shared->getCamera()->getGraphicsContext() : 0L)));
-  view->setCameraManipulator(new osgEarth::Util::EarthManipulator());
-
-  view->addEventHandler(new osgViewer::StatsHandler());
-  view->addEventHandler(new osgGA::StateSetManipulator());
-  view->addEventHandler(new osgViewer::ThreadingHandler());
-
-  if (scene)
-    view->setSceneData(scene);
-
-  addView(view);
-
-  layoutWidgets();
-
-  return view;
-}
-
-osg::Camera* MultiViewerWidget::createCamera(int x, int y, int width, int height, osg::GraphicsContext* shared)
-{
-  osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
-  osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds);
-
-  traits->readDISPLAY();
-  if (traits->displayNum<0) traits->displayNum = 0;
-
-  traits->windowName = "";
-  traits->windowDecoration = false;
-  traits->x = x;
-  traits->y = y;
-  traits->width = width;
-  traits->height = height;
-  traits->doubleBuffer = true;
-  traits->sharedContext = shared;
-  //traits->inheritedWindowData = inherited;
-  //traits->alpha = ds->getMinimumNumAlphaBits();
-  //traits->stencil = ds->getMinimumNumStencilBits();
-  //traits->sampleBuffers = ds->getMultiSamples();
-  //traits->samples = ds->getNumMultiSamples();
-
-  //if (ds->getStereo())
-  //{
-  //  switch(ds->getStereoMode())
-  //  {
-  //  case(osg::DisplaySettings::QUAD_BUFFER): traits->quadBufferStereo = true; break;
-  //  case(osg::DisplaySettings::VERTICAL_INTERLACE):
-  //  case(osg::DisplaySettings::CHECKERBOARD):
-  //  case(osg::DisplaySettings::HORIZONTAL_INTERLACE): traits->stencil = 8; break;
-  //  default: break;
-  //  }
-  //}
-  
-  osg::ref_ptr<osg::Camera> camera = new osg::Camera;
-  camera->setGraphicsContext( new osgQt::GraphicsWindowQt(traits.get()) );
-
-  //camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 1.0) );
-  camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
-  camera->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 1.0f, 10000.0f );
-  
-  return camera.release();
-}
-
-void MultiViewerWidget::layoutWidgets()
-{
-  QGridLayout* grid = new QGridLayout;
-
-  osgViewer::ViewerBase::Windows windows;
-  getWindows(windows);
-
-  int viewCount = 0;
-  for (osgViewer::ViewerBase::Windows::iterator it = windows.begin(); it != windows.end(); ++it)
-  {
-    osgQt::GraphicsWindowQt* gw = dynamic_cast<osgQt::GraphicsWindowQt*>(*it);
-    if (gw)
-      viewCount++;
-  }
-  
-  int cols = (int)(ceil(sqrt((float)viewCount)));
-  int col = 0, row = 0;
-
-  for (osgViewer::ViewerBase::Windows::iterator it = windows.begin(); it != windows.end(); ++it)
-  {
-    osgQt::GraphicsWindowQt* gw = dynamic_cast<osgQt::GraphicsWindowQt*>(*it);
-    if (gw)
-    {
-      grid->addWidget(gw->getGLWidget(), row, col, 1, row * cols + col + 1 == viewCount ? cols - col : 1);
-      col++;
-
-      if (col == cols)
-      {
-        row++;
-        col = 0;
-      }
-    }
-  }
-
-  delete layout();
-  setLayout( grid );
-}
-
-
-void MultiViewerWidget::paintEvent(QPaintEvent* e)
-{
-    if ( getRunFrameScheme() == CONTINUOUS || checkNeedToDoFrame() )
-    {
-        frame();
-    }
-}
-
diff --git a/src/osgEarthQt/images.qrc b/src/osgEarthQt/images.qrc
index 48f4425..2f37408 100644
--- a/src/osgEarthQt/images.qrc
+++ b/src/osgEarthQt/images.qrc
@@ -14,5 +14,6 @@
         <file>images/draw_line_bg.png</file>
         <file>images/draw_poly.png</file>
         <file>images/draw_poly_bg.png</file>
+        <file>images/close.png</file>
     </qresource>
 </RCC>
diff --git a/src/osgEarthQt/images/close.png b/src/osgEarthQt/images/close.png
new file mode 100644
index 0000000..c4bdc13
Binary files /dev/null and b/src/osgEarthQt/images/close.png differ
diff --git a/src/osgEarthSymbology/AltitudeSymbol.cpp b/src/osgEarthSymbology/AltitudeSymbol.cpp
index b8a303e..f4a299c 100644
--- a/src/osgEarthSymbology/AltitudeSymbol.cpp
+++ b/src/osgEarthSymbology/AltitudeSymbol.cpp
@@ -22,6 +22,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(altitude, AltitudeSymbol);
+
 AltitudeSymbol::AltitudeSymbol( const Config& conf ) :
 Symbol             ( conf ),
 _clamping          ( CLAMP_NONE ),
diff --git a/src/osgEarthSymbology/Expression b/src/osgEarthSymbology/Expression
index 581f5e7..decffaa 100644
--- a/src/osgEarthSymbology/Expression
+++ b/src/osgEarthSymbology/Expression
@@ -54,6 +54,9 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~NumericExpression() { }
 
+        /** Set the result to a literal value. */
+        void setLiteral( double staticValue );
+
         /** Access the expression variables. */
         const Variables& variables() const { return _vars; }
 
diff --git a/src/osgEarthSymbology/Expression.cpp b/src/osgEarthSymbology/Expression.cpp
index 97c2b0b..4fbff7b 100644
--- a/src/osgEarthSymbology/Expression.cpp
+++ b/src/osgEarthSymbology/Expression.cpp
@@ -58,6 +58,15 @@ NumericExpression::NumericExpression( const Config& conf )
 }
 
 void
+NumericExpression::setLiteral(double staticValue)
+{
+    _value = staticValue;
+    _dirty = false;
+    _src = Stringify() << staticValue;
+    init();
+}
+
+void
 NumericExpression::mergeConfig( const Config& conf )
 {
     _src = conf.value();
diff --git a/src/osgEarthSymbology/ExtrusionSymbol.cpp b/src/osgEarthSymbology/ExtrusionSymbol.cpp
index db23581..433860d 100644
--- a/src/osgEarthSymbology/ExtrusionSymbol.cpp
+++ b/src/osgEarthSymbology/ExtrusionSymbol.cpp
@@ -22,6 +22,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(extrusion, ExtrusionSymbol);
+
 ExtrusionSymbol::ExtrusionSymbol( const Config& conf ) :
 Symbol    ( conf ),
 _height   ( 10.0 ),
diff --git a/src/osgEarthSymbology/Geometry b/src/osgEarthSymbology/Geometry
index daf068f..552d5ae 100644
--- a/src/osgEarthSymbology/Geometry
+++ b/src/osgEarthSymbology/Geometry
@@ -58,7 +58,7 @@ namespace osgEarth { namespace Symbology
         Geometry( const Vec3dVector* toCopy );
 
         /** dtor - intentionally public */
-        virtual ~Geometry() { }
+        virtual ~Geometry();
 
     public:
         enum Type {
@@ -216,7 +216,7 @@ namespace osgEarth { namespace Symbology
         PointSet( const PointSet& rhs );
 
         /** dtor */
-        virtual ~PointSet() { }
+        virtual ~PointSet();
 
     public:
         virtual Type getType() const { return Geometry::TYPE_POINTSET; }
@@ -233,7 +233,7 @@ namespace osgEarth { namespace Symbology
         LineString( const Vec3dVector* toCopy );
 
         /** dtor */
-        virtual ~LineString() { }
+        virtual ~LineString();
 
         double getLength() const;
         bool getSegment(double length, osg::Vec3d& start, osg::Vec3d& end);
@@ -256,7 +256,7 @@ namespace osgEarth { namespace Symbology
         Ring( const Vec3dVector* toCopy );
 
         /** dtor */
-        virtual ~Ring() { }
+        virtual ~Ring();
 
         // override
         virtual Geometry* cloneAs( const Geometry::Type& newType ) const;
@@ -270,6 +270,9 @@ namespace osgEarth { namespace Symbology
         // ensures that the first and last points are not idential.
         virtual void open();
 
+        // ensures that the first and last points are identical.
+        virtual void close();
+
         // opens and winds the ring in the specified direction
         virtual void rewind( Orientation ori );
 
@@ -293,7 +296,7 @@ namespace osgEarth { namespace Symbology
         Polygon( const Vec3dVector* toCopy );
 
         /** dtor */
-        virtual ~Polygon() { }
+        virtual ~Polygon();
         
     public:
         virtual Type getType() const { return Geometry::TYPE_POLYGON; }
@@ -306,6 +309,8 @@ namespace osgEarth { namespace Symbology
 
         virtual void open();
 
+        virtual void close();
+
     public:
         RingCollection& getHoles() { return _holes; }
         const RingCollection& getHoles() const { return _holes; }
@@ -325,7 +330,7 @@ namespace osgEarth { namespace Symbology
         MultiGeometry( const MultiGeometry& rhs );
 
         /** dtor */
-        virtual ~MultiGeometry() { }
+        virtual ~MultiGeometry();
 
     public:
         virtual Type getType() const { return Geometry::TYPE_MULTI; }        
diff --git a/src/osgEarthSymbology/Geometry.cpp b/src/osgEarthSymbology/Geometry.cpp
index 1591e24..432047b 100644
--- a/src/osgEarthSymbology/Geometry.cpp
+++ b/src/osgEarthSymbology/Geometry.cpp
@@ -55,6 +55,10 @@ Geometry::Geometry( const Vec3dVector* data )
     insert( begin(), data->begin(), data->end() );
 }
 
+Geometry::~Geometry()
+{
+}
+
 int
 Geometry::getTotalPointCount() const
 {
@@ -418,6 +422,10 @@ Geometry( rhs )
     //nop
 }
 
+PointSet::~PointSet()
+{
+}
+
 //----------------------------------------------------------------------------
 
 LineString::LineString( const LineString& rhs ) :
@@ -432,6 +440,10 @@ Geometry( data )
     //nop
 }
 
+LineString::~LineString()
+{
+}
+
 double
 LineString::getLength() const
 {
@@ -478,6 +490,10 @@ Geometry( data )
     open();
 }
 
+Ring::~Ring()
+{
+}
+
 Geometry*
 Ring::cloneAs( const Geometry::Type& newType ) const
 {
@@ -499,6 +515,14 @@ Ring::open()
         erase( end()-1 );
 }
 
+// ensures that the first and last points are idential.
+void 
+Ring::close()
+{
+    if ( size() > 0 && front() != back() )
+        push_back( front() );
+}
+
 // gets the signed area.
 double
 Ring::getSignedArea2D() const
@@ -557,6 +581,10 @@ Ring( data )
     //nop
 }
 
+Polygon::~Polygon()
+{
+}
+
 int
 Polygon::getTotalPointCount() const
 {
@@ -592,6 +620,14 @@ Polygon::open()
         (*i)->open();
 }
 
+void
+Polygon::close() 
+{
+    Ring::close();
+    for( RingCollection::const_iterator i = _holes.begin(); i != _holes.end(); ++i )
+        (*i)->close();
+}
+
 //----------------------------------------------------------------------------
 
 MultiGeometry::MultiGeometry( const MultiGeometry& rhs ) :
@@ -607,6 +643,10 @@ _parts( parts )
     //nop
 }
 
+MultiGeometry::~MultiGeometry()
+{
+}
+
 Geometry::Type
 MultiGeometry::getComponentType() const
 {
diff --git a/src/osgEarthSymbology/GeometryRasterizer b/src/osgEarthSymbology/GeometryRasterizer
index ba69bf8..4455987 100644
--- a/src/osgEarthSymbology/GeometryRasterizer
+++ b/src/osgEarthSymbology/GeometryRasterizer
@@ -35,8 +35,10 @@ namespace osgEarth { namespace Symbology
     public:
         GeometryRasterizer( int width, int height, const Style& style =Style() );
 
+        GeometryRasterizer( osg::Image* image, const Style& style =Style() );
+
         /** dtor */
-        virtual ~GeometryRasterizer() { }
+        virtual ~GeometryRasterizer();
 
         /** draws the geometry to the image. */
         void draw( const Geometry* geom, const osg::Vec4f& color =osg::Vec4f(1,1,1,1) );
diff --git a/src/osgEarthSymbology/GeometryRasterizer.cpp b/src/osgEarthSymbology/GeometryRasterizer.cpp
index 0967e17..fc9aac6 100644
--- a/src/osgEarthSymbology/GeometryRasterizer.cpp
+++ b/src/osgEarthSymbology/GeometryRasterizer.cpp
@@ -57,6 +57,17 @@ _style( style )
     _state = new AggState( _image.get() );
 }
 
+GeometryRasterizer::GeometryRasterizer( osg::Image* image, const Style& style ) :
+_image( image ),
+_style( style )
+{
+    _state = new AggState( _image.get() );
+}
+
+GeometryRasterizer::~GeometryRasterizer()
+{
+}
+
 osg::Image*
 GeometryRasterizer::finalize()
 {
@@ -82,11 +93,9 @@ GeometryRasterizer::draw( const Geometry* geom, const osg::Vec4f& c )
     osg::Vec4f color = c;
     osg::ref_ptr<const Geometry> geomToRender = geom;
 
-    if ( geom->getType() == Geometry::TYPE_POLYGON )
+    if ( _style.has<PolygonSymbol>() )
     {
-        const PolygonSymbol* ps = _style.getSymbol<const PolygonSymbol>();
-        if ( ps )
-            color = ps->fill()->color();
+        color = _style.get<const PolygonSymbol>()->fill()->color();
     }
     else
     {
diff --git a/src/osgEarthSymbology/IconResource b/src/osgEarthSymbology/IconResource
index 516fe14..7882671 100644
--- a/src/osgEarthSymbology/IconResource
+++ b/src/osgEarthSymbology/IconResource
@@ -42,6 +42,8 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~IconResource() { }
 
+        virtual bool is2D() const { return true; }
+
     public: // serialization methods
 
         virtual Config getConfig() const;
diff --git a/src/osgEarthSymbology/IconResource.cpp b/src/osgEarthSymbology/IconResource.cpp
index e1c1226..1c172a8 100644
--- a/src/osgEarthSymbology/IconResource.cpp
+++ b/src/osgEarthSymbology/IconResource.cpp
@@ -19,6 +19,8 @@
 #include <osgEarthSymbology/IconResource>
 #include <osgEarth/StringUtils>
 #include <osgEarth/ImageUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 
 #include <osg/AutoTransform>
 #include <osg/Depth>
@@ -37,6 +39,9 @@ namespace
 {
     osg::Node* buildIconModel(osg::Image* image)
     {
+        // because the ShaderGenerator cannot handle texture rectangles yet.
+        bool useRect = !Registry::capabilities().supportsGLSL();
+
         float width = image->s();
         float height = image->t();
 
@@ -45,18 +50,28 @@ namespace
 
         osg::Vec3Array* verts = new osg::Vec3Array(4);
         (*verts)[0] = osg::Vec3(-width/2.0f, -height/2.0, 0.0f);
-        (*verts)[1] = osg::Vec3(width/2.0f, -height/2.0, 0.0f);
-        (*verts)[2] = osg::Vec3(width/2.0f, height/2.0, 0.0f);
-        (*verts)[3] = osg::Vec3(-width/2.0f,height/2.0, 0.0f);
+        (*verts)[1] = osg::Vec3( width/2.0f, -height/2.0, 0.0f);
+        (*verts)[2] = osg::Vec3( width/2.0f,  height/2.0, 0.0f);
+        (*verts)[3] = osg::Vec3(-width/2.0f,  height/2.0, 0.0f);
         geometry->setVertexArray( verts );
 
         bool flip = image->getOrigin()==osg::Image::TOP_LEFT;
 
         osg::Vec2Array* texcoords = new osg::Vec2Array(4);
-        (*texcoords)[0].set(0.0f,flip ? height-1.0f : 0.0f);
-        (*texcoords)[1].set(width-1.0f,flip ? height-1.0f : 0.0f);
-        (*texcoords)[2].set(width-1.0f,flip ? 0.0 : height-1.0f);
-        (*texcoords)[3].set(0.0f,flip ? 0.0 : height-1.0f);
+        if ( useRect )
+        {
+            (*texcoords)[0].set(0.0f,      flip ? height-1.0f : 0.0f);
+            (*texcoords)[1].set(width-1.0f,flip ? height-1.0f : 0.0f);
+            (*texcoords)[2].set(width-1.0f,flip ? 0.0         : height-1.0f);
+            (*texcoords)[3].set(0.0f,      flip ? 0.0         : height-1.0f);
+        }
+        else
+        {
+            (*texcoords)[0].set(0.0f, flip ? 1.0f : 0.0f);
+            (*texcoords)[1].set(1.0f, flip ? 1.0f : 0.0f);
+            (*texcoords)[2].set(1.0f, flip ? 0.0  : 1.0f);
+            (*texcoords)[3].set(0.0f, flip ? 0.0  : 1.0f);
+        }
         geometry->setTexCoordArray(0, texcoords);
 
         osg::Vec4Array* colors = new osg::Vec4Array(1);
@@ -68,21 +83,39 @@ namespace
 
         osg::StateSet* stateSet = geometry->getOrCreateStateSet();
 
-        osg::TextureRectangle* texture = new osg::TextureRectangle( image );
+        osg::Texture* texture;
+
+        if ( useRect )
+        {
+            texture = new osg::TextureRectangle( image );
+        }
+        else
+        {
+            texture = new osg::Texture2D( image );
+        }
+
+        texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST);
+        texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
+        texture->setWrap  (osg::Texture::WRAP_S,     osg::Texture::CLAMP_TO_EDGE );
+        texture->setWrap  (osg::Texture::WRAP_T,     osg::Texture::CLAMP_TO_EDGE );
+        if ( Registry::capabilities().supportsNonPowerOfTwoTextures() )
+            texture->setResizeNonPowerOfTwoHint( false );
+
         stateSet->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
 
         stateSet->setMode( GL_BLEND, 1 );
-        stateSet->setRenderBinDetails( 95, "RenderBin" );
+        stateSet->setRenderBinDetails( 95, "DepthSortedBin" );
         stateSet->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS,false), 1 );
 
         osg::Geode* geode = new osg::Geode;
         geode->addDrawable( geometry );
 
-        osg::AutoTransform* at = new osg::AutoTransform;
-        at->setAutoScaleToScreen( true );
-        at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
-        at->addChild( geode );
-        return at;
+        return geode;
+        //osg::AutoTransform* at = new osg::AutoTransform;
+        //at->setAutoScaleToScreen( true );
+        //at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
+        //at->addChild( geode );
+        //return at;
     }
 }
 
@@ -103,7 +136,7 @@ IconResource::mergeConfig( const Config& conf )
 Config
 IconResource::getConfig() const
 {
-    Config conf = Resource::getConfig();
+    Config conf = InstanceResource::getConfig();
     conf.key() = "icon";
     //nop
     return conf;
diff --git a/src/osgEarthSymbology/IconSymbol b/src/osgEarthSymbology/IconSymbol
index b58cf18..03058fc 100644
--- a/src/osgEarthSymbology/IconSymbol
+++ b/src/osgEarthSymbology/IconSymbol
@@ -68,6 +68,14 @@ namespace osgEarth { namespace Symbology
         /** Decluttering */
         optional<bool>& declutter() { return _declutter; }
         const optional<bool>& declutter() const { return _declutter; }
+
+        /** Whether to enable occlusion culling on the icon, if applicable */
+        optional<bool>& occlusionCull() { return _occlusionCull; }
+        const optional<bool>& occlusionCull() const { return _occlusionCull; }
+
+        /** The camera altitude at which to start occlusion culling the icon */
+        optional<double>& occlusionCullAltitude() { return _occlusionCullAltitude; }
+        const optional<double>& occlusionCullAltitude() const { return _occlusionCullAltitude; }
         
     public: // non-serialized properties (for programmatic use only)
 
@@ -88,6 +96,8 @@ namespace osgEarth { namespace Symbology
         optional<NumericExpression>      _heading;
         optional<bool>                   _declutter;
         mutable osg::ref_ptr<osg::Image> _image;
+        optional<bool>                   _occlusionCull;
+        optional<double>                 _occlusionCullAltitude;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/IconSymbol.cpp b/src/osgEarthSymbology/IconSymbol.cpp
index e31f9d4..a0299b7 100644
--- a/src/osgEarthSymbology/IconSymbol.cpp
+++ b/src/osgEarthSymbology/IconSymbol.cpp
@@ -27,11 +27,15 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(icon, IconSymbol);
+
 IconSymbol::IconSymbol( const Config& conf ) :
-InstanceSymbol( conf ),
-_alignment    ( ALIGN_CENTER_BOTTOM ),
-_heading      ( NumericExpression(0.0) ),
-_declutter    ( false )
+InstanceSymbol        ( conf ),
+_alignment            ( ALIGN_CENTER_BOTTOM ),
+_heading              ( NumericExpression(0.0) ),
+_declutter            ( false ),
+_occlusionCull        ( false ),
+_occlusionCullAltitude( 200000 )
 {
     mergeConfig( conf );
 }
@@ -53,7 +57,9 @@ IconSymbol::getConfig() const
     conf.addIfSet( "alignment", "right_bottom",  _alignment, ALIGN_RIGHT_BOTTOM );
 
     conf.addObjIfSet( "heading",   _heading );
-    conf.addIfSet   ( "declutter", _declutter );
+    conf.addIfSet   ( "declutter", _declutter );	                  
+	conf.addIfSet   ( "icon-occlusion-cull", _occlusionCull );
+    conf.addIfSet   ( "icon-occlusion-cull-altitude", _occlusionCullAltitude );
 
     conf.addNonSerializable( "IconSymbol::image", _image.get() );
     return conf;
@@ -74,6 +80,8 @@ IconSymbol::mergeConfig( const Config& conf )
 
     conf.getObjIfSet( "heading",   _heading );
     conf.getIfSet   ( "declutter", _declutter );
+	conf.getIfSet   ( "icon-occlusion-cull", _occlusionCull );
+    conf.getIfSet   ( "icon-occlusion-cull-altitude", _occlusionCullAltitude );
 
     _image = conf.getNonSerializable<osg::Image>( "IconSymbol::image" );
 }
@@ -128,6 +136,8 @@ IconSymbol::createResource() const
 void
 IconSymbol::parseSLD(const Config& c, Style& style)
 {
+    IconSymbol defaults;
+
     if ( match(c.key(), "icon") ) {
         style.getOrCreate<IconSymbol>()->url() = c.value();
         style.getOrCreate<IconSymbol>()->url()->setURIContext( c.referrer() );
@@ -146,10 +156,10 @@ IconSymbol::parseSLD(const Config& c, Style& style)
             style.getOrCreate<IconSymbol>()->placement() = ModelSymbol::PLACEMENT_CENTROID;
     }
     else if ( match(c.key(), "icon-density") ) {
-        style.getOrCreate<IconSymbol>()->density() = as<float>(c.value(), 1.0f);
+        style.getOrCreate<IconSymbol>()->density() = as<float>(c.value(), *defaults.density() );
     }
     else if ( match(c.key(), "icon-random-seed") ) {
-        style.getOrCreate<IconSymbol>()->randomSeed() = as<unsigned>(c.value(), 0);
+        style.getOrCreate<IconSymbol>()->randomSeed() = as<unsigned>(c.value(), *defaults.randomSeed());
     }
     else if ( match(c.key(), "icon-scale") ) {
         style.getOrCreate<IconSymbol>()->scale() = NumericExpression(c.value());
@@ -178,6 +188,12 @@ IconSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<IconSymbol>()->heading() = NumericExpression(c.value());
     }
     else if ( match(c.key(), "icon-declutter") ) {
-        style.getOrCreate<IconSymbol>()->declutter() = as<bool>(c.value(), false);
+        style.getOrCreate<IconSymbol>()->declutter() = as<bool>(c.value(), *defaults.declutter());
+    }
+    else if ( match(c.key(), "icon-occlusion-cull") ) {
+        style.getOrCreate<IconSymbol>()->occlusionCull() = as<bool>(c.value(), *defaults.occlusionCull());
+    }
+    else if ( match(c.key(), "icon-occlusion-cull-altitude") ) {
+        style.getOrCreate<IconSymbol>()->occlusionCullAltitude() = as<float>(c.value(), *defaults.occlusionCullAltitude());
     }
 }
diff --git a/src/osgEarthSymbology/InstanceResource b/src/osgEarthSymbology/InstanceResource
index 97c226d..1f0c997 100644
--- a/src/osgEarthSymbology/InstanceResource
+++ b/src/osgEarthSymbology/InstanceResource
@@ -47,6 +47,9 @@ namespace osgEarth { namespace Symbology
          */
         osg::Node* createNode( const osgDB::Options* dbOptions ) const;
 
+        /** Whether this instance type is 2D (orthographic screen space) */
+        virtual bool is2D() const =0;
+
     public:
         /** Source location of the actual data to load.  */
         optional<URI>& uri() { return _uri; }
diff --git a/src/osgEarthSymbology/InstanceResource.cpp b/src/osgEarthSymbology/InstanceResource.cpp
index 2cca5fb..f65343e 100644
--- a/src/osgEarthSymbology/InstanceResource.cpp
+++ b/src/osgEarthSymbology/InstanceResource.cpp
@@ -19,7 +19,6 @@
 #include <osgEarthSymbology/InstanceResource>
 #include <osgEarth/StringUtils>
 #include <osgEarth/ImageUtils>
-#include <osgEarth/ShaderGenerator>
 
 #include <osg/AutoTransform>
 #include <osg/Depth>
@@ -60,20 +59,5 @@ InstanceResource::getConfig() const
 osg::Node*
 InstanceResource::createNode( const osgDB::Options* dbOptions ) const
 {
-    osg::Node* node = createNodeFromURI( _uri.value(), dbOptions );
-
-    // for now, disable any shaders on an instance resource until we can install a 
-    // shader generator
-    if ( node )
-    {
-        OE_DEBUG << LC << "Instance model does NOT have shaders disabled; use shadergen" << std::endl;
-        //node->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF );
-
-        //ShaderGenerator gen;
-        //node->accept( gen );
-
-        //Note. ShaderGen usually runs elsewhere where it can take advantage of a stateset optimizer.
-    }
-
-    return node;
+    return createNodeFromURI( _uri.value(), dbOptions );
 }
diff --git a/src/osgEarthSymbology/InstanceSymbol.cpp b/src/osgEarthSymbology/InstanceSymbol.cpp
index ef21cc0..085468b 100644
--- a/src/osgEarthSymbology/InstanceSymbol.cpp
+++ b/src/osgEarthSymbology/InstanceSymbol.cpp
@@ -27,7 +27,8 @@ InstanceSymbol::InstanceSymbol( const Config& conf ) :
 Symbol     ( conf ),
 _placement ( PLACEMENT_CENTROID ),
 _density   ( 25.0f ),
-_randomSeed( 0 )
+_randomSeed( 0 ),
+_scale     ( NumericExpression(1.0) )
 {
     mergeConfig( conf );
 }
diff --git a/src/osgEarthSymbology/LineSymbol.cpp b/src/osgEarthSymbology/LineSymbol.cpp
index 785057b..51eeba6 100644
--- a/src/osgEarthSymbology/LineSymbol.cpp
+++ b/src/osgEarthSymbology/LineSymbol.cpp
@@ -22,6 +22,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(line, LineSymbol);
+
 LineSymbol::LineSymbol( const Config& conf ) :
 Symbol       ( conf ),
 _stroke      ( Stroke() ),
@@ -86,4 +88,11 @@ LineSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "stroke-min-pixels") ) {
         style.getOrCreate<LineSymbol>()->stroke()->minPixels() = as<float>(c.value(), 0.0f);
     }
+    else if ( match(c.key(), "stroke-stipple-factor") ) {
+        style.getOrCreate<LineSymbol>()->stroke()->stippleFactor() = as<unsigned>(c.value(), 1);
+    }
+    else if ( match(c.key(), "stroke-stipple-pattern") ||
+              match(c.key(), "stroke-stipple") ) {
+        style.getOrCreate<LineSymbol>()->stroke()->stipplePattern() = as<unsigned short>(c.value(), 0xFFFF);
+    }
 }
diff --git a/src/osgEarthSymbology/MarkerResource.cpp b/src/osgEarthSymbology/MarkerResource.cpp
index 90f17c1..171a9b5 100644
--- a/src/osgEarthSymbology/MarkerResource.cpp
+++ b/src/osgEarthSymbology/MarkerResource.cpp
@@ -107,9 +107,6 @@ MarkerResource::getConfig() const
 {
     Config conf = Resource::getConfig();
     conf.key() = "marker";
-
-    conf.updateIfSet( "url", _markerURI );
-
     return conf;
 }
 
diff --git a/src/osgEarthSymbology/MarkerSymbol.cpp b/src/osgEarthSymbology/MarkerSymbol.cpp
index 28a5bf7..08a99f1 100644
--- a/src/osgEarthSymbology/MarkerSymbol.cpp
+++ b/src/osgEarthSymbology/MarkerSymbol.cpp
@@ -31,6 +31,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(marker, MarkerSymbol);
+
 MarkerSymbol::MarkerSymbol( const Config& conf ) :
 Symbol     ( conf ),
 _placement ( PLACEMENT_CENTROID ),
diff --git a/src/osgEarthSymbology/MeshConsolidator b/src/osgEarthSymbology/MeshConsolidator
index 01c551b..277041a 100644
--- a/src/osgEarthSymbology/MeshConsolidator
+++ b/src/osgEarthSymbology/MeshConsolidator
@@ -48,7 +48,7 @@ namespace osgEarth { namespace Symbology
          * into GL_TRIANGLES. Similar to the IndexMeshVisitor, except that this
          * retians user data pointers on the primsets.
          */
-        static void convertToTriangles( osg::Geometry& geom );
+        static void convertToTriangles( osg::Geometry& geom, bool force=false );
 
         /**
          * Consolidates compatible geometries in the geode. First runs the 
diff --git a/src/osgEarthSymbology/MeshConsolidator.cpp b/src/osgEarthSymbology/MeshConsolidator.cpp
index 6398db8..8c7e2aa 100644
--- a/src/osgEarthSymbology/MeshConsolidator.cpp
+++ b/src/osgEarthSymbology/MeshConsolidator.cpp
@@ -221,9 +221,9 @@ namespace
 //------------------------------------------------------------------------
 
 void
-MeshConsolidator::convertToTriangles( osg::Geometry& geom )
+MeshConsolidator::convertToTriangles( osg::Geometry& geom, bool force )
 {
-    if ( !canOptimize(geom) )
+    if ( !force && !canOptimize(geom) )
         return;
 
     osg::Geometry::PrimitiveSetList& primSets = geom.getPrimitiveSetList();
diff --git a/src/osgEarthSymbology/MeshSubdivider.cpp b/src/osgEarthSymbology/MeshSubdivider.cpp
index 83508c3..351c993 100644
--- a/src/osgEarthSymbology/MeshSubdivider.cpp
+++ b/src/osgEarthSymbology/MeshSubdivider.cpp
@@ -21,7 +21,6 @@
 #include <osgEarth/GeoMath>
 #include <osg/TriangleFunctor>
 #include <osg/TriangleIndexFunctor>
-//#include <osgUtil/MeshOptimizers>
 #include <climits>
 #include <queue>
 #include <map>
@@ -63,8 +62,6 @@ namespace
             out_mid.set( 0.5*((g0.x()+2*osg::PI)+g1.x()), 0.5*(g0.y()+g1.y()) );
         else
            out_mid.set( 0.5*(g0.x()+(g1.x()+2*osg::PI)), 0.5*(g0.y()+g1.y()) );
-
-        //GeoMath::midpoint(g0.y(), g0.x(), g1.y(), g1.x(), out_mid.y(), out_mid.x());
     }
 
     // finds the midpoint between two geocentric coordinates. We have to convert
@@ -136,7 +133,7 @@ namespace
         osg::Vec4Array* _sourceColors;
         osg::Vec2Array* _sourceTexCoords;
         osg::Vec3Array* _sourceNormals;
-        osg::ref_ptr<osg::Vec3Array> _verts;        
+        osg::ref_ptr<osg::Vec3Array> _verts;
         osg::ref_ptr<osg::Vec4Array> _colors;
         osg::ref_ptr<osg::Vec2Array> _texcoords;
         osg::ref_ptr<osg::Vec3Array> _normals;
@@ -794,5 +791,9 @@ MeshSubdivider::run(osg::Geometry& geom, double granularity, GeoInterpolation in
     if ( geom.getNumPrimitiveSets() < 1 )
         return;
 
+    // unsupported for now. NYI.
+    if ( geom.getVertexAttribArrayList().size() > 0 )
+        return;
+
     subdivide( granularity, interp, geom, _world2local, _local2world, _maxElementsPerEBO );
 }
diff --git a/src/osgEarthSymbology/ModelResource b/src/osgEarthSymbology/ModelResource
index 63cbc16..f2b9621 100644
--- a/src/osgEarthSymbology/ModelResource
+++ b/src/osgEarthSymbology/ModelResource
@@ -41,6 +41,8 @@ namespace osgEarth { namespace Symbology
         /** dtor */
         virtual ~ModelResource() { }
 
+        virtual bool is2D() const { return false; }
+
     public: // serialization methods
 
         virtual Config getConfig() const;
diff --git a/src/osgEarthSymbology/ModelResource.cpp b/src/osgEarthSymbology/ModelResource.cpp
index eced65c..cc65e5f 100644
--- a/src/osgEarthSymbology/ModelResource.cpp
+++ b/src/osgEarthSymbology/ModelResource.cpp
@@ -18,6 +18,8 @@
  */
 #include <osgEarthSymbology/ModelResource>
 #include <osgEarth/StringUtils>
+#include <osgUtil/Optimizer>
+#include <osgEarth/Utils>
 
 #define LC "[ModelResource] "
 
@@ -56,6 +58,25 @@ ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOption
     if ( r.succeeded() )
     {
         node = r.releaseNode();
+
+        OE_DEBUG << LC << "Loading " << uri.full() << std::endl;
+
+#if 1
+        osgUtil::Optimizer o;
+        o.optimize( node,
+            o.DEFAULT_OPTIMIZATIONS |
+            o.INDEX_MESH |
+            o.VERTEX_PRETRANSFORM |
+            o.VERTEX_POSTTRANSFORM );
+            
+#else
+        osgUtil::Optimizer o;
+        o.optimize( node, osgUtil::Optimizer::INDEX_MESH );
+
+        // GPU performance optimization:
+        VertexCacheOptimizer vco;
+        node->accept( vco );
+#endif
     }
     else // failing that, fall back on the old encoding format..
     {
diff --git a/src/osgEarthSymbology/ModelSymbol b/src/osgEarthSymbology/ModelSymbol
index 8207d86..4afc6cb 100644
--- a/src/osgEarthSymbology/ModelSymbol
+++ b/src/osgEarthSymbology/ModelSymbol
@@ -54,6 +54,10 @@ namespace osgEarth { namespace Symbology
         /** roll in degrees */
         optional<NumericExpression>& roll() { return _roll; }
         const optional<NumericExpression>& roll() const { return _roll; }
+
+        /** whether to automatically scale the model from meters to pixels */
+        optional<bool>& autoScale() { return _autoScale; }
+        const optional<bool>& autoScale() const { return _autoScale; }
         
     public: // non-serialized properties (for programmatic use only)
 
@@ -74,6 +78,7 @@ namespace osgEarth { namespace Symbology
         optional<NumericExpression>  _heading;
         optional<NumericExpression>  _pitch;
         optional<NumericExpression>  _roll;
+        optional<bool>               _autoScale;
         osg::ref_ptr<osg::Node>      _node;
     };
 
diff --git a/src/osgEarthSymbology/ModelSymbol.cpp b/src/osgEarthSymbology/ModelSymbol.cpp
index 73cf271..43b4cbc 100644
--- a/src/osgEarthSymbology/ModelSymbol.cpp
+++ b/src/osgEarthSymbology/ModelSymbol.cpp
@@ -23,11 +23,14 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(model, ModelSymbol);
+
 ModelSymbol::ModelSymbol( const Config& conf ) :
 InstanceSymbol( conf ),
-_heading( NumericExpression(0.0) ),
-_pitch  ( NumericExpression(0.0) ),
-_roll   ( NumericExpression(0.0) )
+_heading  ( NumericExpression(0.0) ),
+_pitch    ( NumericExpression(0.0) ),
+_roll     ( NumericExpression(0.0) ),
+_autoScale( false )
 {
     mergeConfig( conf );
 }
@@ -37,11 +40,12 @@ ModelSymbol::getConfig() const
 {
     Config conf = InstanceSymbol::getConfig();
     conf.key() = "model";
-    conf.addObjIfSet( "heading",   _heading );
-    conf.addObjIfSet( "pitch",     _pitch );
-    conf.addObjIfSet( "roll",      _roll );
-
-    conf.addIfSet   ( "alias_map", _uriAliasMap );
+    conf.addObjIfSet( "heading",    _heading );
+    conf.addObjIfSet( "pitch",      _pitch );
+    conf.addObjIfSet( "roll",       _roll );
+    
+    conf.addIfSet( "auto_scale", _autoScale );
+    conf.addIfSet( "alias_map", _uriAliasMap );
 
     conf.addNonSerializable( "ModelSymbol::node", _node.get() );
     return conf;
@@ -54,7 +58,8 @@ ModelSymbol::mergeConfig( const Config& conf )
     conf.getObjIfSet( "pitch",   _pitch );
     conf.getObjIfSet( "roll",    _roll );
 
-    conf.getIfSet   ( "alias_map", _uriAliasMap );
+    conf.getIfSet( "auto_scale", _autoScale );
+    conf.getIfSet( "alias_map", _uriAliasMap );
 
     _node = conf.getNonSerializable<osg::Node>( "ModelSymbol::node" );
 }
@@ -89,7 +94,10 @@ ModelSymbol::parseSLD(const Config& c, Style& style)
         style.getOrCreate<ModelSymbol>()->randomSeed() = as<unsigned>(c.value(), 0);
     }
     else if ( match(c.key(), "model-scale") ) {
-        style.getOrCreate<ModelSymbol>()->scale() = NumericExpression(c.value());
+        if ( match(c.value(), "auto") )
+            style.getOrCreate<ModelSymbol>()->autoScale() = true;
+        else
+            style.getOrCreate<ModelSymbol>()->scale() = NumericExpression(c.value());
     }
     else if ( match(c.key(), "model-heading") ) {
         style.getOrCreate<ModelSymbol>()->heading() = NumericExpression(c.value());
diff --git a/src/osgEarthSymbology/PointSymbol.cpp b/src/osgEarthSymbology/PointSymbol.cpp
index 3f6ae54..17b0d0b 100644
--- a/src/osgEarthSymbology/PointSymbol.cpp
+++ b/src/osgEarthSymbology/PointSymbol.cpp
@@ -22,6 +22,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(point, PointSymbol);
+
 PointSymbol::PointSymbol( const Config& conf ) :
 Symbol( conf ),
 _fill ( Fill() ), 
@@ -51,10 +53,10 @@ PointSymbol::mergeConfig( const Config& conf )
 void
 PointSymbol::parseSLD(const Config& c, Style& style)
 {
-    if ( match(c.key(), "fill") ) {
+    if ( match(c.key(), "point-fill") ) {
         style.getOrCreate<PointSymbol>()->fill()->color() = Color(c.value());
     }
-    else if ( match(c.key(), "fill-opacity") ) {
+    else if ( match(c.key(), "point-fill-opacity") ) {
         style.getOrCreate<PointSymbol>()->fill()->color().a() = as<float>( c.value(), 1.0f );
     }
     else if ( match(c.key(), "point-size") ) {
diff --git a/src/osgEarthSymbology/PolygonSymbol.cpp b/src/osgEarthSymbology/PolygonSymbol.cpp
index 76fc43c..0adf402 100644
--- a/src/osgEarthSymbology/PolygonSymbol.cpp
+++ b/src/osgEarthSymbology/PolygonSymbol.cpp
@@ -22,6 +22,8 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(polygon, PolygonSymbol);
+
 PolygonSymbol::PolygonSymbol( const Config& conf ) :
 Symbol( conf ),
 _fill ( Fill() )
diff --git a/src/osgEarthSymbology/RenderSymbol b/src/osgEarthSymbology/RenderSymbol
index cb10f14..df289d5 100644
--- a/src/osgEarthSymbology/RenderSymbol
+++ b/src/osgEarthSymbology/RenderSymbol
@@ -53,6 +53,10 @@ namespace osgEarth { namespace Symbology
         optional<DepthOffsetOptions>& depthOffset() { return _depthOffset; }
         const optional<DepthOffsetOptions>& depthOffset() const { return _depthOffset; }
 
+        /** whether to force backface culling on or off */
+        optional<bool>& backfaceCulling() { return _backfaceCulling; }
+        const optional<bool>& backfaceCulling() const { return _backfaceCulling; }
+
     public:
         virtual Config getConfig() const;
         virtual void mergeConfig( const Config& conf );
@@ -62,6 +66,7 @@ namespace osgEarth { namespace Symbology
         optional<bool>               _depthTest;
         optional<bool>               _lighting;
         optional<DepthOffsetOptions> _depthOffset;
+        optional<bool>               _backfaceCulling;
         
         /** dtor */
         virtual ~RenderSymbol() { }
diff --git a/src/osgEarthSymbology/RenderSymbol.cpp b/src/osgEarthSymbology/RenderSymbol.cpp
index 4b52f53..a7c6d71 100644
--- a/src/osgEarthSymbology/RenderSymbol.cpp
+++ b/src/osgEarthSymbology/RenderSymbol.cpp
@@ -22,10 +22,13 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(render, RenderSymbol);
+
 RenderSymbol::RenderSymbol(const Config& conf) :
-Symbol    ( conf ),
-_depthTest( true ),
-_lighting ( true )
+Symbol          ( conf ),
+_depthTest      ( true ),
+_lighting       ( true ),
+_backfaceCulling( true )
 {
     mergeConfig(conf);
 }
@@ -35,42 +38,53 @@ RenderSymbol::getConfig() const
 {
     Config conf = Symbol::getConfig();
     conf.key() = "render";
-    conf.addIfSet   ( "depth_test",   _depthTest );
-    conf.addIfSet   ( "lighting",     _lighting );
-    conf.addObjIfSet( "depth_offset", _depthOffset );
+    conf.addIfSet   ( "depth_test",       _depthTest );
+    conf.addIfSet   ( "lighting",         _lighting );
+    conf.addObjIfSet( "depth_offset",     _depthOffset );
+    conf.addIfSet   ( "backface_culling", _backfaceCulling );
     return conf;
 }
 
 void 
 RenderSymbol::mergeConfig( const Config& conf )
 {
-    conf.getIfSet   ( "depth_test",   _depthTest );
-    conf.getIfSet   ( "lighting",     _lighting );
-    conf.getObjIfSet( "depth_offset", _depthOffset );
+    conf.getIfSet   ( "depth_test",       _depthTest );
+    conf.getIfSet   ( "lighting",         _lighting );
+    conf.getObjIfSet( "depth_offset",     _depthOffset );
+    conf.getIfSet   ( "backface_culling", _backfaceCulling );
 }
 
 void
 RenderSymbol::parseSLD(const Config& c, Style& style)
 {
+    RenderSymbol defaults;
+
     if ( match(c.key(), "render-depth-test") ) {
-        style.getOrCreate<RenderSymbol>()->depthTest() = as<bool>(c.value(), true);
+        style.getOrCreate<RenderSymbol>()->depthTest() = as<bool>(c.value(), *defaults.depthTest());
     }
     else if ( match(c.key(), "render-lighting") ) {
-        style.getOrCreate<RenderSymbol>()->lighting() = as<bool>(c.value(), false);
+        style.getOrCreate<RenderSymbol>()->lighting() = as<bool>(c.value(), *defaults.lighting());
     }
     else if ( match(c.key(), "render-depth-offset") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->enabled() = as<bool>(c.value(), false);
+        style.getOrCreate<RenderSymbol>()->depthOffset()->enabled() = as<bool>(c.value(), *defaults.depthOffset()->enabled() );
     }
     else if ( match(c.key(), "render-depth-offset-min-bias") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->minBias() = as<float>(c.value(), 100.0f);
+        style.getOrCreate<RenderSymbol>()->depthOffset()->minBias() = as<float>(c.value(), *defaults.depthOffset()->minBias() );
+        style.getOrCreate<RenderSymbol>()->depthOffset()->automatic() = false;
     }
     else if ( match(c.key(), "render-depth-offset-max-bias") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->maxBias() = as<float>(c.value(), 10000.0f);
+        style.getOrCreate<RenderSymbol>()->depthOffset()->maxBias() = as<float>(c.value(), *defaults.depthOffset()->maxBias() );
     }
     else if ( match(c.key(), "render-depth-offset-min-range") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->minRange() = as<float>(c.value(), 1000.0f);
+        style.getOrCreate<RenderSymbol>()->depthOffset()->minRange() = as<float>(c.value(), *defaults.depthOffset()->minRange() );
     }
     else if ( match(c.key(), "render-depth-offset-max-range") ) {
-        style.getOrCreate<RenderSymbol>()->depthOffset()->maxRange() = as<float>(c.value(), 10000000.0f);
+        style.getOrCreate<RenderSymbol>()->depthOffset()->maxRange() = as<float>(c.value(), *defaults.depthOffset()->maxRange() );
+    }
+    else if ( match(c.key(), "render-depth-offset-auto") ) {
+        style.getOrCreate<RenderSymbol>()->depthOffset()->automatic() = as<bool>(c.value(), *defaults.depthOffset()->automatic() );
+    }
+    else if ( match(c.key(), "render-backface-culling") ) {
+        style.getOrCreate<RenderSymbol>()->backfaceCulling() = as<bool>(c.value(), *defaults.backfaceCulling() );
     }
 }
diff --git a/src/osgEarthSymbology/ResourceCache b/src/osgEarthSymbology/ResourceCache
index 2fc9f68..8393792 100644
--- a/src/osgEarthSymbology/ResourceCache
+++ b/src/osgEarthSymbology/ResourceCache
@@ -56,7 +56,7 @@ namespace osgEarth { namespace Symbology
         /**
          * Fetches the StateSet implementation corresponding to a Skin.
          */
-        osg::StateSet* getStateSet( SkinResource* skin );
+        bool getStateSet( SkinResource* skin, osg::ref_ptr<osg::StateSet>& output );
 
         /**
          * Get the statistics collected from the skin cache.
@@ -67,26 +67,28 @@ namespace osgEarth { namespace Symbology
          * Gets a node corresponding to a marker.
          * @deprecated
          */
-        osg::Node* getMarkerNode( MarkerResource* marker );
+        bool getMarkerNode( MarkerResource* marker, osg::ref_ptr<osg::Node>& output );
 
         /**
          * Gets a node corresponding to an instance resource.
          */
-        osg::Node* getInstanceNode( InstanceResource* instance );
+        bool getInstanceNode( InstanceResource* instance, osg::ref_ptr<osg::Node>& output );
 
     protected:
         osg::ref_ptr<const osgDB::Options> _dbOptions;
         bool                               _threadSafe;
-        Threading::ReadWriteMutex          _mutex;
 
-        typedef LRUCache<SkinResource*, osg::ref_ptr<osg::StateSet> > SkinCache;
+        typedef LRUCache<std::string, osg::ref_ptr<osg::StateSet> > SkinCache;
         SkinCache _skinCache;
+        Threading::ReadWriteMutex _skinMutex;
 
-        typedef LRUCache<MarkerResource*, osg::ref_ptr<osg::Node> > MarkerCache;
+        typedef LRUCache<std::string, osg::ref_ptr<osg::Node> > MarkerCache;
         MarkerCache _markerCache;
+        Threading::ReadWriteMutex _markerMutex;
 
-        typedef LRUCache<InstanceResource*, osg::ref_ptr<osg::Node> > InstanceCache;
+        typedef LRUCache<std::string, osg::ref_ptr<osg::Node> > InstanceCache;
         InstanceCache _instanceCache;
+        Threading::ReadWriteMutex _instanceMutex;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/ResourceCache.cpp b/src/osgEarthSymbology/ResourceCache.cpp
index 4548090..ca99dbd 100644
--- a/src/osgEarthSymbology/ResourceCache.cpp
+++ b/src/osgEarthSymbology/ResourceCache.cpp
@@ -23,47 +23,52 @@ using namespace osgEarth::Symbology;
 
 ResourceCache::ResourceCache(const osgDB::Options* dbOptions,
                              bool                  threadSafe ) :
-_dbOptions ( dbOptions ),
-_threadSafe( threadSafe )
+_dbOptions    ( dbOptions ),
+_threadSafe   ( threadSafe ),
+_skinCache    ( false ),
+_markerCache  ( false ),
+_instanceCache( false )
 {
     //nop
 }
 
-osg::StateSet*
-ResourceCache::getStateSet( SkinResource* skin )
+bool
+ResourceCache::getStateSet(SkinResource*                skin,
+                           osg::ref_ptr<osg::StateSet>& output)
 {
-    osg::StateSet* result = 0L;
+    output = 0L;
+    std::string key = skin->getConfig().toJSON(false);
 
     if ( _threadSafe )
     {
         // first check if it exists
         {
-            Threading::ScopedReadLock shared( _mutex );
+            Threading::ScopedReadLock shared( _skinMutex );
 
             SkinCache::Record rec;
-            if ( _skinCache.get(skin, rec) )
+            if ( _skinCache.get(key, rec) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
         }
 
         // no? exclusive lock and create it.
-        if ( !result )
+        if ( !output.valid() )
         {
-            Threading::ScopedWriteLock exclusive( _mutex );
+            Threading::ScopedWriteLock exclusive( _skinMutex );
             
             // double check to avoid race condition
             SkinCache::Record rec;
-            if ( _skinCache.get(skin, rec) )
+            if ( _skinCache.get(key, rec) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
             else
             {
                 // still not there, make it.
-                result = skin->createStateSet( _dbOptions.get() );
-                if ( result )
-                    _skinCache.insert( skin, result );
+                output = skin->createStateSet( _dbOptions.get() );
+                if ( output.valid() )
+                    _skinCache.insert( key, output.get() );
             }
         }
     }
@@ -71,56 +76,59 @@ ResourceCache::getStateSet( SkinResource* skin )
     else
     {
         SkinCache::Record rec;
-        if ( _skinCache.get(skin, rec) )
+        if ( _skinCache.get(key, rec) )
         {
-            result = rec.value();
+            output = rec.value();
         }
         else
         {
-            result = skin->createStateSet( _dbOptions.get() );
-            if ( result )
-                _skinCache.insert( skin, result );
+            output = skin->createStateSet( _dbOptions.get() );
+            if ( output.valid() )
+                _skinCache.insert( key, output.get() );
         }
     }
 
-    return result;
+    return output.valid();
 }
 
-osg::Node*
-ResourceCache::getInstanceNode( InstanceResource* res )
+
+bool
+ResourceCache::getInstanceNode(InstanceResource*        res,
+                               osg::ref_ptr<osg::Node>& output)
 {
-    osg::Node* result = 0L;
+    output = 0L;
+    std::string key = res->getConfig().toJSON(false);
 
     if ( _threadSafe )
     {
         // first check if it exists
         {
-            Threading::ScopedReadLock shared( _mutex );
+            Threading::ScopedReadLock shared( _instanceMutex );
 
             InstanceCache::Record rec;
-            if ( _instanceCache.get(res, rec) )
+            if ( _instanceCache.get(key, rec) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
         }
 
         // no? exclusive lock and create it.
-        if ( !result )
+        if ( !output.valid() )
         {
-            Threading::ScopedWriteLock exclusive( _mutex );
+            Threading::ScopedWriteLock exclusive( _instanceMutex );
             
             // double check to avoid race condition
             InstanceCache::Record rec;
-            if ( _instanceCache.get(res, rec) )
+            if ( _instanceCache.get(key, rec) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
             else
             {
                 // still not there, make it.
-                result = res->createNode( _dbOptions.get() );
-                if ( result )
-                    _instanceCache.insert( res, result );
+                output = res->createNode( _dbOptions.get() );
+                if ( output.valid() )
+                    _instanceCache.insert( key, output.get() );
             }
         }
     }
@@ -128,57 +136,59 @@ ResourceCache::getInstanceNode( InstanceResource* res )
     else
     {
         InstanceCache::Record rec;
-        if ( _instanceCache.get(res, rec) )
+        if ( _instanceCache.get(key, rec) )
         {
-            result = rec.value();
+            output = rec.value().get();
         }
         else
         {
-            result = res->createNode( _dbOptions.get() );
-            if ( result )
-                _instanceCache.insert( res, result );
+            output = res->createNode( _dbOptions.get() );
+            if ( output.valid() )
+                _instanceCache.insert( key, output.get() );
         }
     }
 
-    return result;
+    return output.valid();
 }
 
 
-osg::Node*
-ResourceCache::getMarkerNode( MarkerResource* marker )
+bool
+ResourceCache::getMarkerNode(MarkerResource*          marker,
+                             osg::ref_ptr<osg::Node>& output)
 {
-    osg::Node* result = 0L;
+    output = 0L;
+    std::string key = marker->getConfig().toJSON(false);
 
     if ( _threadSafe )
     {
         // first check if it exists
         {
-            Threading::ScopedReadLock shared( _mutex );
+            Threading::ScopedReadLock shared( _markerMutex );
 
             MarkerCache::Record rec;
-            if ( _markerCache.get(marker, rec) )
+            if ( _markerCache.get(key, rec) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
         }
 
         // no? exclusive lock and create it.
-        if ( !result )
+        if ( !output.valid() )
         {
-            Threading::ScopedWriteLock exclusive( _mutex );
+            Threading::ScopedWriteLock exclusive( _markerMutex );
             
             // double check to avoid race condition
             MarkerCache::Record rec;
-            if ( _markerCache.get( marker, rec ) )
+            if ( _markerCache.get( key, rec ) )
             {
-                result = rec.value();
+                output = rec.value().get();
             }
             else
             {
                 // still not there, make it.
-                result = marker->createNode( _dbOptions.get() );
-                if ( result )
-                    _markerCache.insert( marker, result );
+                output = marker->createNode( _dbOptions.get() );
+                if ( output.valid() )
+                    _markerCache.insert( key, output.get() );
             }
         }
     }
@@ -186,17 +196,17 @@ ResourceCache::getMarkerNode( MarkerResource* marker )
     else
     {
         MarkerCache::Record rec;
-        if ( _markerCache.get( marker, rec ) )
+        if ( _markerCache.get( key, rec ) )
         {
-            result = rec.value();
+            output = rec.value().get();
         }
         else
         {
-            result = marker->createNode( _dbOptions.get() );
-            if ( result )
-                _markerCache.insert( marker, result );
+            output = marker->createNode( _dbOptions.get() );
+            if ( output.valid() )
+                _markerCache.insert( key, output.get() );
         }
     }
 
-    return result;
+    return output.valid();
 }
diff --git a/src/osgEarthSymbology/Skins.cpp b/src/osgEarthSymbology/Skins.cpp
index 3d20fd5..aa1c39a 100644
--- a/src/osgEarthSymbology/Skins.cpp
+++ b/src/osgEarthSymbology/Skins.cpp
@@ -130,6 +130,8 @@ SkinResource::createImage( const osgDB::Options* dbOptions ) const
 
 //---------------------------------------------------------------------------
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(skin, SkinSymbol);
+
 SkinSymbol::SkinSymbol( const Config& conf ) :
 _objHeight    ( 0.0f ),
 _minObjHeight ( 0.0f ),
diff --git a/src/osgEarthSymbology/Stroke b/src/osgEarthSymbology/Stroke
index a2957e9..64dd83c 100644
--- a/src/osgEarthSymbology/Stroke
+++ b/src/osgEarthSymbology/Stroke
@@ -86,9 +86,18 @@ namespace osgEarth { namespace Symbology
         optional<float>& minPixels() { return _minPixels; }
         const optional<float>& minPixels() const { return _minPixels; }
 
-        /** Stippling pattern. */
-        optional<unsigned short>& stipple() { return _stipple;}
-        const optional<unsigned short>& stipple() const { return _stipple; }
+        /** Stippling pattern. Bitmask of pixels to draw. */
+        optional<unsigned short>& stipplePattern() { return _stipplePattern; }
+        const optional<unsigned short>& stipplePattern() const { return _stipplePattern; }
+
+        /** Stippling factor; number of times to repeat each bit in the stipplePattern. */
+        optional<unsigned>& stippleFactor() { return _stippleFactor; }
+        const optional<unsigned>& stippleFactor() const { return _stippleFactor; }
+
+        /** Backwards-compatibility only */
+        optional<unsigned short>& stipple() { return _stipplePattern; }
+        const optional<unsigned short>& stipple() const { return _stipplePattern; }
+
 
         /** Rounding ratio - when rounding corners/caps, this is the ratio
             of rounded-segment length to width(). The smaller this value,
@@ -106,7 +115,8 @@ namespace osgEarth { namespace Symbology
         optional<LineJoinStyle>  _lineJoin;
         optional<float>          _width;
         optional<Units>          _widthUnits;
-        optional<unsigned short> _stipple;
+        optional<unsigned>       _stippleFactor;
+        optional<unsigned short> _stipplePattern;
         optional<float>          _roundingRatio;
         optional<float>          _minPixels;
 
diff --git a/src/osgEarthSymbology/Stroke.cpp b/src/osgEarthSymbology/Stroke.cpp
index 117af67..0573655 100644
--- a/src/osgEarthSymbology/Stroke.cpp
+++ b/src/osgEarthSymbology/Stroke.cpp
@@ -74,7 +74,8 @@ Stroke::getConfig() const {
     conf.addIfSet("linejoin", "mitre", _lineJoin, LINEJOIN_MITRE);
     conf.addIfSet("linejoin", "round", _lineJoin, LINEJOIN_ROUND);
     conf.addIfSet("width", _width);
-    conf.addIfSet("stipple", _stipple);
+    conf.addIfSet("stipple_factor", _stippleFactor);
+    conf.addIfSet("stipple_pattern", _stipplePattern);
     conf.addIfSet("rounding_ratio", _roundingRatio);
     if ( _widthUnits.isSet() )
         conf.add( "width_units", _widthUnits->getAbbr() );
@@ -92,7 +93,9 @@ Stroke::mergeConfig( const Config& conf ) {
     conf.getIfSet("linejoin", "miter", _lineJoin, LINEJOIN_MITRE); // alternate spelling
     conf.getIfSet("linejoin", "round", _lineJoin, LINEJOIN_ROUND);
     conf.getIfSet("width", _width);
-    conf.getIfSet("stipple", _stipple);
+    conf.getIfSet("stipple", _stipplePattern); // back compat
+    conf.getIfSet("stipple_factor", _stippleFactor);
+    conf.getIfSet("stipple_pattern", _stipplePattern);
     conf.getIfSet("rounding_ratio", _roundingRatio);
     if ( conf.hasValue("width_units" ) )
         Units::parse( conf.value("width_units"), _widthUnits.mutable_value() );
diff --git a/src/osgEarthSymbology/Style.cpp b/src/osgEarthSymbology/Style.cpp
index ee94120..9d07735 100644
--- a/src/osgEarthSymbology/Style.cpp
+++ b/src/osgEarthSymbology/Style.cpp
@@ -127,18 +127,7 @@ Style::fromSLD( const Config& sld )
     for( ConfigSet::const_iterator kid = sld.children().begin(); kid != sld.children().end(); ++kid )
     {
         const Config& p = *kid;
-
-        AltitudeSymbol::parseSLD (p, *this);
-        ExtrusionSymbol::parseSLD(p, *this);
-        IconSymbol::parseSLD     (p, *this);
-        LineSymbol::parseSLD     (p, *this);
-        MarkerSymbol::parseSLD   (p, *this);
-        ModelSymbol::parseSLD    (p, *this);
-        PolygonSymbol::parseSLD  (p, *this);
-        PointSymbol::parseSLD    (p, *this);
-        RenderSymbol::parseSLD   (p, *this);
-        SkinSymbol::parseSLD     (p, *this);
-        TextSymbol::parseSLD     (p, *this);
+		SymbolRegistry::instance()->parseSLD( p, *this );		
     }
 }
 
@@ -181,51 +170,9 @@ Style::mergeConfig( const Config& conf )
             for( ConfigSet::const_iterator i = symbolConf.children().begin(); i != symbolConf.children().end(); ++i )
             {
                 const Config& c = *i;
+				Symbol* symbol = SymbolRegistry::instance()->create( c );
 
-                if ( c.key() == "text" )
-                {
-                    add( new TextSymbol(c) );
-                }
-                else if ( c.key() == "point" )
-                {
-                    add( new PointSymbol(c) );
-                }
-                else if ( c.key() == "line" )
-                {
-                    add( new LineSymbol(c) );
-                }
-                else if ( c.key() == "polygon" )
-                {
-                    add( new PolygonSymbol(c) );
-                }
-                else if ( c.key() == "extrusion" )
-                {
-                    add( new ExtrusionSymbol(c) );
-                }
-                else if ( c.key() == "altitude" )
-                {
-                    add( new AltitudeSymbol(c) );
-                }
-                else if ( c.key() == "marker" )
-                {
-                    add( new MarkerSymbol(c) );
-                }
-                else if ( c.key() == "render" )
-                {
-                    add( new RenderSymbol(c) );
-                }
-                else if ( c.key() == "skin" )
-                {
-                    add( new SkinSymbol(c) );
-                }
-                else if ( c.key() == "model" )
-                {
-                    add( new ModelSymbol(c) );
-                }
-                else if ( c.key() == "icon" )
-                {
-                    add( new IconSymbol(c) );
-                }
+				add(symbol);
             }
         }
     }
diff --git a/src/osgEarthSymbology/Symbol b/src/osgEarthSymbology/Symbol
index b6bb498..436735d 100644
--- a/src/osgEarthSymbology/Symbol
+++ b/src/osgEarthSymbology/Symbol
@@ -33,6 +33,8 @@
 
 namespace osgEarth { namespace Symbology
 {
+    class Style;
+    
     /**
      * Abstract base class for all Symbol types.
      */
@@ -45,7 +47,7 @@ namespace osgEarth { namespace Symbology
 
     public: // META_Symbol methods
         virtual bool isSameKindAs(const Symbol* sym) const =0;
-        virtual const char* className() const =0;
+        virtual const char* className() const =0;        
 
     protected:
         URIContext _uriContext;
@@ -58,6 +60,83 @@ namespace osgEarth { namespace Symbology
     };
     typedef std::vector<osg::ref_ptr<Symbol> > SymbolList;
 
+    /**
+     * A Factory that can create a Symbol from a Config
+     */
+    class OSGEARTHSYMBOLOGY_EXPORT SymbolFactory : public osg::Referenced
+    {
+    public:
+        virtual Symbol* create( const Config& conf ) = 0;
+
+        virtual void parseSLD(const Config& c, Style& style) const = 0;
+    };    
+
+    typedef std::list< osg::ref_ptr< SymbolFactory > > SymbolFactoryList;
+
+    /**
+     * A registry of Symbol plugins
+     */
+    class OSGEARTHSYMBOLOGY_EXPORT SymbolRegistry : public osg::Referenced
+    {         
+    public:
+        /**
+         * The singleton instance of the factory
+         */
+        static SymbolRegistry* instance();
+
+        /*
+         * Adds a new SymbolFactory to the list
+         */
+        void add( SymbolFactory* factory );
+
+        /**
+         * Creates a Symbol with the registered plugins from the given Config
+         */
+        Symbol* create( const Config& conf );
+
+        /**
+         * Parses the given SLD config into the given Style using all of the registered symbols
+         */
+        void parseSLD(const Config& c, Style& style) const;
+
+    protected:
+        SymbolRegistry();
+        SymbolFactoryList _factories;
+    };
+
+    template<class T>
+    struct SimpleSymbolFactory : public SymbolFactory
+    {
+        SimpleSymbolFactory(const std::string& key):_key(key){}
+
+        virtual Symbol* create(const Config& conf)
+        {
+            if (conf.key() == _key) return new T(conf);            
+            return 0;
+        }
+                
+        virtual void parseSLD(const Config& c, class Style& style) const
+        {
+            T::parseSLD( c, style );
+        }
+
+        std::string _key;
+    };
+
+    template<class T>
+    struct RegisterSymbolProxy
+    {
+        RegisterSymbolProxy( T* factory) { SymbolRegistry::instance()->add( factory ); }
+        RegisterSymbolProxy() { SymbolRegistry::instance()->add( new T ); }
+    };
+
+#define OSGEARTH_REGISTER_SYMBOL( CLASSNAME )\
+    static osgEarth::Symbology::RegisterSymbolProxy<CLASSNAME> s_osgEarthRegisterSymbolProxy_##CLASSNAME;
+
+#define OSGEARTH_REGISTER_SIMPLE_SYMBOL( KEY, CLASSNAME)\
+    static osgEarth::Symbology::RegisterSymbolProxy< osgEarth::Symbology::SimpleSymbolFactory<CLASSNAME> > s_osgEarthRegisterSymbolProxy_##CLASSNAME##KEY(new osgEarth::Symbology::SimpleSymbolFactory<CLASSNAME>(#KEY));
+
+
 } } // namespace osgEarth::Symbology
 
 #endif // OSGEARTH_SYMBOLOGY_SYMBOL_H
diff --git a/src/osgEarthSymbology/Symbol.cpp b/src/osgEarthSymbology/Symbol.cpp
index 28cf3d6..c4cc78c 100644
--- a/src/osgEarthSymbology/Symbol.cpp
+++ b/src/osgEarthSymbology/Symbol.cpp
@@ -21,6 +21,52 @@
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+SymbolRegistry::SymbolRegistry()
+{
+}
+
+SymbolRegistry*
+SymbolRegistry::instance()
+{
+    static SymbolRegistry* s_singleton =0L;
+    static Threading::Mutex    s_singletonMutex;
+
+    if ( !s_singleton )
+    {
+        Threading::ScopedMutexLock lock(s_singletonMutex);
+        if ( !s_singleton )
+        {
+            s_singleton = new SymbolRegistry();
+        }
+    }
+    return s_singleton;
+}
+
+void
+SymbolRegistry::add( SymbolFactory* factory )
+{
+    _factories.push_back( factory );
+}
+
+Symbol*
+SymbolRegistry::create( const Config& conf )
+{
+    for (SymbolFactoryList::iterator itr = _factories.begin(); itr != _factories.end(); itr++)
+    {
+        Symbol* symbol = itr->get()->create( conf );
+        if (symbol) return symbol;
+    }
+    return 0;
+} 
+
+void SymbolRegistry::parseSLD(const Config& c, class Style& style) const
+{
+    for (SymbolFactoryList::const_iterator itr = _factories.begin(); itr != _factories.end(); itr++)
+    {
+        itr->get()->parseSLD( c, style );        
+    }
+}
+
 //------------------------------------------------------------------------
 
 Symbol::Symbol( const Config& conf )
diff --git a/src/osgEarthSymbology/TextSymbol b/src/osgEarthSymbology/TextSymbol
index ccede0b..50bba2f 100644
--- a/src/osgEarthSymbology/TextSymbol
+++ b/src/osgEarthSymbology/TextSymbol
@@ -65,6 +65,12 @@ namespace osgEarth { namespace Symbology
             ALIGN_BASE_LINE = ALIGN_LEFT_BASE_LINE /// default.        
         };
 
+        enum Layout {
+            LAYOUT_LEFT_TO_RIGHT,
+            LAYOUT_RIGHT_TO_LEFT,
+            LAYOUT_VERTICAL
+        };
+
         META_Symbol(TextSymbol);
 
         TextSymbol( const Config& conf =Config() );
@@ -108,6 +114,10 @@ namespace osgEarth { namespace Symbology
         optional<Alignment>& alignment() { return _alignment; }
         const optional<Alignment>& alignment() const { return _alignment; }
 
+        /** Layout of the label*/
+        optional<Layout>& layout() { return _layout; }
+        const optional<Layout>& layout() const { return _layout; }
+
         /** Whether to remove duplicates when drawing multiple labels. */
         optional<bool>& removeDuplicateLabels() { return _removeDuplicateLabels; }
         const optional<bool>& removeDuplicateLabels() const { return _removeDuplicateLabels; }
@@ -116,6 +126,14 @@ namespace osgEarth { namespace Symbology
         optional<bool>& declutter() { return _declutter; }
         const optional<bool>& declutter() const { return _declutter; }
 
+        /** Whether to enable occlusion culling on the text */
+        optional<bool>& occlusionCull() { return _occlusionCull; }
+        const optional<bool>& occlusionCull() const { return _occlusionCull; }
+
+        /** The viewer altitude at which to start occlusion culling the text */
+        optional<double>& occlusionCullAltitude() { return _occlusionCullAltitude; }
+        const optional<double>& occlusionCullAltitude() const { return _occlusionCullAltitude; }
+
         /** Label generation provider to use */
         optional<std::string>& provider() { return _provider; }
         const optional<std::string>& provider() const { return _provider; }
@@ -142,7 +160,10 @@ namespace osgEarth { namespace Symbology
         optional<std::string>       _provider;
         optional<Encoding>          _encoding;
         optional<Alignment>         _alignment;
+        optional<Layout>            _layout;
         optional<bool>              _declutter;
+        optional<bool>              _occlusionCull;
+        optional<double>            _occlusionCullAltitude;
     };
 
 } } // namespace osgEarth::Symbology
diff --git a/src/osgEarthSymbology/TextSymbol.cpp b/src/osgEarthSymbology/TextSymbol.cpp
index 9b732fd..fbf4f67 100644
--- a/src/osgEarthSymbology/TextSymbol.cpp
+++ b/src/osgEarthSymbology/TextSymbol.cpp
@@ -1,27 +1,29 @@
 /* -*-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/>
- */
+* 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 <osgEarthSymbology/TextSymbol>
 #include <osgEarthSymbology/Style>
 
 using namespace osgEarth;
 using namespace osgEarth::Symbology;
 
+OSGEARTH_REGISTER_SIMPLE_SYMBOL(text, TextSymbol);
+
 TextSymbol::TextSymbol( const Config& conf ) :
 Symbol                ( conf ),
 _fill                 ( Fill( 1, 1, 1, 1 ) ),
@@ -30,9 +32,12 @@ _haloOffset           ( 0.07f ),
 _size                 ( 16.0f ),
 _removeDuplicateLabels( false ),
 _alignment            ( ALIGN_BASE_LINE ),
+_layout               ( LAYOUT_LEFT_TO_RIGHT ),
 _provider             ( "annotation" ),
 _encoding             ( ENCODING_ASCII ),
-_declutter            ( true )
+_declutter            ( true ),
+_occlusionCull        ( false ),
+_occlusionCullAltitude( 200000 )
 {
     mergeConfig(conf);
 }
@@ -73,6 +78,10 @@ TextSymbol::getConfig() const
     conf.addIfSet( "alignment", "right_bottom_base_line",  _alignment, ALIGN_RIGHT_BOTTOM_BASE_LINE );
     conf.addIfSet( "alignment", "base_line",               _alignment, ALIGN_BASE_LINE );
 
+    conf.addIfSet( "layout", "ltr",  _layout, LAYOUT_LEFT_TO_RIGHT );
+    conf.addIfSet( "layout", "rtl",  _layout, LAYOUT_RIGHT_TO_LEFT );
+    conf.addIfSet( "layout", "vertical",  _layout, LAYOUT_VERTICAL );
+
     conf.addIfSet( "declutter", _declutter );
 
     conf.addIfSet( "provider", _provider );
@@ -80,6 +89,10 @@ TextSymbol::getConfig() const
         conf.add( "pixel_offset_x", toString(_pixelOffset->x()) );
         conf.add( "pixel_offset_y", toString(_pixelOffset->y()) );
     }
+
+    conf.addIfSet( "text-occlusion-cull", _occlusionCull );
+    conf.addIfSet( "text-occlusion-cull-altitude", _occlusionCullAltitude );
+
     return conf;
 }
 
@@ -117,6 +130,10 @@ TextSymbol::mergeConfig( const Config& conf )
     conf.getIfSet( "alignment", "right_bottom_base_line",  _alignment, ALIGN_RIGHT_BOTTOM_BASE_LINE );
     conf.getIfSet( "alignment", "base_line" ,              _alignment, ALIGN_BASE_LINE );
 
+    conf.getIfSet( "layout", "ltr",  _layout, LAYOUT_LEFT_TO_RIGHT );
+    conf.getIfSet( "layout", "rtl",  _layout, LAYOUT_RIGHT_TO_LEFT );
+    conf.getIfSet( "layout", "vertical",  _layout, LAYOUT_VERTICAL );
+
     conf.getIfSet( "declutter", _declutter );
 
     conf.getIfSet( "provider", _provider );
@@ -124,16 +141,19 @@ TextSymbol::mergeConfig( const Config& conf )
         _pixelOffset = osg::Vec2s( conf.value<short>("pixel_offset_x",0), 0 );
     if ( conf.hasValue( "pixel_offset_y" ) )
         _pixelOffset = osg::Vec2s( _pixelOffset->x(), conf.value<short>("pixel_offset_y",0) );
+
+    conf.getIfSet( "text-occlusion-cull", _occlusionCull );
+    conf.getIfSet( "text-occlusion-cull-altitude", _occlusionCullAltitude );
 }
 
 
 void
 TextSymbol::parseSLD(const Config& c, Style& style)
 {
-    if ( match(c.key(), "fill") ) {
+    if ( match(c.key(), "text-fill") ) {
         style.getOrCreate<TextSymbol>()->fill()->color() = Color(c.value());
     }
-    else if ( match(c.key(), "fill-opacity") ) {
+    else if ( match(c.key(), "text-fill-opacity") ) {
         style.getOrCreate<TextSymbol>()->fill()->color().a() = as<float>( c.value(), 1.0f );
     }
     else if ( match(c.key(), "text-size") ) {
@@ -188,6 +208,14 @@ TextSymbol::parseSLD(const Config& c, Style& style)
         else if ( match(c.value(), "base-line" ) ) 
             style.getOrCreate<TextSymbol>()->alignment() = TextSymbol::ALIGN_BASE_LINE;
     }
+    else if ( match(c.key(), "text-layout") ) {
+        if ( match(c.value(), "ltr") )
+            style.getOrCreate<TextSymbol>()->layout() = TextSymbol::LAYOUT_LEFT_TO_RIGHT;
+        else if ( match(c.value(), "rtl" ) )
+            style.getOrCreate<TextSymbol>()->layout() = TextSymbol::LAYOUT_RIGHT_TO_LEFT;
+        else if ( match(c.value(), "vertical" ) )
+            style.getOrCreate<TextSymbol>()->layout() = TextSymbol::LAYOUT_VERTICAL;
+    }
     else if ( match(c.key(), "text-content") ) {        
         style.getOrCreate<TextSymbol>()->content() = StringExpression( c.value() );
     }
@@ -212,4 +240,10 @@ TextSymbol::parseSLD(const Config& c, Style& style)
     else if ( match(c.key(), "text-declutter") ) {
         style.getOrCreate<TextSymbol>()->declutter() = as<bool>(c.value(), true);
     }
+    else if ( match(c.key(), "text-occlusion-cull") ) {
+        style.getOrCreate<TextSymbol>()->occlusionCull() = as<bool>(c.value(), false);
+    }
+    else if ( match(c.key(), "text-occlusion-cull-altitude") ) {
+        style.getOrCreate<TextSymbol>()->occlusionCullAltitude() = as<double>(c.value(), 200000.0);
+    }
 }
diff --git a/src/osgEarthUtil/ArcGIS b/src/osgEarthUtil/ArcGIS
new file mode 100644
index 0000000..4bf999c
--- /dev/null
+++ b/src/osgEarthUtil/ArcGIS
@@ -0,0 +1,112 @@
+/* -*-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 OSGEARTHUTIL_ARCGIS_H
+#define OSGEARTHUTIL_ARCGIS_H 1
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/Config>
+#include <osg/Referenced>
+#include <osg/ref_ptr>
+#include <osgEarth/Common>
+
+#include <osgDB/ReaderWriter>
+#include <osg/Version>
+#if OSG_MIN_VERSION_REQUIRED(2,9,5)
+#include <osgDB/Options>
+#endif
+
+
+#include <string>
+#include <vector>
+
+namespace osgEarth { namespace Util { namespace ArcGIS {
+
+    using namespace osgEarth;
+
+    /**
+     * An ArcGIS service
+     */
+    struct OSGEARTHUTIL_EXPORT Service
+    {
+        Service( const std::string& _name, const std::string& _type);
+    
+        std::string name;
+        std::string type;
+    };
+
+    /**
+     * A list of Services
+     */
+    typedef std::list< Service > ServiceList;
+
+
+    /**
+     * A list of folders
+     */     
+    typedef std::list< std::string > FolderList;
+
+    /**
+     * The result of an ArcGIS service REST Request
+     */
+    class OSGEARTHUTIL_EXPORT RESTResponse
+    {
+    public:
+        RESTResponse();
+
+        const ServiceList& getServices() const;
+        ServiceList& getServices();
+
+        const FolderList& getFolders() const;
+        FolderList& getFolders();
+        const std::string& getCurrentVersion() const;
+        void setCurrentVersion( const std::string& currentVersion);
+
+        bool getFolder(const std::string& folder, RESTResponse& response ) const;
+
+        const std::string& getServiceURL() const;
+        void setServiceURL( const std::string& serviceURL );
+
+    private:
+        ServiceList _services;
+        FolderList _folders;
+        std::string _currentVersion;
+        std::string _serviceURL;
+    };
+
+    class OSGEARTHUTIL_EXPORT ServiceReader
+    {
+    public:
+        static bool read( const std::string &location, const osgDB::ReaderWriter::Options* options, RESTResponse& response );    
+        static bool read( const Config& conf,  RESTResponse& response );
+
+    private:
+        ServiceReader();
+        ServiceReader(const ServiceReader &rhs);
+
+        /** dtor */
+        virtual ~ServiceReader();
+    };
+
+
+
+
+} } } // namespace osgEarth::Util::ArcGIS
+
+#endif //OSGEARTHUTIL_ARCGIS_H
diff --git a/src/osgEarthUtil/ArcGIS.cpp b/src/osgEarthUtil/ArcGIS.cpp
new file mode 100644
index 0000000..99ea342
--- /dev/null
+++ b/src/osgEarthUtil/ArcGIS.cpp
@@ -0,0 +1,168 @@
+/* -*-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 <osgEarthUtil/ArcGIS>
+
+#include <osgEarth/IOTypes>
+#include <osgEarth/URI>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Util::ArcGIS;
+using namespace std;
+
+Service::Service( const std::string& _name, const std::string& _type):
+name( _name ),
+type( _type )
+{
+}
+
+
+/***************************************************************************************/
+
+RESTResponse::RESTResponse()
+{
+}
+
+ServiceList& 
+RESTResponse::getServices()
+{
+    return _services;
+}
+
+const ServiceList& 
+RESTResponse::getServices() const
+{
+    return _services;
+}
+
+FolderList&
+RESTResponse::getFolders()
+{
+    return _folders;
+}
+
+const FolderList&
+RESTResponse::getFolders() const
+{
+    return _folders;
+}
+
+
+const std::string& RESTResponse::getCurrentVersion() const
+{
+    return _currentVersion;
+}
+
+void RESTResponse::setCurrentVersion( const std::string& currentVersion)
+{
+    _currentVersion = currentVersion;
+}
+
+const std::string& RESTResponse::getServiceURL() const
+{
+    return _serviceURL;
+}
+
+void RESTResponse::setServiceURL( const std::string& serviceURL )
+{
+    _serviceURL = serviceURL;
+}
+
+bool RESTResponse::getFolder(const std::string& folder, RESTResponse& response ) const
+{
+    std::string folderURL = _serviceURL + "/" + folder;
+    return ServiceReader::read(folderURL, 0, response );
+}
+
+/***************************************************************************************/
+
+ServiceReader::ServiceReader()
+{
+}
+
+ServiceReader::ServiceReader(const ServiceReader &rhs)
+{
+}
+
+ServiceReader::~ServiceReader()
+{
+}
+
+bool 
+ServiceReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options, RESTResponse& response )
+{
+    response.setServiceURL( location );
+    std::string serviceLocation = location + "?f=json&pretty=true";
+
+    ReadResult r = URI(serviceLocation).readString();
+    if ( r.failed() )
+    {
+        OE_WARN << "Failed to read ArcGIS Services tile map file from " << serviceLocation << std::endl;
+        return false;
+    }
+
+    // Read tile map into a Config:
+    Config conf;
+    std::stringstream buf( r.getString() );
+    if (!conf.fromJSON( buf.str() ))
+    {
+        return false;
+    }
+
+    return read( conf, response );    
+}
+
+
+bool 
+ServiceReader::read( const Config& conf,  RESTResponse& response )
+{
+    response.getServices().clear();
+    response.getFolders().clear();
+
+
+    if (conf.hasChild("currentVersion"))
+    {
+        response.setCurrentVersion( conf.value("currentVersion") );
+    }
+
+    if (conf.hasChild("services"))
+    {
+        ConfigSet services = conf.child("services").children();
+        for (ConfigSet::iterator itr = services.begin(); itr != services.end(); ++itr)
+        {
+            response.getServices().push_back( Service( itr->value("name"), itr->value("type") ) );            
+        }
+    }
+
+    if (conf.hasChild("folders"))
+    {
+        ConfigSet folders = conf.child("folders").children();
+        for (ConfigSet::iterator itr = folders.begin(); itr != folders.end(); ++itr)
+        {
+            response.getFolders().push_back( itr->value() );            
+        }
+    }
+
+    return true;
+}
+
+
+
+
diff --git a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
index 3e0bf50..1e4aa9c 100644
--- a/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
+++ b/src/osgEarthUtil/BrightnessContrastColorFilter.cpp
@@ -36,7 +36,7 @@ namespace
         "#version 110\n"
         "uniform vec2 __UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
         "    color.rgb = ((color.rgb - 0.5) * __UNIFORM_NAME__.y + 0.5) * __UNIFORM_NAME__.x; \n"
         "    color.rgb = clamp(color.rgb, 0.0, 1.0); \n"
diff --git a/src/osgEarthUtil/CMYKColorFilter.cpp b/src/osgEarthUtil/CMYKColorFilter.cpp
index d5e8047..5db9dd8 100644
--- a/src/osgEarthUtil/CMYKColorFilter.cpp
+++ b/src/osgEarthUtil/CMYKColorFilter.cpp
@@ -36,7 +36,7 @@ namespace
         "#version 110\n"
         "uniform vec4 __UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
         // apply cmy (negative of rgb):
         "   color.rgb -= __UNIFORM_NAME__.xyz; \n"
diff --git a/src/osgEarthUtil/CMakeLists.txt b/src/osgEarthUtil/CMakeLists.txt
index 21a4936..5a05f0c 100644
--- a/src/osgEarthUtil/CMakeLists.txt
+++ b/src/osgEarthUtil/CMakeLists.txt
@@ -11,10 +11,14 @@ SET(HEADER_PATH ${OSGEARTH_SOURCE_DIR}/include/${LIB_NAME})
 SET(HEADERS_ROOT
     AnnotationEvents
     AutoClipPlaneHandler
+    ArcGIS
     Common
     Controls
+    ContourMap
     ClampCallback
     DataScanner
+    DateTime
+    DetailTexture
     EarthManipulator
     ExampleResources
     Export
@@ -22,13 +26,16 @@ SET(HEADERS_ROOT
     FeatureQueryTool
     Formatter
     GeodeticGraticule
+    HTM
     LatLongFormatter
     LineOfSight
     LinearLineOfSight
+    LODBlending
     MeasureTool
     MGRSFormatter
     MGRSGraticule
     MouseCoordsTool
+    NormalMap
     ObjectLocator
     PolyhedralLineOfSight
     RadialLineOfSight
@@ -36,12 +43,15 @@ SET(HEADERS_ROOT
     SpatialData
     StarData
     TerrainProfile
+    TileIndex
+    TileIndexBuilder
     TFS
     TFSPackager
     TMS
     TMSBackFiller
     TMSPackager
     UTMGraticule
+    VerticalScale
     WFS
     WMS
 )
@@ -54,33 +64,43 @@ SOURCE_GROUP( Headers FILES ${HEADERS_ROOT} )
 
 SET(SOURCES_ROOT
     AnnotationEvents.cpp
+    ArcGIS.cpp
     AutoClipPlaneHandler.cpp
     ClampCallback.cpp
     Controls.cpp
+    ContourMap.cpp
     DataScanner.cpp
-    EarthManipulator.cpp    
+    #DateTime.cpp #moved to osgEarth namespace
+    DetailTexture.cpp
+    EarthManipulator.cpp
     ExampleResources.cpp
     FeatureManipTool.cpp
     FeatureQueryTool.cpp
     GeodeticGraticule.cpp
+    HTM.cpp
     LatLongFormatter.cpp
     LinearLineOfSight.cpp
+    LODBlending.cpp
     MeasureTool.cpp
     MGRSFormatter.cpp
     MGRSGraticule.cpp
     MouseCoordsTool.cpp
+    NormalMap.cpp
     ObjectLocator.cpp
     PolyhedralLineOfSight.cpp
     RadialLineOfSight.cpp
     SpatialData.cpp
     SkyNode.cpp
     TerrainProfile.cpp
+    TileIndex.cpp
+    TileIndexBuilder.cpp
     TFS.cpp
     TFSPackager.cpp
     TMS.cpp
     TMSBackFiller.cpp
     TMSPackager.cpp
     UTMGraticule.cpp
+    VerticalScale.cpp
     WFS.cpp
     WMS.cpp
 )
@@ -135,7 +155,7 @@ SET(USE_CUSTOM_SOURCE_GROUPS 1)
 
 
 
-INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIR} ${OSGEARTH_SOURCE_DIR})
+INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIR} ${OSGEARTH_SOURCE_DIR} ${GDAL_INCLUDE_DIR})
 
 IF (WIN32)
   LINK_EXTERNAL(${LIB_NAME} ${TARGET_EXTERNAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${MATH_LIBRARY})
diff --git a/src/osgEarthUtil/ChromaKeyColorFilter.cpp b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
index 92f7fee..d092593 100644
--- a/src/osgEarthUtil/ChromaKeyColorFilter.cpp
+++ b/src/osgEarthUtil/ChromaKeyColorFilter.cpp
@@ -37,7 +37,7 @@ namespace
         "uniform vec3  __COLOR_UNIFORM_NAME__;\n"
         "uniform float __DISTANCE_UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{ \n"
         "    float dist = distance(color.rgb, __COLOR_UNIFORM_NAME__); \n"
         "    if (dist <= __DISTANCE_UNIFORM_NAME__) color.a = 0.0;\n"
diff --git a/src/osgEarthUtil/ContourMap b/src/osgEarthUtil/ContourMap
new file mode 100644
index 0000000..9154e0f
--- /dev/null
+++ b/src/osgEarthUtil/ContourMap
@@ -0,0 +1,79 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_CONTOUR_MAP_H
+#define OSGEARTHUTIL_CONTOUR_MAP_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osg/Texture1D>
+#include <osg/TransferFunction>
+
+namespace osgEarth {
+    class Map;
+}
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Terrain effect that applies a 1D contour coloring texture
+     * to the terrain based an on elevation->color map.
+     */
+    class OSGEARTHUTIL_EXPORT ContourMap : public TerrainEffect
+    {
+    public:
+        /** construct a new effect */
+        ContourMap();
+
+        /** Sets a custom transfer function. */
+        void setTransferFunction(osg::TransferFunction1D* xf);
+        osg::TransferFunction1D* getTransferFunction() const { return _xfer.get(); }
+
+        /** Sets the opacity of the effect (default = 1.0) */
+        void setOpacity(float value);
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        ContourMap(const Config& conf);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~ContourMap();
+        void init();
+
+        int                                   _unit;
+        osg::ref_ptr<osg::TransferFunction1D> _xfer;
+        osg::ref_ptr<osg::Texture1D>          _xferTexture;
+        osg::ref_ptr<osg::Uniform>            _xferSampler;
+        osg::ref_ptr<osg::Uniform>            _xferMin;
+        osg::ref_ptr<osg::Uniform>            _xferRange;
+        osg::ref_ptr<osg::Uniform>            _opacityUniform;
+
+        optional<float> _opacity;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_CONTOUR_MAP_H
diff --git a/src/osgEarthUtil/ContourMap.cpp b/src/osgEarthUtil/ContourMap.cpp
new file mode 100644
index 0000000..8210a05
--- /dev/null
+++ b/src/osgEarthUtil/ContourMap.cpp
@@ -0,0 +1,226 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/ContourMap>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[ContourMap] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "attribute vec4 oe_terrain_attr; \n"
+        "uniform float oe_contour_min; \n"
+        "uniform float oe_contour_range; \n"
+        "varying float oe_contour_lookup; \n"
+
+        "void oe_contour_vertex(inout vec4 VertexModel) \n"
+        "{ \n"
+        "    float height = oe_terrain_attr[3]; \n"
+        "    float height_normalized = (height-oe_contour_min)/oe_contour_range; \n"
+        "    oe_contour_lookup = clamp( height_normalized, 0.0, 1.0 ); \n"
+        "} \n";
+
+
+    const char* fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform sampler1D oe_contour_xfer; \n"
+        "uniform float oe_contour_opacity; \n"
+        "varying float oe_contour_lookup; \n"
+
+        "void oe_contour_fragment( inout vec4 color ) \n"
+        "{ \n"
+        "    vec4 texel = texture1D( oe_contour_xfer, oe_contour_lookup ); \n"
+        "    color.rgb = mix(color.rgb, texel.rgb, texel.a * oe_contour_opacity); \n"
+        "} \n";
+}
+
+
+ContourMap::ContourMap() :
+TerrainEffect()
+{
+    init();
+}
+
+ContourMap::ContourMap(const Config& conf) :
+TerrainEffect()
+{
+    mergeConfig(conf);
+    init();
+}
+
+
+void
+ContourMap::init()
+{
+    // negative means unset:
+    _unit = -1;
+
+    // uniforms we'll need:
+    _xferMin     = new osg::Uniform(osg::Uniform::FLOAT,      "oe_contour_min" );
+    _xferRange   = new osg::Uniform(osg::Uniform::FLOAT,      "oe_contour_range" );
+    _xferSampler = new osg::Uniform(osg::Uniform::SAMPLER_1D, "oe_contour_xfer" );
+    _opacityUniform = new osg::Uniform(osg::Uniform::FLOAT,   "oe_contour_opacity" );
+    _opacityUniform->set( _opacity.getOrUse(1.0f) );
+
+    // Create a 1D texture from the transfer function's image.
+    _xferTexture = new osg::Texture1D();
+    _xferTexture->setResizeNonPowerOfTwoHint( false );
+    _xferTexture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    _xferTexture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+    _xferTexture->setWrap( osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE );
+
+    // build a default transfer function.
+    // TODO: think about scale/bias controls.
+    osg::TransferFunction1D* xfer = new osg::TransferFunction1D();
+    float s = 2500.0f;
+    xfer->setColor( -1.0000 * s, osg::Vec4f(0, 0, 0.5, 1), false);
+    xfer->setColor( -0.2500 * s, osg::Vec4f(0, 0, 1, 1), false);
+    xfer->setColor(  0.0000 * s, osg::Vec4f(0, .5, 1, 1), false);
+    xfer->setColor(  0.0062 * s, osg::Vec4f(.84,.84,.25,1), false);
+    //xfer->setColor(  0.0625 * s, osg::Vec4f(.94,.94,.25,1), false);
+    xfer->setColor(  0.1250 * s, osg::Vec4f(.125,.62,0,1), false);
+    xfer->setColor(  0.3250 * s, osg::Vec4f(.80,.70,.47,1), false);
+    xfer->setColor(  0.7500 * s, osg::Vec4f(.5,.5,.5,1), false);
+    xfer->setColor(  1.0000 * s, osg::Vec4f(1,1,1,1), false);
+    xfer->updateImage();
+    this->setTransferFunction( xfer );
+}
+
+
+ContourMap::~ContourMap()
+{
+    //nop
+}
+
+
+void
+ContourMap::setTransferFunction(osg::TransferFunction1D* xfer)
+{
+    _xfer = xfer;
+
+    _xferTexture->setImage( _xfer->getImage() );
+    _xferMin->set( _xfer->getMinimum() );
+    _xferRange->set( _xfer->getMaximum() - _xfer->getMinimum() );
+}
+
+
+void
+ContourMap::setOpacity(float opacity)
+{
+    _opacity = osg::clampBetween(opacity, 0.0f, 1.0f);
+    _opacityUniform->set( _opacity.get() );
+}
+
+
+void
+ContourMap::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        if ( !engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
+        {
+            OE_WARN << LC << "Failed to reserve a texture image unit; disabled." << std::endl;
+            return;
+        }
+
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+
+        // Install the texture and its sampler uniform:
+        stateset->setTextureAttributeAndModes( _unit, _xferTexture.get(), osg::StateAttribute::ON );
+        stateset->addUniform( _xferSampler.get() );
+        _xferSampler->set( _unit );
+
+        // (By the way: if you want to draw image layers on top of the contoured terrain,
+        // set the "priority" parameter to setFunction() to a negative number so that it draws
+        // before the terrain's layers.)
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+        vp->setFunction( "oe_contour_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL);
+        vp->setFunction( "oe_contour_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING ); //, -1.0);
+
+        // Install some uniforms that tell the shader the height range of the color map.
+        stateset->addUniform( _xferMin.get() );
+        _xferMin->set( _xfer->getMinimum() );
+
+        stateset->addUniform( _xferRange.get() );
+        _xferRange->set( _xfer->getMaximum() - _xfer->getMinimum() );
+
+        stateset->addUniform( _opacityUniform.get() );
+    }
+}
+
+
+void
+ContourMap::onUninstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getStateSet();
+        if ( stateset )
+        {
+            stateset->removeUniform( _xferMin.get() );
+            stateset->removeUniform( _xferRange.get() );
+            stateset->removeUniform( _xferSampler.get() );
+            stateset->removeUniform( _opacityUniform.get() );
+
+            stateset->removeTextureAttribute( _unit, osg::StateAttribute::TEXTURE );
+
+            VirtualProgram* vp = VirtualProgram::get(stateset);
+            if ( vp )
+            {
+                vp->removeShader( "oe_contour_vertex" );
+                vp->removeShader( "oe_contour_fragment" );
+            }
+        }
+
+        if ( _unit >= 0 )
+        {
+            engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
+            _unit = -1;
+        }
+    }
+}
+
+
+//-------------------------------------------------------------
+
+void
+ContourMap::mergeConfig(const Config& conf)
+{
+    conf.getIfSet("opacity", _opacity);
+}
+
+Config
+ContourMap::getConfig() const
+{
+    Config conf("contour_map");
+    conf.addIfSet("opacity", _opacity);
+    return conf;
+}
diff --git a/src/osgEarthUtil/Controls b/src/osgEarthUtil/Controls
index 07a4f24..d17f8f7 100644
--- a/src/osgEarthUtil/Controls
+++ b/src/osgEarthUtil/Controls
@@ -22,6 +22,7 @@
 #include <osgEarthUtil/Common>
 #include <osgEarth/Common>
 #include <osgEarth/Units>
+#include <osgEarth/AlphaEffect>
 #include <osg/Drawable>
 #include <osg/Geode>
 #include <osg/MatrixTransform>
@@ -160,7 +161,7 @@ namespace osgEarth { namespace Util { namespace Controls
      * Base class for all controls. You can actually use a Control directly and it
      * will just render as a rectangle.
      */
-    class OSGEARTHUTIL_EXPORT Control : public osg::Referenced
+    class OSGEARTHUTIL_EXPORT Control : public osg::Group
     {
     public:
         enum Side
@@ -230,6 +231,7 @@ namespace osgEarth { namespace Util { namespace Controls
 
         void setVisible( bool value );
         const bool visible() const { return _visible; }
+        bool parentIsVisible() const;
 
         void setForeColor( const osg::Vec4f& value );
         void setForeColor( float r, float g, float b, float a ) { setForeColor( osg::Vec4f(r,g,b,a) ); }
@@ -246,7 +248,13 @@ namespace osgEarth { namespace Util { namespace Controls
         const osgEarth::optional<osg::Vec4f>& activeColor() const { return _activeColor; }
         void clearActiveColor() { _activeColor.unset(); dirty(); }
 
-        bool getParent( osg::ref_ptr<Control>& out ) const;
+        void setBorderColor( const osg::Vec4f& value );
+        void setBorderColor( float r, float g, float b, float a ) { setBorderColor( osg::Vec4f(r,g,b,a) ); }
+        const osgEarth::optional<osg::Vec4f>& borderColor() const { return _borderColor; }
+        void clearBorderColor() { _borderColor.unset(); dirty(); }
+
+        void setBorderWidth( float width );
+        float borderWidth() const { return _borderWidth; }
 
         void setActive( bool value );
         bool getActive() const { return _active; }
@@ -254,6 +262,10 @@ namespace osgEarth { namespace Util { namespace Controls
         void setAbsorbEvents( bool value ) { _absorbEvents = value; }
         bool getAbsorbEvents() const { return _absorbEvents; }
 
+        /** control opacity [0..1] */
+        void setOpacity(float value) { _alphaEffect->setAlpha(value); }
+        float getOpacity() const { return _alphaEffect->getAlpha(); }
+
         void addEventHandler( ControlEventHandler* handler, bool fire =false );
 
     public:
@@ -265,7 +277,7 @@ namespace osgEarth { namespace Util { namespace Controls
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
         virtual void calcFill( const ControlContext& context ) { }
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw    ( const ControlContext& context, DrawableList& out_drawables );
+        virtual void draw    ( const ControlContext& context );
 
         // actual rendering region on the control surface
         const osg::Vec2f& renderPos() const { return _renderPos; }
@@ -294,6 +306,10 @@ namespace osgEarth { namespace Util { namespace Controls
 
         virtual void fireValueChanged( ControlEventHandler* handler =0L ) { }
 
+    protected:
+        osg::Geode* getGeode() { return _geode; }
+        void clearGeode() { _geode->removeDrawables(0, _geode->getNumDrawables()); }
+
     private:
         osgEarth::optional<float> _x, _y, _width, _height;
         bool _hfill, _vfill;
@@ -301,11 +317,14 @@ namespace osgEarth { namespace Util { namespace Controls
         Gutter _padding;
         bool _visible;
         optional<Alignment> _valign, _halign;
-        optional<osg::Vec4f> _backColor, _foreColor, _activeColor;
+        optional<osg::Vec4f> _backColor, _foreColor, _activeColor, _borderColor;
+        float _borderWidth;
         osg::observer_ptr<Control> _parent;
         bool _active;
         bool _absorbEvents;
+        osg::Geode* _geode;
         osg::ref_ptr<osg::Geometry> _geom;
+        osg::ref_ptr<AlphaEffect> _alphaEffect;
     };
 
     typedef std::vector< osg::ref_ptr<Control> > ControlVector;
@@ -365,7 +384,7 @@ namespace osgEarth { namespace Util { namespace Controls
 
     public: // Control
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
-        virtual void draw    ( const ControlContext& context, DrawableList& out_drawables );
+        virtual void draw    ( const ControlContext& context ); //, DrawableList& out_drawables );
 
     private:
         std::string _text;
@@ -432,13 +451,14 @@ namespace osgEarth { namespace Util { namespace Controls
 
     public: // Control
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
-        virtual void draw( const ControlContext& cx, DrawableList& out );
+        virtual void draw( const ControlContext& cx );
 
     private:
         osg::ref_ptr<osg::Image> _image;
         Angular _rotation;
         bool _fixSizeForRot;
         osg::Geometry* _geom;
+        float _opacity;
     };
 
     /**
@@ -463,7 +483,7 @@ namespace osgEarth { namespace Util { namespace Controls
 
     public: // Control
         //virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
-        virtual void draw( const ControlContext& cx, DrawableList& out );
+        virtual void draw( const ControlContext& cx );
 
     protected:
         virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );
@@ -490,7 +510,7 @@ namespace osgEarth { namespace Util { namespace Controls
         bool getValue() const { return _value; }
 
     public:
-        virtual void draw( const ControlContext& cx, DrawableList& out );
+        virtual void draw( const ControlContext& cx );
 
     protected:
         virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx );
@@ -517,7 +537,7 @@ namespace osgEarth { namespace Util { namespace Controls
 
     public: // Control
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw( const ControlContext& context, DrawableList& drawables );
+        virtual void draw( const ControlContext& context );
     };
 
     /**
@@ -532,7 +552,7 @@ namespace osgEarth { namespace Util { namespace Controls
         virtual ~RoundedFrame() { }
 
     public:
-        virtual void draw( const ControlContext& cx, DrawableList& drawables );
+        virtual void draw( const ControlContext& cx );
     };
 
     /**
@@ -548,10 +568,6 @@ namespace osgEarth { namespace Util { namespace Controls
 
         /** dtor */
         virtual ~Container() { }
-
-        // the Frame connected to this container. can be NULL for no frame.
-        void setFrame( Frame* frame );
-        Frame* getFrame() const { return _frame.get(); }
         
         // space between children
         void setChildSpacing( float value );
@@ -573,17 +589,17 @@ namespace osgEarth { namespace Util { namespace Controls
         // default multiple-add function.
         virtual void addControls( const ControlVector& controls );
 
-        // access to the child list.
-        virtual const ControlList& children() const =0;
-
         // clear the controls list.
         virtual void clearControls() =0;
 
+        // gets a vector of pointers to the container's immediate children
+        virtual void getChildren(std::vector<Control*>& out);
+
     public:
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
         virtual void calcFill( const ControlContext& context );
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw( const ControlContext& context, DrawableList& drawables );
+        virtual void draw( const ControlContext& context );
 
     protected:
 
@@ -598,13 +614,12 @@ namespace osgEarth { namespace Util { namespace Controls
         float& renderWidth(Control* child) { return child->_renderSize.x(); }
         float& renderHeight(Control* child) { return child->_renderSize.y(); }
 
-    private:
-        osg::ref_ptr<Frame> _frame;
+    private:        
         float _spacing;
         optional<Alignment> _childhalign;
         optional<Alignment> _childvalign;
         
-        ControlList& mutable_children() { return const_cast<ControlList&>(children()); }
+        //ControlList& mutable_children() { return const_cast<ControlList&>(children()); }
     };
 
     /**
@@ -620,20 +635,19 @@ namespace osgEarth { namespace Util { namespace Controls
         virtual ~VBox() { }
 
     public: // Container
-        virtual const ControlList& children() const { return _controls; }
         virtual void clearControls();
 
     public: // Control        
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
         virtual void calcFill( const ControlContext& context );
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw( const ControlContext& context, DrawableList& drawables );
+        virtual void draw( const ControlContext& context );
 
     protected:
         virtual Control* addControlImpl( Control* control, int index =-1 );
 
     private:
-        ControlList _controls;
+        //ControlList _controls;
     };
 
     /**
@@ -649,20 +663,17 @@ namespace osgEarth { namespace Util { namespace Controls
         virtual ~HBox() { }
 
     public: // Container
-        virtual const ControlList& children() const { return _controls; }
+        //virtual const ControlList& children() const { return _controls; }
         virtual void clearControls();
 
     public: // Control        
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
         virtual void calcFill( const ControlContext& context );
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw( const ControlContext& context, DrawableList& drawables );
+        virtual void draw( const ControlContext& context );
 
     protected:
         virtual Control* addControlImpl( Control* control, int index =-1 );
-
-    private:
-        ControlList _controls;
     };
 
     /**
@@ -682,36 +693,36 @@ namespace osgEarth { namespace Util { namespace Controls
         T* setControl( int col, int row, T* control ) {
             return dynamic_cast<T*>( setControlImpl(col, row, control)); }
 
-        unsigned getNumRows() const { return _rows.size(); }
-        unsigned getNumColumns() const { return _colWidths.size(); }
+        Control* getControl(int col, int row);
+
+        unsigned getNumRows() const;
+        unsigned getNumColumns() const;
 
     public: // Container
-        virtual const ControlList& children() const { return _children; }
         virtual void clearControls();
 
         // adds the controls as a row at the bottom of the grid.
         virtual void addControls( const ControlVector& controls );
 
+        virtual void getChildren(std::vector<Control*>& out);
+
     public: // Control        
         virtual void calcSize( const ControlContext& context, osg::Vec2f& out_size );
         virtual void calcFill( const ControlContext& context );
         virtual void calcPos ( const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize );
-        virtual void draw( const ControlContext& context, DrawableList& drawables );
+        virtual void draw( const ControlContext& context );
 
     protected:
         virtual Control* addControlImpl( Control* control, int index =-1 );
         virtual Control* setControlImpl( int col, int row, Control* control );
 
     private:
-        typedef std::vector< osg::ref_ptr<Control> > Row;
-        typedef std::vector< Row > RowVector;
-        RowVector _rows;
-        ControlList _children;
-        
-        osg::ref_ptr<Control>& cell(int col, int row);
         void expandToInclude(int cols, int rows);
+
+        osg::Group* getRow(unsigned index);
         
         std::vector<float> _rowHeights, _colWidths;
+        unsigned _maxCols;
     };
 
     class OSGEARTHUTIL_EXPORT RefNodeVector :
@@ -836,7 +847,7 @@ namespace osgEarth { namespace Util { namespace Controls
         void removeControl( Control* control );
 
         /** gets the top-most control that intersects the specified position. */
-        Control* getControlAtMouse( float x, float y ) const;
+        Control* getControlAtMouse( float x, float y );
 
         /** Toggles whether ControlNodes are allowed to overlap. */
         void setAllowControlNodeOverlap( bool value );
@@ -861,12 +872,10 @@ namespace osgEarth { namespace Util { namespace Controls
 
     protected:
 
-        ControlList     _controls;
-        GeodeTable      _geodeTable;
         ControlContext  _context;
         bool            _contextDirty;
         bool            _updatePending;
-
+        
         typedef std::map< osg::observer_ptr<osgGA::GUIEventHandler>, osg::observer_ptr<osgViewer::View> > EventHandlersMap;
         EventHandlersMap _eventHandlersMap;
 
diff --git a/src/osgEarthUtil/Controls.cpp b/src/osgEarthUtil/Controls.cpp
old mode 100644
new mode 100755
index 2b73caa..9f94822
--- a/src/osgEarthUtil/Controls.cpp
+++ b/src/osgEarthUtil/Controls.cpp
@@ -25,15 +25,21 @@
 #include <osgGA/GUIEventHandler>
 #include <osgText/Text>
 #include <osgUtil/RenderBin>
+#include <osgEarthSymbology/Style>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthSymbology/GeometryRasterizer>
+#include <osgEarthFeatures/PolygonizeLines>
 #include <osg/Version>
 #include <osgEarth/Common>
 #include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
 #include <osgEarth/Utils>
 #include <osgEarth/CullingUtils>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/VirtualProgram>
 
 using namespace osgEarth;
+using namespace osgEarth::Features;
 using namespace osgEarth::Symbology;
 using namespace osgEarth::Util;
 using namespace osgEarth::Util::Controls;
@@ -44,43 +50,6 @@ using namespace osgEarth::Util::Controls;
 
 namespace
 {
-    // ControlNodeBin shaders.
-
-    const char* s_controlVertexShader =
-        "void main() \n"
-        "{ \n"
-        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
-        "    gl_TexCoord[0] = gl_MultiTexCoord0; \n"
-        "    gl_FrontColor = gl_Color; \n"
-        "} \n";
-
-    const char* s_imageControlFragmentShader =
-        "uniform sampler2D tex0; \n"
-        "uniform float visibleTime; \n"
-        "uniform float osg_FrameTime; \n"
-        "void main() \n"
-        "{ \n"
-        "    float opacity = clamp( osg_FrameTime - visibleTime, 0.0, 1.0 ); \n"
-        "    vec4 texel = texture2D(tex0, gl_TexCoord[0].st); \n"
-        "    gl_FragColor = vec4(texel.rgb, texel.a * opacity); \n"
-        "} \n";
-
-    const char* s_labelControlFragmentShader =
-        "uniform sampler2D tex0; \n"
-        "uniform float visibleTime; \n"
-        "uniform float osg_FrameTime; \n"
-        "void main() \n"
-        "{ \n"
-        "    float opacity = clamp( osg_FrameTime - visibleTime, 0.0, 1.0 ); \n"
-        "    vec4 texel = texture2D(tex0, gl_TexCoord[0].st); \n"       
-        "    gl_FragColor = vec4(gl_Color.rgb, texel.a * opacity); \n"
-        "} \n";
-}
-
-// ---------------------------------------------------------------------------
-
-namespace
-{
     void calculateRotatedSize( float w, float h, float angle_rad, float& out_w, float& out_h )
     {
         float x1 = -w/2, x2 = w/2, x3 =  w/2, x4 = -w/2;
@@ -117,6 +86,18 @@ namespace
         out.y() = (c.y()-y)*cosa + (c.x()-x)*sina + c.y();
         out.z() = 0.0f;
     }
+
+    // Convenience method to create safe Control geometry.
+    // Since Control geometry can change, we need to always set it
+    // to DYNAMIC data variance.
+    osg::Geometry* newGeometry()
+    {
+        osg::Geometry* geom = new osg::Geometry();
+        geom->setUseVertexBufferObjects( true );
+        geom->setUseDisplayList( false );
+        geom->setDataVariance( osg::Object::DYNAMIC );
+        return geom;
+    }
 }
 
 // ---------------------------------------------------------------------------
@@ -203,8 +184,14 @@ Control::init()
     _vfill = false;    
     _visible = true;
     _active = false;
-    _absorbEvents = false;
+    _absorbEvents = true;
     _dirty = true;
+    _borderWidth = 1.0f;
+
+    _geode = new osg::Geode();
+    this->addChild( _geode );
+
+    _alphaEffect = new AlphaEffect(this->getOrCreateStateSet());
 }
 
 void
@@ -387,6 +374,47 @@ Control::setVertFill( bool vfill, float minHeight ) {
     }
 }
 
+bool 
+Control::parentIsVisible() const
+{
+    bool visible = true;
+
+    // ------------------------------------------------------------------------
+    // -- If visible through any parent, consider it visible and return true --
+    // -- Also visible if the parent is not a control (is a top-level)       --
+    // ------------------------------------------------------------------------
+    for( unsigned i=0; i<getNumParents(); ++i )
+    {
+        const Control* c = dynamic_cast<const Control*>( getParent(i) );
+
+        // ----------------------------------------
+        // -- Parent not a control, keep looking --
+        // ----------------------------------------
+        if( c == NULL )
+            continue;
+        
+        // -----------------------------------------
+        // -- If this path is visible, we're done --
+        // -----------------------------------------
+        if( c->visible() && c->parentIsVisible() )
+        {
+            return true;
+        }
+        else
+        {
+            // ---------------------------------------------
+            // -- If their is a parent control, but it's  --
+            // -- not visible, change our assumption but  --
+            // -- keep looking at other parent controls   --
+            // ---------------------------------------------            
+            visible = false;
+        }
+    }
+
+    return visible;
+}
+
+
 void
 Control::setForeColor( const osg::Vec4f& value ) {
     if ( value != _foreColor.value() ) {
@@ -413,6 +441,14 @@ Control::setActiveColor( const osg::Vec4f& value ) {
 }
 
 void
+Control::setBorderColor( const osg::Vec4f& value ) {
+    if ( value != _borderColor.value() ) {
+        _borderColor = value;
+        dirty();
+    }
+}
+
+void
 Control::addEventHandler( ControlEventHandler* handler, bool fire )
 {
     _eventHandlers.push_back( handler );
@@ -420,13 +456,6 @@ Control::addEventHandler( ControlEventHandler* handler, bool fire )
         fireValueChanged( handler );
 }
 
-bool
-Control::getParent( osg::ref_ptr<Control>& out ) const
-{
-    out = _parent.get();
-    return out.valid();
-}
-
 void
 Control::setActive( bool value ) {
     if ( value != _active ) {
@@ -437,14 +466,46 @@ Control::setActive( bool value ) {
 }
 
 void
+Control::setBorderWidth( float value ) {
+    if ( value != _borderWidth ) {
+        _borderWidth = value;
+        dirty();
+    }
+}
+
+namespace
+{
+    void dirtyParent(osg::Group* p)
+    {
+        if ( p )
+        {
+            Control* c = dynamic_cast<Control*>( p );
+            if ( c )
+            {
+                c->dirty();
+            }
+            else if ( dynamic_cast<ControlCanvas*>( p ) )
+            {
+                return;
+            }
+            else
+            {
+                for( unsigned i=0; i<p->getNumParents(); ++i )
+                {
+                    dirtyParent( p->getParent(i) );
+                }
+            }
+        }
+    }
+}
+
+void
 Control::dirty()
 {
     _dirty = true;
-    osg::ref_ptr<Control> parent;
-    if ( getParent( parent ) )
+    for(unsigned i=0; i<getNumParents(); ++i)
     {
-        parent->dirty();
-        parent.release();
+        dirtyParent( getParent(i) );
     }
 }
 
@@ -498,7 +559,6 @@ Control::calcPos(const ControlContext& cx, const osg::Vec2f& cursor, const osg::
     {
         if ( _valign == ALIGN_CENTER )
         {
-            //_renderPos.y() = cursor.y() + 0.5*(parentSize.y() - _renderSize.y());
             _renderPos.y() = cursor.y() + 0.5*parentSize.y() - 0.5*(_renderSize.y() - padding().y());
         }
         else if ( _valign == ALIGN_BOTTOM )
@@ -521,36 +581,65 @@ Control::intersects( float x, float y ) const
 }
 
 void
-Control::draw(const ControlContext& cx, DrawableList& out )
+Control::draw(const ControlContext& cx)
 {
+    clearGeode();
+
     // by default, rendering a Control directly results in a colored quad. Usually however
     // you will not render a Control directly, but rather one of its subclasses.
-    if ( visible() == true )
+    if ( visible()  && parentIsVisible() )
     {
         if ( !(_backColor.isSet() && _backColor->a() == 0) && _renderSize.x() > 0 && _renderSize.y() > 0 )
         {
-            float vph = cx._vp->height(); // - padding().bottom();
+            float vph = cx._vp->height();
 
-            _geom = new osg::Geometry();
-            _geom->setUseVertexBufferObjects(true);
+            // draw the background poly:
+            {
+                _geom = newGeometry();
 
-            float rx = _renderPos.x() - padding().left();
-            float ry = _renderPos.y() - padding().top();
+                float rx = _renderPos.x() - padding().left();
+                float ry = _renderPos.y() - padding().top();
 
-            osg::Vec3Array* verts = new osg::Vec3Array(4);
-            _geom->setVertexArray( verts );
-            (*verts)[0].set( rx, vph - ry, 0 );
-            (*verts)[1].set( rx, vph - ry - _renderSize.y(), 0 );
-            (*verts)[2].set( rx + _renderSize.x(), vph - ry - _renderSize.y(), 0 );
-            (*verts)[3].set( rx + _renderSize.x(), vph - ry, 0 );
-            _geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 4 ) );
+                osg::Vec3Array* verts = new osg::Vec3Array(4);
+                _geom->setVertexArray( verts );
+                (*verts)[0].set( rx, vph - ry, 0 );
+                (*verts)[1].set( rx, vph - ry - _renderSize.y(), 0 );
+                (*verts)[2].set( rx + _renderSize.x(), vph - ry - _renderSize.y(), 0 );
+                (*verts)[3].set( rx + _renderSize.x(), vph - ry, 0 );
+                _geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 4 ) );
 
-            osg::Vec4Array* colors = new osg::Vec4Array(1);
-            (*colors)[0] = _active && _activeColor.isSet() ? _activeColor.value() : _backColor.value();
-            _geom->setColorArray( colors );
-            _geom->setColorBinding( osg::Geometry::BIND_OVERALL );
+                osg::Vec4Array* colors = new osg::Vec4Array(1);
+                (*colors)[0] = _active && _activeColor.isSet() ? _activeColor.value() : _backColor.value();
+                _geom->setColorArray( colors );
+                _geom->setColorBinding( osg::Geometry::BIND_OVERALL );
 
-            out.push_back( _geom.get() );
+                getGeode()->addDrawable( _geom.get() );
+            }
+
+            // draw the border:
+            if ( _borderColor.isSet() && _borderWidth > 0.0f )
+            {
+                float rx = _renderPos.x() - padding().left();
+                float ry = _renderPos.y() - padding().top();
+
+                osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array(5);
+                (*verts)[0].set( rx, vph - ry, 0 );
+                (*verts)[1].set( rx, vph - ry - _renderSize.y(), 0 );
+                (*verts)[2].set( rx + _renderSize.x(), vph - ry - _renderSize.y(), 0 );
+                (*verts)[3].set( rx + _renderSize.x(), vph - ry, 0 );
+                (*verts)[4].set( rx, vph - ry, 0 );
+
+                Stroke stroke;
+                stroke.color() = *_borderColor;
+                stroke.width() = _borderWidth;
+                stroke.lineCap() = Stroke::LINECAP_SQUARE;
+                stroke.lineJoin() = Stroke::LINEJOIN_MITRE;
+
+                PolygonizeLinesOperator makeBorder(stroke);
+                osg::Geometry* geom = makeBorder( verts.get(), 0L );
+
+                getGeode()->addDrawable( geom );
+            }
         }
 
         _dirty = false;
@@ -560,7 +649,10 @@ Control::draw(const ControlContext& cx, DrawableList& out )
 bool
 Control::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx )
 {
-    bool handled = false;    
+    bool handled = false;
+
+    if( !visible() || !parentIsVisible() )
+        return false;
 
     if ( _eventHandlers.size() > 0 )
     {    
@@ -597,6 +689,7 @@ namespace
     // override osg Text to get at some of the internal properties
     struct LabelText : public osgText::Text
     {
+        LabelText() : osgText::Text() { setDataVariance(osg::Object::DYNAMIC); }
         const osg::BoundingBox& getTextBB() const { return _textBB; }
         const osg::Matrix& getATMatrix(int contextID) const { return _autoTransformCache[contextID]._matrix; }
     };
@@ -634,7 +727,7 @@ _text    ( text ),
 _fontSize( fontSize ),
 _encoding( osgText::String::ENCODING_UNDEFINED ),
 _backdropType( osgText::Text::OUTLINE ),
-_backdropImpl( osgText::Text::STENCIL_BUFFER ),
+_backdropImpl( osgText::Text::NO_DEPTH_BUFFER ),
 _backdropOffset( 0.03f )
 {    
     setFont( Registry::instance()->getDefaultFont() );    
@@ -649,7 +742,7 @@ _text    ( text ),
 _fontSize( fontSize ),
 _encoding( osgText::String::ENCODING_UNDEFINED ),
 _backdropType( osgText::Text::OUTLINE ),
-_backdropImpl( osgText::Text::STENCIL_BUFFER ),
+_backdropImpl( osgText::Text::NO_DEPTH_BUFFER ),
 _backdropOffset( 0.03f )
 {    	
     setFont( Registry::instance()->getDefaultFont() );   
@@ -663,7 +756,7 @@ LabelControl::LabelControl(Control*           valueControl,
 _fontSize( fontSize ),
 _encoding( osgText::String::ENCODING_UNDEFINED ),
 _backdropType( osgText::Text::OUTLINE ),
-_backdropImpl( osgText::Text::STENCIL_BUFFER ),
+_backdropImpl( osgText::Text::NO_DEPTH_BUFFER ),
 _backdropOffset( 0.03f )
 {
     setFont( Registry::instance()->getDefaultFont() );    
@@ -680,7 +773,7 @@ LabelControl::LabelControl(Control*           valueControl,
 _fontSize( fontSize ),
 _encoding( osgText::String::ENCODING_UNDEFINED ),
 _backdropType( osgText::Text::OUTLINE ),
-_backdropImpl( osgText::Text::STENCIL_BUFFER ),
+_backdropImpl( osgText::Text::NO_DEPTH_BUFFER ),
 _backdropOffset( 0.03f )
 {    	
     setFont( Registry::instance()->getDefaultFont() );   
@@ -772,25 +865,25 @@ LabelControl::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
         // we have to create the drawable during the layout pass so we can calculate its size.
         LabelText* t = new LabelText();
 
-#if 1
-        // needs a special shader
-        // todo: doesn't work. why?
-        osg::Program* program = new osg::Program();
-        program->addShader( new osg::Shader( osg::Shader::VERTEX, s_controlVertexShader ) );
-        program->addShader( new osg::Shader( osg::Shader::FRAGMENT, s_labelControlFragmentShader ) );
-        t->getOrCreateStateSet()->setAttributeAndModes( program, osg::StateAttribute::ON );
-#endif
-
         t->setText( _text, _encoding );
-        // yes, object coords. screen coords won't work becuase the bounding box will be wrong.
+        // yes, object coords. screen coords won't work because the bounding box will be wrong.
         t->setCharacterSizeMode( osgText::Text::OBJECT_COORDS );
         t->setCharacterSize( _fontSize );
+
         // always align to top. layout alignment gets calculated layer in Control::calcPos().
         t->setAlignment( osgText::Text::LEFT_TOP ); 
         t->setColor( foreColor().value() );
+
+        // set up the font. When you do this, OSG automatically tries to put the text object
+        // in the transparent render bin. We do not want that, so we will set it back to
+        // INHERIT.
         if ( _font.valid() )
             t->setFont( _font.get() );
 
+        if ( t->getStateSet() )
+            t->getStateSet()->setRenderBinToInherit();
+
+        // set up the backdrop halo:
         if ( haloColor().isSet() )
         {
             t->setBackdropType( _backdropType );
@@ -817,9 +910,9 @@ LabelControl::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
             (_bmax.x() - _bmin.x()) + padding().x(),
             (_bmax.y() - _bmin.y()) + padding().y() );
 
-    // If width explicitly set and > measured width of label text - use it.
-    if (width().isSet() && width().get() > _renderSize.x()) _renderSize.x() = width().get();
-    
+        // If width explicitly set and > measured width of label text - use it.
+        if (width().isSet() && width().get() > _renderSize.x()) _renderSize.x() = width().get();
+
         _drawable = t;
 
         out_size.set(
@@ -830,23 +923,21 @@ LabelControl::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
     {
         out_size.set(0,0);
     }
-
-    //_dirty = false;
 }
 
 void
-LabelControl::draw( const ControlContext& cx, DrawableList& out )
+LabelControl::draw( const ControlContext& cx )
 {
-    if ( _drawable.valid() && visible() == true )
-    {
-        Control::draw( cx, out );
+    Control::draw( cx );
 
-        float vph = cx._vp->height(); // - padding().bottom();
+    if ( _drawable.valid() && visible() && parentIsVisible() )
+    {
+        float vph = cx._vp->height();
 
         LabelText* t = static_cast<LabelText*>( _drawable.get() );
         osg::BoundingBox bbox = t->getTextBB();
         t->setPosition( osg::Vec3( _renderPos.x(), vph - _renderPos.y(), 0 ) );
-        out.push_back( _drawable.get() );
+        getGeode()->addDrawable( _drawable.get() );
     }
 }
 
@@ -897,8 +988,9 @@ LabelControl(text)
 // ---------------------------------------------------------------------------
 
 ImageControl::ImageControl( osg::Image* image ) :
-_rotation( 0.0, Units::RADIANS ),
-_fixSizeForRot( false )
+_rotation     ( 0.0, Units::RADIANS ),
+_fixSizeForRot( false ),
+_opacity      ( 1.0f )
 {
     setImage( image );
 }
@@ -978,13 +1070,14 @@ ImageControl::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 #undef IMAGECONTROL_TEXRECT
 
 void
-ImageControl::draw( const ControlContext& cx, DrawableList& out )
+ImageControl::draw( const ControlContext& cx )
 {
-    if ( visible() == true && _image.valid() )
+    Control::draw( cx );
+
+    if ( visible() && parentIsVisible() && _image.valid() )
     {
         //TODO: this is not precisely correct..images get deformed slightly..
-        osg::Geometry* g = new osg::Geometry();
-        g->setUseVertexBufferObjects(true);
+        osg::Geometry* g = newGeometry();
 
         float rx = osg::round( _renderPos.x() );
         float ry = osg::round( _renderPos.y() );
@@ -1052,15 +1145,8 @@ ImageControl::draw( const ControlContext& cx, DrawableList& out )
 
         osg::TexEnv* texenv = new osg::TexEnv( osg::TexEnv::MODULATE );
         g->getStateSet()->setTextureAttributeAndModes( 0, texenv, osg::StateAttribute::ON );
-        
-#ifndef IMAGECONTROL_TEXRECT
-        osg::Program* program = new osg::Program();
-        program->addShader( new osg::Shader( osg::Shader::VERTEX, s_controlVertexShader ) );
-        program->addShader( new osg::Shader( osg::Shader::FRAGMENT, s_imageControlFragmentShader ) );
-        g->getStateSet()->setAttributeAndModes( program, osg::StateAttribute::ON );
-#endif
 
-        out.push_back( g );
+        getGeode()->addDrawable( g );
 
         _dirty = false;
     }
@@ -1073,13 +1159,6 @@ _min(min),
 _max(max),
 _value(value)
 {
-    //if ( _max <= _min )
-    //    _max = _min+1.0f;
-    //if ( _value < _min )
-    //    _value = _min;
-    //if ( _value > _max )
-    //    _value = _max;
-
     setHorizFill( true );
     setVertAlign( ALIGN_CENTER );
     setHeight( 20.0f );
@@ -1107,7 +1186,6 @@ HSliderControl::fireValueChanged( ControlEventHandler* oneHandler )
 void
 HSliderControl::setValue( float value, bool notify )
 {
-    //value = osg::clampBetween( value, _min, _max );
     if ( value != _value )
     {
         _value = value;
@@ -1156,14 +1234,13 @@ HSliderControl::setMax( float max, bool notify )
 }
 
 void
-HSliderControl::draw( const ControlContext& cx, DrawableList& out )
+HSliderControl::draw( const ControlContext& cx )
 {
-    Control::draw( cx, out );
+    Control::draw( cx );
 
-    if ( visible() == true )
+    if ( visible() == true && parentIsVisible())
     {
-        osg::ref_ptr<osg::Geometry> g = new osg::Geometry();
-        g->setUseVertexBufferObjects(true);
+        osg::ref_ptr<osg::Geometry> g = newGeometry();
 
         float rx = osg::round( _renderPos.x() );
         float ry = osg::round( _renderPos.y() );
@@ -1196,7 +1273,7 @@ HSliderControl::draw( const ControlContext& cx, DrawableList& out )
             g->setColorArray( c );
             g->setColorBinding( osg::Geometry::BIND_OVERALL );
 
-            out.push_back( g.get() );
+            getGeode()->addDrawable( g.get() );
         }
     }
 }
@@ -1204,11 +1281,18 @@ HSliderControl::draw( const ControlContext& cx, DrawableList& out )
 bool
 HSliderControl::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx )
 {
+    if( !visible() || !parentIsVisible() )
+        return false;
+
     if ( ea.getEventType() == osgGA::GUIEventAdapter::DRAG )
     {
         float relX = ea.getX() - _renderPos.x();
 
-        setValue( _min + (_max-_min) * ( relX/_renderSize.x() ) );
+        if ( _min < _max )
+            setValue( osg::clampBetween(_min + (_max-_min) * ( relX/_renderSize.x() ), _min, _max) );
+        else
+            setValue( osg::clampBetween(_min - (_min-_max) * ( relX/_renderSize.x() ), _max, _min) );
+
         aa.requestRedraw();
 
         return true;
@@ -1261,14 +1345,13 @@ CheckBoxControl::setValue( bool value )
 }
 
 void
-CheckBoxControl::draw( const ControlContext& cx, DrawableList& out )
+CheckBoxControl::draw( const ControlContext& cx )
 {
-    Control::draw( cx, out );
+    Control::draw( cx );
 
-    if ( visible() == true )
+    if ( visible() == true && parentIsVisible() )
     {
-        osg::Geometry* g = new osg::Geometry();
-        g->setUseVertexBufferObjects(true);
+        osg::Geometry* g = newGeometry();
 
         float rx = osg::round( _renderPos.x() );
         float ry = osg::round( _renderPos.y() );
@@ -1301,13 +1384,16 @@ CheckBoxControl::draw( const ControlContext& cx, DrawableList& out )
         g->setColorArray( c );
         g->setColorBinding( osg::Geometry::BIND_OVERALL );
 
-        out.push_back( g );
+        getGeode()->addDrawable( g );
     }
 }
 
 bool
 CheckBoxControl::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx )
 {
+    if( !visible() || !parentIsVisible() )
+        return false;
+
     if ( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
     {
         setValue( !_value );
@@ -1331,7 +1417,7 @@ Frame::calcPos(const ControlContext& context, const osg::Vec2f& cursor, const os
 }
 
 void
-Frame::draw( const ControlContext& cx, DrawableList& out )
+Frame::draw( const ControlContext& cx )
 {
     if ( !getImage() || getImage()->s() != _renderSize.x() || getImage()->t() != _renderSize.y() )
     {
@@ -1353,8 +1439,8 @@ Frame::draw( const ControlContext& cx, DrawableList& out )
         const_cast<Frame*>(this)->setImage( image );
     }
 
-    Control::draw( cx, out );       // draws the background
-    ImageControl::draw( cx, out );  // draws the border
+    Control::draw( cx );       // draws the background
+    ImageControl::draw( cx );  // draws the border
 }
 
 // ---------------------------------------------------------------------------
@@ -1365,7 +1451,7 @@ RoundedFrame::RoundedFrame()
 }
 
 void
-RoundedFrame::draw( const ControlContext& cx, DrawableList& out )
+RoundedFrame::draw( const ControlContext& cx )
 {
     if ( Geometry::hasBufferOperation() )
     {
@@ -1392,12 +1478,12 @@ RoundedFrame::draw( const ControlContext& cx, DrawableList& out )
             const_cast<RoundedFrame*>(this)->setImage( image );
         }
 
-        ImageControl::draw( cx, out );
+        ImageControl::draw( cx );
     }
     else
     {
         // fallback: draw a non-rounded frame.
-        Frame::draw( cx, out );
+        Frame::draw( cx );
     }
 }
 
@@ -1416,11 +1502,12 @@ Container::Container( const Alignment& halign, const Alignment& valign, const Gu
 }
 
 void
-Container::setFrame( Frame* frame )
+Container::getChildren(std::vector<Control*>& out)
 {
-    if ( frame != _frame.get() ) {
-        _frame = frame;
-        dirty();
+    for(unsigned i=1; i<getNumChildren(); ++i )
+    {
+        Control* c = dynamic_cast<Control*>( getChild(i) );
+        if ( c ) out.push_back( c );
     }
 }
 
@@ -1458,9 +1545,11 @@ Container::applyChildAligns()
 {
     if ( _childhalign.isSet() || _childvalign.isSet() )
     {
-        for( ControlList::iterator i = mutable_children().begin(); i != mutable_children().end(); ++i )
+        std::vector<Control*> children;
+        getChildren( children );
+        for( std::vector<Control*>::iterator i = children.begin(); i != children.end(); ++i )
         {
-            Control* child = i->get();
+            Control* child = (*i);
 
             if ( _childvalign.isSet() && !child->vertAlign().isSet() )
                 child->setVertAlign( *_childvalign );
@@ -1468,7 +1557,6 @@ Container::applyChildAligns()
             if ( _childhalign.isSet() && !child->horizAlign().isSet() )
                 child->setHorizAlign( *_childhalign );
         }
-
         dirty();
     }
 }
@@ -1478,28 +1566,29 @@ Container::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 {
     if ( visible() == true )
     {
-        if ( _frame.valid() )
-        {
-            _frame->setWidth( _renderSize.x() );
-            _frame->setHeight( _renderSize.y() );
-
-            osg::Vec2f dummy;
-            _frame->calcSize( cx, dummy );
-        }
+        float w = width().isSet()  ? std::max( width().value(),  _renderSize.x() ) : _renderSize.x();
+        float h = height().isSet() ? std::max( height().value(), _renderSize.y() ) : _renderSize.y();
 
-        // no need to set the output vars.
+        _renderSize.set(
+            w + padding().x(),
+            h + padding().y() );
 
-        //_dirty = false;
+        out_size.set(
+            _renderSize.x() + margin().x(),
+            _renderSize.y() + margin().y() );
     }
 }
 
 void
 Container::calcFill(const ControlContext& cx)
 {
-    for( ControlList::iterator i = mutable_children().begin(); i != mutable_children().end(); ++i )
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* child = i->get();
-        child->calcFill( cx );
+        Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
+        {
+            child->calcFill( cx );
+        }
     }
 }
 
@@ -1507,36 +1596,35 @@ void
 Container::calcPos(const ControlContext& context, const osg::Vec2f& cursor, const osg::Vec2f& parentSize)
 {
     Control::calcPos( context, cursor, parentSize );
-
-    // process the frame.. it's not a child of the container
-    if ( visible() == true && _frame.valid() )
-    {
-        _frame->calcPos( context, _renderPos - padding().offset(), parentSize );
-    }
 }
 
 void
-Container::draw( const ControlContext& cx, DrawableList& out )
+Container::draw( const ControlContext& cx )
 {
-    if ( visible() == true )
-    {
-        if ( _frame.valid() )
-            _frame->draw( cx, out );
-        Control::draw( cx, out );
-    }
+    Control::draw( cx );
 }
 
 bool
 Container::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, ControlContext& cx )
 {
+    if( !visible() || !parentIsVisible() )
+        return false;
+
     bool handled = false;
-    for( ControlList::const_reverse_iterator i = children().rbegin(); i != children().rend(); ++i )
-    {
-        Control* child = i->get();
-        if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME || child->intersects( ea.getX(), cx._vp->height() - ea.getY() ) )
-            handled = child->handle( ea, aa, cx );
-        if ( handled )
-            break;
+    std::vector<Control*> children;
+    getChildren( children );
+    //OE_NOTICE << "handling " << children.size() << std::endl;
+    for( std::vector<Control*>::reverse_iterator i = children.rbegin(); i != children.rend(); ++i )
+    {
+        Control* child = *i;
+        //Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
+        {
+            if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME || child->intersects( ea.getX(), cx._vp->height() - ea.getY() ) )
+                handled = child->handle( ea, aa, cx );
+            if ( handled )
+                break;
+        }
     }
 
     return handled ? handled : Control::handle( ea, aa, cx );
@@ -1567,22 +1655,16 @@ Container( halign, valign, padding, spacing )
 Control*
 VBox::addControlImpl( Control* control, int index )
 {
-    if ( index < 0 )
-        _controls.push_back( control );
-    else
-        _controls.insert( _controls.begin() + osg::minimum(index,(int)_controls.size()-1), control );
-    control->setParent( this );
-
+    insertChild( index, control );
     applyChildAligns();
     dirty();
-
     return control;
 }
 
 void
 VBox::clearControls()
 {
-    _controls.clear();
+    removeChildren( 1, getNumChildren()-1 );
     dirty();
 }
 
@@ -1594,29 +1676,21 @@ VBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
         _renderSize.set( 0, 0 );
 
         // collect all the members, growing the container size vertically
-        for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
+        for( unsigned i=1; i<getNumChildren(); ++i )
         {
-            Control* child = i->get();
-            osg::Vec2f childSize;
-            bool first = i == _controls.begin();
+            Control* child = dynamic_cast<Control*>( getChild(i) );
+            if ( child )
+            {
+                osg::Vec2f childSize;
+                bool first = i == 1; //_controls.begin();
 
-            child->calcSize( cx, childSize );
+                child->calcSize( cx, childSize );
 
-            _renderSize.x() = osg::maximum( _renderSize.x(), childSize.x() );
-            _renderSize.y() += first ? childSize.y() : childSpacing() + childSize.y();
+                _renderSize.x() = osg::maximum( _renderSize.x(), childSize.x() );
+                _renderSize.y() += first ? childSize.y() : childSpacing() + childSize.y();
+            }
         }
 
-        _renderSize.set(
-            _renderSize.x() + padding().x(),
-            _renderSize.y() + padding().y() );
-
-        // process fills:
-
-
-        out_size.set(
-            _renderSize.x() + margin().x(),
-            _renderSize.y() + margin().y() );
-
         Container::calcSize( cx, out_size );
     }
     else
@@ -1628,29 +1702,29 @@ VBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 void
 VBox::calcFill(const ControlContext& cx)
 {
-    //Container::calcFill( cx );
-
     float used_x = padding().x();
     float used_y = padding().y() - childSpacing();
 
     Control* hc = 0L;
     Control* vc = 0L;
 
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end() && (!hc || !vc); ++i )
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* child = i->get();
-
-        used_y += child->margin().y() + childSpacing();
-        if ( !hc && child->horizFill() )
+        Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
         {
-            hc = child;
-            used_x += child->margin().x();
-        }
+            used_y += child->margin().y() + childSpacing();
+            if ( !hc && child->horizFill() )
+            {
+                hc = child;
+                used_x += child->margin().x();
+            }
 
-        if ( !vc && child->vertFill() )
-            vc = child;
-        else
-            used_y += child->renderSize().y();
+            if ( !vc && child->vertFill() )
+                vc = child;
+            else
+                used_y += child->renderSize().y();
+        }
     }
 
     if ( hc && renderWidth(hc) < (_renderSize.x() - used_x) )
@@ -1671,24 +1745,29 @@ VBox::calcPos(const ControlContext& cx, const osg::Vec2f& cursor, const osg::Vec
 
     osg::Vec2f renderArea = _renderSize - padding().size();
 
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* child = i->get();
-        child->calcPos( cx, childCursor, renderArea ); // GW1
-        float deltaY = child->margin().top() + child->renderSize().y() + child->margin().bottom() + childSpacing();
-        childCursor.y() += deltaY;
-        renderArea.y() -= deltaY;
+        Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
+        {
+            child->calcPos( cx, childCursor, renderArea ); // GW1
+            float deltaY = child->margin().top() + child->renderSize().y() + child->margin().bottom() + childSpacing();
+            childCursor.y() += deltaY;
+            renderArea.y() -= deltaY;
+        }
     }
 }
 
 void
-VBox::draw( const ControlContext& cx, DrawableList& out )
+VBox::draw( const ControlContext& cx )
 {
-    if ( visible() == true )
+    Container::draw( cx );
+
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Container::draw( cx, out );
-        for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
-            i->get()->draw( cx, out );
+        Control* c = dynamic_cast<Control*>(getChild(i));
+        if ( c )
+            c->draw( cx );
     }
 }
 
@@ -1708,23 +1787,16 @@ Container( halign, valign, padding, spacing )
 Control*
 HBox::addControlImpl( Control* control, int index )
 {
-    if ( index < 0 )
-        _controls.push_back( control );
-    else
-        _controls.insert( _controls.begin() + osg::minimum(index,(int)_controls.size()-1), control );
-    
-    control->setParent( this );
+    insertChild(index, control);
     applyChildAligns();
-
     dirty();
-
     return control;
 }
 
 void
 HBox::clearControls()
 {
-    _controls.clear();
+    removeChildren(1, getNumChildren()-1);
     dirty();
 }
 
@@ -1736,28 +1808,23 @@ HBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
         _renderSize.set( 0, 0 );
 
         // collect all the members, growing the container is its orientation.
-        for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
+        for( unsigned i=1; i<getNumChildren(); ++i )
         {
-            Control* child = i->get();
-            osg::Vec2f childSize;
-            bool first = i == _controls.begin();
+            Control* child = dynamic_cast<Control*>( getChild(i) );
+            if ( child )
+            {
+                osg::Vec2f childSize;
+                bool first = i == 1;
 
-            child->calcSize( cx, childSize );
+                child->calcSize( cx, childSize );
 
-            _renderSize.x() += first ? childSize.x() : childSpacing() + childSize.x();
-            _renderSize.y() = osg::maximum( _renderSize.y(), childSize.y() );
+                _renderSize.x() += first ? childSize.x() : childSpacing() + childSize.x();
+                _renderSize.y() = osg::maximum( _renderSize.y(), childSize.y() );
+            }
         }
     
-    // If width explicitly set and > total width of children - use it
-    if (width().isSet() && width().get() > _renderSize.x()) _renderSize.x() = width().get();
-
-        _renderSize.set(
-            _renderSize.x() + padding().x(),
-            _renderSize.y() + padding().y() );
-
-        out_size.set(
-            _renderSize.x() + margin().x(),
-            _renderSize.y() + margin().y() );
+        // If width explicitly set and > total width of children - use it
+        if (width().isSet() && width().get() > _renderSize.x()) _renderSize.x() = width().get();
 
         Container::calcSize( cx, out_size );
     }
@@ -1766,30 +1833,28 @@ HBox::calcSize(const ControlContext& cx, osg::Vec2f& out_size)
 void
 HBox::calcFill(const ControlContext& cx)
 {
-    //Container::calcFill( cx );
-
     float used_x = padding().x() - childSpacing();
     float used_y = padding().y();
 
     Control* hc = 0L;
     Control* vc = 0L;
 
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end() && (!hc || !vc); ++i )
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* child = i->get();
-
-        //child->calcFill(cx);
-
-        used_x += child->margin().x() + childSpacing();
-        if ( !hc && child->horizFill() )
-            hc = child;
-        else
-            used_x += child->renderSize().x();
-
-        if ( !vc && child->vertFill() )
+        Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
         {
-            vc = child;
-            used_y += child->margin().y();
+            used_x += child->margin().x() + childSpacing();
+            if ( !hc && child->horizFill() )
+                hc = child;
+            else
+                used_x += child->renderSize().x();
+
+            if ( !vc && child->vertFill() )
+            {
+                vc = child;
+                used_y += child->margin().y();
+            }
         }
     }
 
@@ -1810,56 +1875,96 @@ HBox::calcPos(const ControlContext& cx, const osg::Vec2f& cursor, const osg::Vec
     osg::Vec2f childCursor = _renderPos;
 
     osg::Vec2f renderArea = _renderSize - padding().size();
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
+
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* child = i->get();
-        child->calcPos( cx, childCursor, renderArea );
-        float deltaX = child->margin().left() + child->renderSize().x() + child->margin().right() + childSpacing();
-        childCursor.x() += deltaX;
-        renderArea.x() -= deltaX;        
+        Control* child = dynamic_cast<Control*>( getChild(i) );
+        if ( child )
+        {
+            child->calcPos( cx, childCursor, renderArea );
+            float deltaX = child->margin().left() + child->renderSize().x() + child->margin().right() + childSpacing();
+            childCursor.x() += deltaX;
+            renderArea.x() -= deltaX;
+        }
     }
 }
 
 void
-HBox::draw( const ControlContext& cx, DrawableList& out ) 
+HBox::draw( const ControlContext& cx )
 {
-    Container::draw( cx, out );
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
-        i->get()->draw( cx, out );
+    Container::draw( cx );
+
+    for( unsigned i=1; i<getNumChildren(); ++i )
+    {
+        Control* c = dynamic_cast<Control*>(getChild(i));
+        if ( c )
+            c->draw( cx );
+    }
 }
 
 // ---------------------------------------------------------------------------
 
-Grid::Grid()
+Grid::Grid() :
+Container(),
+_maxCols(0)
 {
     setChildHorizAlign( ALIGN_LEFT );
     setChildVertAlign( ALIGN_CENTER );
 }
 
 Grid::Grid( const Alignment& halign, const Alignment& valign, const Gutter& padding, float spacing ) :
-Container( halign, valign, padding, spacing )
+Container( halign, valign, padding, spacing ),
+_maxCols(0)
 {
     //nop
 }
 
+void
+Grid::getChildren(std::vector<Control*>& out)
+{
+    for(unsigned i=1; i<getNumChildren(); ++i )
+    {
+        osg::Group* row = getChild(i)->asGroup();
+        if ( row )
+        {
+            for( unsigned j=0; j<row->getNumChildren(); ++j )
+            {
+                Control* c = dynamic_cast<Control*>( row->getChild(j) );
+                if ( c ) out.push_back( c );
+            }
+        }
+    }
+}
+
+unsigned
+Grid::getNumRows() const
+{
+    return getNumChildren()-1;
+}
+
+unsigned
+Grid::getNumColumns() const
+{
+    if ( getNumRows() == 0 )
+        return 0;
+    else
+        return const_cast<Grid*>(this)->getRow(0)->getNumChildren();
+}
+
+osg::Group*
+Grid::getRow(unsigned index)
+{
+    return getNumChildren() >= 2+index ? getChild(1+index)->asGroup() : 0L;
+}
+
 Control*
 Grid::setControlImpl( int col, int row, Control* child )
 {
     if ( child )
     {
         expandToInclude( col, row );
-
-        Control* oldControl = cell( col, row ).get();
-        if ( oldControl ) {
-            ControlList::iterator i = std::find( _children.begin(), _children.end(), oldControl );
-            if ( i != _children.end() ) 
-                _children.erase( i );
-        }
-
-        cell( col, row ) = child;
-        _children.push_back( child );
-
-        child->setParent( this );
+        osg::Group* rowGroup = getRow(row);
+        rowGroup->setChild( col, child );
         applyChildAligns();
 
         dirty();
@@ -1868,42 +1973,59 @@ Grid::setControlImpl( int col, int row, Control* child )
     return child;
 }
 
-osg::ref_ptr<Control>&
-Grid::cell(int col, int row)
+Control*
+Grid::getControl(int col, int row)
 {
-    return _rows[row][col];
+    if ( row < (int)getNumChildren()+1 )
+    {
+        osg::Group* rowGroup = getRow(row);
+        if ( col < (int)rowGroup->getNumChildren() )
+        {
+            return dynamic_cast<Control*>( rowGroup->getChild(col) );
+        }
+    }
+    return 0L;
 }
 
 void
 Grid::expandToInclude( int col, int row )
 {
-    while( (int)_rows.size() <= row )
-        _rows.push_back( Row() );
-
-    int maxCol = col;
-    for( RowVector::iterator i = _rows.begin(); i != _rows.end(); ++i ) {
-        if ( ((int)i->size())-1 > maxCol )
-            maxCol = ((int)i->size())-1;
+    // ensure all rows have sufficient columns:
+    if ( col+1 > (int)_maxCols )
+    {
+        _maxCols = col+1;
     }
 
-    for( RowVector::iterator i = _rows.begin(); i != _rows.end(); ++i ) {
-        Row& row = *i;
-        while( (int)row.size() <= maxCol )
-            row.push_back( 0L );
+    // and that we have sufficient rows:
+    unsigned maxRows = std::max( (unsigned)getNumRows(), (unsigned)(row+1) );
+
+    // expand everything and use empty groups as placeholders
+    for( unsigned r=0; r<maxRows; ++r )
+    {
+        osg::Group* rowGroup = getRow(r);
+        if ( !rowGroup )
+        {
+            rowGroup = new osg::Group();
+            addChild( rowGroup );
+        }
+        while ( rowGroup->getNumChildren() < _maxCols )
+        {
+            rowGroup->addChild( new osg::Group() );
+        }
     }
 }
 
 Control*
 Grid::addControlImpl( Control* control, int index )
 {
-    // creates a new row and puts the control in its first column
-    return setControlImpl( 0, _rows.size(), control );
+    // creates a new row and puts the control in its first column (index is ignored)
+    return setControlImpl( 0, getNumRows(), control );
 }
 
 void
 Grid::addControls( const ControlVector& controls )
 {
-    unsigned row = _rows.size();
+    unsigned row = getNumRows();
     unsigned col = 0;
     for( ControlVector::const_iterator i = controls.begin(); i != controls.end(); ++i, ++col )
     {
@@ -1917,10 +2039,7 @@ Grid::addControls( const ControlVector& controls )
 void
 Grid::clearControls()
 {
-    _rows.clear();
-    _children.clear();
-    _rowHeights.clear();
-    _colWidths.clear();
+    removeChildren(1, getNumChildren()-1);
     dirty();
 }
 
@@ -1931,20 +2050,19 @@ Grid::calcSize( const ControlContext& cx, osg::Vec2f& out_size )
     {
         _renderSize.set( 0, 0 );
 
-        int numRows = _rows.size();
-        int numCols = numRows > 0 ? _rows[0].size() : 0;
+        int nRows = (int)getNumRows();
+        int nCols = (int)getNumColumns();
 
-        _rowHeights.assign( numRows, 0.0f );
-        _colWidths.assign( numCols, 0.0f );
+        _rowHeights.assign( nRows, 0.0f );
+        _colWidths.assign ( nCols, 0.0f );
 
-        if ( numRows > 0 && numCols > 0 )
+        if ( nRows > 0 && nCols > 0 )
         {
-            for( int r=0; r<numRows; ++r )
+            for( int r=0; r<nRows; ++r )
             { 
-                //for( int c=0; c<_rows[r].size(); ++c )
-                for( int c=0; c<numCols; ++c )
+                for( int c=0; c<nCols; ++c )
                 {
-                    Control* child = cell(c,r).get();
+                    Control* child = getControl(c, r);
                     if ( child )
                     {
                         osg::Vec2f childSize;
@@ -1958,22 +2076,14 @@ Grid::calcSize( const ControlContext& cx, osg::Vec2f& out_size )
                 }
             }
 
-            for( int c=0; c<numCols; ++c )
+            for( int c=0; c<nCols; ++c )
                 _renderSize.x() += _colWidths[c];
-            _renderSize.x() += childSpacing() * (numCols-1);
+            _renderSize.x() += childSpacing() * (nCols-1);
 
-            for( int r=0; r<numRows; ++r )
+            for( int r=0; r<nRows; ++r )
                 _renderSize.y() += _rowHeights[r];
-            _renderSize.y() += childSpacing() * (numRows-1);
+            _renderSize.y() += childSpacing() * (nRows-1);
         }
-        
-        _renderSize.set(
-            _renderSize.x() + padding().x(),
-            _renderSize.y() + padding().y() );
-
-        out_size.set(
-            _renderSize.x() + margin().x(),
-            _renderSize.y() + margin().y() );
 
         Container::calcSize( cx, out_size );
     }
@@ -1984,14 +2094,14 @@ Grid::calcFill(const ControlContext& cx)
 {
     Container::calcFill( cx );
 
-    int numRows = _rows.size();
-    int numCols = numRows > 0 ? _rows[0].size() : 0;
+    int nRows = (int)getNumRows();
+    int nCols = (int)getNumColumns();
 
-    for( int r=0; r<numRows; ++r )
+    for( int r=0; r<nRows; ++r )
     {
-        for( int c=0; c<numCols; ++c ) //<_rows[r].size(); ++c )
+        for( int c=0; c<nCols; ++c )
         {
-            Control* child = cell(c,r).get();
+            Control* child = getControl(c, r);
 
             if ( child )
             {
@@ -2002,8 +2112,6 @@ Grid::calcFill(const ControlContext& cx)
             }
         }
     }
-
-    //Container::calcFill( cx );
 }
 
 void
@@ -2011,16 +2119,16 @@ Grid::calcPos( const ControlContext& cx, const osg::Vec2f& cursor, const osg::Ve
 {
     Container::calcPos( cx, cursor, parentSize );
 
-    int numRows = _rows.size();
-    int numCols = numRows > 0 ? _rows[0].size() : 0;
+    int nRows = (int)getNumRows();
+    int nCols = (int)getNumColumns();
 
     osg::Vec2f childCursor = _renderPos;
 
-    for( int r=0; r<numRows; ++r )
+    for( int r=0; r<nRows; ++r )
     {
-        for( int c=0; c<numCols; ++c )
+        for( int c=0; c<nCols; ++c )
         {
-            Control* child = cell(c,r).get();
+            Control* child = getControl(c, r);
             if ( child )
             {
                 osg::Vec2f cellSize( _colWidths[c], _rowHeights[r] );
@@ -2034,13 +2142,24 @@ Grid::calcPos( const ControlContext& cx, const osg::Vec2f& cursor, const osg::Ve
 }
 
 void
-Grid::draw( const ControlContext& cx, DrawableList& out )
+Grid::draw( const ControlContext& cx )
 {
-    if (visible() == true)
+    Container::draw( cx );
+
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Container::draw( cx, out );
-        for( ControlList::const_iterator i = _children.begin(); i != _children.end(); ++i )
-            i->get()->draw( cx, out );
+        osg::Group* rowGroup = getChild(i)->asGroup();
+        if ( rowGroup )
+        {
+            for( unsigned j=0; j<rowGroup->getNumChildren(); ++j )
+            {
+                Control* c = dynamic_cast<Control*>( rowGroup->getChild(j) );
+                if ( c )
+                {
+                    c->draw( cx );
+                }
+            }
+        }
     }
 }
 
@@ -2261,16 +2380,12 @@ _fading        ( true )
 
     osg::StateSet* stateSet = _group->getOrCreateStateSet();
 
-    osg::Program* program = new osg::Program();
-    program->addShader( new osg::Shader( osg::Shader::VERTEX, s_controlVertexShader ) );
-    program->addShader( new osg::Shader( osg::Shader::FRAGMENT, s_labelControlFragmentShader ) );
-    stateSet->setAttributeAndModes( program, osg::StateAttribute::ON );
-
-    osg::Uniform* defaultOpacity = new osg::Uniform( osg::Uniform::FLOAT, "opacity" );
+    //TODO: appears to be unused
+    osg::Uniform* defaultOpacity = new osg::Uniform( osg::Uniform::FLOAT, "oe_controls_opacity" );
     defaultOpacity->set( 1.0f );
     stateSet->addUniform( defaultOpacity );
 
-    osg::Uniform* defaultVisibleTime = new osg::Uniform( osg::Uniform::FLOAT, "visibleTime" );
+    osg::Uniform* defaultVisibleTime = new osg::Uniform( osg::Uniform::FLOAT, "oe_controls_visibleTime" );
     defaultVisibleTime->set( 0.0f );
     stateSet->addUniform( defaultVisibleTime );    
 }
@@ -2409,14 +2524,7 @@ ControlNodeBin::draw( const ControlContext& context, bool newContext, int bin )
                       control->calcPos( context, osg::Vec2f(0,0), size );
                    
                       // build the drawables for the geode and insert them:
-                      DrawableList drawables;
-                      control->draw( context, drawables );
-
-                      for( DrawableList::iterator j = drawables.begin(); j != drawables.end(); ++j )
-                      {
-                          j->get()->setDataVariance( osg::Object::DYNAMIC );
-                          geode->addDrawable( j->get() );
-                      }
+                      control->draw( context );
                   }
 
                   if ( _fading )
@@ -2533,6 +2641,7 @@ ControlCanvas::init( osgViewer::View* view, bool registerCanvas )
     _contextDirty  = true;
     _updatePending = false;
 
+    // deter the optimizer
     this->setDataVariance( osg::Object::DYNAMIC );
 
     osg::ref_ptr<osgGA::GUIEventHandler> pViewportHandler = new ViewportHandler(this);
@@ -2557,11 +2666,16 @@ ControlCanvas::init( osgViewer::View* view, bool registerCanvas )
     ss->setMode( GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE );
     ss->setMode( GL_BLEND, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
     ss->setAttributeAndModes( new osg::Depth( osg::Depth::ALWAYS, 0, 1, false ) );
-    ss->setRenderBinMode( osg::StateSet::USE_RENDERBIN_DETAILS );
-    ss->setBinName( OSGEARTH_CONTROLS_BIN );
+    ss->setRenderBinDetails( 0, "TraversalOrderBin" );
 
+#if 0
+    // come on we don't really need this...gw
     // keeps the control bin shaders from "leaking out" into the scene graph :/
-    ss->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
+    if ( Registry::capabilities().supportsGLSL() )
+    {
+        ss->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED );
+    }
+#endif
 
     _controlNodeBin = new ControlNodeBin();
     this->addChild( _controlNodeBin->getControlGroup() );
@@ -2600,36 +2714,27 @@ ControlCanvas::setAllowControlNodeOverlap( bool value )
 Control*
 ControlCanvas::addControlImpl( Control* control )
 {
-    osg::Geode* geode = new osg::Geode();
-    _geodeTable[control] = geode;
-    addChild( geode );
-    control->dirty();    
-    _controls.push_back( control );
+    control->dirty();
+    this->addChild( control );
     return control;
 }
 
 void
 ControlCanvas::removeControl( Control* control )
 {
-    GeodeTable::iterator i = _geodeTable.find( control );
-    if ( i != _geodeTable.end() )
-    {
-         removeChild( i->second );
-         _geodeTable.erase( i );
-    }
-    ControlList::iterator j = std::find( _controls.begin(), _controls.end(), control );
-    if ( j != _controls.end() )
-        _controls.erase( j );
+    removeChild( control );
 }
 
 Control*
-ControlCanvas::getControlAtMouse( float x, float y ) const
+ControlCanvas::getControlAtMouse( float x, float y )
 {
-    for( ControlList::const_iterator i = _controls.begin(); i != _controls.end(); ++i )
+    for( osg::NodeList::iterator i = _children.begin(); i != _children.end(); ++i )
     {
-        Control* control = i->get();
+        Control* control = dynamic_cast<Control*>( i->get() );
         if ( control->intersects( x, _context._vp->height() - y ) )
+        {
             return control;
+        }
     }
     return 0L;
 }
@@ -2640,10 +2745,10 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
     if ( !_context._vp )
         return false;
 
-    for (ControlList::reverse_iterator i = _controls.rbegin(); i != _controls.rend(); ++i)
+    for( unsigned i=getNumChildren()-1; i>0; --i )
     {
-        Control* control = i->get();
-        if (control->isDirty())
+        Control* control = static_cast<Control*>( getChild(i) );
+        if ( control->isDirty() )
         {
             aa.requestRedraw();
             break;
@@ -2654,9 +2759,10 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
     //Send a frame event to all controls
     if ( ea.getEventType() == osgGA::GUIEventAdapter::FRAME )
     {
-        for( ControlList::reverse_iterator i = _controls.rbegin(); i != _controls.rend(); ++i )
+        for( unsigned i=1; i<getNumChildren(); ++i )
         {
-            i->get()->handle(ea, aa, _context);
+            Control* control = static_cast<Control*>( getChild(i) );
+            control->handle(ea, aa, _context);
         }
         return handled;
     }
@@ -2664,9 +2770,10 @@ ControlCanvas::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter
 
     float invY = _context._vp->height() - ea.getY();
 
-    for( ControlList::reverse_iterator i = _controls.rbegin(); i != _controls.rend(); ++i )
+    for( unsigned i=getNumChildren()-1; i>0; --i )
     {
-        Control* control = i->get();
+        Control* control = static_cast<Control*>( getChild(i) );
+
         if ( control->intersects( ea.getX(), invY ) )
         {
             handled = control->handle( ea, aa, _context );
@@ -2701,9 +2808,10 @@ ControlCanvas::update( const osg::FrameStamp* frameStamp )
         return;
 
     int bin = 0;
-    for( ControlList::iterator i = _controls.begin(); i != _controls.end(); ++i )
+    for( unsigned i=1; i<getNumChildren(); ++i )
     {
-        Control* control = i->get();
+        Control* control = static_cast<Control*>( getChild(i) );
+
         if ( control->isDirty() || _contextDirty )
         {
             osg::Vec2f size;
@@ -2713,16 +2821,7 @@ ControlCanvas::update( const osg::FrameStamp* frameStamp )
             osg::Vec2f surfaceSize( _context._vp->width(), _context._vp->height() );
             control->calcPos( _context, osg::Vec2f(0,0), surfaceSize );
 
-            osg::Geode* geode = _geodeTable[control];
-            geode->removeDrawables( 0, geode->getNumDrawables() );
-            DrawableList drawables;
-            control->draw( _context, drawables );
-
-            for( DrawableList::iterator j = drawables.begin(); j != drawables.end(); ++j )
-            {
-                j->get()->setDataVariance( osg::Object::DYNAMIC );
-                geode->addDrawable( j->get() );
-            }
+            control->draw( _context );
         }
     }
 
@@ -2731,43 +2830,59 @@ ControlCanvas::update( const osg::FrameStamp* frameStamp )
         _controlNodeBin->draw( _context, _contextDirty, bin );
     }
 
+    // shaderize.
+    // we don't really need to rebuild shaders on every dirty; we could probably
+    // just do it on add/remove controls; but that's an optimization for later
+    ShaderGenerator shaderGen;
+    shaderGen.run( this );
+
     _contextDirty = false;
 }
 
 void
 ControlCanvas::traverse( osg::NodeVisitor& nv )
 {
-    if ( nv.getVisitorType() == osg::NodeVisitor::EVENT_VISITOR )
+    switch( nv.getVisitorType() )
     {
-        if ( !_updatePending )
+        case osg::NodeVisitor::EVENT_VISITOR:
         {
-            bool needsUpdate = _contextDirty;
-            if ( !needsUpdate )
+            if ( !_updatePending )
             {
-                for( ControlList::iterator i = _controls.begin(); i != _controls.end(); ++i )
+                bool needsUpdate = _contextDirty;
+                if ( !needsUpdate )
                 {
-                    Control* control = i->get();
-                    if ( control->isDirty() )
+                    for( unsigned i=1; i<getNumChildren(); ++i )
                     {
-                        needsUpdate = true;
-                        break;
+                        Control* control = static_cast<Control*>( getChild(i) );
+                        if ( control->isDirty() )
+                        {
+                            needsUpdate = true;
+                            break;
+                        }
                     }
                 }
-            }
 
-            if ( needsUpdate )
-            {
-                _updatePending = true;
-                ADJUST_UPDATE_TRAV_COUNT( this, 1 );
+                if ( needsUpdate )
+                {
+                    _updatePending = true;
+                    ADJUST_UPDATE_TRAV_COUNT( this, 1 );
+                }
             }
         }
-    }
+        break;
 
-    else if ( nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR )
-    {
-        update( nv.getFrameStamp() );
-        ADJUST_UPDATE_TRAV_COUNT( this, -1 );
-        _updatePending = false;
+    case osg::NodeVisitor::UPDATE_VISITOR:
+        {
+            update( nv.getFrameStamp() );
+            ADJUST_UPDATE_TRAV_COUNT( this, -1 );
+            _updatePending = false;
+        }
+        break;
+
+    case osg::NodeVisitor::CULL_VISITOR:
+        {
+        }
+        break;
     }
 
     osg::Camera::traverse( nv );
diff --git a/src/osgEarthDrivers/engine_mp/LODFactorCallback b/src/osgEarthUtil/DateTime
similarity index 65%
rename from src/osgEarthDrivers/engine_mp/LODFactorCallback
rename to src/osgEarthUtil/DateTime
index 2c4a8c9..2352564 100644
--- a/src/osgEarthDrivers/engine_mp/LODFactorCallback
+++ b/src/osgEarthUtil/DateTime
@@ -1,6 +1,6 @@
 /* -*-c++-*- */
 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
-* Copyright 2008-2011 Pelican Mapping
+* Copyright 2008-2012 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
@@ -16,19 +16,18 @@
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
+#ifndef OSGEARTHUTIL_DATE_TIME_H
+#define OSGEARTHUTIL_DATE_TIME_H
 
-#ifndef OSGEARTH_ENGINE_OSGTERRAIN_LOD_FACTOR_CALLBACK_H
-#define OSGEARTH_ENGINE_OSGTERRAIN_LOD_FACTOR_CALLBACK_H 1
+#include <osgEarthUtil/Common>
+#include <osgEarth/DateTime>
 
-#include <osg/NodeCallback>
-
-namespace osgEarth_engine_mp
+namespace osgEarth { namespace Util
 {
-    struct LODFactorCallback : public osg::NodeCallback
-    {
-        void operator()(osg::Node* node, osg::NodeVisitor* nv);
-    };
+    // for backwards-compability.
+    // @deprecated - place use osgEarth::DateTime instead.
+    typedef osgEarth::DateTime DateTime;
 
-} // namespace osgEarth_engine_mp
+} } // namespace osgEarth::Util
 
-#endif
+#endif // OSGEARTHUTIL_DATE_TIME_H
diff --git a/src/osgEarthUtil/DateTime.cpp b/src/osgEarthUtil/DateTime.cpp
new file mode 100644
index 0000000..de021d3
--- /dev/null
+++ b/src/osgEarthUtil/DateTime.cpp
@@ -0,0 +1,85 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/DateTime>
+#include <string.h>
+using namespace osgEarth::Util;
+
+DateTime::DateTime()
+{
+    ::time( &_time_t );
+    _tm = *::gmtime(&_time_t);
+}
+
+DateTime::DateTime(const ::time_t& utc)
+{
+    _time_t = utc;
+    _tm = *::gmtime( &utc );
+}
+
+DateTime::DateTime(const ::tm& tm)
+: _tm( tm )
+{
+    _time_t = ::mktime( &_tm );
+}
+
+DateTime::DateTime(int year, int month, int day, double hour)
+{
+    _tm.tm_year = year - 1900;
+    _tm.tm_mon  = month - 1;
+    _tm.tm_mday = day;
+
+    double hour_whole = ::floor(hour);
+    _tm.tm_hour = (int)hour_whole;
+    double frac = hour - (double)_tm.tm_hour;
+    double min = frac*60.0;
+    _tm.tm_min = (int)::floor(min);
+    frac = min - (double)_tm.tm_min;
+    _tm.tm_sec = (int)(frac*60.0);
+}
+
+DateTime::DateTime(const DateTime& rhs) :
+_tm    ( rhs._tm ),
+_time_t( rhs._time_t )
+{
+    //nop
+}
+
+int 
+DateTime::year() const 
+{ 
+    return _tm.tm_year + 1900;
+}
+
+int
+DateTime::month() const
+{
+    return _tm.tm_mon + 1;
+}
+
+int
+DateTime::day() const
+{
+    return _tm.tm_mday;
+}
+
+double
+DateTime::hours() const
+{
+    return (double)_tm.tm_hour + ((double)_tm.tm_min)/60. + ((double)_tm.tm_sec)/3600.;
+}
diff --git a/src/osgEarthUtil/DetailTexture b/src/osgEarthUtil/DetailTexture
new file mode 100644
index 0000000..604d2de
--- /dev/null
+++ b/src/osgEarthUtil/DetailTexture
@@ -0,0 +1,80 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_DETAIL_TEXTURE_H
+#define OSGEARTHUTIL_DETAIL_TEXTURE_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/URI>
+#include <osg/Image>
+#include <osg/Uniform>
+#include <osg/Texture2D>
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Controller that applies a detail texture to the terrain.
+     */
+    class OSGEARTHUTIL_EXPORT DetailTexture : public TerrainEffect
+    {
+    public:
+        /** construct a new detail texture controller */
+        DetailTexture();
+
+        /** Sets the LOD at which to start detailing */
+        void setStartLOD( unsigned lod );
+        unsigned getStartLOD() const { return _startLOD.get(); }
+
+        /** Sets the intensity (0=none, 1=full) */
+        void setIntensity( float value );
+        float getIntensity() const { return _intensity.get(); }
+
+        /** Sets the image to use as the detail texture */
+        void setImage( const osg::Image* image );
+        const osg::Image* getImage() const { return _texture->getImage(); }
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        DetailTexture(const Config& conf);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~DetailTexture() { }
+        void init();
+
+        optional<float>    _intensity;
+        optional<unsigned> _startLOD;
+        optional<URI>      _imageURI;
+
+        osg::ref_ptr<osg::Uniform>   _intensityUniform;
+        osg::ref_ptr<osg::Uniform>   _startLODUniform;
+        osg::ref_ptr<osg::Uniform>   _samplerUniform;
+        osg::ref_ptr<osg::Texture2D> _texture;
+        int                          _unit;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_DATA_SCANNER_H
diff --git a/src/osgEarthUtil/DetailTexture.cpp b/src/osgEarthUtil/DetailTexture.cpp
new file mode 100644
index 0000000..7d67a3b
--- /dev/null
+++ b/src/osgEarthUtil/DetailTexture.cpp
@@ -0,0 +1,252 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/DetailTexture>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[DetailTexture] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec4  oe_tile_key; \n"
+        "varying vec4  oe_layer_tilec; \n"
+        "uniform float oe_dtex_L0; \n"
+        "varying vec2  oe_dtex_tc; \n"
+
+        "int oe_dtex_ipow(in int x, in int y) { \n"
+        "   int r = 1; \n"
+        "   while( y > 0 ) { \n"
+        "       r *= x; \n"
+        "       --y; \n"
+        "   } \n"
+        "   return r; \n"
+        "}\n"
+
+        "void oe_dtex_vertex(inout vec4 VertexMODEL) \n"
+        "{ \n"
+        "    float dL = oe_tile_key.z - oe_dtex_L0; \n"
+        "    float twoPowDeltaL = float(oe_dtex_ipow(2, int(abs(dL)))); \n"
+        "    float factor = dL >= 0.0 ? twoPowDeltaL : 1.0/twoPowDeltaL; \n"
+
+        "    vec2 a = floor(oe_tile_key.xy / factor); \n"
+        "    vec2 b = a * factor; \n"
+        "    vec2 c = (a+1.0) * factor; \n"
+        "    vec2 offset = (oe_tile_key.xy-b)/(c-b); \n"
+        "    vec2 scale = vec2(1.0/factor); \n"
+
+        "    oe_dtex_tc = (oe_layer_tilec.st * scale) + offset; \n"
+        "} \n";
+
+    const char* fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec4      oe_tile_key; \n"
+        "uniform float     oe_dtex_L0; \n"
+        "uniform sampler2D oe_dtex_tex; \n"
+        "uniform float     oe_dtex_intensity; \n"
+        "varying vec2      oe_dtex_tc; \n"
+
+        "void oe_dtex_fragment(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_tile_key.z >= oe_dtex_L0 ) \n"
+        "    { \n"
+        "        vec4 texel = texture2D(oe_dtex_tex, oe_dtex_tc); \n"
+        "        if ( oe_tile_key.z >= oe_dtex_L0+3.0 ) \n"
+        "        { \n"
+        "            texel += texture2D(oe_dtex_tex, oe_dtex_tc*8.0)-0.5; \n"
+        "            if ( oe_tile_key.z >= oe_dtex_L0+6.0 ) \n"
+        "            { \n"
+        "                texel += texture2D(oe_dtex_tex, oe_dtex_tc*32.0)-0.5; \n"
+        "                if ( oe_tile_key.z >= oe_dtex_L0+9.0 ) \n"
+        "                { \n"
+        "                    texel += texture2D(oe_dtex_tex, oe_dtex_tc*64.0)-0.5; \n"
+        "                } \n"
+        "            } \n"
+        "        } \n"
+        "        texel.rgb -= 0.5; \n"
+        "        color.rgb = clamp( color.rgb + (texel.rgb*oe_dtex_intensity), 0.0, 1.0 ); \n"
+        "    } \n"
+        "} \n";
+}
+
+
+DetailTexture::DetailTexture() :
+TerrainEffect(),
+_startLOD    ( 8 ),
+_intensity   ( 0.25f )
+{
+    init();
+}
+
+DetailTexture::DetailTexture(const Config& conf) :
+TerrainEffect(),
+_startLOD    ( 8 ),
+_intensity   ( 0.25f )
+{
+    mergeConfig(conf);
+    init();
+}
+
+
+void
+DetailTexture::init()
+{
+    // negative means unset:
+    _unit = -1;
+
+    _startLODUniform   = new osg::Uniform(osg::Uniform::FLOAT, "oe_dtex_L0");
+    _startLODUniform->set( (float)_startLOD.get() );
+
+    _intensityUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_dtex_intensity");
+    _intensityUniform->set( _intensity.get() );
+    
+    _texture = new osg::Texture2D();
+    _texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
+    _texture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
+    _texture->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR );
+    _texture->setFilter( osg::Texture::MAG_FILTER, osg::Texture::LINEAR );
+    _texture->setResizeNonPowerOfTwoHint( false );
+
+    if ( _imageURI.isSet() )
+    {
+        osg::Image* image = _imageURI->getImage();
+        if ( image )
+            _texture->setImage( image );
+    }
+}
+
+
+void
+DetailTexture::setStartLOD(unsigned lod)
+{
+    if ( lod != _startLOD.get() )
+    {
+        _startLOD = lod;
+        _startLODUniform->set( (float)_startLOD.get() );
+    }
+}
+
+
+void
+DetailTexture::setIntensity(float intensity)
+{
+    _intensity = osg::clampBetween( intensity, 0.0f, 1.0f );
+    _intensityUniform->set( _intensity.get() );
+}
+
+
+void
+DetailTexture::setImage(const osg::Image* image)
+{
+    if ( image )
+    {
+        _texture->setImage( const_cast<osg::Image*>(image) );
+    }
+}
+
+
+void
+DetailTexture::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+
+        if ( engine->getTextureCompositor()->reserveTextureImageUnit(_unit) )
+        {
+            _samplerUniform = stateset->getOrCreateUniform( "oe_dtex_tex", osg::Uniform::SAMPLER_2D );
+            _samplerUniform->set( _unit );
+            stateset->setTextureAttributeAndModes( _unit, _texture.get() );
+        }
+
+        stateset->addUniform( _startLODUniform.get() );
+        stateset->addUniform( _intensityUniform.get() );
+
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setFunction( "oe_dtex_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL );
+        vp->setFunction( "oe_dtex_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    }
+}
+
+
+void
+DetailTexture::onUninstall(TerrainEngineNode* engine)
+{
+    osg::StateSet* stateset = engine->getStateSet();
+    if ( stateset )
+    {
+        stateset->removeUniform( _startLODUniform.get() );
+        stateset->removeUniform( _intensityUniform.get() );
+
+        if ( _samplerUniform.valid() )
+        {
+            int unit;
+            _samplerUniform->get(unit);
+            stateset->removeUniform( _samplerUniform.get() );
+            stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXTURE);
+        }
+
+        VirtualProgram* vp = VirtualProgram::get(stateset);
+        if ( vp )
+        {
+            vp->removeShader( "oe_dtex_vertex" );
+            vp->removeShader( "oe_dtex_fragment" );
+        }
+    }
+
+    if ( _unit >= 0 )
+    {
+        engine->getTextureCompositor()->releaseTextureImageUnit( _unit );
+        _unit = -1;
+    }
+}
+
+
+
+
+//-------------------------------------------------------------
+
+void
+DetailTexture::mergeConfig(const Config& conf)
+{
+    conf.getIfSet( "start_lod", _startLOD );
+    conf.getIfSet( "intensity", _intensity );
+    conf.getIfSet( "image",     _imageURI );
+}
+
+Config
+DetailTexture::getConfig() const
+{
+    Config conf("detail_texture");
+    conf.addIfSet( "start_lod", _startLOD );
+    conf.addIfSet( "intensity", _intensity );
+    conf.addIfSet( "image",     _imageURI );
+
+    return conf;
+}
diff --git a/src/osgEarthUtil/EarthManipulator b/src/osgEarthUtil/EarthManipulator
index 0f30f10..8507e39 100644
--- a/src/osgEarthUtil/EarthManipulator
+++ b/src/osgEarthUtil/EarthManipulator
@@ -331,6 +331,10 @@ namespace osgEarth { namespace Util
 
             void bindPinch(
                 ActionType action, const ActionOptions& =ActionOptions() );
+                
+            void bindTwist(
+                ActionType action, const ActionOptions& =ActionOptions() );
+
    
             void bindMultiDrag(
                 ActionType action, const ActionOptions& =ActionOptions() );
@@ -484,6 +488,16 @@ namespace osgEarth { namespace Util
             void setCameraFrustumOffsets( const osg::Vec2s& offsets );
             const osg::Vec2s& getCameraFrustumOffsets() const { return _camFrustOffsets; }            
 
+            /** Whether or not to disable collision avoidance when new data pages in */
+            bool getDisableCollisionAvoidance() const { return _disableCollisionAvoidance; }
+            void setDisableCollisionAvoidance( bool disableCollisionAvoidance ) { _disableCollisionAvoidance = disableCollisionAvoidance; }
+
+            void setThrowingEnabled(bool throwingEnabled) { _throwingEnabled = throwingEnabled; }
+            bool getThrowingEnabled () const { return _throwingEnabled; }
+            
+            void setThrowDecayRate(double throwDecayRate) { _throwDecayRate = osg::clampBetween(throwDecayRate, 0.0, 1.0); }
+            double getThrowDecayRate () const { return _throwDecayRate; }
+
         private:
 
             friend class EarthManipulator;
@@ -522,7 +536,12 @@ namespace osgEarth { namespace Util
             double _min_vp_duration_s, _max_vp_duration_s;
 
             CameraProjection _camProjType;
-            osg::Vec2s _camFrustOffsets;			
+            osg::Vec2s _camFrustOffsets;	
+
+            bool _disableCollisionAvoidance;
+
+            bool _throwingEnabled;
+            double _throwDecayRate;
         };
 
     public:
@@ -636,7 +655,16 @@ namespace osgEarth { namespace Util
          */
         void  setRotation( const osg::Quat& rotation) { _rotation = rotation; }
 
+        /**
+         * Gets the traversal node mask used to find root MapNode and CoordinateSystemNodes. Default is 0x1.
+         */
+        osg::Node::NodeMask getFindNodeTraversalMask( ) { return _findNodeTraversalMask; }
 
+        /**
+         * Sets the traversal node mask used to find root MapNode and CoordinateSystemNode. Default is 0x1. 
+         * Use this method if you change MapNode or CoordinateSystemNode mask and want manipulator to work with them correctly.  
+         */
+        void  setFindNodeTraversalMask( const osg::Node::NodeMask & nodeMask ) { _findNodeTraversalMask = nodeMask; }
 
     public: // osgGA::MatrixManipulator
 
@@ -689,7 +717,7 @@ namespace osgEarth { namespace Util
         bool intersect(const osg::Vec3d& start, const osg::Vec3d& end, osg::Vec3d& intersection) const;
 
         // resets the mouse event stack and pushes the provided event.
-        void resetMouse( osgGA::GUIActionAdapter& );
+        void resetMouse( osgGA::GUIActionAdapter& aa, bool flushEventStack=true);
 
         // Reset the internal event stack.
         void flushMouseEventStack();
@@ -840,6 +868,10 @@ namespace osgEarth { namespace Util
         double                  _delta_t;
         double                  _t_factor;
         bool                    _thrown;
+        double                  _throw_dx;
+        double                  _throw_dy;
+        double                  _dx;
+        double                  _dy;
 
         // The world coordinate of the Viewpoint focal point.
         osg::Vec3d              _center;
@@ -896,6 +928,10 @@ namespace osgEarth { namespace Util
         double _homeViewpointDuration;
 
         Action _last_action;
+        
+        EventType _last_event;
+        double    _time_s_last_event;
+        
 
         // to support updating the camera after the update traversal (e.g., for tethering)
         osg::observer_ptr<osg::Camera> _viewCamera;
@@ -922,6 +958,8 @@ namespace osgEarth { namespace Util
 
         osg::ref_ptr< TerrainCallback > _terrainCallback;
 
+        // Traversal mask used in established and dtor methods to find MapNode and CoordinateSystemNode
+        osg::Node::NodeMask  _findNodeTraversalMask;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/EarthManipulator.cpp b/src/osgEarthUtil/EarthManipulator.cpp
index 17f96ff..1c26f18 100644
--- a/src/osgEarthUtil/EarthManipulator.cpp
+++ b/src/osgEarthUtil/EarthManipulator.cpp
@@ -206,7 +206,10 @@ _auto_vp_duration               ( false ),
 _min_vp_duration_s              ( 3.0 ),
 _max_vp_duration_s              ( 8.0 ),
 _camProjType                    ( PROJ_PERSPECTIVE ),
-_camFrustOffsets                ( 0, 0 )
+_camFrustOffsets                ( 0, 0 ),
+_disableCollisionAvoidance      ( false ),
+_throwingEnabled                ( false ),
+_throwDecayRate                 ( 0.05 )
 {
     //NOP
 }
@@ -233,7 +236,10 @@ _min_vp_duration_s( rhs._min_vp_duration_s ),
 _max_vp_duration_s( rhs._max_vp_duration_s ),
 _camProjType( rhs._camProjType ),
 _camFrustOffsets( rhs._camFrustOffsets ),
-_breakTetherActions( rhs._breakTetherActions )
+_breakTetherActions( rhs._breakTetherActions ),
+_disableCollisionAvoidance( rhs._disableCollisionAvoidance),
+_throwingEnabled( rhs._throwingEnabled ),
+_throwDecayRate( rhs._throwDecayRate )
 {
     //NOP
 }
@@ -348,6 +354,14 @@ EarthManipulator::Settings::bindPinch(ActionType action, const ActionOptions& op
 }
 
 void
+EarthManipulator::Settings::bindTwist(ActionType action, const ActionOptions& options)
+{
+    bind(
+         InputSpec( EarthManipulator::EVENT_MULTI_TWIST, 0, 0 ),
+         Action( action, options ) );
+}
+
+void
 EarthManipulator::Settings::bindMultiDrag(ActionType action, const ActionOptions& options)
 {
     bind(
@@ -431,8 +445,11 @@ EarthManipulator::Settings::setCameraFrustumOffsets( const osg::Vec2s& value )
 
 EarthManipulator::EarthManipulator() :
 osgGA::CameraManipulator(),
-_last_action      ( ACTION_NULL ),
-_frame_count      ( 0 )
+_last_action           ( ACTION_NULL ),
+_last_event            ( EVENT_MOUSE_DOUBLE_CLICK ),
+_time_s_last_event     (0.0),
+_frame_count           ( 0 ),
+_findNodeTraversalMask ( 0x01 )
 {
     reinitialize();
     configureDefaultSettings();
@@ -441,8 +458,11 @@ _frame_count      ( 0 )
 EarthManipulator::EarthManipulator( const EarthManipulator& rhs ) :
 osgGA::CameraManipulator( rhs ),
 _last_action            ( ACTION_NULL ),
+_last_event             ( EVENT_MOUSE_DOUBLE_CLICK ),
+_time_s_last_event      (0.0),
 _frame_count            ( 0 ),
-_settings               ( new Settings(*rhs.getSettings()) )
+_settings               ( new Settings(*rhs.getSettings()) ),
+_findNodeTraversalMask  ( rhs._findNodeTraversalMask )
 {
     reinitialize();
 }
@@ -454,7 +474,7 @@ EarthManipulator::~EarthManipulator()
     if (safeNode && _terrainCallback)
     {
         // find a map node.
-        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), 0x01 );
+        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), _findNodeTraversalMask );
         if ( mapNode )
         {             
             mapNode->getTerrain()->removeTerrainCallback( _terrainCallback );
@@ -510,6 +530,7 @@ EarthManipulator::configureDefaultSettings()
     _settings->bindPinch( ACTION_ZOOM, options );
 
     options.clear();
+    _settings->bindTwist( ACTION_ROTATE, options );
     _settings->bindMultiDrag( ACTION_ROTATE, options );
 
     //_settings->setThrowingEnabled( false );
@@ -559,6 +580,10 @@ EarthManipulator::reinitialize()
     _offset_x = 0.0;
     _offset_y = 0.0;
     _thrown = false;
+    _dx = 0.0;
+    _dy = 0.0;
+    _throw_dx = 0.0;
+    _throw_dy = 0.0;
     _continuous = false;
     _task = new Task();
     _last_action = ACTION_NULL;
@@ -589,15 +614,15 @@ EarthManipulator::established()
             return false;
 
         // find a map node.
-        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), 0x01 );
-        if ( mapNode )
-        {
+        MapNode* mapNode = MapNode::findMapNode( safeNode.get(), _findNodeTraversalMask );
+        if ( mapNode && !_settings->getDisableCollisionAvoidance() )
+        {            
             _terrainCallback = new ManipTerrainCallback( this );
             mapNode->getTerrain()->addTerrainCallback( _terrainCallback );
-        }
+        }   
 
         // find a CSN node - if there is one, we want to attach the manip to that
-        _csn = findRelativeNodeOfType<osg::CoordinateSystemNode>( safeNode.get(), 0x01 );
+        _csn = findRelativeNodeOfType<osg::CoordinateSystemNode>( safeNode.get(), _findNodeTraversalMask );
 
         if ( _csn.valid() )
         {
@@ -1194,9 +1219,11 @@ EarthManipulator::getUsage(osg::ApplicationUsage& usage) const
 }
 
 void
-EarthManipulator::resetMouse( osgGA::GUIActionAdapter& aa )
+EarthManipulator::resetMouse( osgGA::GUIActionAdapter& aa, bool flushEventStack )
 {
-    flushMouseEventStack();
+    if (flushEventStack)
+      flushMouseEventStack();
+    
     aa.requestContinuousUpdate( false );
     _thrown = false;
     _continuous = false;
@@ -1342,10 +1369,12 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
     osg::View* view = aa.asView();
     updateCamera( view->getCamera() );
 
+    double time_s_now = osg::Timer::instance()->time_s();
+
     if ( ea.getEventType() == osgGA::GUIEventAdapter::FRAME )
     {
         _time_s_last_frame = _time_s_now;
-        _time_s_now = osg::Timer::instance()->time_s();
+        _time_s_now = time_s_now;
         _delta_t = _time_s_now - _time_s_last_frame;
         // this factor adjusts for the variation of frame rate relative to 60fps
         _t_factor = _delta_t / 0.01666666666;
@@ -1367,13 +1396,25 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
             aa.requestContinuousUpdate( _setting_viewpoint );
         }
 
-        if ( _thrown || _continuous )
+        else if (_thrown)
+        {
+            double decayFactor = 1.0 - _settings->getThrowDecayRate();
+
+            _throw_dx = osg::absolute(_throw_dx) > osg::absolute(_dx * 0.01) ? _throw_dx * decayFactor : 0.0;
+            _throw_dy = osg::absolute(_throw_dy) > osg::absolute(_dy * 0.01) ? _throw_dy * decayFactor : 0.0;
+
+            if (_throw_dx == 0.0 && _throw_dy == 0.0)
+                _thrown = false;
+            else            
+                handleMovementAction(_last_action._type, _throw_dx, _throw_dy, aa.asView());
+        }
+
+        if ( _continuous )
         {
             handleContinuousAction( _last_action, aa.asView() );
             aa.requestRedraw();
         }
-
-        if ( !_continuous )
+        else
         {
             _continuous_dx = 0.0;
             _continuous_dy = 0.0;
@@ -1431,8 +1472,8 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
     if ( ea.isMultiTouchEvent() )
     {
         // not a mouse event; clear the mouse queue.
-        resetMouse( aa );
-
+        resetMouse( aa, false );
+        
         // queue up a touch event set and figure out the current state:
         addTouchEvents(ea);
         TouchEvents te;
@@ -1440,20 +1481,41 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
         {
             for( TouchEvents::iterator i = te.begin(); i != te.end(); ++i )
             {
-                //OE_WARN << LC << "P: " << i->_dx << ", " << i->_dy << std::endl;
-                Action action = _settings->getAction(i->_eventType, i->_mbmask, 0);
-
-                // here we adjust for action scale, global sensitivy
-                double dx = i->_dx, dy = i->_dy;
-                dx *= _settings->getMouseSensitivity();
-                dy *= _settings->getMouseSensitivity();
-                applyOptionsToDeltas( action, dx, dy );
-
-                handleMovementAction(action._type, dx, dy, view);
-                aa.requestRedraw();
+                action = _settings->getAction(i->_eventType, i->_mbmask, 0);
+                
+                if (action._type != ACTION_NULL)
+                {
+                    _last_event = i->_eventType;
+                    
+                    // here we adjust for action scale, global sensitivy
+                    double dx = i->_dx, dy = i->_dy;
+                    dx *= _settings->getMouseSensitivity();
+                    dy *= _settings->getMouseSensitivity();
+                    applyOptionsToDeltas( action, dx, dy );
+                
+                    _dx = dx;
+                    _dy = dy;
+                
+                    if (action._type == ACTION_GOTO)
+                        handlePointAction(action, ea.getX(), ea.getY(), view);
+                    else
+                        handleMovementAction(action._type, dx, dy, view);
+                
+                    aa.requestRedraw();
+                }
             }
+            
             handled = true;
         }
+        else
+        {
+            // The only multitouch event we want passed on if not handled is a release
+            handled = ea.getEventType() != osgGA::GUIEventAdapter::RELEASE;
+            
+            // if a new push occurs we want to reset the dx/dy values to stop/prevent throwing
+            if (ea.getEventType() == osgGA::GUIEventAdapter::PUSH)
+                _dx = _dy = 0.0;
+        }
     }
 
     if ( !handled )
@@ -1472,7 +1534,6 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
                 break;       
             
             case osgGA::GUIEventAdapter::RELEASE:
-
                 if ( _continuous )
                 {
                     // bail out of continuous mode if necessary:
@@ -1481,21 +1542,18 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
                 }
                 else
                 {
-    #if 0 // disabled - not implemented
-                    // check for a mouse-throw continuation:
-                    if ( _settings->getThrowingEnabled() && isMouseMoving() )
+                    action = _last_action;
+                    
+                    _throw_dx = fabs(_dx) > 0.01 ? _dx : 0.0;
+                    _throw_dy = fabs(_dy) > 0.01 ? _dy : 0.0;
+                    
+                    if (_settings->getThrowingEnabled() && ( time_s_now - _time_s_last_event < 0.05 ) && (_throw_dx != 0.0 || _throw_dy != 0.0))
                     {
-                        action = _last_action;
-                        if( handleMouseAction( action, aa.asView() ) )
-                        {
-                            aa.requestRedraw();
-                            aa.requestContinuousUpdate( true );
-                            _thrown = true;
-                        }
+                        _thrown = true;
+                        aa.requestRedraw();
+                        aa.requestContinuousUpdate( true );
                     }
-                    else 
-    #endif
-                    if ( isMouseClick( &ea ) )
+                    else if ( isMouseClick( &ea ) )
                     {
                         addMouseEvent( ea );
                         if ( _mouse_down_event )
@@ -1589,6 +1647,7 @@ EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapt
     if ( handled && action._type != ACTION_NULL )
     {
         _last_action = action;
+        _time_s_last_event = time_s_now;
     }
 
     return handled;
@@ -1603,9 +1662,8 @@ EarthManipulator::postUpdate()
 void
 EarthManipulator::updateTether()
 {
-    // capture a temporary ref since _tether_node is just an observer:
-    osg::ref_ptr<osg::Node> temp = _tether_node.get();
-    if ( temp.valid() )
+    osg::ref_ptr<osg::Node> tether_node;
+    if ( _tether_node.lock(tether_node) )
     {
         osg::Matrix localToWorld;
 
@@ -1614,16 +1672,23 @@ EarthManipulator::updateTether()
             osg::NodePathList nodePaths = _tether_xform->getParentalNodePaths();
             if ( nodePaths.empty() )
                 return;
+
             localToWorld = osg::computeLocalToWorld( nodePaths[0] );
-            //setCenter( localToWorld.getTrans() + (localToWorld.getRotate() * _tether_local_center) );
+            if ( !localToWorld.valid() )
+                return;
+
             setCenter( _tether_local_center * localToWorld );
         }
         else
         {
-            osg::NodePathList nodePaths = temp->getParentalNodePaths();
+            osg::NodePathList nodePaths = tether_node->getParentalNodePaths();
             if ( nodePaths.empty() )
                 return;
+
             localToWorld = osg::computeLocalToWorld( nodePaths[0] );
+            if ( !localToWorld.valid() )
+                return;
+
             setCenter( localToWorld.getTrans() );
         }
 
@@ -1741,6 +1806,9 @@ EarthManipulator::addMouseEvent(const osgGA::GUIEventAdapter& ea)
 void
 EarthManipulator::addTouchEvents(const osgGA::GUIEventAdapter& ea)
 {
+    _ga_t1 = _ga_t0;
+    _ga_t0 = &ea;
+    
     // first, push the old event to the back of the queue.
     while ( _touchPointQueue.size() > 1 )
         _touchPointQueue.pop_front();
@@ -1756,8 +1824,7 @@ EarthManipulator::addTouchEvents(const osgGA::GUIEventAdapter& ea)
         for( unsigned i=0; i<data->getNumTouchPoints(); ++i )
         {
             osgGA::GUIEventAdapter::TouchData::TouchPoint tp = data->get(i);
-            ev.resize(tp.id+1);
-            ev[tp.id] = tp; // overwrites duplicates automatically.
+            ev.push_back(tp);
         }
     }
 }
@@ -1765,9 +1832,8 @@ EarthManipulator::addTouchEvents(const osgGA::GUIEventAdapter& ea)
 bool
 EarthManipulator::parseTouchEvents( TouchEvents& output )
 {
-    const float sens = 0.005f;
-
-    // two-finger drag gestures:
+    const float sens = 0.005f;    
+        
     if (_touchPointQueue.size() == 2 )
     {
         if (_touchPointQueue[0].size()   == 2 &&     // two fingers
@@ -1791,36 +1857,49 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
                 osg::Vec2f vec0 = osg::Vec2f(p0[1].x,p0[1].y)-osg::Vec2f(p0[0].x,p0[0].y);
                 osg::Vec2f vec1 = osg::Vec2f(p1[1].x,p1[1].y)-osg::Vec2f(p1[0].x,p1[0].y);
                 float deltaDistance = vec1.length() - vec0.length();
+                
+                float angle[2];
+                angle[0] = atan2(p0[0].y - p0[1].y, p0[0].x - p0[1].x);
+                angle[1] = atan2(p1[0].y - p1[1].y, p1[0].x - p1[1].x);
+                float da = angle[0] - angle[1];
 
-                vec0.normalize();
-                vec1.normalize();
-                float dot = fabs( vec0 * vec1 );
-
-                // how see if that corresponds to any touch events:
-                {
-                    // distance between the fingers changed: a pinch.
-                    output.push_back(TouchEvent());
-                    TouchEvent& ev = output.back();
-                    ev._eventType = EVENT_MULTI_PINCH;
-                    ev._dx = 0.0, ev._dy = deltaDistance * -sens;
-                }
+                float dragThres = 2.0f;         
 
-                {
-                    // angle between vectors changed: a twist.
-                    output.push_back(TouchEvent());
-                    TouchEvent& ev = output.back();
-                    ev._eventType = EVENT_MULTI_TWIST;
-                    ev._dx = 0.0, ev._dy = dot * sens;
-                }
-
-                {
+                // now see if that corresponds to any touch events:
+                
+                if (osg::equivalent( vec0.x(), vec1.x(), dragThres) && 
+                    osg::equivalent( vec0.y(), vec1.y(), dragThres))
+                {                    
                     // two-finger drag.
                     output.push_back(TouchEvent());
                     TouchEvent& ev = output.back();
                     ev._eventType = EVENT_MULTI_DRAG;
                     ev._dx = 0.5 * (dx[0]+dx[1]) * sens;
                     ev._dy = 0.5 * (dy[0]+dy[1]) * sens;
-                }
+                }                                                
+                else
+                {                                 
+                    // otherwise it's a pinch and/or a zoom.  You can do them together.
+                    if (fabs(deltaDistance) > 1.0)
+                    {
+                        // distance between the fingers changed: a pinch.
+                        output.push_back(TouchEvent());
+                        TouchEvent& ev = output.back();
+                        ev._eventType = EVENT_MULTI_PINCH;
+                        ev._dx = 0.0, ev._dy = deltaDistance * -sens;
+                    }
+
+                    if (fabs(da) > 0.01)
+                    {
+                        // angle between vectors changed: a twist.
+                        output.push_back(TouchEvent());
+                        TouchEvent& ev = output.back();
+                        ev._eventType = EVENT_MULTI_TWIST;                    
+                        ev._dx = da;
+                        //ev._dy = 0.5 * (dy[0]+dy[1]) * sens;
+                        ev._dy = 0.0;
+                    }
+                }             
             }
         }
 
@@ -1830,15 +1909,25 @@ EarthManipulator::parseTouchEvents( TouchEvents& output )
             MultiTouchPoint& p0 = _touchPointQueue[0];
             MultiTouchPoint& p1 = _touchPointQueue[1];
 
-            if (p0[0].phase != osgGA::GUIEventAdapter::TOUCH_ENDED &&
-                p1[0].phase == osgGA::GUIEventAdapter::TOUCH_MOVED )
+            if (p1[0].tapCount == 2)
+            {
+                // double tap
+                output.push_back(TouchEvent());
+                TouchEvent& ev = output.back();
+                ev._eventType = EVENT_MOUSE_DOUBLE_CLICK;
+                ev._mbmask = osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
+                ev._dx = 0.0;
+                ev._dy = 0.0;
+            }
+            else if ((p0[0].phase != osgGA::GUIEventAdapter::TOUCH_ENDED &&
+                      p1[0].phase == osgGA::GUIEventAdapter::TOUCH_MOVED ))
             {
                 output.push_back(TouchEvent());
                 TouchEvent& ev = output.back();
                 ev._eventType = EVENT_MOUSE_DRAG;
                 ev._mbmask = osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
-                ev._dx = (p1[0].x - p0[0].x) * sens;
-                ev._dy = (p1[0].y - p0[0].y) * sens;
+                ev._dx =  (p1[0].x - p0[0].x) * sens;
+                ev._dy = -(p1[0].y - p0[0].y) * sens;
             }
         }
     }
@@ -2040,7 +2129,6 @@ EarthManipulator::recalculateCenter( const osg::CoordinateFrame& frame )
 void
 EarthManipulator::pan( double dx, double dy )
 {
-    //OE_NOTICE << "pan " << dx << "," << dy <<  std::endl;
     if (!_tether_node.valid())
     {
         double scale = -0.3f*_distance;
@@ -2332,7 +2420,10 @@ EarthManipulator::handleMovementAction( const ActionType& type, double dx, doubl
         break;
 
     case ACTION_EARTH_DRAG:
-        drag( dx, dy, view );
+        if (_thrown)
+          pan(dx*0.5, dy*0.5);  //TODO: create proper drag throwing instead of panning trick
+        else
+          drag( dx, dy, view );
         break;
     default:break;
     }
@@ -2432,6 +2523,9 @@ EarthManipulator::handleMouseAction( const Action& action, osg::View* view )
     }
     else
     {
+        
+        _dx = dx;
+        _dy = dy;
         handleMovementAction( action._type, dx, dy, view );
     }
 
@@ -2491,22 +2585,6 @@ EarthManipulator::handleScrollAction( const Action& action, double duration )
     return handleAction( action, dx, dy, duration );
 }
 
-#if 0
-bool
-EarthManipulator::handleMultiTouchAction( const Action& action, const EarthManipulator::TouchEvent& te, osg::View* view )
-{
-    if ( action._type == ACTION_ZOOM )
-    {
-        handleMovementAction( action._type, 0.0, te._deltaDistance, view );
-        return true;
-    }
-    else
-    {
-        return false;
-    }
-}
-#endif
-
 bool
 EarthManipulator::handleAction( const Action& action, double dx, double dy, double duration )
 {
diff --git a/src/osgEarthUtil/ExampleResources.cpp b/src/osgEarthUtil/ExampleResources.cpp
index 5e7f49d..7ef71af 100644
--- a/src/osgEarthUtil/ExampleResources.cpp
+++ b/src/osgEarthUtil/ExampleResources.cpp
@@ -25,9 +25,15 @@
 #include <osgEarthUtil/AutoClipPlaneHandler>
 #include <osgEarthUtil/DataScanner>
 
+#include <osgEarthUtil/NormalMap>
+#include <osgEarthUtil/DetailTexture>
+#include <osgEarthUtil/LODBlending>
+#include <osgEarthUtil/VerticalScale>
+#include <osgEarthUtil/ContourMap>
+
 #include <osgEarthAnnotation/AnnotationData>
 #include <osgEarthAnnotation/AnnotationRegistry>
-#include <osgEarthAnnotation/Decluttering>
+#include <osgEarth/Decluttering>
 
 #include <osgEarth/XmlUtils>
 #include <osgEarth/StringUtils>
@@ -41,8 +47,9 @@
 
 #define KML_PUSHPIN_URL "http://demo.pelicanmapping.com/icons/pushpin_yellow.png"
 
-#define VP_DURATION 4.5 // time to fly to a viewpoint
-
+#define VP_MIN_DURATION      2.0     // minimum fly time.
+#define VP_METERS_PER_SECOND 2500.0  // fly speed
+#define VP_MAX_DURATION      2.0 //8.0     // maximum fly time.
 
 using namespace osgEarth;
 using namespace osgEarth::Util;
@@ -55,6 +62,17 @@ using namespace osgEarth::Annotation;
 /** Shared event handlers. */
 namespace
 {
+    void flyToViewpoint(EarthManipulator* manip, const Viewpoint& vp)
+    {
+        Viewpoint currentVP = manip->getViewpoint();
+        GeoPoint vp0(currentVP.getSRS(), currentVP.getFocalPoint(), ALTMODE_ABSOLUTE);
+        GeoPoint vp1(vp.getSRS(), vp.getFocalPoint(), ALTMODE_ABSOLUTE);
+        double distance = vp0.distanceTo(vp1);
+        double duration = osg::clampBetween(distance / VP_METERS_PER_SECOND, VP_MIN_DURATION, VP_MAX_DURATION);
+        manip->setViewpoint( vp, duration );
+    }
+
+
     // flies to a viewpoint in response to control event (click)
     struct ClickViewpointHandler : public ControlEventHandler
     {
@@ -67,7 +85,7 @@ namespace
         virtual void onClick( class Control* control )
         {
             if ( _manip )
-                _manip->setViewpoint( _vp, VP_DURATION );
+                flyToViewpoint(_manip, _vp);
         }
     };
 
@@ -112,12 +130,15 @@ namespace
         {
             if ( ea.getEventType() == ea.KEYDOWN )
             {
-                int index = (int)ea.getKey() - (int)'1';
-                if ( index >= 0 && index < (int)_viewpoints.size() )
+                if ( !_viewpoints.empty() )
                 {
-                    _manip->setViewpoint( _viewpoints[index], VP_DURATION );
+                    int index = (int)ea.getKey() - (int)'1';
+                    if ( index >= 0 && index < (int)_viewpoints.size() )
+                    {
+                        flyToViewpoint( _manip, _viewpoints[index] );
+                    }
                 }
-                else if ( ea.getKey() == 'v' )
+                if ( ea.getKey() == 'v' )
                 {
                     XmlDocument xml( _manip->getViewpoint().getConfig() );
                     xml.store( std::cout );
@@ -161,10 +182,10 @@ ViewpointControlFactory::create(const std::vector<Viewpoint>& viewpoints,
             vpc->addEventHandler( new ClickViewpointHandler(vp, view->getCameraManipulator()) );
             grid->setControl( 1, i, vpc );
         }
-
-        view->addEventHandler( new ViewpointHandler(viewpoints, view) );
     }
 
+    view->addEventHandler( new ViewpointHandler(viewpoints, view) );
+
     return grid;
 }
 
@@ -199,10 +220,9 @@ namespace
 
         virtual void onValueChanged( class Control* control, float value )
         {
-            int year, month, date;
-            double h;
-            _sky->getDateTime( year, month, date, h);
-            _sky->setDateTime( year, month, date, value );
+            DateTime d;
+            _sky->getDateTime(d);
+            _sky->setDateTime(DateTime(d.year(), d.month(), d.day(), value));
         }
     };
 
@@ -219,6 +239,9 @@ namespace
     };
 }
 
+//#undef USE_AMBIENT_SLIDER
+#define USE_AMBIENT_SLIDER 1
+
 Control*
 SkyControlFactory::create(SkyNode*         sky,
                           osgViewer::View* view) const
@@ -230,20 +253,21 @@ SkyControlFactory::create(SkyNode*         sky,
 
     grid->setControl( 0, 0, new LabelControl("Time: ", 16) );
 
-    int year, month, date;
-    double h;
-    sky->getDateTime( year, month, date, h);
+    DateTime dt;
+    sky->getDateTime(dt);
 
-    HSliderControl* skySlider = grid->setControl(1, 0, new HSliderControl( 0.0f, 24.0f, h ));
+    HSliderControl* skySlider = grid->setControl(1, 0, new HSliderControl( 0.0f, 24.0f, dt.hours() ));
     skySlider->setHorizFill( true, 200 );
     skySlider->addEventHandler( new SkySliderHandler(sky) );
 
     grid->setControl(2, 0, new LabelControl(skySlider) );
 
+#ifdef USE_AMBIENT_SLIDER
     grid->setControl(0, 1, new LabelControl("Ambient: ", 16) );
     HSliderControl* ambient = grid->setControl(1, 1, new HSliderControl(0.0f, 1.0f, sky->getAmbientBrightness()));
     ambient->addEventHandler( new AmbientBrightnessHandler(sky) );
     grid->setControl(2, 1, new LabelControl(ambient) );
+#endif
 
     return grid;
 }
@@ -347,7 +371,8 @@ namespace
     struct AnnoControlBuilder : public osg::NodeVisitor
     {
         AnnoControlBuilder(osgViewer::View* view)
-            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+            : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+              _mindepth(-1)
         {
             _grid = new Grid();
             _grid->setHorizFill( true );
@@ -370,8 +395,10 @@ namespace
                 std::string name = trim(data->getName());
                 if ( name.empty() ) name = "<unnamed>";
                 LabelControl* label = new LabelControl( name, 14.0f );
-                unsigned relDepth = osg::clampAbove(3u, (unsigned int)this->getNodePath().size());
-                label->setMargin(Gutter(0,0,0,(relDepth-3)*20));
+                int depth = (int)this->getNodePath().size();
+                if ( _mindepth < 0 )
+                    _mindepth = depth;
+                label->setMargin(Gutter(0,0,0,(depth-_mindepth)*20));
                 if ( data->getViewpoint() )
                 {
                     label->addEventHandler( new ClickViewpointHandler(*data->getViewpoint(), _manip) );
@@ -385,6 +412,7 @@ namespace
 
         Grid*             _grid;
         EarthManipulator* _manip;
+        int               _mindepth;
     };
 }
 
@@ -393,7 +421,7 @@ AnnotationGraphControlFactory::create(osg::Node*       graph,
                                       osgViewer::View* view) const
 {
     AnnoControlBuilder builder( view );
-	builder.setNodeMaskOverride(~0);
+    builder.setNodeMaskOverride(~0);
     if ( graph )
         graph->accept( builder );
 
@@ -428,8 +456,8 @@ MapNodeHelper::load(osg::ArgumentParser& args,
 
     if ( !node )
     {
-        OE_WARN << LC << "No earth file from the command line; making one." << std::endl;
-        node = new MapNode();
+        OE_WARN << LC << "No earth file." << std::endl;
+        return 0L;
     }
 
     osg::ref_ptr<MapNode> mapNode = MapNode::get(node);
@@ -501,7 +529,7 @@ MapNodeHelper::parse(MapNode*             mapNode,
     bool useOrtho      = args.read("--ortho");
     bool useAutoClip   = args.read("--autoclip");
 
-    float ambientBrightness = 0.4f;
+    float ambientBrightness = 0.2f;
     args.read("--ambientBrightness", ambientBrightness);
 
     std::string kmlFile;
@@ -517,6 +545,7 @@ MapNodeHelper::parse(MapNode*             mapNode,
     ControlCanvas* canvas = ControlCanvas::get(view, false);
 
     Container* mainContainer = canvas->addControl( new VBox() );
+    mainContainer->setAbsorbEvents( true );
     mainContainer->setBackColor( Color(Color::Black, 0.8) );
     mainContainer->setHorizAlign( Control::ALIGN_LEFT );
     mainContainer->setVertAlign( Control::ALIGN_BOTTOM );
@@ -534,6 +563,13 @@ MapNodeHelper::parse(MapNode*             mapNode,
     const Config& declutterConf   = externals.child("decluttering");
     Config        viewpointsConf  = externals.child("viewpoints");
 
+    // some terrain effects.
+    const Config& normalMapConf   = externals.child("normal_map");
+    const Config& detailTexConf   = externals.child("detail_texture");
+    const Config& lodBlendingConf = externals.child("lod_blending");
+    const Config& vertScaleConf   = externals.child("vertical_scale");
+    const Config& contourMapConf  = externals.child("contour_map");
+
     // backwards-compatibility: read viewpoints at the top level:
     const ConfigSet& old_viewpoints = externals.children("viewpoint");
     for( ConfigSet::const_iterator i = old_viewpoints.begin(); i != old_viewpoints.end(); ++i )
@@ -566,9 +602,8 @@ MapNodeHelper::parse(MapNode*             mapNode,
     {
         double hours = skyConf.value( "hours", 12.0 );
         SkyNode* sky = new SkyNode( mapNode->getMap() );
-        //sky->setAmbientBrightness( ambientBrightness );
-        sky->setAutoAmbience( true );
-        sky->setDateTime( 2011, 3, 6, hours );
+        sky->setAmbientBrightness( ambientBrightness );
+        sky->setDateTime( DateTime(2011, 3, 6, hours) );
         sky->attach( view );
         root->addChild( sky );
         Control* c = SkyControlFactory().create(sky, view);
@@ -693,6 +728,44 @@ MapNodeHelper::parse(MapNode*             mapNode,
         OE_INFO << LC << "...found " << imageLayers.size() << " image layers." << std::endl;
     }
 
+    // Install a normal map layer.
+    if ( !normalMapConf.empty() )
+    {
+        osg::ref_ptr<NormalMap> effect = new NormalMap(normalMapConf, mapNode->getMap());
+        if ( effect->getNormalMapLayer() )
+        {
+            mapNode->getTerrainEngine()->addEffect( effect.get() );
+        }
+    }
+
+    // Install a detail texturer
+    if ( !detailTexConf.empty() )
+    {
+        osg::ref_ptr<DetailTexture> effect = new DetailTexture(detailTexConf);
+        if ( effect->getImage() )
+        {
+            mapNode->getTerrainEngine()->addEffect( effect.get() );
+        }
+    }
+
+    // Install elevation morphing
+    if ( !lodBlendingConf.empty() )
+    {
+        mapNode->getTerrainEngine()->addEffect( new LODBlending(lodBlendingConf) );
+    }
+
+    // Install vertical scaler
+    if ( !vertScaleConf.empty() )
+    {
+        mapNode->getTerrainEngine()->addEffect( new VerticalScale(vertScaleConf) );
+    }
+
+    // Install a contour map effect.
+    if ( !contourMapConf.empty() )
+    {
+        mapNode->getTerrainEngine()->addEffect( new ContourMap(contourMapConf) );
+    }
+
     // Generic named value uniform with min/max.
     VBox* uniformBox = 0L;
     while( args.find( "--uniform" ) >= 0 )
@@ -752,5 +825,6 @@ MapNodeHelper::usage() const
         << "  --autoclip                    : installs an auto-clip plane callback\n"
         << "  --images [path]               : finds and loads image layers from folder [path]\n"
         << "  --image-extensions [ext,...]  : with --images, extensions to use\n"
-        << "  --out-earth [file]            : write the loaded map to an earth file\n";
+        << "  --out-earth [file]            : write the loaded map to an earth file\n"
+        << "  --uniform [name] [min] [max]  : create a uniform controller with min/max values\n";
 }
diff --git a/src/osgEarthUtil/FeatureQueryTool.cpp b/src/osgEarthUtil/FeatureQueryTool.cpp
index e357e79..ebe5d10 100644
--- a/src/osgEarthUtil/FeatureQueryTool.cpp
+++ b/src/osgEarthUtil/FeatureQueryTool.cpp
@@ -99,14 +99,14 @@ FeatureQueryTool::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdap
             for(Picker::Hits::iterator hit = hits.begin(); hit != hits.end(); ++hit )
             {
                 FeatureSourceIndexNode* index = picker.getNode<FeatureSourceIndexNode>( *hit );
-                if ( index && (hit->distance < closestDistance) )
+                if ( index && (hit->ratio < closestDistance) )
                 {
                     FeatureID fid;
                     if ( index->getFID( hit->drawable, hit->primitiveIndex, fid ) )
                     {
                         closestIndex    = index;
                         closestFID      = fid;
-                        closestDistance = hit->distance;
+                        closestDistance = hit->ratio;
                         closestWorldPt  = hit->matrix.valid() ? hit->localIntersectionPoint * (*hit->matrix.get()) : hit->localIntersectionPoint;
                     }
                 }
diff --git a/src/osgEarthUtil/GLSLColorFilter.cpp b/src/osgEarthUtil/GLSLColorFilter.cpp
index bbafdfd..9d3bc21 100644
--- a/src/osgEarthUtil/GLSLColorFilter.cpp
+++ b/src/osgEarthUtil/GLSLColorFilter.cpp
@@ -35,7 +35,7 @@ namespace
     static const char* s_localShaderSource =
         "#version " GLSL_VERSION_STR "\n"
         GLSL_DEFAULT_PRECISION_FLOAT "\n"
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
         "__CODE__ \n"
         "} \n";
diff --git a/src/osgEarthUtil/GammaColorFilter.cpp b/src/osgEarthUtil/GammaColorFilter.cpp
index adb96f5..e4362a6 100644
--- a/src/osgEarthUtil/GammaColorFilter.cpp
+++ b/src/osgEarthUtil/GammaColorFilter.cpp
@@ -36,7 +36,7 @@ namespace
         "#version 110\n"
         "uniform vec3 __UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
         "    color.rgb = pow(color.rgb, 1.0 / __UNIFORM_NAME__.rgb); \n"
         "}\n";
diff --git a/src/osgEarthUtil/GeodeticGraticule.cpp b/src/osgEarthUtil/GeodeticGraticule.cpp
index 6b6322a..c21c726 100644
--- a/src/osgEarthUtil/GeodeticGraticule.cpp
+++ b/src/osgEarthUtil/GeodeticGraticule.cpp
@@ -22,8 +22,8 @@
 #include <osgEarthFeatures/GeometryCompiler>
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthAnnotation/LabelNode>
-#include <osgEarthAnnotation/Decluttering>
 
+#include <osgEarth/Decluttering>
 #include <osgEarth/Registry>
 #include <osgEarth/NodeUtils>
 #include <osgEarth/Utils>
@@ -422,57 +422,6 @@ GeodeticGraticule::buildChildren( unsigned level, unsigned x, unsigned y ) const
     else return 0L;
 }
 
-#if 0
-void
-GeodeticGraticule::addLevel(float        maxRange,
-                            float        minRange,
-                            unsigned     subdivFactor,
-                            const Style& style)
-{
-    if ( _autoLevels )
-    {
-        _autoLevels = false;
-        _levels.clear();
-    }
-
-    // the "-2" here is because normal tile paging gives you one subdivision already,
-    // so we only need to account for > 1 subdivision factor.
-    unsigned cellsPerTile = subdivFactor <= 2u ? 1u : 1u << (subdivFactor-2u);
-
-    Level level;
-    level._maxRange = maxRange;
-    level._minRange = minRange;
-    _profile->getNumTiles( _levels.size(), level._tilesX, level._tilesY );
-    level._cellsPerTileX = std::max(1u, cellsPerTile);
-    level._cellsPerTileY = std::max(1u, cellsPerTile);
-
-    if ( !style.empty() )
-    {
-        level._style = style;
-    }
-    else
-    {
-        level._style = _levels.size() > 0 ? _levels[_levels.size()-1]._style : _defaultLineStyle;
-    }
-
-    _levels.push_back( level );
-}
-
-bool
-GeodeticGraticule::getLevel( unsigned level, GeodeticGraticule::Level& out_level ) const
-{
-    if ( level < _levels.size() )
-    {
-        out_level = _levels[level];
-        return true;
-    }
-    else
-    {
-        return false;
-    }
-}
-#endif
-
 void
 GeodeticGraticule::traverse( osg::NodeVisitor& nv )
 {
@@ -527,24 +476,6 @@ namespace osgEarth { namespace Util
 
             osg::Node* result = graticule->buildChildren( levelNum, x, y );
             return result ? ReadResult(result) : ReadResult::ERROR_IN_READING_FILE;
-
-#if 0
-            if ( marker == GRID_MARKER )
-            {
-                osg::Node* result = graticule->createGridLevel( levelNum );
-                return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE;
-            }
-            else if ( marker == TEXT_MARKER )
-            {
-                osg::Node* result = graticule->createTextLevel( levelNum );
-                return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE;
-            }
-            else
-            {
-                OE_NOTICE << "oh no! no markers" << std::endl;
-                return ReadResult::FILE_NOT_HANDLED;
-            }
-#endif
         }
     };
     REGISTER_OSGPLUGIN(GRATICULE_EXTENSION, GeodeticGraticuleFactory)
diff --git a/src/osgEarthUtil/HSLColorFilter.cpp b/src/osgEarthUtil/HSLColorFilter.cpp
index b1c6892..7b5eff0 100644
--- a/src/osgEarthUtil/HSLColorFilter.cpp
+++ b/src/osgEarthUtil/HSLColorFilter.cpp
@@ -119,7 +119,7 @@ namespace
         "void oe_hsl_HSL_2_RGB(in float h, in float s, in float l, out float r, out float g, out float b);\n"
         "uniform vec3 __UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{ \n"
         "    if (__UNIFORM_NAME__.x != 0.0 || __UNIFORM_NAME__.y != 0.0 || __UNIFORM_NAME__.z != 0.0) \n"
         "    { \n"
diff --git a/src/osgEarthUtil/HTM b/src/osgEarthUtil/HTM
new file mode 100644
index 0000000..345efe0
--- /dev/null
+++ b/src/osgEarthUtil/HTM
@@ -0,0 +1,195 @@
+/* -*-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_UTIL_HTM_H
+#define OSGEARTH_UTIL_HTM_H 1
+
+#include <osgEarthUtil/Common>
+#include <osg/Geode>
+#include <osg/Group>
+#include <osg/Polytope>
+#include <vector>
+
+namespace osgEarth { namespace Util
+{
+    class HTMNode;
+
+    /**
+     * Hierarchical Triangular Mesh group
+     * http://www.geog.ucsb.edu/~hu/papers/spatialIndex.pdf
+     *
+     * An osg::Group that automatically organizes its contents spatially
+     * in order to improve culling performance and proximity search.
+     *
+     * Note! This group will improve performance when you have a huge
+     * number of entities and you zoom in to a smaller area. It will NOT
+     * improve the performance when viewing the entire set from a far
+     * range.
+     */
+    class OSGEARTHUTIL_EXPORT HTMGroup : public osg::Group
+    {
+    public:
+        HTMGroup();
+
+        /** total number of objects in the group. */
+        unsigned dataCount() const { return _dataCount; }
+
+        /** The maximum number of objects to hold in an index cell before
+            splitting it up into smaller parts. */
+        unsigned getSplitThreshold() const { return _splitThreshold; }
+
+        /** The minimum number of objects across a set of child cells before
+            merging them back into a single cell. */
+        unsigned getMergeThreshold() const { return _mergeThreshold; }
+
+        /** enable or disable clustering (experimental) */
+        void setCluster( bool value ) { _cluster = value; }
+        bool getCluster() const       { return _cluster; }
+
+        /** activate debugging mode */
+        void setDebug() { _debug = true; }
+        bool getDebug() const { return _debug; }
+
+        /** check a node to see whether we need to move it. */
+        bool refresh(osg::Node* node);
+
+        /** removes a node from the group. */
+        bool remove(osg::Node* node);
+
+    public: // osg::Group
+
+        /** Add a node to the group. */
+        virtual bool addChild(osg::Node* child);
+
+        /** Add a node to the group. Ignores the "index". */
+        virtual bool insertChild(unsigned index, osg::Node* child);
+
+
+    public: // osg::Group (internal)
+
+        /** These methods are derived from Group but are NOOPs for the HTMGroup. */
+        virtual bool removeChildren(unsigned pos, unsigned numChildrenToRemove);
+        virtual bool replaceChild(osg::Node* origChild, osg::Node* newChild);
+        virtual bool setChild(unsigned index, osg::Node* node);
+
+        /** custom traversal */
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        virtual ~HTMGroup() { }
+
+        bool insert(osg::Node* node);
+
+        unsigned _dataCount;
+        bool     _debug;
+        bool     _cluster;
+        unsigned _splitThreshold;
+        unsigned _mergeThreshold;
+
+        typedef std::map<osg::Node*, HTMNode*> NodeMap;
+        NodeMap _nodeTable;
+    };
+
+
+    /**
+     * Internal index cell for the HTMGroup (do not use directly).
+     */
+    class HTMNode : public osg::Group
+    {
+    public:
+        HTMNode(HTMGroup* root, const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2);
+
+    public: // osg::Group
+
+        virtual void traverse(osg::NodeVisitor& nv);
+
+    protected:
+        virtual ~HTMNode() { }
+
+        HTMNode* insert(osg::Node* node);
+
+        bool remove(osg::Node* node);
+
+        bool refresh(osg::Node* node);
+
+        void split();
+
+        void merge();
+
+        bool isLeaf() const {
+            return getNumChildren() == 0;
+        }
+
+        bool contains(const osg::Vec3d& p) const {
+            return _tri.contains(p);
+        }
+
+        unsigned dataCount() const {
+            return _dataCount;
+        }
+
+        HTMNode* findLeaf(osg::Node*);
+
+        // test whether the node's triangle lies entirely withing a frustum
+        bool entirelyWithin(const osg::Polytope& tope) const;
+        
+        // test whether the node's triangle intersects a frustum
+        bool intersects(const osg::Polytope& tope) const;
+
+        osg::BoundingSphere computeBound() const;
+
+    private:
+
+        struct PolytopeDP : public osg::Polytope
+        {
+            bool contains(const osg::Vec3d& p) const;
+            bool containsAnyOf(const std::vector<osg::Vec3d>& p) const;
+        };
+
+        struct Triangle
+        {
+            std::vector<osg::Vec3d> _v;
+            PolytopeDP              _tope;
+
+            void set(const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2);
+
+            void getMidpoints(osg::Vec3d* w) const;
+
+            bool contains(const osg::Vec3d& p) const {
+                return _tope.contains(p);
+            }
+        };
+
+        typedef std::list<osg::ref_ptr<osg::Node> > NodeList;
+
+        Triangle      _tri;
+        NodeList      _data;
+        unsigned      _dataCount;
+        HTMGroup*     _root;
+        osg::ref_ptr<osg::Geode> _debugGeode;
+        osg::ref_ptr<osg::Node>  _clusterNode;
+        osg::BoundingSphere      _bs;
+
+        friend class HTMGroup;
+    };
+
+} } // namesapce osgEarth::Util
+
+
+#endif // OSGEARTH_UTIL_HTM_H
diff --git a/src/osgEarthUtil/HTM.cpp b/src/osgEarthUtil/HTM.cpp
new file mode 100644
index 0000000..780a38b
--- /dev/null
+++ b/src/osgEarthUtil/HTM.cpp
@@ -0,0 +1,604 @@
+/* -*-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 <osgEarthUtil/HTM>
+#include <osgEarth/CullingUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/StringUtils>
+#include <osgEarthAnnotation/LabelNode>
+#include <osg/Geometry>
+#include <osgText/Text>
+
+#define LC "[HTMGroup] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Annotation;
+
+//-----------------------------------------------------------------------
+
+bool
+HTMNode::PolytopeDP::contains(const osg::Vec3d& p) const
+{
+    for( PlaneList::const_iterator i = _planeList.begin(); i != _planeList.end(); ++i )
+    {
+        if ( i->distance(p) < 0 )
+            return false;
+    }
+    return true;
+}
+
+bool
+HTMNode::PolytopeDP::containsAnyOf(const std::vector<osg::Vec3d>& points) const
+{
+    for( PlaneList::const_iterator i = _planeList.begin(); i != _planeList.end(); ++i )
+    {
+        if ( i->intersect(points) < 0 )
+            return false;
+    }
+    return true;
+}
+
+//-----------------------------------------------------------------------
+
+void
+HTMNode::Triangle::set(const osg::Vec3d& v0, const osg::Vec3d& v1, const osg::Vec3d& v2)
+{
+    _v.resize(4);
+
+    _v[0] = v0;
+    _v[1] = v1;
+    _v[2] = v2;
+    _v[3].set(0,0,0);
+
+    // assume verts are CCW.
+    osg::Vec3d n0 = _v[0] ^ _v[1]; n0.normalize();
+    osg::Vec3d n1 = _v[1] ^ _v[2]; n1.normalize();
+    osg::Vec3d n2 = _v[2] ^ _v[0]; n2.normalize();
+
+    // assemble the bounding polytope.
+    _tope.add( osg::Plane(n0, _v[3]) );
+    _tope.add( osg::Plane(n1, _v[3]) );
+    _tope.add( osg::Plane(n2, _v[3]) );
+}
+
+void
+HTMNode::Triangle::getMidpoints(osg::Vec3d* w) const
+{
+    w[0] = (_v[0]+_v[1]); w[0].normalize();
+    w[1] = (_v[1]+_v[2]); w[1].normalize();
+    w[2] = (_v[2]+_v[0]); w[2].normalize();
+}
+
+//-----------------------------------------------------------------------
+
+HTMNode::HTMNode(HTMGroup*         root,
+                 const osg::Vec3d& v0, 
+                 const osg::Vec3d& v1, 
+                 const osg::Vec3d& v2)
+{
+    this->setCullingActive(false);
+
+    _root = root;
+    _tri.set( v0, v1, v2 );
+    _dataCount = 0;
+
+    // init the bounding sphere
+    _bs.expandBy( _tri._v[0] * 6380000);
+    _bs.expandBy( _tri._v[1] * 6380000);
+    _bs.expandBy( _tri._v[2] * 6380000);
+
+    if ( true )
+    {
+        _debugGeode = new osg::Geode();
+        osg::Geometry* g = new osg::Geometry();
+        osg::Vec3Array* v = new osg::Vec3Array();
+        v->push_back( v0 * 6372000 );
+        v->push_back( v1 * 6372000 );
+        v->push_back( v2 * 6372000 );
+        g->setVertexArray( v );
+        osg::Vec4Array* c = new osg::Vec4Array();
+        c->push_back( osg::Vec4(1,1,0,1) );
+        g->setColorArray( c );
+        g->setColorBinding( osg::Geometry::BIND_OVERALL );
+        g->addPrimitiveSet( new osg::DrawArrays(GL_LINE_LOOP, 0, 3) );
+        g->getOrCreateStateSet()->setMode(GL_LIGHTING, 0);
+        _debugGeode->addDrawable( g );
+        _debugGeode->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "DepthSortedBin");
+        _debugGeode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
+    }
+
+    {
+        osgText::Text* text = new osgText::Text();
+        text->setText("Hi.");
+        text->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
+        text->setCharacterSize(48.0f);
+        text->setAutoRotateToScreen(true);
+        text->setPosition( ((v0+v1+v2)/(v0+v1+v2).length()) * 6400000 );
+        text->setDataVariance(osg::Object::DYNAMIC);
+        text->setFont( Registry::instance()->getDefaultFont() );
+        text->setStateSet(new osg::StateSet());
+        osg::Geode* geode = new osg::Geode();
+        geode->addDrawable(text);
+        geode->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "DepthSortedBin");
+        geode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, 0);
+        geode->setCullingActive( false );
+        _clusterNode = geode;
+    }
+}
+
+HTMNode*
+HTMNode::insert(osg::Node* node)
+{
+    HTMNode* leaf = this;
+
+    dirtyBound();
+
+    _data.push_back( node );
+
+    if (_data.size() >= _root->getSplitThreshold() &&
+        getNumChildren() == 0 )
+    {
+        split();
+    }
+
+    if ( _children.size() > 0 )
+    {
+        const osg::Vec3d& p = node->getBound().center();
+
+        for(unsigned i=0; i<_children.size(); ++i)
+        {
+            HTMNode* child = dynamic_cast<HTMNode*>(_children[i].get());
+            if ( child && child->contains(p) )
+            {
+                leaf = child->insert(node);
+                break;
+            }
+        }
+    }
+
+    _dataCount++;
+
+    dynamic_cast<osgText::Text*>(dynamic_cast<osg::Geode*>(_clusterNode.get())->getDrawable(0))
+        ->setText( Stringify() << _dataCount );
+
+    return leaf;
+}
+
+bool
+HTMNode::remove(osg::Node* node)
+{
+    NodeList::iterator i = std::find( _data.begin(), _data.end(), node );
+    if ( i != _data.end() )
+    {
+        dirtyBound();
+
+        _data.erase( i );
+        _dataCount--;
+
+        bool found = false;
+        for(unsigned i=0; i<_children.size() && !found; ++i)
+        {
+            HTMNode* child = dynamic_cast<HTMNode*>(_children[i].get());
+            if ( child )
+            {
+                found = child->remove( node );
+            }
+        }
+
+        return found;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+HTMNode*
+HTMNode::findLeaf(osg::Node* node)
+{
+    HTMNode* leaf = 0L;
+
+    NodeList::iterator i = std::find( _data.begin(), _data.end(), node );
+    if ( i != _data.end() )
+    {
+        leaf = this;
+        for(unsigned i=0; i<_children.size(); ++i)
+        {
+            HTMNode* child = dynamic_cast<HTMNode*>(_children[i].get());
+            HTMNode* leaf2 = child->findLeaf( node );
+            if ( leaf2 )
+            {
+                leaf = leaf2;
+                break;
+            }
+        }
+    }
+
+    return leaf;
+}
+
+bool
+HTMNode::refresh(osg::Node* node)
+{
+    const osg::Vec3d& p = node->getBound().center();
+
+    if ( contains(p) )
+    {
+        // already in this node; if there are children, re-insert
+        // into the new child.
+        if ( _children.size() > 0 )
+        {
+            for(unsigned i=0; i<_children.size(); ++i)
+            {
+                HTMNode* child = dynamic_cast<HTMNode*>(_children[i].get());
+                if ( child && child->contains(p) )
+                {
+                    child->insert(node);
+                    break;
+                }
+            }
+        }
+        return true; // done.
+    }
+    else
+    {
+        std::remove( _data.begin(), _data.end(), node );
+        _dataCount--;
+
+        HTMNode* parentNode = dynamic_cast<HTMNode*>( getParent(0) );
+        if ( parentNode )
+        {
+            return parentNode->refresh( node );
+        }
+        HTMGroup* parentGroup = dynamic_cast<HTMGroup*>( getParent(0) );
+        if ( parentGroup )
+        {
+            return parentGroup->addChild( node );
+        }
+
+        // should never get here
+        OE_WARN << LC << "trouble." << std::endl;
+        return false;
+    }
+}
+
+void
+HTMNode::split()
+{
+    OE_DEBUG << LC << "Splitting htmid:" << getName() << std::endl;
+
+    // find the midpoints of each side of the triangle
+    osg::Vec3d w[3];
+    _tri.getMidpoints( w );
+
+    // split into four children, each wound CCW
+    HTMNode* c[4];
+    c[0] = new HTMNode(_root, _tri._v[0], w[0], w[2]);
+    c[1] = new HTMNode(_root, _tri._v[1], w[1], w[0]);
+    c[2] = new HTMNode(_root, _tri._v[2], w[2], w[1]);
+    c[3] = new HTMNode(_root, w[0], w[1], w[2]);
+
+    // distibute the data amongst the children
+    for(NodeList::iterator i = _data.begin(); i != _data.end(); ++i)
+    {
+        osg::Node* node = i->get();
+        const osg::BoundingSphere& bs = node->getBound();
+        
+        osg::Vec3d p = bs.center();
+        p.normalize(); // need?
+
+        for(unsigned j=0; j<4; ++j)
+        {
+            if ( c[j]->contains(p) )
+            {
+                c[j]->insert( node );
+                break;
+            }
+        }
+    }
+
+    // add the node children
+    for(unsigned i=0; i<4; ++i)
+    {
+        c[i]->setName( Stringify() << getName() << i );
+        osg::Group::addChild( c[i] );
+
+        OE_DEBUG << LC << "  htmid " << c[i]->getName() << " size = " << c[i]->dataCount() << std::endl;
+    }
+}
+
+void
+HTMNode::merge()
+{
+    dirtyBound();
+
+    OE_INFO << LC << "Merging htmid:" << getName() << std::endl;
+    //todo
+}
+
+bool
+HTMNode::entirelyWithin(const osg::Polytope& tope) const
+{
+    const osg::Polytope::PlaneList& planes = tope.getPlaneList();
+    for(unsigned i=0; i<3; ++i)
+    {
+        osg::Vec3d v = _tri._v[i] * 6372000;
+        for( osg::Polytope::PlaneList::const_iterator plane = planes.begin(); plane != planes.end(); ++plane )
+        {
+            if ( plane->distance(v) < 0.0 )
+                return false;
+        }
+    }
+    return true;
+}
+
+bool
+HTMNode::intersects(const osg::Polytope& tope) const
+{
+    const osg::Polytope::PlaneList& planes = tope.getPlaneList();
+
+    for( osg::Polytope::PlaneList::const_iterator plane = planes.begin(); plane != planes.end(); ++plane )
+    {
+        unsigned pointsVisibleToPlane = 0;
+
+        for(unsigned i=0; i<3; ++i)
+        {
+            osg::Vec3d v;
+            
+            v = _tri._v[i] * 6000000;
+            if ( plane->distance(v) >= 0.0 )
+                ++pointsVisibleToPlane;
+
+            v = _tri._v[i] * 12000000;
+            if ( plane->distance(v) >= 0.0 )
+                ++pointsVisibleToPlane;
+        }
+
+        // if none of the points are visible to any one plane, intersection fails.
+        if ( pointsVisibleToPlane == 0 )
+            return false;
+    }
+
+    return true;
+}
+
+void
+HTMNode::traverse(osg::NodeVisitor& nv)
+{
+    bool accepted   = true;
+    bool inRange    = true;
+    bool cull       = nv.getVisitorType()   == nv.CULL_VISITOR;
+    bool activeOnly = nv.getTraversalMode() == nv.TRAVERSE_ACTIVE_CHILDREN;
+
+    osg::Polytope* frustum = 0L;
+
+    if ( activeOnly )
+    {
+        // first make sure this node is in range.
+        //float alt = nv.getViewPoint().length() - 6300000;
+        //inRange = alt <= _bs.radius()*5.0f;
+        
+        //float d = nv.getDistanceToViewPoint(_bs.center(),true);
+        //inRange = d <= _bs.radius()*5.0f;
+
+        // if this isn't a leaf node (i.e. has child nodes), check whether we
+        // can "trivially accept" them all.
+        if ( !isLeaf() && inRange )
+        {
+            osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
+            if ( cv )
+            {
+                frustum = &cv->getCurrentCullingSet().getFrustum();
+                if ( entirelyWithin(*frustum) )
+                {
+                    OE_DEBUG << LC << getName() << ": trivially accepted. yay!" << std::endl;
+                }
+                else
+                {
+                    accepted = false;
+                }
+            }
+        }
+    }
+
+    if ( accepted )
+    {
+        // should we draw a clustering node instead of the data?
+        if ( _root->getCluster() && (!isLeaf() || !inRange) )
+        {
+            _clusterNode->accept(nv);
+        }
+
+        // draw the data itself?
+        else if ( inRange )
+        {
+            for(NodeList::iterator i = _data.begin(); i != _data.end(); ++i)
+            {
+                i->get()->accept( nv );
+            }
+        }
+
+        // draw a debugging node?
+        if ( _debugGeode.valid() )
+        {
+            _debugGeode->accept( nv );
+        }
+    }
+    else
+    {
+        // traverse the children.
+        if ( frustum )
+        {
+            // A frustum is available, so cull against it
+            for(unsigned i=0; i<_children.size(); ++i)
+            {
+                HTMNode* child = static_cast<HTMNode*>(_children[i].get());
+                //if ( frustum->contains(child->getBound()) )
+                if ( child->intersects(*frustum) )
+                {
+                    child->accept( nv );
+                }
+            }
+        }
+        else
+        {
+            // no frustum, just traverse them all as normal
+            osg::Group::traverse( nv );
+        }
+    }
+}
+
+osg::BoundingSphere
+HTMNode::computeBound() const
+{
+    return _bs;
+}
+
+
+//-----------------------------------------------------------------------
+
+HTMGroup::HTMGroup() :
+_dataCount     ( 0 ),
+_splitThreshold( 48 ),
+_mergeThreshold( 48 ),
+_debug         ( false ),
+_cluster       ( false )
+{
+    // hopefully prevent the OSG optimizer from altering this graph:
+    setDataVariance( osg::Object::DYNAMIC );
+
+    // assemble the base manifold of 8 triangles.
+    osg::Vec3d v0( 0, 0, 1);      // lat= 90  long=  0
+    osg::Vec3d v1( 1, 0, 0);      // lat=  0  long=  0
+    osg::Vec3d v2( 0, 1, 0);      // lat=  0  long= 90
+    osg::Vec3d v3(-1, 0, 0);      // lat=  0  long=180
+    osg::Vec3d v4( 0,-1, 0);      // lat=  0  long=-90
+    osg::Vec3d v5( 0, 0,-1);      // lat=-90  long=  0
+
+    // CCW triangles.
+    osg::Group::addChild( new HTMNode(this, v0, v1, v2) );
+    osg::Group::addChild( new HTMNode(this, v0, v2, v3) );
+    osg::Group::addChild( new HTMNode(this, v0, v3, v4) );
+    osg::Group::addChild( new HTMNode(this, v0, v4, v1) );
+    osg::Group::addChild( new HTMNode(this, v5, v1, v4) );
+    osg::Group::addChild( new HTMNode(this, v5, v4, v3) );
+    osg::Group::addChild( new HTMNode(this, v5, v3, v2) );
+    osg::Group::addChild( new HTMNode(this, v5, v2, v1) );
+
+    // HTMIDs.
+    for(unsigned i=0; i<8; ++i)
+    {
+        getChild(i)->setName( Stringify() << i );
+    }
+}
+
+bool
+HTMGroup::insert(osg::Node* node)
+{
+    osg::Vec3d p = node->getBound().center();
+    p.normalize();
+
+    bool inserted = false;
+
+    for(unsigned i=0; i<8; ++i)
+    {
+        HTMNode* child = static_cast<HTMNode*>(_children[i].get());
+        if ( child->contains(p) )
+        {
+            child->insert(node);
+            inserted = true;
+            //_dataCount++;
+            break;
+        }
+    }
+
+    return inserted;
+}
+
+bool
+HTMGroup::remove(osg::Node* node)
+{
+    bool found = false;
+
+    for(unsigned i=0; i<8 && !found; ++i)
+    {
+        HTMNode* child = static_cast<HTMNode*>(_children[i].get());
+        found = child->remove( node );
+    }
+
+    return found;
+}
+
+bool
+HTMGroup::refresh(osg::Node* node)
+{
+    HTMNode* leaf = 0L;
+
+    for(unsigned i=0; i<8 && !leaf; ++i)
+    {
+        HTMNode* child = static_cast<HTMNode*>(_children[i].get());
+        leaf = child->findLeaf( node );
+        if ( leaf )
+        {
+            leaf->refresh( node );
+            break;
+        }
+    }
+
+    return leaf != 0L;
+}
+
+void 
+HTMGroup::traverse(osg::NodeVisitor& nv)
+{
+    osg::Group::traverse(nv);
+}
+
+bool 
+HTMGroup::addChild(osg::Node* child)
+{
+    return insert( child );
+}
+
+bool 
+HTMGroup::insertChild(unsigned index, osg::Node* child)
+{
+    return insert( child );
+}
+
+bool 
+HTMGroup::removeChildren(unsigned pos, unsigned numChildrenToRemove)
+{
+    OE_WARN << LC << "removeChildren() not implemented for HTM" << std::endl;
+    return false;
+}
+
+bool 
+HTMGroup::replaceChild(osg::Node* origChild, osg::Node* newChild)
+{
+    OE_WARN << LC << "replaceChild() not implemented for HTM" << std::endl;
+    return false;
+}
+
+bool 
+HTMGroup::setChild(unsigned index, osg::Node* node)
+{
+    OE_WARN << LC << "setChild() not implemented for HTM" << std::endl;
+    return false;
+}
diff --git a/src/osgEarthUtil/LODBlending b/src/osgEarthUtil/LODBlending
new file mode 100644
index 0000000..d8fdd87
--- /dev/null
+++ b/src/osgEarthUtil/LODBlending
@@ -0,0 +1,84 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_LOD_BLENDING_H
+#define OSGEARTHUTIL_LOD_BLENDING_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osg/Uniform>
+#include <osg/Node>
+#include <osg/observer_ptr>
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Terrain effect that blends terrain levels of detail for a 
+     * smooth transition effect. Operates on elevation data and
+     * image data.
+     *
+     * Known limitation: LOD blending requires either (a) a fully-textured
+     * terrain, by way of a base layer that completely covers the map; or
+     * (b) a transparent globe color. It will not work properly with
+     * untextures (but colored) terrain tiles.
+     */
+    class OSGEARTHUTIL_EXPORT LODBlending : public TerrainEffect
+    {
+    public:
+        /** construct a new blending effect */
+        LODBlending();
+
+        /** Sets the delay (in seconds) between the time the tile appears
+            and when it starts its transition */
+        void setDelay( float value );
+        float getDelay() const { return _delay.get(); }
+
+        /** Sets the duration of the transition */
+        void setDuration( float value );
+        float getDuration() const { return _duration.get(); }
+
+        /** Vertical scale factor to apply to elevation */
+        void setVerticalScale( float value );
+        float getVerticalScale() const { return _vscale.get(); }
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        LODBlending(const Config& conf);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~LODBlending() { }
+        void init();
+
+        optional<float>              _delay;
+        optional<float>              _duration;
+        optional<float>              _vscale;
+        osg::ref_ptr<osg::Uniform>   _delayUniform;
+        osg::ref_ptr<osg::Uniform>   _durationUniform;
+        osg::ref_ptr<osg::Uniform>   _vscaleUniform;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_LOD_BLENDING_H
diff --git a/src/osgEarthUtil/LODBlending.cpp b/src/osgEarthUtil/LODBlending.cpp
new file mode 100644
index 0000000..ded1b5b
--- /dev/null
+++ b/src/osgEarthUtil/LODBlending.cpp
@@ -0,0 +1,249 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/LODBlending>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[LODBlending] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    // This shader will morph elevation from old heights to new heights as
+    // installed in the terrain tile's vertex attributes. oe_terrain_attr[3] holds
+    // the new value; oe_terrain_attr[3] holds the old one. 
+    //
+    // We use two methods: distance to vertex and time. The morph ratio is
+    // a function of the distance from the camera to the vertex (taking into
+    // consideration the tile range factor), but when we limit that based on
+    // a timer. This prevents fast zooming from skipping the morph altogether.
+    //
+    // It will also transition between a parent texture and the current texture.
+    //
+    // Caveats: You can still fake out the morph by zooming around very quickly.
+    // Also, it will only morph properly if you use odd-numbers post spacings
+    // in your terrain tile. (See MapOptions::elevation_tile_size). Finally,
+    // a large PAGEDLOD cache will negate the blending effect when zooming out
+    // and then back in. See MPGeometry.
+
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "attribute vec4 oe_terrain_attr; \n"
+        "attribute vec4 oe_terrain_attr2; \n"
+        "varying vec3 oe_Normal; \n"
+
+        "uniform float oe_min_tile_range_factor; \n"
+        "uniform vec4 oe_tile_key; \n"
+        "uniform float osg_FrameTime; \n"
+        "uniform float oe_tile_birthtime; \n"
+        "uniform float oe_lodblend_delay; \n"
+        "uniform float oe_lodblend_duration; \n"
+        "uniform float oe_lodblend_vscale; \n"
+
+        "uniform mat4 oe_layer_parent_matrix; \n"
+        "varying vec4 oe_layer_texc; \n"
+        "varying vec4 oe_lodblend_texc; \n"
+        "varying float oe_lodblend_r; \n"
+
+        "void oe_lodblend_vertex(inout vec4 VertexMODEL) \n"
+        "{ \n"
+        "    float radius     = oe_tile_key.w; \n"
+        "    float near       = oe_min_tile_range_factor*radius; \n"
+        "    float far        = near + radius*2.0; \n"
+        "    vec4  VertexVIEW = gl_ModelViewMatrix * VertexMODEL; \n"
+        "    float d          = length(VertexVIEW.xyz/VertexVIEW.w); \n"
+        "    float r_dist     = clamp((d-near)/(far-near), 0.0, 1.0); \n"
+
+        "    float r_time     = 1.0 - clamp(osg_FrameTime-(oe_tile_birthtime+oe_lodblend_delay), 0.0, oe_lodblend_duration)/oe_lodblend_duration; \n"
+        "    float r          = max(r_dist, r_time); \n"
+
+        "    vec3  upVector   = oe_terrain_attr.xyz; \n"
+        "    float elev       = oe_terrain_attr.w; \n"
+        "    float elevOld    = oe_terrain_attr2.w; \n"
+
+        "    vec3  vscaleOffset = upVector * elev * (oe_lodblend_vscale-1.0); \n"
+        "    vec3  blendOffset  = upVector * r * oe_lodblend_vscale * (elevOld-elev); \n"
+        "    VertexMODEL       += vec4( (vscaleOffset + blendOffset)*VertexMODEL.w, 0.0 ); \n"
+
+        "    oe_lodblend_texc    = oe_layer_parent_matrix * oe_layer_texc; \n"
+        "    oe_lodblend_r       = oe_layer_parent_matrix[0][0] > 0.0 ? r : 0.0; \n" // obe?
+
+        "    oe_Normal = normalize(mix(normalize(oe_Normal), oe_terrain_attr2.xyz, r)); \n"
+        "} \n";
+
+    const char* fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform vec4 oe_tile_key; \n"
+        "uniform int oe_layer_uid; \n"
+        "varying vec4 oe_lodblend_texc; \n"
+        "varying float oe_lodblend_r; \n"
+        "uniform sampler2D oe_layer_tex_parent; \n"
+
+        "void oe_lodblend_fragment(inout vec4 color) \n"
+        "{ \n"
+        "    if ( oe_layer_uid >= 0 ) \n"
+        "    { \n"
+        "        vec4 texel = texture2D(oe_layer_tex_parent, oe_lodblend_texc.st); \n"
+        "        float enable = step(0.0001, texel.a); \n"          // did we get a parent texel?
+        "        texel.rgb = mix(color.rgb, texel.rgb, enable); \n" // if not, use the incoming color for the blend
+        "        texel.a = mix(0.0, color.a, enable); \n"           // ...and blend from alpha=0 for a fade-in effect.
+        "        color = mix(color, texel, oe_lodblend_r); \n"
+        "    } \n"
+        "} \n";
+}
+
+
+LODBlending::LODBlending() :
+TerrainEffect(),
+_delay       ( 0.0f ),
+_duration    ( 0.25f ),
+_vscale      ( 1.0f )
+{
+    init();
+}
+
+
+LODBlending::LODBlending(const Config& conf) :
+TerrainEffect(),
+_delay       ( 0.0f ),
+_duration    ( 0.25f ),
+_vscale      ( 1.0f )
+{
+    mergeConfig(conf);
+    init();
+}
+
+
+void
+LODBlending::init()
+{
+    _delayUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_lodblend_delay");
+    _delayUniform->set( (float)*_delay );
+
+    _durationUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_lodblend_duration");
+    _durationUniform->set( (float)*_duration );
+
+    _vscaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_lodblend_vscale");
+    _vscaleUniform->set( (float)*_vscale );
+}
+
+
+void
+LODBlending::setDelay(float delay)
+{
+    if ( delay != _delay.get() )
+    {
+        _delay = osg::clampAbove( delay, 0.0f );
+        _delayUniform->set( _delay.get() );
+    }
+}
+
+
+void
+LODBlending::setDuration(float duration)
+{
+    if ( duration != _duration.get() )
+    {
+        _duration = osg::clampAbove( duration, 0.0f );
+        _durationUniform->set( _duration.get() );
+    }
+}
+
+
+void
+LODBlending::setVerticalScale(float vscale)
+{
+    if ( vscale != _vscale.get() )
+    {
+        _vscale = osg::clampAbove( vscale, 0.0f );
+        _vscaleUniform->set( _vscale.get() );
+    }
+}
+
+
+void
+LODBlending::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+
+        stateset->addUniform( _delayUniform.get() );
+        stateset->addUniform( _durationUniform.get() );
+        stateset->addUniform( _vscaleUniform.get() );
+
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setName( "osgEarth::Util::LODBlending" );
+        vp->setFunction( "oe_lodblend_vertex",   vs, ShaderComp::LOCATION_VERTEX_MODEL );
+        vp->setFunction( "oe_lodblend_fragment", fs, ShaderComp::LOCATION_FRAGMENT_COLORING );
+    }
+}
+
+
+void
+LODBlending::onUninstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getStateSet();
+        if ( stateset )
+        {
+            stateset->removeUniform( _delayUniform.get() );
+            stateset->removeUniform( _durationUniform.get() );
+            stateset->removeUniform( _vscaleUniform.get() );
+
+            VirtualProgram* vp = VirtualProgram::get(stateset);
+            if ( vp )
+            {
+                vp->removeShader( "oe_lodblend_vertex" );
+                vp->removeShader( "oe_lodblend_fragment" );
+            }
+        }
+    }
+}
+
+
+//-------------------------------------------------------------
+
+
+void
+LODBlending::mergeConfig(const Config& conf)
+{
+    conf.getIfSet( "delay",    _delay );
+    conf.getIfSet( "duration", _duration );
+    conf.getIfSet( "vertical_scale", _vscale );
+}
+
+Config
+LODBlending::getConfig() const
+{
+    Config conf("lod_blending");
+    conf.addIfSet( "delay",    _delay );
+    conf.addIfSet( "duration", _duration );
+    conf.addIfSet( "vertical_scale", _vscale );
+    return conf;
+}
diff --git a/src/osgEarthUtil/LinearLineOfSight b/src/osgEarthUtil/LinearLineOfSight
index a8a1f31..4c7f9eb 100644
--- a/src/osgEarthUtil/LinearLineOfSight
+++ b/src/osgEarthUtil/LinearLineOfSight
@@ -141,30 +141,12 @@ namespace osgEarth { namespace Util
         void terrainChanged( const osgEarth::TileKey& tileKey, osg::Node* terrain );
 
         void addChangedCallback( LOSChangedCallback* callback );
-        void removeChangedCallback( LOSChangedCallback* callback );
-
-        virtual void traverse(osg::NodeVisitor& nv);
+        void removeChangedCallback( LOSChangedCallback* callback );        
 
         bool getTerrainOnly() const;
 
         void setTerrainOnly( bool terrainOnly );
 
-        /**
-         * Utility method to compute LOS with a MapNode
-         * @param mapNode
-         *        The MapNode to intersect
-         * @param start
-         *        The start point in map coordinates
-         * @param end
-         *        The end point in map coordinates
-         * @param hit
-         *        The hit point, in map coordinates.
-         * @returns
-         *        Whether or not there is line of sight from start to end.  hit is only valid if this function return false.
-         */
-        //static bool computeLOS( osgEarth::MapNode* mapNode, const osg::Vec3d& start, const osg::Vec3d& end, AltitudeMode altitudeMode, osg::Vec3d& hit );
-
-
     public: // MapNodeObserver
         
         /**
@@ -198,8 +180,7 @@ namespace osgEarth { namespace Util
         LOSChangedCallbackList _changedCallbacks;
 
         osg::ref_ptr < osgEarth::TerrainCallback > _terrainChangedCallback;
-
-        osg::ref_ptr< osg::Node > _pendingNode;
+        
         bool _clearNeeded;
         bool _terrainOnly;
     };
diff --git a/src/osgEarthUtil/LinearLineOfSight.cpp b/src/osgEarthUtil/LinearLineOfSight.cpp
index db0dcf9..09c13fc 100644
--- a/src/osgEarthUtil/LinearLineOfSight.cpp
+++ b/src/osgEarthUtil/LinearLineOfSight.cpp
@@ -76,21 +76,14 @@ namespace
 LinearLineOfSightNode::LinearLineOfSightNode(osgEarth::MapNode *mapNode):
 LineOfSightNode(),
 _mapNode(mapNode),
-//_start(0,0,0),
-//_end(0,0,0),
-//_hit(0,0,0),
 _hasLOS( true ),
-_clearNeeded( false ),
 _goodColor(0.0f, 1.0f, 0.0f, 1.0f),
 _badColor(1.0f, 0.0f, 0.0f, 1.0f),
 _displayMode( LineOfSight::MODE_SPLIT ),
-//_startAltitudeMode( ALTMODE_ABSOLUTE ),
-//_endAltitudeMode( ALTMODE_ABSOLUTE ),
 _terrainOnly( false )
 {
     compute(getNode());
-    subscribeToTerrain();
-    setNumChildrenRequiringUpdateTraversal( 1 );
+    subscribeToTerrain();    
 }
 
 
@@ -101,19 +94,14 @@ LineOfSightNode(),
 _mapNode(mapNode),
 _start(start),
 _end(end),
-//_hit(0,0,0),
 _hasLOS( true ),
-_clearNeeded( false ),
 _goodColor(0.0f, 1.0f, 0.0f, 1.0f),
 _badColor(1.0f, 0.0f, 0.0f, 1.0f),
 _displayMode( LineOfSight::MODE_SPLIT ),
-//_startAltitudeMode( ALTMODE_ABSOLUTE ),
-//_endAltitudeMode( ALTMODE_ABSOLUTE ),
 _terrainOnly( false )
 {
     compute(getNode());    
-    subscribeToTerrain();
-    setNumChildrenRequiringUpdateTraversal( 1 );
+    subscribeToTerrain();    
 }
 
 
@@ -159,14 +147,7 @@ LinearLineOfSightNode::setMapNode( MapNode* mapNode )
 void
 LinearLineOfSightNode::terrainChanged( const osgEarth::TileKey& tileKey, osg::Node* terrain )
 {
-    OE_DEBUG << "LineOfSightNode::terrainChanged" << std::endl;
-    //Make a temporary group that contains both the old MapNode as well as the new incoming terrain.
-    //Because this function is called from the database pager thread we need to include both b/c 
-    //the new terrain isn't yet merged with the new terrain.
-    osg::ref_ptr < osg::Group > group = new osg::Group;
-    group->addChild( terrain );
-    group->addChild( getNode() );
-    compute( group, true );
+    compute( getNode() );
 }
 
 const GeoPoint&
@@ -247,52 +228,27 @@ LinearLineOfSightNode::removeChangedCallback( LOSChangedCallback* callback )
     }    
 }
 
-
-#if 0
-bool
-LinearLineOfSightNode::computeLOS( osgEarth::MapNode* mapNode, const osg::Vec3d& start, const osg::Vec3d& end, AltitudeMode altitudeMode, osg::Vec3d& hit )
-{
-    const SpatialReference* mapSRS = mapNode->getMapSRS();
-
-    // convert endpoint to world coordinates:
-    osg::Vec3d startWorld, endWorld;
-    GeoPoint(mapSRS, start, altitudeMode).toWorld( startWorld, mapNode->getTerrain() );
-    GeoPoint(mapSRS, end,   altitudeMode).toWorld( endWorld,   mapNode->getTerrain() );
-    
-    osgSim::LineOfSight los;
-    los.setDatabaseCacheReadCallback(0);
-    unsigned int index = los.addLOS(startWorld, endWorld);
-    los.computeIntersections(mapNode);
-    osgSim::LineOfSight::Intersections hits = los.getIntersections(0);    
-    if (hits.size() > 0)
-    {
-        osg::Vec3d hitWorld = *hits.begin();
-        GeoPoint mapHit;
-        mapHit.fromWorld( mapNode->getMapSRS(), hitWorld );
-        //mapNode->getMap()->worldPointToMapPoint(hitWorld, mapHit);
-        hit = mapHit.vec3d();
-        return false;
-    }
-    return true;
-}
-#endif
-
-
 void
 LinearLineOfSightNode::compute(osg::Node* node, bool backgroundThread)
-{
+{    
     if ( !getMapNode() )
         return;
 
+    if (!_start.isValid() || !_end.isValid() )
+    {
+        return;          
+    }
+
     if (_start != _end)
     {
       const SpatialReference* mapSRS = getMapNode()->getMapSRS();
       const Terrain* terrain = getMapNode()->getTerrain();
 
-      //Computes the LOS and redraws the scene
-
-      _start.transform(mapSRS).toWorld( _startWorld, terrain );
-      _end.transform(mapSRS).toWorld( _endWorld, terrain );
+      //Computes the LOS and redraws the scene      
+      if (!_start.transform(mapSRS).toWorld( _startWorld, terrain ) || !_end.transform(mapSRS).toWorld( _endWorld, terrain ))
+      {
+          return;
+      }
 
 
       DPLineSegmentIntersector* lsi = new DPLineSegmentIntersector(_startWorld, _endWorld);
@@ -384,19 +340,11 @@ LinearLineOfSightNode::draw(bool backgroundThread)
     }
 
 
-    if (!backgroundThread)
-    {
         //Remove all children from this group
         removeChildren(0, getNumChildren());
 
         if (mt)
           addChild( mt );
-    }
-    else
-    {
-        _clearNeeded = true;
-        _pendingNode = mt;
-    }
 }
 
 void
@@ -473,25 +421,6 @@ LinearLineOfSightNode::getNode()
     return _mapNode.get();
 }
 
-void
-LinearLineOfSightNode::traverse(osg::NodeVisitor& nv)
-{
-    if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
-    {
-        if (_pendingNode.valid() || _clearNeeded)
-        {
-            removeChildren(0, getNumChildren());
-
-            if (_pendingNode.valid())
-              addChild( _pendingNode.get());
-
-            _pendingNode = 0;
-            _clearNeeded = false;
-        }
-    }
-    osg::Group::traverse(nv);
-}
-
 /**********************************************************************/
 LineOfSightTether::LineOfSightTether(osg::Node* startNode, osg::Node* endNode):
 _startNode(startNode),
@@ -551,23 +480,7 @@ namespace
               if ( _start )
                   _los->setStart( position );
               else
-                  _los->setEnd( position );
-
-              //GeoPoint location(position);
-              //if ((_start ? _los->getStartAltitudeMode() : _los->getEndAltitudeMode()) == ALTMODE_RELATIVE)
-              //{
-              //    double z = _start ? _los->getStart().z() : _los->getEnd().z();
-              //    location.z() = z;              
-              //}
-
-              //if (_start)
-              //{
-              //    _los->setStart( location.vec3d() );
-              //}
-              //else
-              //{
-              //    _los->setEnd( location.vec3d() );
-              //}
+                  _los->setEnd( position );          
           }
 
           
diff --git a/src/osgEarthUtil/MGRSGraticule.cpp b/src/osgEarthUtil/MGRSGraticule.cpp
index cff362e..a184473 100644
--- a/src/osgEarthUtil/MGRSGraticule.cpp
+++ b/src/osgEarthUtil/MGRSGraticule.cpp
@@ -79,7 +79,7 @@ UTMGraticule( 0L )
     {
         LineSymbol* line = _options->secondaryStyle()->getOrCreate<LineSymbol>();
         line->stroke()->color() = Color(Color::White, 0.5f);
-        line->stroke()->stipple() = 0x1111;
+        line->stroke()->stipplePattern() = 0x1111;
 
         TextSymbol* text = _options->secondaryStyle()->getOrCreate<TextSymbol>();
         text->fill()->color() = Color(Color::White, 0.3f);
@@ -87,8 +87,8 @@ UTMGraticule( 0L )
         text->alignment() = TextSymbol::ALIGN_CENTER_CENTER;
     }
 
-    _minDepthOffset = DepthOffsetUtils::createMinOffsetUniform();
-    _minDepthOffset->set( 11000.0f );
+//    _minDepthOffset = DepthOffsetUtils::createMinOffsetUniform();
+//    _minDepthOffset->set( 11000.0f );
 }
 
 MGRSGraticule::MGRSGraticule( MapNode* mapNode, const MGRSGraticuleOptions& options ) :
@@ -546,8 +546,8 @@ MGRSGraticule::buildSQIDTiles( const std::string& gzd )
     group->addChild( mt );
 
     // prep for depth offset:
-    DepthOffsetUtils::prepareGraph( group );
-    group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() );
+    //DepthOffsetUtils::prepareGraph( group );
+    //group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() );
 
     return group;
 }
diff --git a/src/osgEarthUtil/MeasureTool.cpp b/src/osgEarthUtil/MeasureTool.cpp
index cd17959..2a9c3b0 100644
--- a/src/osgEarthUtil/MeasureTool.cpp
+++ b/src/osgEarthUtil/MeasureTool.cpp
@@ -85,20 +85,28 @@ MeasureToolHandler::rebuild()
         return;
     }
 
-    AltitudeSymbol* alt = new AltitudeSymbol();
-    alt->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
-    alt->technique() = AltitudeSymbol::TECHNIQUE_GPU;
 
     // Define the path feature:
     _feature = new Feature(new LineString(), getMapNode()->getMapSRS());
     _feature->geoInterp() = _geoInterpolation;
 
-    //Define a style for the line
+    // clamp to the terrain skin as it pages in
+    AltitudeSymbol* alt = _feature->style()->getOrCreate<AltitudeSymbol>();
+    alt->clamping() = alt->CLAMP_TO_TERRAIN;
+    //alt->technique() = alt->TECHNIQUE_GPU;
+    alt->technique() = alt->TECHNIQUE_SCENE;
+
+    // offset to mitigate Z fighting
+    RenderSymbol* render = _feature->style()->getOrCreate<RenderSymbol>();
+    render->depthOffset()->enabled() = true;
+    render->depthOffset()->minBias() = 1000;
+
+    // define a style for the line
     LineSymbol* ls = _feature->style()->getOrCreate<LineSymbol>();
     ls->stroke()->color() = Color::Yellow;
     ls->stroke()->width() = 2.0f;
-    ls->tessellation() = 20;
-    _feature->style()->add( alt );
+    ls->stroke()->widthUnits() = Units::PIXELS;
+    ls->tessellation() = 150;
 
     _featureNode = new FeatureNode( getMapNode(), _feature.get() );
     _featureNode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
diff --git a/src/osgEarthUtil/MouseCoordsTool.cpp b/src/osgEarthUtil/MouseCoordsTool.cpp
index 956729d..52ceb93 100644
--- a/src/osgEarthUtil/MouseCoordsTool.cpp
+++ b/src/osgEarthUtil/MouseCoordsTool.cpp
@@ -77,7 +77,11 @@ _label    ( label ),
 _formatter( formatter )
 {
     if ( !formatter )
-        _formatter = new LatLongFormatter( LatLongFormatter::FORMAT_DECIMAL_DEGREES );
+    {
+        LatLongFormatter* formatter = new LatLongFormatter( LatLongFormatter::FORMAT_DECIMAL_DEGREES );
+        formatter->setPrecision( 5 );
+        _formatter = formatter;
+    }
 }
 
 void
diff --git a/src/osgEarthUtil/NormalMap b/src/osgEarthUtil/NormalMap
new file mode 100644
index 0000000..63f8fed
--- /dev/null
+++ b/src/osgEarthUtil/NormalMap
@@ -0,0 +1,78 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_NORMAL_MAP_H
+#define OSGEARTHUTIL_NORMAL_MAP_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osgEarth/ImageLayer>
+#include <osg/observer_ptr>
+
+namespace osgEarth {
+    class Map;
+}
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Terrain effect that applies a normal map sampler to the
+     * terrain during the lighting phase. The normal map is 
+     * provided by a shared ImageLayer.
+     */
+    class OSGEARTHUTIL_EXPORT NormalMap : public TerrainEffect
+    {
+    public:
+        /** construct a new normal mapping effect */
+        NormalMap();
+
+        /** Sets the image layer that generates the normal map. 
+            You must call this prior to installing the effect. */
+        void setNormalMapLayer(ImageLayer* layer) { _layer = layer; }
+        ImageLayer* getNormalMapLayer() { return _layer.get(); }
+
+        /** Sets the LOD at which to start normal mapping */
+        void setStartLOD(unsigned lod);
+        unsigned getStartLOD() const { return *_startLOD; }
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        NormalMap(const Config& conf, Map* map);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~NormalMap();
+        void init();
+
+        optional<unsigned>    _startLOD;
+        optional<std::string> _layerName;
+
+        osg::observer_ptr<ImageLayer> _layer;
+        osg::ref_ptr<osg::Uniform>    _samplerUniform;
+        osg::ref_ptr<osg::Uniform>    _startLODUniform;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_NORMAL_MAP_H
diff --git a/src/osgEarthUtil/NormalMap.cpp b/src/osgEarthUtil/NormalMap.cpp
new file mode 100644
index 0000000..4a1fc48
--- /dev/null
+++ b/src/osgEarthUtil/NormalMap.cpp
@@ -0,0 +1,209 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/NormalMap>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[NormalMap] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+namespace
+{
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "varying vec3 oe_nmap_light; \n"
+        "varying vec3 oe_nmap_view; \n"
+        "varying vec3 oe_Normal; \n"
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+
+        "void oe_lighting_vertex(inout vec4 VertexVIEW) \n"
+        "{ \n"
+        "    if (oe_mode_GL_LIGHTING) \n"
+        "    { \n"
+        "        vec3 tangent = normalize(cross(gl_Normal, vec3(0,-1,0))); \n"
+
+        "        vec3 n = oe_Normal; \n" //normalize(gl_NormalMatrix * gl_Normal); \n"
+        "        vec3 t = normalize(gl_NormalMatrix * tangent); \n"
+        "        vec3 b = cross(n, t); \n"
+
+        "        vec3 tmp = gl_LightSource[0].position.xyz; \n"
+        "        oe_nmap_light.x = dot(tmp, t); \n"
+        "        oe_nmap_light.y = dot(tmp, b); \n"
+        "        oe_nmap_light.z = dot(tmp, n); \n"
+
+        "        tmp = -VertexVIEW.xyz; \n"
+        "        oe_nmap_view.x = dot(tmp, t); \n"
+        "        oe_nmap_view.y = dot(tmp, b); \n"
+        "        oe_nmap_view.z = dot(tmp, n); \n"
+        "    } \n"
+        "} \n";
+
+    const char* fs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "uniform sampler2D oe_nmap_tex; \n"
+        "uniform float oe_nmap_startlod; \n"
+        "uniform bool oe_mode_GL_LIGHTING; \n"
+
+        "varying vec4 oe_layer_tilec; \n"
+        "varying vec3 oe_nmap_light; \n"
+        "varying vec3 oe_nmap_view; \n"
+
+        "void oe_lighting_fragment(inout vec4 color) \n"
+        "{\n"
+        "    if (oe_mode_GL_LIGHTING) \n"
+        "    { \n"
+        "        vec3 L = normalize(oe_nmap_light); \n"
+        "        vec3 N = normalize(texture2D(oe_nmap_tex, oe_layer_tilec.st).xyz * 2.0 - 1.0); \n"
+        "        vec3 V = normalize(oe_nmap_view); \n"
+
+        "        vec4 ambient  = gl_LightSource[0].ambient * gl_FrontMaterial.ambient; \n"
+
+        "        float D = max(dot(L, N), 0.0); \n"
+        "        vec4 diffuse  = gl_LightSource[0].diffuse * gl_FrontMaterial.diffuse * D; \n"
+
+        //"        float S = pow(clamp(dot(reflect(-L,N),V),0.0,1.0), gl_FrontMaterial.shininess); \n"
+        //"        vec4 specular = gl_LightSource[0].specular * gl_FrontMaterial.specular * S; \n"
+
+        "        color.rgb = (ambient.rgb*color.rgb) + (diffuse.rgb*color.rgb); \n" // + specular.rgb; \n"
+        "    } \n"
+        "}\n";
+}
+
+
+NormalMap::NormalMap() :
+TerrainEffect(),
+_startLOD    ( 0 )
+{
+    init();
+}
+
+NormalMap::NormalMap(const Config& conf, Map* map) :
+TerrainEffect(),
+_startLOD    ( 0 )
+{
+    mergeConfig(conf);
+
+    if ( map && _layerName.isSet() )
+    {
+        setNormalMapLayer( map->getImageLayerByName(*_layerName) );
+    }
+
+    init();
+}
+
+
+void
+NormalMap::init()
+{
+    _startLODUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_nmap_startlod");
+    _startLODUniform->set( 0.0f );
+}
+
+
+void
+NormalMap::setStartLOD(unsigned value)
+{
+    _startLOD = value;
+    _startLODUniform->set( (float)value );
+}
+
+
+NormalMap::~NormalMap()
+{
+    //nop
+}
+
+void
+NormalMap::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+        if ( _layer.valid() )
+        {
+            OE_NOTICE << LC << "Installing layer " << _layer->getName() << " as normal map" << std::endl;
+            int unit = *_layer->shareImageUnit();
+            _samplerUniform = stateset->getOrCreateUniform("oe_nmap_tex", osg::Uniform::SAMPLER_2D);
+            _samplerUniform->set(unit);
+        }
+        
+        stateset->addUniform( _startLODUniform.get() );
+
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+
+        // these special (built-in) function names are for the main lighting shaders.
+        // using them here will override the default lighting.
+        vp->setFunction( "oe_lighting_vertex",   vs, ShaderComp::LOCATION_VERTEX_VIEW, 0.0 );
+        vp->setFunction( "oe_lighting_fragment", fs, ShaderComp::LOCATION_FRAGMENT_LIGHTING, 0.0 );
+    }
+}
+
+
+void
+NormalMap::onUninstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getStateSet();
+        if ( stateset )
+        {
+            stateset->removeUniform( _samplerUniform.get() );
+            stateset->removeUniform( _startLODUniform.get() );
+            VirtualProgram* vp = VirtualProgram::get(stateset);
+            if ( vp )
+            {
+                vp->removeShader( "oe_lighting_vertex" );
+                vp->removeShader( "oe_lighting_fragment" );
+            }
+        }
+    }
+}
+
+
+//-------------------------------------------------------------
+
+void
+NormalMap::mergeConfig(const Config& conf)
+{
+    conf.getIfSet( "layer",       _layerName );
+    conf.getIfSet( "start_level", _startLOD );
+    conf.getIfSet( "start_lod",   _startLOD );
+}
+
+Config
+NormalMap::getConfig() const
+{
+    optional<std::string> layername;
+
+    if ( _layer.valid() && !_layer->getName().empty() )
+        layername = _layer->getName();
+
+    Config conf("normal_map");
+    conf.addIfSet( "layer",       layername );
+    conf.addIfSet( "start_level", _startLOD );
+    return conf;
+}
diff --git a/src/osgEarthUtil/RGBColorFilter.cpp b/src/osgEarthUtil/RGBColorFilter.cpp
index 353d7fe..14ae782 100644
--- a/src/osgEarthUtil/RGBColorFilter.cpp
+++ b/src/osgEarthUtil/RGBColorFilter.cpp
@@ -36,7 +36,7 @@ namespace
         "#version 110\n"
         "uniform vec3 __UNIFORM_NAME__;\n"
 
-        "void __ENTRY_POINT__(in int slot, inout vec4 color)\n"
+        "void __ENTRY_POINT__(inout vec4 color)\n"
         "{\n"
         "    color.rgb = clamp(color.rgb + __UNIFORM_NAME__.rgb, 0.0, 1.0); \n"
         "} \n";
diff --git a/src/osgEarthUtil/RadialLineOfSight b/src/osgEarthUtil/RadialLineOfSight
index 857222b..b370407 100644
--- a/src/osgEarthUtil/RadialLineOfSight
+++ b/src/osgEarthUtil/RadialLineOfSight
@@ -140,8 +140,7 @@ namespace osgEarth { namespace Util
          * Called when the underlying terrain has changed.
          */
         void terrainChanged( const osgEarth::TileKey& tileKey, osg::Node* terrain );
-
-        virtual void traverse(osg::NodeVisitor& nv);
+        
 
         bool getTerrainOnly() const;
         void setTerrainOnly( bool terrainOnly );
@@ -156,9 +155,9 @@ namespace osgEarth { namespace Util
 
     private:
         osg::Node* getNode();
-        void compute(osg::Node* node, bool backgroundThread = false);
-        void compute_line(osg::Node* node, bool backgroundThread = false);
-        void compute_fill(osg::Node* node, bool backgroundThread = false);
+        void compute(osg::Node* node);
+        void compute_line(osg::Node* node);
+        void compute_fill(osg::Node* node);
         int _numSpokes;
         double _radius;
 
@@ -167,13 +166,10 @@ namespace osgEarth { namespace Util
         osg::Vec4 _goodColor;
         osg::Vec4 _badColor;
         osg::Vec4 _outlineColor;
-        GeoPoint   _center;
-        //osg::Vec3d _center;
+        GeoPoint   _center;        
         osg::Vec3d _centerWorld;
-        osg::observer_ptr< MapNode > _mapNode;
-        //AltitudeMode _altitudeMode;
-        LOSChangedCallbackList _changedCallbacks;
-        osg::ref_ptr< osg::Node > _pendingNode;
+        osg::observer_ptr< MapNode > _mapNode;        
+        LOSChangedCallbackList _changedCallbacks;        
         osg::ref_ptr < osgEarth::TerrainCallback > _terrainChangedCallback;
         bool _terrainOnly;
     };
diff --git a/src/osgEarthUtil/RadialLineOfSight.cpp b/src/osgEarthUtil/RadialLineOfSight.cpp
index cf7a2ab..1e1ebca 100644
--- a/src/osgEarthUtil/RadialLineOfSight.cpp
+++ b/src/osgEarthUtil/RadialLineOfSight.cpp
@@ -28,46 +28,6 @@ using namespace osgEarth::Util;
 
 namespace
 {
-#if 0
-    bool getRelativeWorld(double x, double y, double relativeHeight, MapNode* mapNode, osg::Vec3d& world )
-    {
-        GeoPoint mapPoint(mapNode->getMapSRS(), x, y);
-        osg::Vec3d pos;
-        mapPoint.toWorld( pos, mapNode->getTerrain() );
-        //mapNode->getMap()->toWorldPoint(mapPoint, pos);
-
-        osg::Vec3d up(0,0,1);
-        const osg::EllipsoidModel* em = mapNode->getMap()->getProfile()->getSRS()->getEllipsoid();
-        if (em)
-        {
-            up = em->computeLocalUpVector( world.x(), world.y(), world.z());
-        }    
-        up.normalize();
-
-        double segOffset = 50000;
-
-        osg::Vec3d start = pos + (up * segOffset);
-        osg::Vec3d end = pos - (up * segOffset);
-        
-        osgUtil::LineSegmentIntersector* i = new osgUtil::LineSegmentIntersector( start, end );
-        
-        osgUtil::IntersectionVisitor iv;    
-        iv.setIntersector( i );
-        mapNode->getTerrainEngine()->accept( iv );
-
-        osgUtil::LineSegmentIntersector::Intersections& results = i->getIntersections();
-        if ( !results.empty() )
-        {
-            const osgUtil::LineSegmentIntersector::Intersection& result = *results.begin();
-            world = result.getWorldIntersectPoint();
-            world += up * relativeHeight;
-            return true;
-        }
-        return false;    
-    }
-#endif
-
-
     osg::Vec3d getNodeCenter(osg::Node* node)
     {
         osg::NodePathList nodePaths = node->getParentalNodePaths();
@@ -263,30 +223,24 @@ void
 RadialLineOfSightNode::terrainChanged( const osgEarth::TileKey& tileKey, osg::Node* terrain )
 {
     OE_DEBUG << "RadialLineOfSightNode::terrainChanged" << std::endl;
-    //Make a temporary group that contains both the old MapNode as well as the new incoming terrain.
-    //Because this function is called from the database pager thread we need to include both b/c 
-    //the new terrain isn't yet merged with the new terrain.
-    osg::ref_ptr < osg::Group > group = new osg::Group;
-    group->addChild( terrain );
-    group->addChild( getNode() );
-    compute( group, true );
+    compute( getNode() );    
 }
 
 void
-RadialLineOfSightNode::compute(osg::Node* node, bool backgroundThread)
+RadialLineOfSightNode::compute(osg::Node* node )
 {
     if (_fill)
     {
-        compute_fill( node, backgroundThread );
+        compute_fill( node );
     }
     else
     {
-        compute_line( node, backgroundThread );
+        compute_line( node );
     }
 }
 
 void
-RadialLineOfSightNode::compute_line(osg::Node* node, bool backgroundThread)
+RadialLineOfSightNode::compute_line(osg::Node* node)
 {    
     if ( !getMapNode() )
         return;
@@ -417,16 +371,9 @@ RadialLineOfSightNode::compute_line(osg::Node* node, bool backgroundThread)
     mt->setMatrix(osg::Matrixd::translate(_centerWorld));
     mt->addChild(geode);
     
-    if (!backgroundThread)
-    {
-        //Remove all the children
-        removeChildren(0, getNumChildren());
-        addChild( mt );  
-    }
-    else
-    {
-        _pendingNode = mt;
-    }
+    //Remove all the children
+    removeChildren(0, getNumChildren());
+    addChild( mt );  
 
     for( LOSChangedCallbackList::iterator i = _changedCallbacks.begin(); i != _changedCallbacks.end(); i++ )
     {
@@ -435,7 +382,7 @@ RadialLineOfSightNode::compute_line(osg::Node* node, bool backgroundThread)
 }
 
 void
-RadialLineOfSightNode::compute_fill(osg::Node* node, bool backgroundThread)
+RadialLineOfSightNode::compute_fill(osg::Node* node)
 {
     if ( !getMapNode() )
         return;
@@ -611,17 +558,10 @@ RadialLineOfSightNode::compute_fill(osg::Node* node, bool backgroundThread)
     osg::MatrixTransform* mt = new osg::MatrixTransform;
     mt->setMatrix(osg::Matrixd::translate(_centerWorld));
     mt->addChild(geode);
-    
-    if (!backgroundThread)
-    {
-        //Remove all the children
-        removeChildren(0, getNumChildren());
-        addChild( mt );  
-    }
-    else
-    {
-        _pendingNode = mt;
-    }
+        
+    //Remove all the children
+    removeChildren(0, getNumChildren());
+    addChild( mt );  
 
     for( LOSChangedCallbackList::iterator i = _changedCallbacks.begin(); i != _changedCallbacks.end(); i++ )
     {
@@ -629,21 +569,6 @@ RadialLineOfSightNode::compute_fill(osg::Node* node, bool backgroundThread)
     }	
 }
 
-void
-RadialLineOfSightNode::traverse(osg::NodeVisitor& nv)
-{
-    if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
-    {
-        if (_pendingNode.valid())
-        {
-            removeChildren(0, getNumChildren());
-            addChild( _pendingNode.get());
-            _pendingNode = 0;            
-        }
-    }
-    osg::Group::traverse(nv);
-}
-
 
 void
 RadialLineOfSightNode::setGoodColor( const osg::Vec4f &color )
diff --git a/src/osgEarthUtil/ShadowUtils.cpp b/src/osgEarthUtil/ShadowUtils.cpp
index 384c24d..2110173 100644
--- a/src/osgEarthUtil/ShadowUtils.cpp
+++ b/src/osgEarthUtil/ShadowUtils.cpp
@@ -40,18 +40,6 @@ using namespace osgEarth::Util;
 
 namespace
 {
-    MapNode* findMapNode(osg::Group* node)
-    {
-        return findTopMostNodeOfType<MapNode>(node);
-    }
-
-    osgShadow::ViewDependentShadowMap*
-        getTechniqueAsVdsm(osgShadow::ShadowedScene* sscene)
-    {
-        osgShadow::ShadowTechnique* st = sscene->getShadowTechnique();
-        return dynamic_cast<osgShadow::ViewDependentShadowMap*>(st);
-    }
-
     bool setShadowUnit(osgShadow::ShadowedScene* sscene, int unit)
     {
         osgShadow::ShadowTechnique* st = sscene->getShadowTechnique();
@@ -67,8 +55,7 @@ namespace
             }
             else
             {
-                osgShadow::ViewDependentShadowMap* vdsm
-                    = getTechniqueAsVdsm(sscene);
+                osgShadow::ViewDependentShadowMap* vdsm = dynamic_cast< osgShadow::ViewDependentShadowMap*>( st );
                 if (vdsm)
                 {
                     sscene->getShadowSettings()
@@ -88,7 +75,7 @@ ShadowUtils::setUpShadows(osgShadow::ShadowedScene* sscene, osg::Group* root)
 {
     osg::StateSet* ssStateSet = sscene->getOrCreateStateSet();
 
-    MapNode* mapNode = findMapNode(root);
+    MapNode* mapNode = MapNode::findMapNode(root);
     TerrainEngineNode* engine = mapNode->getTerrainEngine();
     if (!engine)
         return false;
@@ -100,7 +87,7 @@ ShadowUtils::setUpShadows(osgShadow::ShadowedScene* sscene, osg::Group* root)
 
     OE_INFO << LC << "Reserved texture unit " << su << " for shadowing" << std::endl;
 
-    osgShadow::ViewDependentShadowMap* vdsm = getTechniqueAsVdsm(sscene);
+    osgShadow::ViewDependentShadowMap* vdsm =  dynamic_cast< osgShadow::ViewDependentShadowMap*>(sscene->getShadowTechnique());
     int su1 = -1;
     if (vdsm && sscene->getShadowSettings()->getNumShadowMapsPerLight() == 2)
     {
diff --git a/src/osgEarthUtil/SkyNode b/src/osgEarthUtil/SkyNode
index f83cd29..472fa64 100644
--- a/src/osgEarthUtil/SkyNode
+++ b/src/osgEarthUtil/SkyNode
@@ -20,6 +20,7 @@
 #define OSGEARTHUTIL_SKY_NODE
 
 #include <osgEarthUtil/Common>
+#include <osgEarth/DateTime>
 #include <osgEarth/Map>
 #include <osg/MatrixTransform>
 #include <osg/Uniform>
@@ -39,12 +40,12 @@ namespace osgEarth { namespace Util
         /**
         * Gets the moon position in geocentric coordinates at the given time
         */
-        virtual osg::Vec3d getMoonPosition( int year, int month, int date, double hoursUTC ) = 0;
+        virtual osg::Vec3d getMoonPosition( const DateTime& dt ) = 0;
 
         /**
         * Gets the sun position in geocentric coordinates at the given time
         */
-        virtual osg::Vec3d getSunPosition( int year, int month, int date, double hoursUTC ) = 0;
+        virtual osg::Vec3d getSunPosition( const DateTime& dt ) = 0;
     };
 
 
@@ -57,12 +58,12 @@ namespace osgEarth { namespace Util
         /**
         * Gets the moon position in geocentric coordinates at the given time
         */
-        virtual osg::Vec3d getMoonPosition( int year, int month, int date, double hoursUTC );
+        virtual osg::Vec3d getMoonPosition( const DateTime& dt );
 
         /**
         * Gets the sun position in geocentric coordinates at the given time
         */
-        virtual osg::Vec3d getSunPosition( int year, int month, int date, double hoursUTC );
+        virtual osg::Vec3d getSunPosition( const DateTime& dt );
     };
 
 
@@ -105,10 +106,10 @@ namespace osgEarth { namespace Util
         void attach( osg::View* view, int lightNum =0 );
        
         /** Gets the date time for the sky position  */
-        void getDateTime( int &year, int &month, int &date, double &hoursUTC, osg::View* view=0L );
+        void getDateTime(DateTime& output, osg::View* view =0L) const;
 
         /** Sets the sky's position based on a julian date. */
-        void setDateTime( int year, int month, int date, double hoursUTC, osg::View* view =0L );
+        void setDateTime(const DateTime& dt, osg::View* view =0L );
 
         /** The minimum brightness for non-sunlit areas. */
         void setAmbientBrightness( float value, osg::View* view =0L );
@@ -126,6 +127,13 @@ namespace osgEarth { namespace Util
         void setStarsVisible( bool value, osg::View* view =0L );
         bool getStarsVisible( osg::View* view =0L ) const;
 
+    public: // deprecated
+
+        /** @deprecated Please use setDateTime(const DateTime&) above */
+        void setDateTime( int year, int month, int date, double hoursUTC, osg::View* view =0L );
+        /** @deprecated Please use getDateTime(DateTime&) above */
+        void getDateTime( int &year, int &month, int &date, double &hoursUTC, osg::View* view=0L );
+
     public:
         //override
         virtual void traverse( osg::NodeVisitor& nv );
@@ -161,8 +169,8 @@ namespace osgEarth { namespace Util
             osg::Vec3f                         _lightPos;
             osg::ref_ptr<osg::Light>           _light;
             osg::ref_ptr<osg::Uniform>         _lightPosUniform;
-            osg::Matrixd                       _sunMatrix;            
-            osg::Matrixd                       _moonMatrix;            
+            osg::Matrixd                       _sunMatrix;
+            osg::Matrixd                       _moonMatrix;
             osg::Matrixd                       _starsMatrix;
             bool                               _starsVisible;
             bool                               _moonVisible;
@@ -172,11 +180,8 @@ namespace osgEarth { namespace Util
             osg::ref_ptr<osg::MatrixTransform> _sunXform;
             osg::ref_ptr<osg::MatrixTransform> _moonXform;
             osg::ref_ptr<osg::MatrixTransform> _starsXform;
-            
-            int _year;
-            int _month;
-            int _date;
-            double _hoursUTC;
+
+            DateTime _date;
         };
 
         PerViewData _defaultPerViewData;
diff --git a/src/osgEarthUtil/SkyNode.cpp b/src/osgEarthUtil/SkyNode.cpp
index 3f1c447..4f44c82 100644
--- a/src/osgEarthUtil/SkyNode.cpp
+++ b/src/osgEarthUtil/SkyNode.cpp
@@ -568,9 +568,6 @@ namespace
         "varying vec3 atmos_rayleighColor; \n"
 
         "const float fExposure = 4.0; \n";
-
-    //static char s_atmosphereFragmentShared[] =
-    //    "void applyFragLighting( inout color )
         
     static char s_atmosphereFragmentMain[] =
         "void main(void) \n"			
@@ -694,7 +691,7 @@ namespace
                 << "{ \n"
                 << "    float b1 = 1.0-(2.0*abs(gl_PointCoord.s-0.5)); \n"
                 << "    float b2 = 1.0-(2.0*abs(gl_PointCoord.t-0.5)); \n"
-                << "    float i = b1*b1 * b2*b2; \n" //b1*b1*b1 * b2*b2*b2; \n"
+                << "    float i = b1*b1 * b2*b2; \n"
                 << "    gl_FragColor = osg_FrontColor * i * visibility; \n"
                 << "} \n";
         }
@@ -705,17 +702,17 @@ namespace
 //---------------------------------------------------------------------------
 
 osg::Vec3d
-DefaultEphemerisProvider::getSunPosition( int year, int month, int date, double hoursUTC )
+DefaultEphemerisProvider::getSunPosition(const DateTime& date)
 {
     Sun sun;
-    return sun.getPosition( year, month, date, hoursUTC );
+    return sun.getPosition( date.year(), date.month(), date.day(), date.hours() );
 }
 
 osg::Vec3d
-DefaultEphemerisProvider::getMoonPosition( int year, int month, int date, double hoursUTC )
+DefaultEphemerisProvider::getMoonPosition(const DateTime& date)
 {
     Moon moon;
-    return moon.getPosition( year, month, date, hoursUTC );
+    return moon.getPosition( date.year(), date.month(), date.day(), date.hours() );
 }
 
 //---------------------------------------------------------------------------
@@ -741,7 +738,7 @@ SkyNode::initialize( Map *map, const std::string& starFile )
     _defaultPerViewData._lightPos.set( osg::Vec3f(0.0f, 1.0f, 0.0f) );
     _defaultPerViewData._light = new osg::Light( 0 );  
     _defaultPerViewData._light->setPosition( osg::Vec4( _defaultPerViewData._lightPos, 0 ) );
-    _defaultPerViewData._light->setAmbient( osg::Vec4(0.4f, 0.4f, 0.4f ,1.0) );
+    _defaultPerViewData._light->setAmbient( osg::Vec4(0.2f, 0.2f, 0.2f, 2.0) );
     _defaultPerViewData._light->setDiffuse( osg::Vec4(1,1,1,1) );
     _defaultPerViewData._light->setSpecular( osg::Vec4(0,0,0,1) );
     _defaultPerViewData._starsVisible = true;
@@ -774,10 +771,10 @@ SkyNode::initialize( Map *map, const std::string& starFile )
     makeStars(starFile);
 
     // automatically compute ambient lighting based on the eyepoint
-    _autoAmbience = true;
+    _autoAmbience = false;
 
     //Set a default time
-    setDateTime( 2011, 3, 6, 18 );
+    setDateTime( DateTime(2011, 3, 6, 18.) );
 }
 
 osg::BoundingSphere
@@ -788,61 +785,48 @@ SkyNode::computeBound() const
 
 void
 SkyNode::traverse( osg::NodeVisitor& nv )
-{
+{    
     osgUtil::CullVisitor* cv = Culling::asCullVisitor(nv);
     if ( cv )
     {
+
         // If there's a custom projection matrix clamper installed, remove it temporarily.
         // We dont' want it mucking with our sky elements.
         osg::ref_ptr<osg::CullSettings::ClampProjectionMatrixCallback> cb = cv->getClampProjectionMatrixCallback();
         cv->setClampProjectionMatrixCallback( 0L );
 
         osg::View* view = cv->getCurrentCamera()->getView();
-        PerViewDataMap::iterator i = _perViewData.find( view );
-        if ( i != _perViewData.end() )
-        {
-            if ( _autoAmbience )
-            {
-                const float minAmb = 0.3f;
-                const float maxAmb = 1.0f;
-                const float minDev = -0.2f;
-                const float maxDev = 0.75f;
-                osg::Vec3 eye = cv->getViewPoint(); eye.normalize();
-                osg::Vec3 sun = i->second._lightPos; sun.normalize();
-                float dev = osg::clampBetween(eye*sun, minDev, maxDev);
-                float r   = (dev-minDev)/(maxDev-minDev);
-                float amb = minAmb + r*(maxAmb-minAmb);
-                i->second._light->setAmbient( osg::Vec4(amb,amb,amb,1.0) );
-                //OE_INFO << "dev=" << dev << ", amb=" << amb << std::endl;
-            }
-#if 0
-            // adjust the light color based on the eye point and the sun position.
-            float aMin =  0.1f;
-            float aMax =  0.9f;
-            float dMin = -0.5f;
-            float dMax =  0.5f;
 
-            osg::Vec3 eye = cv->getViewPoint();
-            eye.normalize();
-
-            osg::Vec3 sun = i->second._lightPos;
-            sun.normalize();
-
-            // clamp to valid range:
-            float d = osg::clampBetween(eye * sun, dMin, dMax);
-
-            // remap to [0..1]:
-            d = (d-dMin) / (dMax-dMin);
-
-            // map to ambient level:
-            float diff = aMin + d * (aMax-aMin);
-
-            i->second._light->setDiffuse( osg::Vec4(diff,diff,diff,1.0) );
-#endif
+                
+        //Try to find the per view data for camera's view if there is one.
+        PerViewDataMap::iterator itr = _perViewData.find( view );
+        
+        if ( itr == _perViewData.end() )
+        {
+            // If we don't find any per view data, just use the first one that is stored.
+            // This needs to be reworked to be per camera and also to automatically create a 
+            // new data structure on demand since camera's can be added/removed on the fly.
+            itr = _perViewData.begin();
+        }
+        
 
-            i->second._cullContainer->accept( nv );
+        if ( _autoAmbience )
+        {
+            const float minAmb = 0.2f;
+            const float maxAmb = 0.92f;
+            const float minDev = -0.2f;
+            const float maxDev = 0.75f;
+            osg::Vec3 eye = cv->getViewPoint(); eye.normalize();
+            osg::Vec3 sun = itr->second._lightPos; sun.normalize();
+            float dev = osg::clampBetween(eye*sun, minDev, maxDev);
+            float r   = (dev-minDev)/(maxDev-minDev);
+            float amb = minAmb + r*(maxAmb-minAmb);
+            itr->second._light->setAmbient( osg::Vec4(amb,amb,amb,1.0) );
+            //OE_INFO << "dev=" << dev << ", amb=" << amb << std::endl;
         }
 
+        itr->second._cullContainer->accept( nv );
+
         // restore a custom clamper.
         if ( cb.valid() ) cv->setClampProjectionMatrixCallback( cb.get() );
     }
@@ -869,7 +853,7 @@ SkyNode::setEphemerisProvider(EphemerisProvider* ephemerisProvider )
         //Update the positions of the planets
         for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
         {
-            setDateTime(i->second._year, i->second._month, i->second._date, i->second._hoursUTC, i->first);
+            setDateTime(i->second._date, i->first);
         }
     }
 }
@@ -926,10 +910,7 @@ SkyNode::attach( osg::View* view, int lightNum )
     view->setLight( data._light.get() );
     view->getCamera()->setClearColor( osg::Vec4(0,0,0,1) );
 
-    data._year = _defaultPerViewData._year;
-    data._month = _defaultPerViewData._month;
     data._date = _defaultPerViewData._date;
-    data._hoursUTC = _defaultPerViewData._hoursUTC;
 }
 
 void
@@ -1059,26 +1040,39 @@ SkyNode::setMoonPosition( PerViewData& data, const osg::Vec3d& pos )
 
 
 void
-SkyNode::getDateTime( int &year, int &month, int &date, double &hoursUTC, osg::View* view )
+SkyNode::getDateTime( DateTime& out, osg::View* view ) const
 {    
-    PerViewData& data = _defaultPerViewData;
     if ( view )
     {
-        if ( _perViewData.find(view) != _perViewData.end() )
+        PerViewDataMap::const_iterator i = _perViewData.find(view);
+        if (i != _perViewData.end() )
         {
-            data = _perViewData[view];
+            out = i->second._date;
+            return;
         }
     }
+    out = _defaultPerViewData._date;
+}
+
+void
+SkyNode::getDateTime(int& year, int& month, int& date, double& hoursUTC, osg::View* view)
+{
+    DateTime temp;
+    getDateTime(temp, view);
+
+    year = temp.year();
+    month = temp.month();
+    date = temp.day();
+    hoursUTC = temp.hours();
 
-    year = data._year;
-    month = data._month;
-    date = data._date;
-    hoursUTC = data._hoursUTC;
+    OE_WARN << LC <<
+        "The method getDateTime(int&,int&,int&,double&,View*) is deprecated; "
+        "please use getDateTime(DateTime&, View*) instead" << std::endl;
 }
 
 
 void
-SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View* view )
+SkyNode::setDateTime(const DateTime& dt, osg::View* view)
 {    
     if ( _ellipsoidModel.valid() )
     {
@@ -1087,8 +1081,8 @@ SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View*
 
         if (_ephemerisProvider)
         {
-            sunPosition = _ephemerisProvider->getSunPosition( year, month, date, hoursUTC );
-            moonPosition = _ephemerisProvider->getMoonPosition( year, month, date, hoursUTC );
+            sunPosition = _ephemerisProvider->getSunPosition( dt );
+            moonPosition = _ephemerisProvider->getMoonPosition( dt );
         }
         else
         {
@@ -1097,29 +1091,23 @@ SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View*
 
         sunPosition.normalize();
         setSunPosition( sunPosition, view );
-        setMoonPosition( moonPosition, view );       
+        setMoonPosition( moonPosition, view );
 
         // position the stars:
-        double time_r = hoursUTC/24.0; // 0..1
+        double time_r = dt.hours()/24.0; // 0..1
         double rot_z = -osg::PI + TWO_PI*time_r;
 
         osg::Matrixd starsMatrix = osg::Matrixd::rotate( -rot_z, 0, 0, 1 );
         if ( !view )
         {
             _defaultPerViewData._starsMatrix = starsMatrix;
-            _defaultPerViewData._year = year;
-            _defaultPerViewData._month = month;
-            _defaultPerViewData._date = date;
-            _defaultPerViewData._hoursUTC = hoursUTC;
+            _defaultPerViewData._date = dt;
 
             for( PerViewDataMap::iterator i = _perViewData.begin(); i != _perViewData.end(); ++i )
             {
                 i->second._starsMatrix = starsMatrix;
                 i->second._starsXform->setMatrix( starsMatrix );
-                i->second._year = year;
-                i->second._month = month;
-                i->second._date = date;
-                i->second._hoursUTC = hoursUTC;
+                i->second._date = dt;
             }
         }
         else if ( _perViewData.find(view) != _perViewData.end() )
@@ -1127,14 +1115,25 @@ SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View*
             PerViewData& data = _perViewData[view];
             data._starsMatrix = starsMatrix;
             data._starsXform->setMatrix( starsMatrix );
-            data._year = year;
-            data._month = month;
-            data._date = date;
-            data._hoursUTC = hoursUTC;
+            data._date = dt;
         }
     }
 }
 
+
+void
+SkyNode::setDateTime( int year, int month, int date, double hoursUTC, osg::View* view )
+{
+    // backwards compatibility
+    setDateTime( DateTime(year, month, date, hoursUTC), view );
+
+    OE_WARN << LC << 
+        "The method setDateTime(int,int,int,double,View*) is deprecated; "
+        "please use setDateTime(const DateTime&, View*) instead"
+        << std::endl;
+}
+
+
 void
 SkyNode::setStarsVisible( bool value, osg::View* view )
 {
@@ -1424,7 +1423,7 @@ SkyNode::makeMoon()
     //If we couldn't load the moon texture, turn the moon off
     if (!image)
     {
-        OSG_ALWAYS << "Couldn't load moon texture, add osgEarth's data directory your OSG_FILE_PATH" << std::endl;
+        OE_INFO << LC << "Couldn't load moon texture, add osgEarth's data directory your OSG_FILE_PATH" << std::endl;
         _defaultPerViewData._moonXform->setNodeMask( 0 );
         _defaultPerViewData._moonVisible = false;
     }
diff --git a/src/osgEarthUtil/SpatialData.cpp b/src/osgEarthUtil/SpatialData.cpp
index 247b336..4df7518 100644
--- a/src/osgEarthUtil/SpatialData.cpp
+++ b/src/osgEarthUtil/SpatialData.cpp
@@ -358,23 +358,7 @@ GeoCell::adjustCount( int delta )
 
     if ( _depth > 0 && getNumParents() > 0 )
     {
-        static_cast<GeoCell*>(getParent(0))->adjustCount( delta );        
-
-#if 0
-        if ( !_clusterGeode.valid() )
-        {
-            _clusterGeode = makeClusterGeode( _extent, _count );
-        }
-        else
-        {
-            osgText::Text* t = static_cast<osgText::Text*>( _clusterGeode->getDrawable(0) );
-            std::stringstream buf;
-            buf << _count;
-            std::string str;
-            str = buf.str();
-            t->setText( str );
-        }
-#endif
+        static_cast<GeoCell*>(getParent(0))->adjustCount( delta );
     }
 }
 
@@ -514,38 +498,3 @@ GeoCell::reindexObject( GeoObject* object )
         return insertObject( object );
     }
 }
-
-#if 0
-bool
-GeoCell::reindex( GeoObject* object )
-{
-    osg::Vec3d location;
-    if ( object->getLocation(location) && !_extent.contains(location.x(), location.y()) )
-    {
-        // first remove from its current cell
-        osg::ref_ptr<GeoCell> safeCell = object->_cell.get();
-        if ( safeCell.valid() )
-        {
-            object->_cell = 0L;
-            safeCell->_objects.erase( findObject(safeCell->_objects, object) );
-            //safeCell->_objects.erase( std::find( _objects.begin(), _objects.end(), std::make_pair(object->getPriority(),object) ) );
-            //safeCell->_objects.erase( std::find( safeCell->_objects.begin(), safeCell->_objects.end(), object ) );
-            safeCell->adjustCount( -1 );
-            //safeCell->removeObject( object );
-        }
-
-        GeoCell* cell = dynamic_cast<GeoCell*>( this->getParent(0) );
-        while( cell )
-        {
-            if ( cell->getExtent().contains(location.x(), location.y()) )
-            {
-                if ( cell->insertObject( object ) )
-                    return true;
-            }
-            cell = dynamic_cast<GeoCell*>( cell->getParent(0) );
-        }
-    }
-
-    return true;
-}
-#endif
diff --git a/src/osgEarthUtil/TFSPackager b/src/osgEarthUtil/TFSPackager
index 8c3c0b4..16501be 100644
--- a/src/osgEarthUtil/TFSPackager
+++ b/src/osgEarthUtil/TFSPackager
@@ -80,6 +80,13 @@ namespace osgEarth { namespace Util {
         void setDestSRS(const std::string& srs ) { _destSRSString = srs; }
 
         /**
+         * A GeoExtent to use for LOD Level 0, in the SRS of the input dataset.  If not set the
+         * GeoExtent of the FeatureSource will be used
+         */
+        const GeoExtent getLod0Extent() const { return _customExtent; }
+        void setLod0Extent(const GeoExtent& extent) { _customExtent = extent; }
+
+        /**
          * Package the given feature source
          * @param features
          *     The feature source to package
@@ -103,7 +110,7 @@ namespace osgEarth { namespace Util {
         CropFilter::Method _method;
         std::string _destSRSString;
         osg::ref_ptr< const SpatialReference > _srs;
-
+        GeoExtent _customExtent;
     };
 
 } } // namespace osgEarth::Util
diff --git a/src/osgEarthUtil/TFSPackager.cpp b/src/osgEarthUtil/TFSPackager.cpp
index eacd94b..44025f9 100644
--- a/src/osgEarthUtil/TFSPackager.cpp
+++ b/src/osgEarthUtil/TFSPackager.cpp
@@ -133,7 +133,20 @@ public:
           bool traverse = true;
 
           GeoExtent featureExtent(_feature->getSRS(), _feature->getGeometry()->getBounds());
-          if (featureExtent.intersects( tile->getExtent()))
+
+          bool valid = false;
+          // It's a single point, so we do a contains check instead of an intersection check b/c the bounds really aren't valid.
+          if (featureExtent.width() == 0 && featureExtent.height() == 0)
+          {                            
+              valid = tile->getExtent().contains( featureExtent.xMin(), featureExtent.yMin());
+          }
+          else
+          {
+              // Do a normal intersection check
+              valid = featureExtent.intersects( tile->getExtent());
+          }
+
+          if (valid)
           {
               //If the node contains the feature, and it doesn't contain the max number of features add it.  If it's already full then 
               //split it.
@@ -174,9 +187,7 @@ public:
                   tile->traverse( this );
               }
 
-          }
-
-
+          }          
       }
 
       int _levelAdded;
@@ -296,9 +307,14 @@ void
     {
         _srs = features->getFeatureProfile()->getSRS();
     }
+	
+    //Get the extent of the dataset, or use the custom extent value
+    GeoExtent srsExtent = _customExtent;
+    if (!srsExtent.isValid())
+        srsExtent = features->getFeatureProfile()->getExtent();
 
     //Transform to lat/lon extents
-    GeoExtent extent = features->getFeatureProfile()->getExtent().transform( _srs.get() );
+    GeoExtent extent = srsExtent.transform( _srs.get() );
 
     osg::ref_ptr< const osgEarth::Profile > profile = osgEarth::Profile::create(extent.getSRS(), extent.xMin(), extent.yMin(), extent.xMax(), extent.yMax(), 1, 1);
 
@@ -352,6 +368,16 @@ void
     }   
     OE_NOTICE << "Added=" << added << " Skipped=" << skipped << " Failed=" << failed << std::endl;
 
+#if 1
+    // Print the width of tiles at each level
+    for (int i = 0; i <= highestLevel; ++i)
+    {
+        TileKey tileKey(i, 0, 0, profile);
+        GeoExtent tileExtent = tileKey.getExtent();
+        OE_NOTICE << "Level " << i << " tile size: " << tileExtent.width() << std::endl;
+    }
+#endif
+
     WriteFeaturesVisitor write(features, destination, _method, _srs);
     root->accept( &write );
 
diff --git a/src/osgEarthUtil/TMS b/src/osgEarthUtil/TMS
index b804b9b..914b4a2 100644
--- a/src/osgEarthUtil/TMS
+++ b/src/osgEarthUtil/TMS
@@ -32,6 +32,7 @@
 #include <iostream>
 #include <osgEarth/Profile>
 #include <osgEarth/Common>
+#include <osgEarth/DateTime>
 
 #include <osg/Referenced>
 #include <osgDB/ReaderWriter>
@@ -275,6 +276,16 @@ namespace osgEarth { namespace Util { namespace TMS
         void setOriginY(double y) {_originY = y;}
 
         /**
+        * Sets the timestamp of the TimeMap, i.e. the last modified time
+        */
+        void setTimeStamp(TimeStamp t) { _timestamp = t; }
+
+        /**
+        * Gets the timestap of the TileMap
+        */
+        TimeStamp getTimeStamp() const { return _timestamp; }
+
+        /**
         *Sets the origin of this TileMap
         *
         *@param x
@@ -419,7 +430,9 @@ namespace osgEarth { namespace Util { namespace TMS
         unsigned int _numTilesWide;
         unsigned int _numTilesHigh;
 
-        osgEarth::Profile::ProfileType _profile_type;        
+        osgEarth::Profile::ProfileType _profile_type;
+
+        TimeStamp _timestamp;
 
         DataExtentList _dataExtents;
     };
@@ -442,6 +455,41 @@ namespace osgEarth { namespace Util { namespace TMS
         virtual ~TileMapReaderWriter() { }
     };
 
+
+    /**
+     * An entry in a TileMapService's list of TileMaps.
+     */
+    struct OSGEARTHUTIL_EXPORT TileMapEntry
+    {
+        TileMapEntry( const std::string& _title, const std::string& _href, const std::string& _srs, const std::string& _profile);
+
+        std::string title;
+        std::string href;
+        std::string srs;
+        std::string profile;
+    };
+
+    typedef std::list< TileMapEntry > TileMapEntryList;
+
+    /**
+     * Reads a list of TileMapEntry's from a server.  Useful for displaying a list of layers to the user
+     */
+    class OSGEARTHUTIL_EXPORT TileMapServiceReader
+    {
+    public:
+        /**
+         * Reads a list of TileMapEntry's from a server
+         * @param location
+         *    A URL to the TMS service URL like http://server.com/tiles/1.0.0/
+         */
+        static bool read( const std::string &location, const osgDB::ReaderWriter::Options* options, TileMapEntryList& tileMaps );    
+        static bool read( const Config& conf, TileMapEntryList& tileMaps);
+
+    private:
+        TileMapServiceReader();
+        TileMapServiceReader( const TileMapServiceReader& rhs );
+    };
+
 } } } // namespace osgEarth::Util::TMS
 
 #endif //OSGEARTHUTIL_TMS_H
diff --git a/src/osgEarthUtil/TMS.cpp b/src/osgEarthUtil/TMS.cpp
index 93bb654..2285a0e 100644
--- a/src/osgEarthUtil/TMS.cpp
+++ b/src/osgEarthUtil/TMS.cpp
@@ -70,7 +70,8 @@ _maxY(0.0),
 _minLevel(0),
 _maxLevel(0),
 _numTilesHigh(-1),
-_numTilesWide(-1)
+_numTilesWide(-1),
+_timestamp(0)
 {   
 }
 
@@ -387,11 +388,11 @@ TileMap::create(const std::string& url,
     tileMap->_format.setWidth( tile_width );
     tileMap->_format.setHeight( tile_height );
     tileMap->_format.setExtension( format );
-	profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
+    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );
+
+    tileMap->generateTileSets();
+    tileMap->computeMinMaxLevel();
 
-	tileMap->generateTileSets();
-	tileMap->computeMinMaxLevel();
-        
     return tileMap;
 }
 
@@ -451,6 +452,10 @@ TileMapReaderWriter::read( const std::string& location, const osgDB::ReaderWrite
     if (tileMap)
     {
         tileMap->setFilename( location );
+
+        // record the timestamp (if there is one) in the tilemap. It's not a persistent field
+        // but will help with things like per-session caching.
+        tileMap->setTimeStamp( r.lastModifiedTime() );
     }
 
     return tileMap;
@@ -677,3 +682,76 @@ TileMapReaderWriter::write(const TileMap* tileMap, std::ostream &output)
 }
 
 
+//----------------------------------------------------------------------------
+
+TileMapEntry::TileMapEntry( const std::string& _title, const std::string& _href, const std::string& _srs, const std::string& _profile ):
+title( _title ),
+href( _href ),
+srs( _srs ),
+profile( _profile )
+{
+}
+
+//----------------------------------------------------------------------------
+
+TileMapServiceReader::TileMapServiceReader()
+{
+}
+
+TileMapServiceReader::TileMapServiceReader(const TileMapServiceReader& rhs)
+{
+}
+
+bool
+TileMapServiceReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options, TileMapEntryList& tileMaps )
+{     
+    ReadResult r = URI(location).readString();
+    if ( r.failed() )
+    {
+        OE_WARN << LC << "Failed to read TileMapServices from " << location << std::endl;
+        return 0L;
+    }    
+    
+    // Read tile map into a Config:
+    Config conf;
+    std::stringstream buf( r.getString() );
+    conf.fromXML( buf );    
+
+    // parse that into a tile map:        
+    return read( conf, tileMaps );    
+}
+
+bool
+TileMapServiceReader::read( const Config& conf, TileMapEntryList& tileMaps)
+{    
+    const Config* TileMapServiceConf = conf.find("tilemapservice");
+
+    if (!TileMapServiceConf)
+    {
+        OE_NOTICE << "Couldn't find root TileMapService element" << std::endl;
+    }
+
+    const Config* TileMapsConf = TileMapServiceConf->find("tilemaps");
+    if (TileMapsConf)
+    {
+        const ConfigSet& TileMaps = TileMapsConf->children("tilemap");
+        if (TileMaps.size() == 0)
+        {            
+            return false;
+        }
+        
+        for (ConfigSet::const_iterator itr = TileMaps.begin(); itr != TileMaps.end(); ++itr)
+        {
+            std::string href = itr->value("href");
+            std::string title = itr->value("title");
+            std::string profile = itr->value("profile");
+            std::string srs = itr->value("srs");            
+
+            tileMaps.push_back( TileMapEntry( title, href, srs, profile ) );
+        }        
+
+        return true;
+    }    
+    return false;
+}
+
diff --git a/src/osgEarthUtil/TMSPackager b/src/osgEarthUtil/TMSPackager
index 0493605..1a17056 100644
--- a/src/osgEarthUtil/TMSPackager
+++ b/src/osgEarthUtil/TMSPackager
@@ -23,6 +23,7 @@
 #include <osgEarth/ImageLayer>
 #include <osgEarth/ElevationLayer>
 #include <osgEarth/Profile>
+#include <osgEarth/TaskService>
 
 namespace osgEarth { namespace Util
 {
@@ -97,11 +98,12 @@ namespace osgEarth { namespace Util
          * Result structure for method calls
          */
         struct Result {
-            Result() : ok(true) { }
-            Result(const std::string& m) : message(m), ok(false) { }
+            Result(int tasks=0) : ok(true), taskCount(tasks) { }
+            Result(const std::string& m) : message(m), ok(false), taskCount(0) { }
             operator bool() const { return ok; }
             bool ok;
             std::string message;
+            int taskCount;
         };
 
         /**
@@ -113,7 +115,8 @@ namespace osgEarth { namespace Util
         Result package(
             ImageLayer*        layer,
             const std::string& rootFolder,
-            const std::string& imageExtension ="png" );
+            osgEarth::ProgressCallback* progress=0L,
+            const std::string& imageExtension="png" );
 
         /**
          * Packages an elevation layer as a TMS repository.
@@ -122,22 +125,29 @@ namespace osgEarth { namespace Util
          */
         Result package( 
             ElevationLayer*    layer,
-            const std::string& rootFolder );
+            const std::string& rootFolder,
+            osgEarth::ProgressCallback* progress=0L );
 
     protected:
 
-        Result packageImageTile(
+        int packageImageTile(
             ImageLayer*          layer,
             const TileKey&       key,
             const std::string&   rootDir,
             const std::string&   extension,
+            osgEarth::TaskRequestVector& tasks,
+            Threading::MultiEvent* semaphore,
+            osgEarth::ProgressCallback* progress,
             unsigned&            out_maxLevel );
 
-        Result packageElevationTile(
+        int packageElevationTile(
             ElevationLayer*      layer,
             const TileKey&       key,
             const std::string&   rootDir,
             const std::string&   extension,
+            osgEarth::TaskRequestVector& tasks,
+            Threading::MultiEvent* semaphore,
+            osgEarth::ProgressCallback* progress,
             unsigned&            out_maxLevel );
 
         bool shouldPackageKey( 
diff --git a/src/osgEarthUtil/TMSPackager.cpp b/src/osgEarthUtil/TMSPackager.cpp
index ceebf77..f528f33 100644
--- a/src/osgEarthUtil/TMSPackager.cpp
+++ b/src/osgEarthUtil/TMSPackager.cpp
@@ -20,6 +20,7 @@
 #include <osgEarthUtil/TMS>
 #include <osgEarth/ImageUtils>
 #include <osgEarth/ImageToHeightFieldConverter>
+#include <osgEarth/TaskService>
 #include <osgDB/FileUtils>
 #include <osgDB/FileNameUtils>
 #include <osgDB/WriteFile>
@@ -30,6 +31,175 @@ using namespace osgEarth::Util;
 using namespace osgEarth;
 
 
+namespace
+{
+    struct CreateImageTileTask
+    {
+        void init(ImageLayer* layer, const TileKey& key, const std::string& path, const std::string& extension, osgDB::Options* imageWriteOptions, bool keepEmpties, bool verbose)
+        {
+          _layer = layer;
+          _key = key;
+          _path = path;
+          _extension = extension;
+          _imageWriteOptions = imageWriteOptions;
+          _keepEmptyImageTiles = keepEmpties;
+          _verbose = verbose;
+        }
+
+        void execute()
+        {
+            //bool isSingleColor = false;
+            bool tileOK = false;
+
+            GeoImage image = _layer->createImage( _key );
+            if ( image.valid() )
+            {
+                // Check for single color
+                //if ( !_subdivideSingleColorImageTiles )
+                //{
+                //    isSingleColor = ImageUtils::isSingleColorImage(image.getImage());
+                //    if ( isSingleColor && _verbose )
+                //    {
+                //        OE_NOTICE << LC << "Not subdividing single color tile " << key.str() << std::endl;
+                //    }
+                //}
+
+                // check for empty:
+                if ( !_keepEmptyImageTiles && ImageUtils::isEmptyImage(image.getImage()) )
+                {
+                    if (  _verbose )
+                    {
+                        OE_NOTICE << LC << "Skipping empty tile " << _key.str() << std::endl;
+                    }
+                }
+                else
+                {
+                    // convert to RGB if necessary
+                    osg::ref_ptr<osg::Image> final = image.getImage();
+                    if ( _extension == "jpg" && final->getPixelFormat() != GL_RGB )
+                        final = ImageUtils::convertToRGB8( image.getImage() );
+
+                    // dump it to disk
+                    osgDB::makeDirectoryForFile( _path );
+                    tileOK = osgDB::writeImageFile( *final.get(), _path, _imageWriteOptions);
+
+                    if ( _verbose )
+                    {
+                        if ( tileOK ) {
+                            OE_NOTICE << LC << "Wrote tile " << _key.str() << " (" << _key.getExtent().toString() << ")" << std::endl;
+                        }
+                        else {
+                            OE_NOTICE << LC << "Error write tile " << _key.str() << std::endl;
+                        }
+                    }
+
+                    //if ( _abortOnError && !tileOK )
+                    //{
+                    //    return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
+                    //}
+                }
+            }
+        }
+
+    private:
+        osg::ref_ptr<ImageLayer> _layer;
+        TileKey _key;
+        std::string _path;
+        std::string _extension;
+        osg::ref_ptr<osgDB::Options> _imageWriteOptions;
+        bool _keepEmptyImageTiles;
+        bool _verbose;
+    };
+
+
+    struct CreateElevationTileTask
+    {
+        void init(ElevationLayer* layer, const TileKey& key, const std::string& path, bool verbose)
+        {
+          _layer = layer;
+          _key = key;
+          _path = path;
+          _verbose = verbose;
+        }
+
+        void execute()
+        {
+            bool tileOK = false;
+
+            GeoHeightField hf = _layer->createHeightField( _key );
+            if ( hf.valid() )
+            {
+                // convert the HF to an image
+                ImageToHeightFieldConverter conv;
+                osg::ref_ptr<osg::Image> image = conv.convert( hf.getHeightField() );
+
+                // dump it to disk
+                osgDB::makeDirectoryForFile( _path );
+                tileOK = osgDB::writeImageFile( *image.get(), _path );
+
+                if ( _verbose )
+                {
+                    if ( tileOK ) {
+                        OE_NOTICE << LC << "Wrote tile " << _key.str() << " (" << _key.getExtent().toString() << ")" << std::endl;
+                    }
+                    else {
+                        OE_NOTICE << LC << "Error write tile " << _key.str() << std::endl;
+                    }
+                }
+
+                //if ( _abortOnError && !tileOK )
+                //{
+                //    return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
+                //}
+            }
+        }
+
+    private:
+        osg::ref_ptr<ElevationLayer> _layer;
+        TileKey _key;
+        std::string _path;
+        bool _verbose;
+    };
+
+
+    class PackageTileProgressCallback : public osgEarth::ProgressCallback
+    {
+    public:
+      PackageTileProgressCallback(osgEarth::ProgressCallback* proxyProgress)
+        : _progress(proxyProgress), _total(0), _completed(0)
+      {
+      }
+
+      virtual ~PackageTileProgressCallback() { }
+
+      void setTotalTasks(int total) { _total = total; }
+
+      bool reportProgress(double current, double total, unsigned currentStage, unsigned totalStages, const std::string& msg)
+      {
+        return false;
+      }
+
+      void onCompleted()
+      {
+        if (_completed >= _total)
+          return;
+
+        _completed++;
+        if (_progress.valid())
+          _progress->reportProgress(_completed, _total);
+
+        if (_completed >= _total)
+          _progress->onCompleted();
+      }
+
+    private:
+      osg::ref_ptr<osgEarth::ProgressCallback> _progress;
+      int _total;
+      int _completed;
+    };
+}
+
+
 TMSPackager::TMSPackager(const Profile* outProfile, osgDB::Options* imageWriteOptions) :
 _outProfile         ( outProfile ),
 _maxLevel           ( 99 ),
@@ -70,19 +240,26 @@ TMSPackager::shouldPackageKey( const TileKey& key ) const
 }
 
 
-TMSPackager::Result
-TMSPackager::packageImageTile(ImageLayer*          layer,
-                              const TileKey&       key,
-                              const std::string&   rootDir,
-                              const std::string&   extension,
-                              unsigned&            out_maxLevel )
+int
+TMSPackager::packageImageTile(ImageLayer*                  layer,
+                              const TileKey&               key,
+                              const std::string&           rootDir,
+                              const std::string&           extension,
+                              TaskRequestVector&           tasks,
+                              Threading::MultiEvent*       semaphore,
+                              osgEarth::ProgressCallback*  progress,
+                              unsigned&                    out_maxLevel )
 {
     unsigned minLevel = layer->getImageLayerOptions().minLevel().isSet() ?
         *layer->getImageLayerOptions().minLevel() : 0;
     
+    int taskCount = 0;
 
-    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel )
-    {
+    bool hasData = layer->getTileSource()->hasData( key );
+
+    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel && hasData )
+    {        
+        OE_DEBUG << "Packaging key " << key.str() << std::endl;
         unsigned w, h;
         key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );
 
@@ -97,54 +274,13 @@ TMSPackager::packageImageTile(ImageLayer*          layer,
         bool tileOK = osgDB::fileExists(path) && !_overwrite;
         if ( !tileOK )
         {
-            GeoImage image = layer->createImage( key );
-            if ( image.valid() )
-            {
-                // Check for single color
-                if ( !_subdivideSingleColorImageTiles )
-                {
-                    isSingleColor = ImageUtils::isSingleColorImage(image.getImage());
-                    if ( _verbose )
-                    {
-                        OE_NOTICE << LC << "Not subdividing single color tile " << key.str() << std::endl;
-                    }
-                }
-
-                // check for empty:
-                if ( !_keepEmptyImageTiles && ImageUtils::isEmptyImage(image.getImage()) )
-                {
-                    if ( _verbose )
-                    {
-                        OE_NOTICE << LC << "Skipping empty tile " << key.str() << std::endl;
-                    }
-                }
-                else
-                {
-                    // convert to RGB if necessary
-                    osg::ref_ptr<osg::Image> final = image.getImage();
-                    if ( extension == "jpg" && final->getPixelFormat() != GL_RGB )
-                        final = ImageUtils::convertToRGB8( image.getImage() );
-
-                    // dump it to disk
-                    osgDB::makeDirectoryForFile( path );
-                    tileOK = osgDB::writeImageFile( *final.get(), path, _imageWriteOptions);
-
-                    if ( _verbose )
-                    {
-                        if ( tileOK ) {
-                            OE_NOTICE << LC << "Wrote tile " << key.str() << " (" << key.getExtent().toString() << ")" << std::endl;
-                        }
-                        else {
-                            OE_NOTICE << LC << "Error write tile " << key.str() << std::endl;
-                        }
-                    }
+            ParallelTask<CreateImageTileTask>* task = new ParallelTask<CreateImageTileTask>( semaphore );
+            task->init(layer, key, path, extension, _imageWriteOptions, _keepEmptyImageTiles, _verbose);
+            task->setProgressCallback(progress);
+            tasks.push_back(task);            
+            taskCount++;
 
-                    if ( _abortOnError && !tileOK )
-                    {
-                        return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
-                    }
-                }
-            }
+            tileOK = true;
         }
         else
         {
@@ -174,30 +310,36 @@ TMSPackager::packageImageTile(ImageLayer*          layer,
         if ( (subdivide == true) && (isSingleColor == false) )
         {
             for( unsigned q=0; q<4; ++q )
-            {
+            {                
                 TileKey childKey = key.createChildKey(q);
-                Result r = packageImageTile( layer, childKey, rootDir, extension, out_maxLevel );
-                if ( _abortOnError && !r.ok )
-                    return r;
+
+                taskCount += packageImageTile( layer, childKey, rootDir, extension, tasks, semaphore, progress, out_maxLevel );                
             }
         }
     }
 
-    return Result();
+    return taskCount;
 }
 
 
-TMSPackager::Result
-TMSPackager::packageElevationTile(ElevationLayer*      layer,
-                                  const TileKey&       key,
-                                  const std::string&   rootDir,
-                                  const std::string&   extension,
-                                  unsigned&            out_maxLevel)
+int
+TMSPackager::packageElevationTile(ElevationLayer*               layer,
+                                  const TileKey&                key,
+                                  const std::string&            rootDir,
+                                  const std::string&            extension,
+                                  osgEarth::TaskRequestVector&  tasks,
+                                  Threading::MultiEvent*        semaphore,
+                                  osgEarth::ProgressCallback*   progress,
+                                  unsigned&                     out_maxLevel)
 {
     unsigned minLevel = layer->getElevationLayerOptions().minLevel().isSet() ?
         *layer->getElevationLayerOptions().minLevel() : 0;
 
-    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel )
+    int taskCount = 0;
+
+    bool hasData = layer->getTileSource()->hasData( key );
+
+    if ( shouldPackageKey(key) && key.getLevelOfDetail() >= minLevel && hasData )
     {
         unsigned w, h;
         key.getProfile()->getNumTiles( key.getLevelOfDetail(), w, h );
@@ -212,33 +354,13 @@ TMSPackager::packageElevationTile(ElevationLayer*      layer,
         bool tileOK = osgDB::fileExists(path) && !_overwrite;
         if ( !tileOK )
         {
+            ParallelTask<CreateElevationTileTask>* task = new ParallelTask<CreateElevationTileTask>( semaphore );
+            task->init(layer, key, path, _verbose);
+            task->setProgressCallback(progress);
+            tasks.push_back(task);
+            taskCount++;
 
-            GeoHeightField hf = layer->createHeightField( key );
-            if ( hf.valid() )
-            {
-                // convert the HF to an image
-                ImageToHeightFieldConverter conv;
-                osg::ref_ptr<osg::Image> image = conv.convert( hf.getHeightField() );
-
-                // dump it to disk
-                osgDB::makeDirectoryForFile( path );
-                tileOK = osgDB::writeImageFile( *image.get(), path );
-
-                if ( _verbose )
-                {
-                    if ( tileOK ) {
-                        OE_NOTICE << LC << "Wrote tile " << key.str() << " (" << key.getExtent().toString() << ")" << std::endl;
-                    }
-                    else {
-                        OE_NOTICE << LC << "Error write tile " << key.str() << std::endl;
-                    }
-                }
-
-                if ( _abortOnError && !tileOK )
-                {
-                    return Result( Stringify() << "Aborting, write failed for tile " << key.str() );
-                }
-            }
+            tileOK = true;
         }
         else
         {
@@ -269,23 +391,25 @@ TMSPackager::packageElevationTile(ElevationLayer*      layer,
         {
             for( unsigned q=0; q<4; ++q )
             {
-                TileKey childKey = key.createChildKey(q);
-                Result r = packageElevationTile( layer, childKey, rootDir, extension, out_maxLevel );
-                if ( _abortOnError && !r.ok )
-                    return r;
+                TileKey childKey = key.createChildKey(q);                
+                taskCount += packageElevationTile( layer, childKey, rootDir, extension, tasks, semaphore, progress, out_maxLevel );
             }
         }
     }
 
-    return Result();
+    return taskCount;
 }
 
 
 TMSPackager::Result
 TMSPackager::package(ImageLayer*        layer,
                      const std::string& rootFolder,
+                     osgEarth::ProgressCallback* progress,
                      const std::string& overrideExtension )
 {
+  osg::Timer* timer = osg::Timer::instance();
+  osg::Timer_t start_t = timer->tick();
+
     if ( !layer || !_outProfile.valid() )
         return Result( "Illegal null layer or profile" );
 
@@ -347,15 +471,44 @@ TMSPackager::package(ImageLayer*        layer,
         OE_NOTICE << LC << "MIME-TYPE = " << mimeType << ", Extension = " << extension << std::endl;
     }
 
+
+    // semaphore and tasks collection for multithreading
+    osgEarth::Threading::MultiEvent semaphore;
+    osgEarth::TaskRequestVector tasks;
+    int taskCount = 0;
+    
+    PackageTileProgressCallback* tileProgress = 0L;
+    if (progress)
+      tileProgress = new PackageTileProgressCallback(progress);
+
     // package the tile hierarchy
     unsigned maxLevel = 0;
     for( std::vector<TileKey>::const_iterator i = rootKeys.begin(); i != rootKeys.end(); ++i )
     {
-        Result r = packageImageTile( layer, *i, rootFolder, extension, maxLevel );
-        if ( _abortOnError && !r.ok )
-            return r;
+        taskCount += packageImageTile( layer, *i, rootFolder, extension, tasks, &semaphore, tileProgress, maxLevel );
     }
 
+    // Run all the tasks in parallel
+    OE_DEBUG << LC << "Packaging image layer \"" << layer->getName() << "\", total number of tiles: " << taskCount << std::endl;
+
+    semaphore.reset( taskCount );
+    if (tileProgress) tileProgress->setTotalTasks(taskCount);
+
+    unsigned num = 2 * OpenThreads::GetNumberOfProcessors();
+    osg::ref_ptr<osgEarth::TaskService> taskService = new osgEarth::TaskService("TMS Packager", num);
+
+    for( TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i )
+          taskService->add( i->get() );
+
+    // Wait for them to complete
+    semaphore.wait();
+
+    osg::Timer_t end_t = timer->tick();
+    double elapsed = (end_t - start_t) * timer->getSecondsPerTick();
+    OE_DEBUG << LC << "Packaging image layer\"" << layer->getName() << "\" complete. Seconds elapsed: " << elapsed << std::endl;
+
+
+
     // create the tile map metadata:
     osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
         "",
@@ -379,8 +532,12 @@ TMSPackager::package(ImageLayer*        layer,
 
 TMSPackager::Result
 TMSPackager::package(ElevationLayer*    layer,
-                     const std::string& rootFolder)
+                     const std::string& rootFolder,
+                     osgEarth::ProgressCallback* progress )
 {
+    osg::Timer* timer = osg::Timer::instance();
+    osg::Timer_t start_t = timer->tick();
+
     if ( !layer || !_outProfile.valid() )
         return Result( "Illegal null layer or profile" );
 
@@ -411,14 +568,36 @@ TMSPackager::package(ElevationLayer*    layer,
     if ( !testHF.valid() )
         return Result( "Unable to determine heightfield size" );
 
+    osgEarth::Threading::MultiEvent semaphore;
+    osgEarth::TaskRequestVector tasks;
+    int taskCount = 0;
+
+    PackageTileProgressCallback* tileProgress = 0L;
+    if (progress)
+      tileProgress = new PackageTileProgressCallback(progress);
+
+    // package the tile hierarchy
     unsigned maxLevel = 0;
     for( std::vector<TileKey>::const_iterator i = rootKeys.begin(); i != rootKeys.end(); ++i )
     {
-        Result r = packageElevationTile( layer, *i, rootFolder, extension, maxLevel );
-        if ( _abortOnError && !r.ok )
-            return r;
+        taskCount += packageElevationTile( layer, *i, rootFolder, extension, tasks, &semaphore, tileProgress, maxLevel );
     }
 
+    // run all the tasks in parallel
+    OE_DEBUG << LC << "Packaging elevation layer \"" << layer->getName() << "\", total number of tiles: " << taskCount << std::endl;
+
+    semaphore.reset( taskCount );
+    if (tileProgress) tileProgress->setTotalTasks(taskCount);
+
+    unsigned num = 2 * OpenThreads::GetNumberOfProcessors();
+    osg::ref_ptr<osgEarth::TaskService> taskService = new osgEarth::TaskService("TMS Elevation Packager", num);
+
+    for (TaskRequestVector::iterator i = tasks.begin(); i != tasks.end(); ++i)
+        taskService->add( i->get() );
+
+    semaphore.wait();
+
+
     // create the tile map metadata:
     osg::ref_ptr<TMS::TileMap> tileMap = TMS::TileMap::create(
         "",
@@ -436,5 +615,9 @@ TMSPackager::package(ElevationLayer*    layer,
     std::string tileMapFilename = osgDB::concatPaths(rootFolder, "tms.xml");
     TMS::TileMapReaderWriter::write( tileMap.get(), tileMapFilename );
 
+    osg::Timer_t end_t = timer->tick();
+    double elapsed = (end_t - start_t) * timer->getSecondsPerTick();
+    OE_DEBUG << LC << "Packaging elevation layer \"" << layer->getName() << "\" complete. Seconds elapsed: " << elapsed << std::endl;
+
     return Result();
 }
diff --git a/src/osgEarthUtil/TileIndex b/src/osgEarthUtil/TileIndex
new file mode 100644
index 0000000..7715794
--- /dev/null
+++ b/src/osgEarthUtil/TileIndex
@@ -0,0 +1,68 @@
+/* -*-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 OSGEARTHUTIL_TILEINDEX_H
+#define OSGEARTHUTIL_TILEINDEX_H 1
+
+#include <osgEarthUtil/Common>
+#include <osg/Referenced>
+#include <osg/ref_ptr>
+#include <osgEarthFeatures/FeatureSource>
+
+#include <string>
+#include <vector>
+
+namespace osgEarth { namespace Util
+{    
+    /**
+     * Manages a FeatureSource that is an index of geospatial data files     
+     */
+    class OSGEARTHUTIL_EXPORT TileIndex : public osg::Referenced
+    {
+    public:        
+
+        static TileIndex* load( const std::string& filename );
+        static TileIndex* create( const std::string& filename, const osgEarth::SpatialReference* srs);        
+
+        /**
+         * Gets files within the given extent.
+         */
+        void getFiles(const osgEarth::GeoExtent& extent, std::vector< std::string >& files);
+
+        /**
+         * Adds the given filename to the index
+         */
+        bool add( const std::string& filename, const GeoExtent& extent );
+        
+        /**
+         * Gets the filename of the shapefile used for this index.
+         */
+        const std::string& getFilename() const { return _filename;}
+
+    protected:
+        TileIndex();        
+        ~TileIndex();
+
+        osg::ref_ptr< osgEarth::Features::FeatureSource > _features;
+        std::string _filename;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif //OSGEARTHUTIL_TILEINDEX_H
diff --git a/src/osgEarthUtil/TileIndex.cpp b/src/osgEarthUtil/TileIndex.cpp
new file mode 100644
index 0000000..591e091
--- /dev/null
+++ b/src/osgEarthUtil/TileIndex.cpp
@@ -0,0 +1,143 @@
+/* -*-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/Registry>
+#include <osgEarth/FileUtils>
+#include <osgEarthUtil/TileIndex>
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <ogr_api.h>
+#include <osgEarthFeatures/OgrUtils>
+#include <osgDB/FileUtils>
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers;
+using namespace osgEarth::Features;
+using namespace std;
+
+#define OGR_SCOPED_LOCK GDAL_SCOPED_LOCK
+
+TileIndex::TileIndex()
+{
+}
+
+TileIndex::~TileIndex()
+{
+
+}
+
+TileIndex*
+    TileIndex::load(const std::string& filename)
+{        
+    if (!osgDB::fileExists( filename ) )
+    {
+        return 0;
+    }
+
+    //Load up an index file
+    OGRFeatureOptions featureOpt;
+    featureOpt.url() = filename;        
+    featureOpt.buildSpatialIndex() = true;
+    featureOpt.openWrite() = true;
+
+    osg::ref_ptr< FeatureSource> features = FeatureSourceFactory::create( featureOpt );        
+    if (!features.valid())
+    {
+        OE_NOTICE << "Can't load " << filename << std::endl;
+        return 0;
+    }
+    features->initialize();
+    features->getFeatureProfile();    
+
+    TileIndex* index = new TileIndex();
+    index->_features = features.get();
+    index->_filename = filename;
+    return index;
+}
+
+TileIndex*
+    TileIndex::create( const std::string& filename, const osgEarth::SpatialReference* srs )
+{
+    // Make sure the registry is loaded since that is where the OGR/GDAL registration happens
+    osgEarth::Registry::instance();
+
+    OGR_SCOPED_LOCK;
+
+    OGRSFDriverH driver = OGRGetDriverByName( "ESRI Shapefile" );    
+
+    //Create the datasource itself.
+    OGRDataSourceH dataSource = OGR_Dr_CreateDataSource( driver, filename.c_str(), NULL );
+    if (dataSource == NULL)
+    {
+        OE_WARN << "failed to create " << filename.c_str() << std::endl;
+        return 0;
+    }
+
+    //Create the layer
+    OGRLayerH layer = OGR_DS_CreateLayer( dataSource, "index", (OGRSpatialReferenceH)srs->getHandle(), wkbPolygon, NULL );
+
+    //Create the attribute name to use for the filename
+    OGRFieldDefnH  field = OGR_Fld_Create("location", OFTString);
+    OGR_L_CreateField( layer, field, TRUE);
+
+    OGR_DS_Destroy( dataSource );
+
+    return load( filename );
+}
+
+
+void
+    TileIndex::getFiles(const osgEarth::GeoExtent& extent, std::vector< std::string >& files)
+{            
+    files.clear();
+    osgEarth::Symbology::Query query;    
+
+    GeoExtent transformed = extent.transform( _features->getFeatureProfile()->getSRS() );
+    query.bounds() = transformed.bounds();
+    osg::ref_ptr< osgEarth::Features::FeatureCursor> cursor = _features->createFeatureCursor( query );
+
+    while (cursor->hasMore())
+    {
+        osg::ref_ptr< osgEarth::Features::Feature> feature = cursor->nextFeature();
+        if (feature.valid())
+        {
+            std::string location = getFullPath(_filename, feature->getString("location"));
+            files.push_back( location );
+        }
+    }    
+}
+
+bool TileIndex::add( const std::string& filename, const GeoExtent& extent )
+{       
+    osg::ref_ptr< Polygon > polygon = new Polygon();
+    polygon->push_back( osg::Vec3d(extent.bounds().xMin(), extent.bounds().yMin(), 0) );
+    polygon->push_back( osg::Vec3d(extent.bounds().xMax(), extent.bounds().yMin(), 0) );
+    polygon->push_back( osg::Vec3d(extent.bounds().xMax(), extent.bounds().yMax(), 0) );
+    polygon->push_back( osg::Vec3d(extent.bounds().xMin(), extent.bounds().yMax(), 0) );
+    polygon->push_back( osg::Vec3d(extent.bounds().xMin(), extent.bounds().yMin(), 0) );
+   
+    osg::ref_ptr< Feature > feature = new Feature( polygon, extent.getSRS()  );
+    feature->set("location", filename );
+    
+    const SpatialReference* wgs84 = SpatialReference::create("epsg:4326");
+    feature->transform( wgs84 );
+
+    return _features->insertFeature( feature.get() );    
+    return true;
+}
diff --git a/src/osgEarthUtil/TileIndexBuilder b/src/osgEarthUtil/TileIndexBuilder
new file mode 100644
index 0000000..35fc4f4
--- /dev/null
+++ b/src/osgEarthUtil/TileIndexBuilder
@@ -0,0 +1,75 @@
+/* -*-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 OSGEARTHUTIL_TILEINDEXBUILDER_H
+#define OSGEARTHUTIL_TILEINDEXBUILDER_H 1
+
+#include <osgEarthUtil/Common>
+
+#include <osgEarthUtil/TileIndex>
+
+namespace osgEarth { namespace Util
+{    
+	/**
+	 * Utility class for buildling a TileIndex shapefile.
+	 */
+	class OSGEARTHUTIL_EXPORT TileIndexBuilder : public osg::Referenced
+	{
+	public:
+
+		/**
+		 * Creates a new TileIndexBuilder
+		 */
+		TileIndexBuilder();
+
+		/**
+		 * Sets the progress callback
+		 */
+		void setProgressCallback( osgEarth::ProgressCallback* progress );
+
+		/**
+		 * Gets the list of filenames to process.  If you pass in a directory name
+		 * it will recursively try all the files within the directory and it's subdirectories.
+		 */
+		std::vector< std::string >& getFilenames() { return _filenames; }
+
+		/**
+		 * Builds the TileIndex
+		 * @param indexFilename
+		 *    The filename of the index shapefile to create.
+		 * @param srs
+		 *    The SRS to use for the output shapefile.  Default is epsg:4326
+		 */
+		void build(const std::string& indexFilename, const osgEarth::SpatialReference* srs = 0);
+
+
+	protected:
+
+		void expandFilenames();
+
+		std::string _indexFilename;
+		std::vector< std::string > _filenames;
+		std::vector< std::string > _expandedFilenames;
+
+		osg::ref_ptr<ProgressCallback> _progress;    
+	};
+
+} } // namespace osgEarth::Util
+
+#endif //OSGEARTHUTIL_TILEINDEXBUILDER_H
diff --git a/src/osgEarthUtil/TileIndexBuilder.cpp b/src/osgEarthUtil/TileIndexBuilder.cpp
new file mode 100644
index 0000000..e83d8f2
--- /dev/null
+++ b/src/osgEarthUtil/TileIndexBuilder.cpp
@@ -0,0 +1,126 @@
+/* -*-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 <osgEarthUtil/TileIndexBuilder>
+#include <osgEarth/Registry>
+#include <osgEarth/FileUtils>
+#include <osgEarth/Progress>
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+using namespace osgDB;
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Drivers;
+using namespace osgEarth::Features;
+using namespace std;
+
+TileIndexBuilder::TileIndexBuilder()
+{
+}
+
+void TileIndexBuilder::setProgressCallback( osgEarth::ProgressCallback* progress )
+{
+    _progress = progress;
+}
+
+void TileIndexBuilder::build(const std::string& indexFilename, const osgEarth::SpatialReference* srs)
+{
+    expandFilenames();
+
+    if (!srs)
+    {
+        srs = osgEarth::SpatialReference::create("wgs84");
+    }
+
+    osg::ref_ptr< osgEarth::Util::TileIndex > index = osgEarth::Util::TileIndex::create( indexFilename, srs );
+
+    _indexFilename = indexFilename;
+    std::string indexDir = getFilePath( _indexFilename );    
+    
+    unsigned int total = _expandedFilenames.size();
+
+    for (unsigned int i = 0; i < _expandedFilenames.size(); i++)
+    {   
+        std::string filename = _expandedFilenames[ i ];        
+
+        GDALOptions opt;
+        opt.url() = filename;
+        
+        osg::ref_ptr< ImageLayer > layer = new ImageLayer( ImageLayerOptions("", opt) );        
+
+        bool ok = false;
+                
+        if ( layer.valid() )        
+        {            
+            osg::ref_ptr< TileSource > source = layer->getTileSource();
+            if (source.valid())
+            {
+                for (DataExtentList::iterator itr = source->getDataExtents().begin(); itr != source->getDataExtents().end(); ++itr)
+                {
+                    // We want the filename as it is relative to the index file                
+                    std::string relative = getPathRelative( indexDir, filename );                
+                    index->add( relative, *itr);    
+                    ok = true;
+                }                
+            }
+        }        
+
+        if (_progress.valid())
+        {
+            std::stringstream buf;
+            if (ok)
+            {
+                buf << "Processed ";
+            }
+            else
+            {
+                buf << "Skipped ";
+            }
+
+            buf << filename;
+            _progress->reportProgress( (double)i+1, (double)total, buf.str() );
+        }
+    }
+
+    osg::Timer_t end = osg::Timer::instance()->tick();    
+}
+
+void TileIndexBuilder::expandFilenames()
+{
+    // Expand the filenames since they might contain directories    
+    for (unsigned int i = 0; i < _filenames.size(); i++)
+    {
+        std::string filename = _filenames[i];
+        if (osgDB::fileType(filename) == osgDB::DIRECTORY)
+        {            
+            CollectFilesVisitor v;
+            v.traverse( filename );
+            for (unsigned int j = 0; j < v.filenames.size(); j++)
+            {
+                _expandedFilenames.push_back( v.filenames[ j ] );
+            }
+        }   
+        else
+        {
+            _expandedFilenames.push_back( filename );
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/osgEarthUtil/UTMGraticule b/src/osgEarthUtil/UTMGraticule
index 0c287b0..ba81c3b 100644
--- a/src/osgEarthUtil/UTMGraticule
+++ b/src/osgEarthUtil/UTMGraticule
@@ -130,7 +130,6 @@ namespace osgEarth { namespace Util
         void init();
         void rebuild();
         osg::Node* buildGZDTile( const std::string& name, const GeoExtent& extent );
-        //osg::Node* buildPolarGZDTiles();
 
         virtual osg::Group* buildGZDChildren( osg::Group* node, const std::string& gzd ) {
             return node; }
diff --git a/src/osgEarthUtil/UTMGraticule.cpp b/src/osgEarthUtil/UTMGraticule.cpp
index 3bc7e5d..2b35836 100644
--- a/src/osgEarthUtil/UTMGraticule.cpp
+++ b/src/osgEarthUtil/UTMGraticule.cpp
@@ -24,7 +24,6 @@
 
 #include <osgEarthSymbology/Geometry>
 #include <osgEarthAnnotation/LabelNode>
-#include <osgEarthAnnotation/Decluttering>
 
 #include <osgEarth/Registry>
 #include <osgEarth/DepthOffset>
@@ -185,16 +184,6 @@ UTMGraticule::rebuild()
     _root = new DrapeableNode( getMapNode(), false );
     this->addChild( _root );
 
-#if 0
-    // set up depth offsetting.
-    osg::StateSet* s = _root->getOrCreateStateSet();
-    s->setAttributeAndModes( DepthOffsetUtils::getOrCreateProgram(), 1 );
-    s->addUniform( DepthOffsetUtils::getIsNotTextUniform() );
-    osg::Uniform* u = DepthOffsetUtils::createMinOffsetUniform();
-    u->set( 10000.0f );
-    s->addUniform( u );
-#endif
-
     // build the base Grid Zone Designator (GZD) loolup table. This is a table
     // that maps the GZD string to its extent.
     static std::string s_gzdRows( "CDEFGHJKLMNPQRSTUVWX" );
@@ -245,7 +234,7 @@ UTMGraticule::rebuild()
             _root->addChild( tile );
     }
 
-    DepthOffsetUtils::prepareGraph( _root );
+    //DepthOffsetUtils::prepareGraph( _root );
 }
 
 
diff --git a/src/osgEarthUtil/VerticalScale b/src/osgEarthUtil/VerticalScale
new file mode 100644
index 0000000..bd86cb6
--- /dev/null
+++ b/src/osgEarthUtil/VerticalScale
@@ -0,0 +1,65 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef OSGEARTHUTIL_VERTICAL_SCALE_H
+#define OSGEARTHUTIL_VERTICAL_SCALE_H
+
+#include <osgEarthUtil/Common>
+#include <osgEarth/TerrainEffect>
+#include <osg/Uniform>
+#include <osg/Node>
+#include <osg/observer_ptr>
+
+
+namespace osgEarth { namespace Util
+{
+    /**
+     * Terrain effect that scales the terrain height.
+     */
+    class OSGEARTHUTIL_EXPORT VerticalScale : public TerrainEffect
+    {
+    public:
+        /** construct a new vertical scaling controller */
+        VerticalScale();
+
+        /** Sets the scale factor. (1=default) */
+        void setScale( float value );
+        float getScale() const { return _scale.get(); }
+
+    public: // TerrainEffect interface
+
+        void onInstall(TerrainEngineNode* engine);
+        void onUninstall(TerrainEngineNode* engine);
+
+    public: // serialization
+
+        VerticalScale(const Config& conf);
+        void mergeConfig(const Config& conf);
+        virtual Config getConfig() const;
+
+    protected:
+        virtual ~VerticalScale() { }
+        void init();
+
+        optional<float>              _scale;
+        osg::ref_ptr<osg::Uniform>   _scaleUniform;
+    };
+
+} } // namespace osgEarth::Util
+
+#endif // OSGEARTHUTIL_VERTICAL_SCALE_H
diff --git a/src/osgEarthUtil/VerticalScale.cpp b/src/osgEarthUtil/VerticalScale.cpp
new file mode 100644
index 0000000..6d7b8bd
--- /dev/null
+++ b/src/osgEarthUtil/VerticalScale.cpp
@@ -0,0 +1,148 @@
+/* -*-c++-*- */
+/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program.  If not, see <http://www.gnu.org/licenses/>
+*/
+#include <osgEarthUtil/VerticalScale>
+#include <osgEarth/Registry>
+#include <osgEarth/Capabilities>
+#include <osgEarth/VirtualProgram>
+#include <osgEarth/TerrainEngineNode>
+
+#define LC "[VerticalScale] "
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+
+
+namespace
+{
+    // In the vertex shader, we use a vertex attribute that's genreated by the
+    // terrain engine. In this example it's called "oe_vertscale_attribs" 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 "up vector", the length of
+    // which is the elevation, in indexes[0,1,2]. The height value is in
+    // index[3].
+    //
+    // Here, we use the vertical scale uniform to move the vertex up or down
+    // along its extrusion vector.
+
+    const char* vs =
+        "#version " GLSL_VERSION_STR "\n"
+        GLSL_DEFAULT_PRECISION_FLOAT "\n"
+
+        "attribute vec4 oe_terrain_attr; \n"
+        "uniform float oe_vertscale_scale; \n"
+
+        "void oe_vertscale_vertex(inout vec4 VertexMODEL) \n"
+        "{ \n"
+        "    vec3  upVector  = oe_terrain_attr.xyz; \n"
+        "    float elev      = oe_terrain_attr.w; \n"
+        "    vec3  offset    = upVector * elev * (oe_vertscale_scale-1.0); \n"
+        "    VertexMODEL    += vec4(offset/VertexMODEL.w, 0.0); \n"
+        "} \n";
+}
+
+
+VerticalScale::VerticalScale() :
+TerrainEffect(),
+_scale       ( 1.0f )
+{
+    init();
+}
+
+VerticalScale::VerticalScale(const Config& conf) :
+TerrainEffect(),
+_scale       ( 1.0f )
+{
+    mergeConfig(conf);
+    init();
+}
+
+
+void
+VerticalScale::init()
+{
+    _scaleUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_vertscale_scale");
+    _scaleUniform->set( _scale.get() );
+}
+
+
+void
+VerticalScale::setScale(float scale)
+{
+    if ( scale != _scale.get() )
+    {
+        _scale = scale;
+        _scaleUniform->set( _scale.get() );
+    }
+}
+
+
+void
+VerticalScale::onInstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getOrCreateStateSet();
+
+        stateset->addUniform( _scaleUniform.get() );
+
+        VirtualProgram* vp = VirtualProgram::getOrCreate(stateset);
+        vp->setFunction( "oe_vertscale_vertex", vs, ShaderComp::LOCATION_VERTEX_MODEL );
+    }
+}
+
+
+void
+VerticalScale::onUninstall(TerrainEngineNode* engine)
+{
+    if ( engine )
+    {
+        osg::StateSet* stateset = engine->getStateSet();
+        if ( stateset )
+        {
+            stateset->removeUniform( _scaleUniform.get() );
+
+            VirtualProgram* vp = VirtualProgram::get(stateset);
+            if ( vp )
+            {
+                vp->removeShader( "oe_vertscale_vertex" );
+            }
+        }
+    }
+}
+
+
+
+//-------------------------------------------------------------
+
+
+void
+VerticalScale::mergeConfig(const Config& conf)
+{
+    conf.getIfSet( "scale", _scale );
+}
+
+Config
+VerticalScale::getConfig() const
+{
+    Config conf("vertical_scale");
+    conf.addIfSet( "scale", _scale );
+    return conf;
+}
diff --git a/src/osgEarthUtil/WFS.cpp b/src/osgEarthUtil/WFS.cpp
index bc78ecb..1d3f453 100644
--- a/src/osgEarthUtil/WFS.cpp
+++ b/src/osgEarthUtil/WFS.cpp
@@ -142,6 +142,13 @@ WFSCapabilitiesReader::read(std::istream &in)
                 featureType->setFirstLevel( as<int>(firstLevelStr, 0));
             }
 
+            // Read the SRS            
+            std::string srsText = e_featureType->getSubElementText(ELEM_SRS);
+            if (srsText.compare("") != 0)
+            {                
+                featureType->setSRS( srsText );                
+            }
+
             osg::ref_ptr<XmlElement> e_bb = e_featureType->getSubElement( ELEM_LATLONGBOUNDINGBOX );
             if (e_bb.valid())
             {
@@ -149,9 +156,9 @@ WFSCapabilitiesReader::read(std::istream &in)
                 minX = as<double>(e_bb->getAttr( ATTR_MINX ), 0);
                 minY = as<double>(e_bb->getAttr( ATTR_MINY ), 0);
                 maxX = as<double>(e_bb->getAttr( ATTR_MAXX ), 0);
-                maxY = as<double>(e_bb->getAttr( ATTR_MAXY ), 0);
-                featureType->setExtent( GeoExtent(SpatialReference::create( "epsg:4326"), minX, minY, maxX, maxY));
-            }
+                maxY = as<double>(e_bb->getAttr( ATTR_MAXY ), 0);                
+                featureType->setExtent( GeoExtent( osgEarth::SpatialReference::create( srsText ), minX, minY, maxX, maxY) );
+            }                       
 
             capabilities->getFeatureTypes().push_back( featureType );
         }        
diff --git a/tests/arcgisonline-utm.earth b/tests/arcgisonline-utm.earth
deleted file mode 100644
index 45a4232..0000000
--- a/tests/arcgisonline-utm.earth
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-osgEarth Sample
-
-This example shows how to pull map tiles from an ESRI ArcGIS Server.
-
-Please note that use of ESRI's free maps is subject to certain restrictions:
-http://resources.esri.com/arcgisonlineservices/index.cfm?fa=content
--->
-
-<map name="ESRI ArcGIS Online UTM" type="projected" version="2">
-    
-    <options>
-        <!--Specify the profile to use since we want to reproject to UTM-->        
-        <profile srs="+proj=utm +zone=17 +ellps=GRS80 +datum=NAD83 +units=m +no_defs" 
-                 xmin="560725.500" ymin="4385762.500" 
-                 xmax="573866.500" ymax="4400705.500"/>
-                 
-        <lighting>false</lighting>
-    </options>
-        
-    <image name="arcgis-world-imagery" driver="arcgis">
-        <url>http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer</url>
-		<nodata_image>http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/100/0/0.jpeg</nodata_image>
-    </image>
-      
-    <image name="arcgis-transportation" driver="arcgis">
-        <url>http://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer</url>
-    </image>
-    
-    <options>
-        <lighting>false</lighting>
-        <terrain>
-            <min_tile_range_factor>9</min_tile_range_factor>
-        </terrain>
-    </options>
-</map>
diff --git a/tests/arcgisonline.earth b/tests/arcgisonline.earth
index 2c23bdd..1cd13e9 100644
--- a/tests/arcgisonline.earth
+++ b/tests/arcgisonline.earth
@@ -10,22 +10,26 @@ http://resources.esri.com/arcgisonlineservices/index.cfm?fa=content
 <map name="ArcGIS Online" type="geocentric" version="2">
         
     <image name="arcgis-world-imagery" driver="arcgis">
-        <url>http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer</url>
-		<nodata_image>http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/100/0/0.jpeg</nodata_image>
+        <url>http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer</url>
+		<nodata_image>http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/100/0/0.jpeg</nodata_image>
+		<cache_policy usage="no_cache"/>
     </image>
       
-    <image name="arcgis-transportation" driver="arcgis">
+    <image name="arcgis-transportation" driver="arcgis" enabled="false">
         <url>http://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer</url>
+		<cache_policy usage="no_cache"/>
     </image>
     
-    <image name="arcgis-reference-overlay" driver="arcgis">
+    <image name="arcgis-reference-overlay" driver="arcgis" enabled="false">
         <url>http://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer</url>
+		<cache_policy usage="no_cache"/>
     </image>
     
     <options>
         <lighting>false</lighting>
-        <terrain>
-            <min_tile_range_factor>9</min_tile_range_factor>
-        </terrain>
     </options>
+	
+	<external>
+		<lod_blending/>
+	</external>
 </map>
diff --git a/tests/bing.earth b/tests/bing.earth
new file mode 100644
index 0000000..f8270c6
--- /dev/null
+++ b/tests/bing.earth
@@ -0,0 +1,24 @@
+<!--
+Mircosoft Bing Maps example.
+
+  Usage of Bing Maps requires a key. Get a key here:
+  http://www.bing.com/developers/
+
+  Bing Maps Terms of Service:
+  http://www.microsoft.com/maps/product/terms.html
+
+Supported imagery sets are Aerial, AerialWithLabels, and Road.
+-->
+
+<map>
+    <options>
+        <elevation_tile_size>5</elevation_tile_size>        
+        <terrain lighting="false" first_lod="1"/>
+    </options>
+    
+    <image driver="bing">
+        <key>your-api-key-here</key>
+        <imagery_set>AerialWithLabels</imagery_set>
+    </image>
+    
+</map>
diff --git a/tests/boston.earth b/tests/boston.earth
index 5a24ddf..80ffe5e 100644
--- a/tests/boston.earth
+++ b/tests/boston.earth
@@ -7,10 +7,9 @@ to extruded buildings.
 
 <map name="Boston Demo" type="geocentric" version="2">
     
-    <options>
-        <terrain sample_ratio="0.25" lighting="true"/>
-    </options>
-    
+    <options elevation_tile_size="15">
+        <terrain lighting="true"/>
+    </options>    
     
     <image name="mapquest_open_aerial" driver="xyz" enabled="false">
         <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
@@ -39,14 +38,9 @@ to extruded buildings.
             <build_spatial_index>true</build_spatial_index>
         </features>
         
+		<!--
         <feature_indexing/>
-        
-        <!--
-        <fading
-            duration="1.0"
-            max_range="20000"
-            attenuation_distance="2000"/>
-            -->
+		-->
         
         <!--
            The "layout" element activates tiling and paging of the feature set. If you
@@ -84,7 +78,6 @@ to extruded buildings.
                     altitude-clamping:       terrain;
                     altitude-technique:      map;
                     altitude-binding:        vertex;
-                    //altitude-resolution:     0.001;
                 }            
                 building-wall {
                     skin-library:     us_resources;
@@ -104,16 +97,12 @@ to extruded buildings.
     </model>
     
     
-    <model name="Streets" driver="feature_geom" enabled="true">
+    <model name="Streets" driver="feature_geom" enabled="false">
         <features name="streets" driver="ogr" build_spatial_index="true">
             <url>../data/boston-scl-utm19n-meters.shp</url>
             <resample max_length="25"/>
         </features>
         
-        <!--
-        <fading max_range="3500"/>
-        -->
-        
         <layout crop_features="true" tile_size_factor="7.5">
             <level max_range="5000"/>
         </layout>
@@ -132,7 +121,7 @@ to extruded buildings.
     </model>
     
     
-    <model name="Parks" driver="feature_geom" enabled="true">
+    <model name="Parks" driver="feature_geom" enabled="false">
         <features name="parks" driver="ogr" build_spatial_index="true">
             <url>../data/boston-parks.shp</url>
         </features>
@@ -148,7 +137,7 @@ to extruded buildings.
                 parks {
                    model:                  "../data/tree.ive";
                    model-placement:        random;
-                   model-density:          4000;
+                   model-density:          12000;
                    model-scale:            1.0;
                    altitude-clamping:      terrain;
                    altitude-technique:     map;
diff --git a/tests/byo.earth b/tests/byo.earth
deleted file mode 100644
index 44f0a8c..0000000
--- a/tests/byo.earth
+++ /dev/null
@@ -1,12 +0,0 @@
-<!--
-osgEarth Sample - "Bring Your Own" Terrain Engine.
--->
-<map name="byo" type="geocentric" version="2">
-
-	<options>
-		<terrain driver="byo">
-			<url>http://www.openscenegraph.org/data/earth_bayarea/earth.ive</url>
-		</terrain>
-	</options>
-    
-</map>
diff --git a/tests/cloudmade.earth b/tests/cloudmade.earth
deleted file mode 100644
index 8436b37..0000000
--- a/tests/cloudmade.earth
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- 
-osgEarth Sample - TMS
-
-Show how to use CloudMade's XYZ interface.
--->
-
-<map name="Cloudmade" type="geocentric" version="2">
-
-    <image driver="gdal" name="world-tiff">
-        <url>../data/world.tif</url>
-    </image>
-
-   <image name="osm-cloudmade-35117" driver="xyz">
-       <url>http://[abc].tile.cloudmade.com/f10ecb40ab2159639c5f2d4b9a273f1d/35117/256/{z}/{x}/{y}.png</url>
-       <profile>global-mercator</profile>
-   </image> 
-
-   <options>   
-       <lighting>false</lighting>
-   </options>
-   
-</map>
diff --git a/tests/readymap.earth b/tests/detail_texture.earth
similarity index 72%
copy from tests/readymap.earth
copy to tests/detail_texture.earth
index 82bdc7b..c95a71c 100644
--- a/tests/readymap.earth
+++ b/tests/detail_texture.earth
@@ -12,22 +12,24 @@ http://readymap.org
 
 -->
 <map name="readymap.org" type="geocentric" version="2">
-
-    <image name="readymap_imagery" driver="tms">
+    
+    <options>
+        <elevation_tile_size>15</elevation_tile_size>
+        <terrain lighting="true" first_lod="1" min_lod="19"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-        <color_filters>
-            <gamma rgb="1.3"/>
-        </color_filters>
     </image>
         
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-    
-    <options>
-		<lighting>false</lighting>
-		<elevation_tile_size>8</elevation_tile_size>
-        <terrain normalize_edges="true" first_lod="1"/>
-    </options>
+	
+	<external>
+	    <detail_texture>
+			<image>../data/noise3.png</image>
+		</detail_texture>
+	</external>
     
 </map>
diff --git a/tests/feature_overlay.earth b/tests/feature_draped_lines.earth
similarity index 81%
copy from tests/feature_overlay.earth
copy to tests/feature_draped_lines.earth
index ff2de35..09daf6b 100644
--- a/tests/feature_overlay.earth
+++ b/tests/feature_draped_lines.earth
@@ -8,10 +8,12 @@ Demonstrate the Altitude Symbol and GPU-based vertex clamping.
   
     <options>
         <lighting>false</lighting>
+        <terrain min_lod="16"/>
     </options>
 
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
+        <cache_policy usage="no_cache"/>
     </image>
     
     <model name="world_boundaries" driver="feature_geom">
@@ -21,14 +23,15 @@ Demonstrate the Altitude Symbol and GPU-based vertex clamping.
             <url>../data/world.shp</url>
             <build_spatial_index>true</build_spatial_index>
         </features>
+		
+		<feature_indexing/>	
                 
         <styles>
             <style type="text/css">
                 world {
                    stroke:             #ffff00;
-                   stroke-opacity:     1.0;
-                   stroke-width:       3.0;
-                   altitude-clamping:  terrain-gpu;
+                   stroke-width:       5px;
+                   altitude-clamping:  terrain-drape;
                 }            
             </style>
         </styles>
diff --git a/tests/feature_overlay_polys.earth b/tests/feature_draped_polygons.earth
similarity index 98%
rename from tests/feature_overlay_polys.earth
rename to tests/feature_draped_polygons.earth
index 663d968..c9b1ab0 100644
--- a/tests/feature_overlay_polys.earth
+++ b/tests/feature_draped_polygons.earth
@@ -10,6 +10,7 @@ on the map using the overlay technique.
     <options>
         <lighting>false</lighting>
         <overlay_blending>false</overlay_blending>
+		<terrain min_lod="8"/>
     </options>
 
     <image name="world" driver="gdal">
diff --git a/tests/feature_geom.earth b/tests/feature_geom.earth
index 698c844..538fafe 100644
--- a/tests/feature_geom.earth
+++ b/tests/feature_geom.earth
@@ -7,6 +7,9 @@ OSG geometry out of it.
 
 <map name="Feature Geometry Demo" type="geocentric" version="2">
     
+	<options lighting="false">
+	</options>
+	
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
     </image>
@@ -14,10 +17,10 @@ OSG geometry out of it.
     <model name="states" driver="feature_geom">
 
         <!-- Configure the OGR feature driver to read the shapefile -->
-        <features name="states" driver="ogr">
+        <features name="world" driver="ogr">
             <url>../data/usa.shp</url>
         </features>
-        
+		
         <!-- Ensure a vertex at least every 5 degrees -->
         <max_granularity>5.0</max_granularity>
         
@@ -25,13 +28,12 @@ OSG geometry out of it.
         <styles>
             <style type="text/css">
                 states {
-                   stroke: #ffff00;
-                   altitude-offset: 1000;                   
+                   stroke:          #ffff00;
+                   altitude-offset: 1000;      
+				   render-lighting: false;
                 }                    
             </style>
         </styles>
-		
-		<lighting>false</lighting>
         
     </model>
   
diff --git a/tests/feature_inline.earth b/tests/feature_inline_geometry.earth
similarity index 89%
rename from tests/feature_inline.earth
rename to tests/feature_inline_geometry.earth
index 39b311e..31abb2c 100644
--- a/tests/feature_inline.earth
+++ b/tests/feature_inline_geometry.earth
@@ -24,7 +24,7 @@ display. The options are:
     </image>
     
     
-    <model name="great_circle" driver="feature_geom" overlay="true">
+    <model name="great_circle" driver="feature_geom">
 	    <features driver="ogr">
 			<geometry>
 				POLYGON((-120 30, -120 50, -70 50, -70 30))
@@ -37,13 +37,14 @@ display. The options are:
             <style type="text/css">
                 default {
                    fill: #ffff006f;
+				   altitude-clamping: terrain-drape;
                 }                    
             </style>
         </styles>
         
     </model>
     
-    <model name="rhumb_line" driver="feature_geom" overlay="true">
+    <model name="rhumb_line" driver="feature_geom">
 	    <features driver="ogr">
 			<geometry>
 				POLYGON((-68 30, -68 50, -20 50, -20 30))
@@ -56,6 +57,7 @@ display. The options are:
             <style type="text/css">
                 default {
                    fill: #ff00ff6f;
+				   altitude-clamping: terrain-drape;
                 }                    
             </style>
         </styles>
diff --git a/tests/feature_labels.earth b/tests/feature_labels.earth
index 4e40b16..3bf09d9 100644
--- a/tests/feature_labels.earth
+++ b/tests/feature_labels.earth
@@ -18,12 +18,16 @@ This shows how to label point features with an attribute.
         <styles>
             <style type="text/css">              
                 cities {
-                   text-provider:  annotation;
-                   text-content:   [cntry_name];
-                   text-priority:  [pop_cntry];
-                   text-halo:      #3f3f7f;
-                   text-align:     center_center;
-                   text-declutter: true;
+					icon: "../data/placemark32.png";
+					icon-placement: centroid;
+					icon-scale: 1.0;
+					icon-occlusion-cull: false;
+					icon-occlusion-cull-altitude: 8000;
+					icon-declutter: true;
+					text-content: [cntry_name];
+					altitude-offset: 100;
+					altitude-clamping: terrain;
+					altitude-technique: scene;
                 }     
             </style>
         </styles>
diff --git a/tests/feature_model_scatter.earth b/tests/feature_model_scatter.earth
index 088a137..1114deb 100644
--- a/tests/feature_model_scatter.earth
+++ b/tests/feature_model_scatter.earth
@@ -32,7 +32,7 @@ is randomized, but it is randomized exactly the same way each time.
         <feature_indexing>false</feature_indexing>
         
         <!-- Fade in new tiles over one second. -->
-         <fade_in_duration>1.0</fade_in_duration>
+        <!-- <fade_in_duration>1.0</fade_in_duration> -->
         
         <!-- The stylesheet will describe how to render the feature data. In this case
              we indicate model substitution with density-based scattering. The "density"
@@ -51,7 +51,7 @@ is randomized, but it is randomized exactly the same way each time.
                    model:               "../data/tree.ive";
                    model-placement:     random;
                    model-density:       1000;
-                   model-scale:         1.5;
+                   model-scale:         2.0;
                    altitude-clamping:   terrain;
                    altitude-resolution: 0.001;
                 }            
@@ -59,7 +59,7 @@ is randomized, but it is randomized exactly the same way each time.
                    model:               "../data/tree.ive";
                    model-placement:     random;
                    model-density:       2500;
-                   model-scale:         2;
+                   model-scale:         2.5;
                    model-random-seed:   1;
                    altitude-clamping:   terrain;
                    altitude-resolution: 0.001;
@@ -68,7 +68,7 @@ is randomized, but it is randomized exactly the same way each time.
                    model:               "../data/tree.ive";
                    model-placement:     random;
                    model-density:       5000;
-                   model-scale:         2.5;
+                   model-scale:         1.0
                    model-random-seed:   2;
                    altitude-clamping:   terrain;
                    altitude-resolution: 0.001;
@@ -78,7 +78,7 @@ is randomized, but it is randomized exactly the same way each time.
     </model>
     
     
-    <model name="parks" driver="feature_geom" overlay="true">
+    <model name="parks" driver="feature_geom" enabled="false">
         <features name="parks" driver="ogr">
             <url>../data/parks.shp</url>
             <build_spatial_index>false</build_spatial_index>
@@ -87,8 +87,7 @@ is randomized, but it is randomized exactly the same way each time.
             <style type="text/css">      
                 default {
                    fill:                #00ff007f;
-                   stroke:              #ffff00ff;
-                   altitide-clamping:   terrain;
+                   altitide-clamping:   terrain-drape;
                 }                
             </style>
         </styles>
@@ -97,16 +96,13 @@ is randomized, but it is randomized exactly the same way each time.
     
     <options>
         <lighting>false</lighting>
-        <cache type="filesystem">
-            <path>osgearth_cache</path>
-        </cache>
     </options> 
     
     <image name="mapquest_open_aerial" driver="xyz">
         <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
         <profile>spherical-mercator</profile>
         <cache_policy usage="no_cache"/>
-        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/18/49761/99026.jpg</nodata_image>
+        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
     </image>
     
     <elevation name="ReadyMap.org - Elevation" driver="tms">
diff --git a/tests/feature_models.earth b/tests/feature_models.earth
index 242badc..af0c7b9 100644
--- a/tests/feature_models.earth
+++ b/tests/feature_models.earth
@@ -1,11 +1,7 @@
-<!--
-osgEarth Sample
+<map name="Model Auto-Scale Demo" type="geocentric" version="2">
 
-Shows how to use point model substitution with a model encoded in the shapefile.
-
--->
-
-<map name="Model Demo" type="geocentric" version="2">
+    <options lighting="false">
+    </options>
       
     <!-- Our features layer. The "feature_geom" driver will analyze the
          style sheet and determine how to render the feature data. -->
@@ -20,29 +16,21 @@ Shows how to use point model substitution with a model encoded in the shapefile.
         <styles>
             <style type="text/css">
                 points {
-                   model:               [model];
-                   model-scale:         200;    
+                   model:               "../data/red_flag.osg.25.scale";
+                   model-scale:         auto;    
                    altitude-clamping:   terrain;
-                   altitude-offset:     500;				   
                 }                                            
             </style>
         </styles>   
         
     </model>
     
-    
-    <options>
-		<osg_file_paths>
-		    <url>../data</url>
-		</osg_file_paths>
-    </options> 
-    
     <image name="ReadyMap.org - Imagery" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
 	
     <external>	  
-        <viewpoint name="Models" heading="0" height="25.83" long="-81.124351" lat="25.426836 " pitch="-89.9" range="28262"/>        
+        <viewpoint name="Models" lat="25.311" long="-80.807" pitch="-21" range="177351"/>        
     </external>
   
-</map>
+</map>
\ No newline at end of file
diff --git a/tests/feature_occlusion_culling.earth b/tests/feature_occlusion_culling.earth
new file mode 100644
index 0000000..4c89cca
--- /dev/null
+++ b/tests/feature_occlusion_culling.earth
@@ -0,0 +1,51 @@
+<!--
+osgEarth Sample - Feature Occlusion Culling
+
+Demonstrates occlusion culling on feature labels.
+-->
+<map name="occlusion culling" type="geocentric" version="2">
+        
+    <image name="readymap_imagery" driver="tms" visible="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+			
+	<model name="cities" driver="feature_geom">
+
+        <features name="cities" driver="ogr">
+            <url>../data/world.shp</url>
+        </features>
+
+        <styles>
+            <style type="text/css">              
+                cities {                   			
+                   text-provider:       annotation;
+                   text-content:        [cntry_name];
+                   text-priority:       [pop_cntry];
+                   text-halo:           #3f3f7f;
+                   text-align:          center_center;
+                   text-declutter:      true;
+				   text-occlusion-cull: true;	                   
+				   altitude-clamping:   terrain;	                   				   				   
+				   altitude-technique:  scene;
+                }     
+            </style>
+        </styles>        
+    </model>
+		
+    <options lighting="false"/>
+    
+    <external>
+        <decluttering>
+            <out_animation_time>  0.0  </out_animation_time>
+            <in_animation_time>   0.25 </in_animation_time>
+            <min_animation_scale> 0.45 </min_animation_scale>
+            <min_animation_alpha> 0.35 </min_animation_alpha>
+            <sort_by_priority>    true </sort_by_priority>
+        </decluttering>
+    </external>
+    
+</map>
diff --git a/tests/feature_overlay.earth b/tests/feature_overlay.earth
index ff2de35..a2293e5 100644
--- a/tests/feature_overlay.earth
+++ b/tests/feature_overlay.earth
@@ -1,7 +1,6 @@
 <!--
 osgEarth Sample
-
-Demonstrate the Altitude Symbol and GPU-based vertex clamping.
+Drawing simple lines at a set altitude.
 -->
 
 <map name="Geometry Rasterizer Demo" type="round" version="2">
@@ -12,6 +11,7 @@ Demonstrate the Altitude Symbol and GPU-based vertex clamping.
 
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
+        <cache_policy usage="no_cache"/>
     </image>
     
     <model name="world_boundaries" driver="feature_geom">
@@ -28,7 +28,7 @@ Demonstrate the Altitude Symbol and GPU-based vertex clamping.
                    stroke:             #ffff00;
                    stroke-opacity:     1.0;
                    stroke-width:       3.0;
-                   altitude-clamping:  terrain-gpu;
+				   altitude-offset:    1000.0;
                 }            
             </style>
         </styles>
diff --git a/tests/feature_rasterize.earth b/tests/feature_rasterize.earth
index 5fe3be6..0f9a87b 100644
--- a/tests/feature_rasterize.earth
+++ b/tests/feature_rasterize.earth
@@ -4,38 +4,40 @@ Demonstrates use of the "agglite" feature rasterization driver.
 -->
 
 <map name="Geometry Rasterizer Demo" type="round" version="2">
-  
-    <options lighting="false"/>
+
+    <options lighting="false">
+        <terrain first_lod="1">
+        </terrain>
+    </options>
+
+    <external>
+        <lod_blending/>
+    </external>
 
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
+        <cache_policy usage="no_cache"/>
     </image>
     
-    <image name="world_boundaries" driver="agglite">
+    <image name="world_boundaries" driver="agglite" opacity="0.5">
 
-        <!-- Configure the OGR feature driver to read the shapefile. -->             
+        <!-- Configure the OGR feature driver to read the shapefile. -->
         <features name="world" driver="ogr">
             <url>../data/world.shp</url>
             <build_spatial_index>true</build_spatial_index>
         </features>
-		
-        <styles>
-			<selector class="outline"/>
-			<selector class="line"/>
-		
+        
+        <styles>        
             <style type="text/css">
-				outline {
-					stroke:		   #ffffff;
-					stroke-width:  5px;
-				}
-                line {
-                   stroke:         #ff0000;
-                   stroke-width:   2.5px;
-                }            
+                default {
+					fill:          #ff7700;
+                    stroke:		   #ffff00;
+                    stroke-width:  1px;
+                }
             </style>
         </styles>
-		
-		<cache_policy usage="no_cache"/>
+        
+        <cache_policy usage="no_cache"/>
         
     </image>
   
diff --git a/tests/feature_rasterize_2.earth b/tests/feature_rasterize_2.earth
index faefc6f..84eab4d 100644
--- a/tests/feature_rasterize_2.earth
+++ b/tests/feature_rasterize_2.earth
@@ -5,7 +5,7 @@ This demonstrates the "agglite" feature rasterizing driver. It "drapes" vector d
 rasterizing it into image tiles.
 -->
 
-<map name="AGGLite Sample" type="geocentric" version="2">
+<map name="Rasterization Example" type="geocentric" version="2">
   
     <options>
         <lighting>false</lighting>
@@ -60,4 +60,8 @@ rasterizing it into image tiles.
         </styles>
         
     </image>
+	
+	<external>
+		<lod_blending/>
+	</external>
 </map>
diff --git a/tests/feature_scripted_styling.earth b/tests/feature_scripted_styling.earth
index e78b9cd..c32f59f 100644
--- a/tests/feature_scripted_styling.earth
+++ b/tests/feature_scripted_styling.earth
@@ -16,7 +16,7 @@ and a custom selector.
         <url>../data/world.tif</url>
     </image>
     
-    <model name="countries" driver="feature_geom" overlay="true">
+    <model name="countries" driver="feature_geom">
                           
         <features name="states" driver="ogr">
             <url>../data/world.shp</url>
@@ -40,7 +40,7 @@ and a custom selector.
                         var r = Math.floor(Math.random()*255);
                         var g = Math.floor(Math.random()*255);
                         var b = Math.floor(Math.random()*255);
-                        g_styles.push( "{fill:#"+hex(r)+hex(g)+hex(b)+"6f;}" );
+                        g_styles.push( "{fill:#"+hex(r)+hex(g)+hex(b)+"6f; altitude-clamping:terrain-drape;}" );
                     }
                 }
                 initialize();
diff --git a/tests/feature_scripted_styling_2.earth b/tests/feature_scripted_styling_2.earth
index 2602096..48e3fc7 100644
--- a/tests/feature_scripted_styling_2.earth
+++ b/tests/feature_scripted_styling_2.earth
@@ -15,7 +15,7 @@ Demonstrates how to select a style name using javascript.
         <url>../data/world.tif</url>
     </image>
     
-    <model name="countries" driver="feature_geom" overlay="true">
+    <model name="countries" driver="feature_geom">
                           
         <features name="states" driver="ogr">
             <url>../data/world.shp</url>
@@ -24,11 +24,11 @@ Demonstrates how to select a style name using javascript.
         
         <styles>        
             <style type="text/css">
-                p1 { fill: #ffff8066; }       
-                p2 { fill: #80ffff66; }   
-                p3 { fill: #ff80ff66; }       
-                p4 { fill: #ff808066; }     
-                p5 { fill: #80ff8066; }                                      
+                p1 { fill: #ffff8066; altitude-clamping: terrain-drape; }
+                p2 { fill: #80ffff66; altitude-clamping: terrain-drape; }
+                p3 { fill: #ff80ff66; altitude-clamping: terrain-drape; }
+                p4 { fill: #ff808066; altitude-clamping: terrain-drape; }
+                p5 { fill: #80ff8066; altitude-clamping: terrain-drape; }
             </style>
             
             <script language="javascript">
diff --git a/tests/feature_scripting.earth b/tests/feature_scripting.earth
index 4ec54b8..c278666 100644
--- a/tests/feature_scripting.earth
+++ b/tests/feature_scripting.earth
@@ -71,7 +71,6 @@ This example demonstrates the use of scripting to style features.
                    text-font:     arial.ttf;
                    text-size:     16;
                    text-remove-duplicate-labels: true;
-                   altitude-clamping: terrain;
                 }     
             </style>
         </styles>
diff --git a/tests/feature_scripted_styling_2.earth b/tests/feature_style_selector.earth
similarity index 57%
copy from tests/feature_scripted_styling_2.earth
copy to tests/feature_style_selector.earth
index 2602096..6ffa0dc 100644
--- a/tests/feature_scripted_styling_2.earth
+++ b/tests/feature_style_selector.earth
@@ -15,7 +15,7 @@ Demonstrates how to select a style name using javascript.
         <url>../data/world.tif</url>
     </image>
     
-    <model name="countries" driver="feature_geom" overlay="true">
+    <model name="countries" driver="feature_geom">
                           
         <features name="states" driver="ogr">
             <url>../data/world.shp</url>
@@ -24,18 +24,27 @@ Demonstrates how to select a style name using javascript.
         
         <styles>        
             <style type="text/css">
-                p1 { fill: #ffff8066; }       
-                p2 { fill: #80ffff66; }   
-                p3 { fill: #ff80ff66; }       
-                p4 { fill: #ff808066; }     
-                p5 { fill: #80ff8066; }                                      
+                p1 { fill: #ffffff9f; altitude-clamping: terrain-drape; }       
+                p2 { fill: #cccccc9f; altitude-clamping: terrain-drape; }       
+                p3 { fill: #9999999f; altitude-clamping: terrain-drape; }       
+                p4 { fill: #6666669f; altitude-clamping: terrain-drape; }       
+                p5 { fill: #3333339f; altitude-clamping: terrain-drape; }       
             </style>
             
             <script language="javascript">
             <![CDATA[
                 function getStyleClass()
                 {
-                    var pop = parseInt(feature.attributes['pop_cntry']);
+					// Exclude any countries beginning with the letter A: 
+					if ( feature.attributes['cntry_name'].charAt(0) === 'A' )
+						return null;
+						
+					// If it starts with the letter C, return an inline style:
+					if ( feature.attributes['cntry_name'].charAt(0) == 'C' )
+						return '{ fill: #ffc8389f; }';
+						
+					// Otherwise, color by population and return a stylesheet style:
+                    var pop = parseFloat(feature.attributes['pop_cntry']);
                     if      ( pop <= 14045470 )  return "p1";
                     else if ( pop <= 43410900 )  return "p2";
                     else if ( pop <= 97228750 )  return "p3";
diff --git a/tests/feature_tfs.earth b/tests/feature_tfs.earth
index 27411ec..a55d04e 100644
--- a/tests/feature_tfs.earth
+++ b/tests/feature_tfs.earth
@@ -23,11 +23,10 @@ This example shows how to use the TFS driver.
                     extrusion-flatten: true;
                     fill:              #ff7f2f;
                     altitude-clamping: terrain;
+					render-lighting:   true;
                 }            
             </style>
-        </styles>  
-        
-        <lighting>true</lighting>
+        </styles>
     </model>
                     
  
diff --git a/tests/feature_wfs.earth b/tests/feature_wfs.earth
index ffc57e9..6ee166f 100644
--- a/tests/feature_wfs.earth
+++ b/tests/feature_wfs.earth
@@ -12,7 +12,7 @@ This one demonstrates how to read feature data from a WFS server
         <url>../data/world.tif</url>
     </image>
     
-    <overlay name="states" driver="feature_geom">          
+    <model name="states" driver="feature_geom">          
                 
         <features name="states" driver="wfs">
             <url> http://demo.opengeo.org/geoserver/wfs</url>     		
@@ -23,13 +23,14 @@ This one demonstrates how to read feature data from a WFS server
         <styles>        
             <style type="text/css">
                 states {
-                   stroke:          #ffff00;
-                   stroke-opacity:  1.0;
-                   stroke-width:    3.0;
+                   stroke:            #ffff00;
+                   stroke-opacity:    1.0;
+                   stroke-width:      3.0;
+				   altitude-clamping: terrain-drape;
                 }                       
             </style>                   
             
         </styles>
         
-    </overlay>
+    </model>
 </map>
diff --git a/tests/fractal_detail.earth b/tests/fractal_detail.earth
new file mode 100644
index 0000000..f9baa89
--- /dev/null
+++ b/tests/fractal_detail.earth
@@ -0,0 +1,58 @@
+<!-- 
+osgEarth Sample - Fractal detail
+
+Uses the noise driver to add fractal detail to the terrain,
+making it appear higher resolution than it really is.
+This can be useful for presentation purposes when you don't
+have very high resolution data.
+-->
+
+<map version="2">
+    
+    <!--
+    Start with the ReadyMap elevation as our basis. 
+    -->
+    <elevation name="readymap_elevation" driver="tms" enabled="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+    
+	
+	<!--
+	Set the frequency to be the reciprocal of your elevation resolution.
+	i.e., if your elevation data is 90m, set freq = 1/90. That will cause
+	the noise function to cycle every 90m. This means you will only get
+	noise between your real elevation values.
+	
+	Within that 90m span, the noise function will generate values between
+	[0..scale]. So if the scale=10, you will get up to a +/-10m variation in
+	elevation at each interpolated point eithin the 90m span.
+	
+	Octaves controls the recursion of this operation. The noise function will
+	run once as normal, then again to further refine the results of the first
+	output, and so on.
+	
+	With each succesive octave, the output is scaled like this:
+	freq *= lacunarity, and scale *= persistence. This happens successivly for
+	each octave.
+	-->
+    <elevation driver="noise" name="noise" enabled="true" offset="true">
+		<tile_size>31</tile_size>
+		<frequency>0.004</frequency>	   <!-- one cycle every 250m -->
+		<lacunarity>2.0</lacunarity>      <!-- freq *= lac for each octave (default = 2) -->
+		<persistence>0.5</persistence>    <!-- amp *= pers for each octave (default = 0.5) -->
+		<octaves>4</octaves>
+		<scale>20</scale>
+		<bias>0</bias>
+	</elevation>
+	
+
+    <options>
+       <elevation_tile_size>31</elevation_tile_size>
+	   <elevation_interpolation>bilinear</elevation_interpolation>
+	   <terrain min_lod="16"/>
+    </options>
+
+    <external>
+        <contour_map/>
+    </external>
+</map>
diff --git a/tests/glsl_filter.earth b/tests/glsl_filter.earth
index 6f8b65d..3ac7377 100644
--- a/tests/glsl_filter.earth
+++ b/tests/glsl_filter.earth
@@ -27,8 +27,9 @@ http://readymap.org
     </elevation>
     
     <options>
-		<elevation_tile_size>8</elevation_tile_size>
-        <terrain normalize_edges="true" first_lod="2"/>
+		<lighting>false</lighting>
+		<elevation_tile_size>7</elevation_tile_size>
+        <terrain first_lod="1"/>
     </options>
     
 </map>
diff --git a/tests/hires-inset.earth b/tests/hires-inset.earth
index 046b063..b9cfce2 100644
--- a/tests/hires-inset.earth
+++ b/tests/hires-inset.earth
@@ -1,8 +1,8 @@
 <!--
 osgEarth Sample : Hi-resolution inset
 
-This example shows how to use a global basemap and superimpose a high-
-resolution imagery inset from a local GeoTIFF file.
+This example shows how to use a global basemap and superimpose
+high-resolution imagery insets.
 
 Look for hi-res insets over the cities of Boston and New York.
 -->
@@ -13,20 +13,24 @@ Look for hi-res insets over the cities of Boston and New York.
         <cache_policy usage="no_cache"/>        
     </options>
     
+	<!-- Low resolution worldwide image -->
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
     </image>
 
-    <image name="hires insets" driver="composite">
-    
-        <image name="boston_inset" driver="gdal">
-            <url>../data/boston-inset-wgs84.tif</url>
-        </image>
-        
-        <image name="nyc_inset" driver="gdal">
-            <url>../data/nyc-inset-wgs84.tif</url>
-        </image>
-        
-    </image>
+	<!-- Higher resolution inset of Boston -->
+	<image name="boston_inset" driver="gdal">
+		<url>../data/boston-inset-wgs84.tif</url>
+	</image>
+	
+	<!-- Higher resolution inset of New York City -->
+	<image name="nyc_inset" driver="gdal">
+		<url>../data/nyc-inset-wgs84.tif</url>
+	</image>
     
+	<external>
+	    <viewpoints>
+		    <viewpoint name="Hi-res insets" lat="41.642" long="-72.333" pitch="-71" range="657510"/>
+		</viewpoints>
+	</external>
 </map>
\ No newline at end of file
diff --git a/tests/shadows.earth b/tests/lod_blending.earth
similarity index 50%
rename from tests/shadows.earth
rename to tests/lod_blending.earth
index fb2a188..51da94b 100644
--- a/tests/shadows.earth
+++ b/tests/lod_blending.earth
@@ -1,8 +1,5 @@
 <!--
-osgEarth Sample - ReadyMap Shadows - http://readymap.org
-
-Run with the osgearth_shadow example.  Shows how to set the nodemasks separately for the terrain skirts and surface
-so that skirts don't cast shadows to avoid visual artifacts.
+osgEarth Sample - ReadyMap.ORG Server - http://readymap.org
 
 ReadyMap.ORG provides free global base map data for osgEarth developers!
 This tiled, worldwide dataset of imagery, elevation, and street map data
@@ -14,26 +11,28 @@ YOU ARE RESPONSIBLE for abiding by the TERMS AND CONDITIONS outlined at:
 http://readymap.org
 
 -->
-<map name="shadows" type="geocentric" version="2">
-
-    <image name="readymap_imagery" driver="tms">
+<map name="readymap.org" type="geocentric" version="2">
+    
+    <options>
+        <!-- elevation_tile_size must be an odd number for morphing. -->
+        <elevation_tile_size>15</elevation_tile_size>
+        
+        <!-- extend the min_lod so we can see MORE morphing. -->
+        <terrain first_lod="1" min_lod="19"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
     </image>
         
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
-	
-	<options>
-	  <terrain>
-	    <surface_node_mask>1</surface_node_mask>
-	    <skirt_node_mask>2</skirt_node_mask>
-	  </terrain>
-	</options>
-       
+    
     <external>
-        <viewpoints>
-            <viewpoint name="Mt Rainier" heading="0" height="97.48" lat="46.852" long="-121.759" pitch="-17" range="30000"/>
-        </viewpoints>
+        <lod_blending>
+            <duration>1.0</duration>
+        </lod_blending>
     </external>
+    
 </map>
diff --git a/tests/mapquest_open_aerial.earth b/tests/mapquest_open_aerial.earth
index 2fb09f7..15d6b96 100644
--- a/tests/mapquest_open_aerial.earth
+++ b/tests/mapquest_open_aerial.earth
@@ -20,9 +20,6 @@ TIP: set your OSG_NUM_HTTP_DATABASE_THREADS to 4 or more!
     
     <options>
         <lighting>false</lighting>
-        <terrain>
-            <min_tile_range_factor>9</min_tile_range_factor>
-        </terrain>
     </options>
     
 </map>
diff --git a/tests/mapquest_open_aerial.earth b/tests/mapquest_with_srtm.earth
similarity index 76%
copy from tests/mapquest_open_aerial.earth
copy to tests/mapquest_with_srtm.earth
index 2fb09f7..79580e7 100644
--- a/tests/mapquest_open_aerial.earth
+++ b/tests/mapquest_with_srtm.earth
@@ -17,11 +17,13 @@ TIP: set your OSG_NUM_HTTP_DATABASE_THREADS to 4 or more!
         <cache_policy usage="no_cache"/>
         <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
     </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
     
-    <options>
-        <lighting>false</lighting>
-        <terrain>
-            <min_tile_range_factor>9</min_tile_range_factor>
+    <options elevation_tile_size="9">
+        <terrain min_tile_range_factor="8">
         </terrain>
     </options>
     
diff --git a/tests/mask.earth b/tests/mask.earth
index d1d40b7..120bac3 100644
--- a/tests/mask.earth
+++ b/tests/mask.earth
@@ -18,5 +18,7 @@ Demonstrates the use a a MaskLayer to cut out an area of the globe.
         <profile>global-geodetic</profile>
     </mask>
     
-    <options lighting="false"/>
+    <options lighting="false">
+	    <terrain min_lod="14"/>
+	</options>
 </map>
diff --git a/tests/mb_tiles.earth b/tests/mb_tiles.earth
index d04007a..d224c34 100644
--- a/tests/mb_tiles.earth
+++ b/tests/mb_tiles.earth
@@ -5,12 +5,16 @@ This example shows how to access an MBTiles dataset.  The MBTiles datasets are v
 -->
 
 <map name="MBTiles" type="geocentric" version="2">    
-   
+      
+      <!--
    <image driver="gdal" name="world-tiff">
         <url>../data/world.tif</url>
-    </image>	
+    </image>	   
+    -->
 	  
-    <!--Add the haiti mbtiles dataset.  You may need to change the path to point to your download location-->	  
+    <!--
+    Add the haiti mbtiles dataset available at http://maps.internews.eu/v2/haiti-terrain-grey.mbtiles.
+    You may need to change the path to point to your download location-->	  
     <image name="haiti" driver="mbtiles">
         <filename>../data/haiti-terrain-grey.mbtiles</filename>
 		<format>jpg</format>
diff --git a/tests/min_max_resolutions.earth b/tests/min_max_resolutions.earth
index f674a42..8ac4c4c 100644
--- a/tests/min_max_resolutions.earth
+++ b/tests/min_max_resolutions.earth
@@ -1,25 +1,27 @@
 <!--
 This example demonstrates how to use two image sources and switch between them
 at a given resolution level.
-
-Please note that usage of Yahoo! map data is subject to Yahoo!'s terms of service.
 -->
 
-<map name="Yahoo Levels" type="geocentric" version="2">
-
-    <image name="yahoo" driver="composite">
+<map name="Min Max Resolution Levels" type="geocentric" version="2">
     
-        <!-- this level will be visible at lower resolutions -->
-        <image name="yahoo_sat" driver="yahoo">
-            <dataset>satellite</dataset>            
-			<max_resolution>4891</max_resolution>
-        </image> 
+	<!-- this level will be visible at lower resolutions -->
+	<image name="mapquest_aerial" driver="xyz">
+		<url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
+		<profile>spherical-mercator</profile>
+		<cache_policy usage="no_cache"/>
+		<nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>      
+		<min_resolution>7500</min_resolution>
+	</image> 
 
-        <!-- this level will be visible at higher resolutions -->
-        <image name="yahoo_maps" driver="yahoo">            
-			<min_resolution>2445</min_resolution>
-        </image> 
+	<!-- this level will be visible at higher resolutions -->
+	<image name="mapquest_osm" driver="xyz">
+		<url>http://otile[1234].mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg</url>
+		<profile>spherical-mercator</profile>       
+		<cache_policy usage="no_cache"/>
+		<max_resolution>2500</max_resolution>
+	</image>
+	
+	<options lighting="false"/>
         
-    </image>
-   
 </map>
diff --git a/tests/multiple_heightfields.earth b/tests/multiple_heightfields.earth
index f8d68af..f438a59 100644
--- a/tests/multiple_heightfields.earth
+++ b/tests/multiple_heightfields.earth
@@ -9,28 +9,30 @@ lo-res heightfield underlay.
 -->
 
 <map type="geocentric" version="2">
-      
-    <image name="arcgisonline esri imagery" driver="arcgis">
-        <url>http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer</url>
+
+    <image driver="gdal" name="world-tiff">
+        <url>../data/world.tif</url>
     </image>
 
-    <heightfield name = "mt_fuji" driver = "gdal">
+    <elevation name = "mt_fuji" driver = "gdal">
       <url>..\data\terrain\mt_fuji_90m.tif</url>
-    </heightfield>
+    </elevation>
 
-    <heightfield name = "mt_rainier" driver = "gdal">
+    <elevation name = "mt_rainier" driver = "gdal">
       <url>..\data\terrain\mt_rainier_90m.tif</url>
-    </heightfield>
+    </elevation>
 
-    <heightfield name = "mt_everest" driver = "gdal">
+    <elevation name = "mt_everest" driver = "gdal">
       <url>..\data\terrain\mt_everest_90m.tif</url>
-     </heightfield>
+    </elevation>
 
     <options>
         <lighting>false</lighting>
-        <terrain>
-            <vertical_scale>1.5</vertical_scale>
-        </terrain>
+        <cache_policy usage="no_cache"/>
     </options>
     
+    <external>
+        <contour_map opacity="0.35"/>
+        <vertical_scale scale="2.0"/>
+    </external>
 </map>
\ No newline at end of file
diff --git a/tests/noise.earth b/tests/noise.earth
new file mode 100644
index 0000000..8ae330c
--- /dev/null
+++ b/tests/noise.earth
@@ -0,0 +1,27 @@
+<!-- 
+osgEarth Sample - Noise Driver
+
+Demonstrates the use of libnoise to procedurally generate a
+fictional fractal landscape.
+
+We use a contour map to better visualize the terrain.
+-->
+
+<map version="2">
+
+    <elevation driver="noise" name="noisy_terrain"
+               resolution ="3185500"
+               octaves    ="12"
+               persistence="0.49"
+               lacunarity ="3.0"
+               scale      ="5000" />
+               
+
+    <options
+        lighting="true"/>
+
+    <external>
+        <contour_map/>
+        <sky hours="20.0"/>
+    </external>
+</map>
diff --git a/tests/normalmap.earth b/tests/normalmap.earth
new file mode 100644
index 0000000..ad561c6
--- /dev/null
+++ b/tests/normalmap.earth
@@ -0,0 +1,43 @@
+<!-- 
+osgEarth Sample - Noise Driver
+Demonstrates the application of a normal map to add terrain detail.
+-->
+
+<map>
+    <options elevation_tile_size="15">
+        <terrain min_level="20"/>
+    </options>
+
+    <image name="readymap_imagery" driver="tms" visible="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+            
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+               
+    <!--
+    The normal map layer using the 'noise' driver to generate bump map textures.
+    Note that we set visible='false' since we don't actually want to display this
+    layer in its raw form - rather, the shared='true' will make it available to
+    the <normal_map> effect below.
+    -->
+    <image name="normalmap" driver="noise" shared="true" visible="false"
+           tile_size="64"
+           normal_map="true"
+           frequency=".001"
+           persistence="0.45"
+           lacunarity="3.0"
+           octaves="6"
+           scale="15" >
+    </image>
+           
+    <!--
+    Install the normal map terrain effect and point it at our normal map layer.
+    -->
+    <external>
+        <normal_map layer="normalmap"/>
+        <contour_map/>
+        <sky hours="16.0"/>
+    </external>
+</map>
diff --git a/tests/ocean.earth b/tests/ocean.earth
index 1f8a3ca..4235087 100644
--- a/tests/ocean.earth
+++ b/tests/ocean.earth
@@ -28,12 +28,7 @@ http://readymap.org
     <options>
         <terrain>
             <lighting>true</lighting>
-            <sample_ratio>0.125</sample_ratio>
         </terrain>
-        
-        <cache driver="filesystem">
-            <path>osgearth_cache</path>
-        </cache>
     </options>
     
     <external>
@@ -57,5 +52,7 @@ http://readymap.org
         </ocean>
         
         <viewpoint name="Los Angeles" heading="35.27" height="97.48" lat="34.051" long="-117.974" pitch="-17" range="136405"/>
+        <viewpoint name="Above water" heading="-76.17264538992794" height="-199.5569639196619" lat="33.27975381179682" long="-118.3307776586542" pitch="-10.06523772274543" range="3739.161570538204"/>
+        <viewpoint name="Below water" heading="-24.96310172368127" height="-1300.000076910481" lat="33.27360337088133" long="-118.5514448058582" pitch="-10.0770016631354" range="6375.084038302656"/>
     </external>
 </map>
diff --git a/tests/readymap.earth b/tests/readymap.earth
index 82bdc7b..6e9c4ee 100644
--- a/tests/readymap.earth
+++ b/tests/readymap.earth
@@ -12,22 +12,18 @@ http://readymap.org
 
 -->
 <map name="readymap.org" type="geocentric" version="2">
-
-    <image name="readymap_imagery" driver="tms">
+    
+    <options>
+        <elevation_tile_size>15</elevation_tile_size>
+        <terrain first_lod="1"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
         <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
-        <color_filters>
-            <gamma rgb="1.3"/>
-        </color_filters>
     </image>
         
     <elevation name="readymap_elevation" driver="tms">
         <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
     </elevation>
     
-    <options>
-		<lighting>false</lighting>
-		<elevation_tile_size>8</elevation_tile_size>
-        <terrain normalize_edges="true" first_lod="1"/>
-    </options>
-    
 </map>
diff --git a/tests/refresh.earth b/tests/refresh.earth
deleted file mode 100644
index 6902616..0000000
--- a/tests/refresh.earth
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-osgEarth Sample - Refresh
-
-This example is a test of a dynamic image capability that refreshes an image every N seconds.  This doesn't really serve any purpose other than to serve
-as a an example of how one might go about providing a dynamically refreshing image for a tile.
--->
-<map name="refresh" type="geocentric" version="2">
-
-    <image name="Refresh" driver="refresh">
-	    <!--This url is a traffic camera that changes periodically.  The resolution isn't great but you can get the idea.
-		You can also point to a local file, load it in an image editing program and save it and the new image will appear
-		-->
-        <url>http://webcam.mta.info/mta3/servlet/MtaImageServlet?cam_id=5</url>
-		
-		<!--Polling frequency.  How often to refresh the image.  You don't want this too high or too many images will be loaded since the same image applies to every tile.-->
-		<frequency>2.0</frequency>
-    </image>
-  
-    
-    <options>
-        <terrain>
-            <lighting>false</lighting>            
-			<!--
-			Setting the filters to linear disables mipmapping and reduces frame breaks b/c many new images are being sent to the graphics card when it refresh
-			and can cause stalls when generating mipmaps
-			-->
-			<min_filter>LINEAR</min_filter>
-			<mag_filter>LINEAR</mag_filter>
-			
-        </terrain>
-    </options>
-    
-</map>
diff --git a/tests/simple_model.earth b/tests/simple_model.earth
index cea3881..35e9ea0 100644
--- a/tests/simple_model.earth
+++ b/tests/simple_model.earth
@@ -5,21 +5,23 @@ needs to be absolutely positioned.
 -->
 
 <map version="2">
-    <image driver="gdal" name="world-tiff" cache_enabled="false">
-        <url>../data/world.tif</url>
-        <caching_policy usage="no_cache"/>
+    <image name="mapquest_open_aerial" driver="xyz">
+        <url>http://oatile[1234].mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg</url>
+        <profile>spherical-mercator</profile>
+        <cache_policy usage="no_cache"/>
+        <nodata_image>http://oatile3.mqcdn.com/tiles/1.0.0/sat/13/636/6210.jpg</nodata_image>
     </image>
-	
-	<model name = "cow" driver="simple">
-	  <url>../data/red_flag.osg.100,100,100.scale</url>
-	  <location>-74.018 40.717 10</location>	  
-	</model>
+    
+    <model name = "cow" driver="simple">
+      <url>../data/red_flag.osg.100,100,100.scale</url>
+      <location>-74.018 40.717 10</location>	  
+    </model>
     
     <options lighting="false">
         <terrain min_lod="12"/>
     </options>
-	
-	<external>
-        <viewpoint name="Zoom to model" heading="0" height="0" lat="40.717" long="-74.018" pitch="-90" range="6000"/>    
+    
+    <external>
+        <viewpoint name="Zoom to model" lat="40.717" long="-74.018" pitch="-60" range="6000"/>    
     </external>	
 </map>
diff --git a/tests/vertical_datum.earth b/tests/vertical_datum.earth
index 014e999..6d65f7f 100644
--- a/tests/vertical_datum.earth
+++ b/tests/vertical_datum.earth
@@ -1,8 +1,7 @@
 <!--
 osgEarth Sample - EGM96
 This example shows how to request an EGM96 vertical datum. Since there's no
-heightfield layer, you will see the raw EGM96 geoid. We apply a large vertical
-exaggeration in order to make the geoid's shape stand out.
+heightfield layer, you will see the raw EGM96 geoid.
 -->
 
 <map version="2">   
@@ -17,7 +16,6 @@ exaggeration in order to make the geoid's shape stand out.
             <vdatum>egm96</vdatum>
         </profile>
         <terrain>
-            <vertical_scale>5000.0</vertical_scale>
             <lighting>false</lighting>
         </terrain>
     </options>
diff --git a/tests/vertical_scale.earth b/tests/vertical_scale.earth
new file mode 100644
index 0000000..ff9c613
--- /dev/null
+++ b/tests/vertical_scale.earth
@@ -0,0 +1,29 @@
+<!--
+osgEarth Sample
+
+VerticalScale terrain effect.
+Try running it with a controller uniform:
+
+  osgearth_viewer vertical_scale --uniform oe_vertscale_scale 1.0 4.0
+
+-->
+<map name="readymap.org" type="geocentric" version="2">
+    
+    <options>
+        <elevation_tile_size>15</elevation_tile_size>
+        <terrain lighting="false" first_lod="1" min_lod="19"/>
+    </options>
+    
+    <image name="readymap_imagery" driver="tms" visible="true">
+        <url>http://readymap.org/readymap/tiles/1.0.0/7/</url>
+    </image>
+        
+    <elevation name="readymap_elevation" driver="tms">
+        <url>http://readymap.org/readymap/tiles/1.0.0/9/</url>
+    </elevation>
+	
+	<external>
+	    <vertical_scale scale="2.0"/>
+	</external>
+    
+</map>
diff --git a/tests/vpb_earth_bayarea.earth b/tests/vpb_earth_bayarea.earth
deleted file mode 100644
index 562c34c..0000000
--- a/tests/vpb_earth_bayarea.earth
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-osgEarth Sample
-
-This example pulls imagery and dems from an online VirtualPlanetBuilder generated database.
--->
-
-<map name="Virtual Planet Builder model" type="geocentric" version="2">
-
-    <image name="imagery layer 0" driver="vpb">
-        <url>http://www.openscenegraph.org/data/earth_bayarea/earth.ive</url>
-        <primary_split_level>5</primary_split_level>
-        <secondary_split_level>11</secondary_split_level>
-    </image>
-
-    <heightfield name="dem" driver="vpb">
-        <url>http://www.openscenegraph.org/data/earth_bayarea/earth.ive</url>
-    </heightfield>
-    
-    <options>
-        <lighting>false</lighting>
-        <cache_policy usage="no_cache"/>
-    </options>
-
-</map>
diff --git a/tests/vpb_with_inset.earth b/tests/vpb_with_inset.earth
index 2e7b115..5d44c95 100644
--- a/tests/vpb_with_inset.earth
+++ b/tests/vpb_with_inset.earth
@@ -19,6 +19,7 @@ Zoom in to the city of Boston (east coast of the US) to see the inset.
 	
     <options>
         <lighting>false</lighting>
+		<cache_policy usage="no_cache"/>
     </options>
     
 </map>
diff --git a/tests/wms-t_nexrad_animated.earth b/tests/wms-t_nexrad_animated.earth
index 15c3d58..943f900 100644
--- a/tests/wms-t_nexrad_animated.earth
+++ b/tests/wms-t_nexrad_animated.earth
@@ -2,6 +2,10 @@
 osgEarth Sample
 
 US NEXRAD 45 minute radar returns overlaid on imagery.
+
+Try running this with:
+
+  osgearth_sequencecontrol <file.earth>
 -->
 
 <map name="WMS Radar returns" type="geocentric" version="2">   
@@ -34,5 +38,8 @@ US NEXRAD 45 minute radar returns overlaid on imagery.
     
     <options>
         <lighting>false</lighting>
+		
+		<!-- default engine doesn't support sequencing yet -->
+		<terrain driver="quadtree"/>
     </options>
 </map>
diff --git a/tests/wms_metacarta.earth b/tests/wms_metacarta.earth
deleted file mode 100644
index 01fcde2..0000000
--- a/tests/wms_metacarta.earth
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-osgEarth Sample
-
-MetaCarta WMS/TileCache Service - VMap0
-
-This shows the basic use of the WMS driver. It is actually using
-WMS to hit a TileCache server, which has all the WMS tiles
-pre-rendered for speed.
--->
-
-<map name="MetaCarta" type="geocentric" version="2">
-
-   <image name="vmap0" driver="wms">
-       <url>http://labs.metacarta.com/wms-c/Basic.py</url>
-       <layers>basic</layers>
-       <format>png</format>
-       <tile_size>256</tile_size>
-   </image>
-   
-   <options lighting="false"/>
-   
-</map>
diff --git a/tests/wms_nexrad.earth b/tests/wms_nexrad.earth
index 328a1ba..14fd2c5 100644
--- a/tests/wms_nexrad.earth
+++ b/tests/wms_nexrad.earth
@@ -8,6 +8,7 @@ US NEXRAD 45 minute radar returns overlaid on imagery.
      
     <image name="world" driver="gdal">
         <url>../data/world.tif</url>
+        <cache_policy usage="no_cache"/>
     </image>
     
     <image name="nexrad45min" driver="wms">

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



More information about the Pkg-grass-devel mailing list