/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#include <osgEarth/Composite>
#include <osgEarth/Progress>
#include <osgEarth/Notify>
#include "HeightFieldUtils"

using namespace osgEarth;

#undef  LC
#define LC "[CompositeImageLayer] "

//........................................................................

Config
CompositeImageLayer::Options::getConfig() const
{
    Config conf = ImageLayer::Options::getConfig();
    if (_layers.empty() == false)
    {
        Config layersConf("layers");
        for( std::vector<ConfigOptions>::const_iterator i = _layers.begin(); i != _layers.end(); ++i )
        {
            layersConf.add(i->getConfig());
        }
        conf.set(layersConf);
    }
    conf.set("composite_function", "blend", function(), FUNCTION_BLEND);
    conf.set("composite_function", "less", function(), FUNCTION_LESS);
    conf.set("composite_function", "greater", function(), FUNCTION_GREATER);
    conf.set("composite_function", "more", function(), FUNCTION_GREATER);
    return conf;
}

void
CompositeImageLayer::Options::fromConfig(const Config& conf)
{
    function().setDefault(FUNCTION_BLEND);

    const ConfigSet& layers = conf.child("layers").children();
    for( ConfigSet::const_iterator i = layers.begin(); i != layers.end(); ++i )
    {
        _layers.push_back(ConfigOptions(*i));
    }
    conf.get("composite_function", "blend", function(), FUNCTION_BLEND);
    conf.get("composite_function", "less", function(), FUNCTION_LESS);
    conf.get("composite_function", "greater", function(), FUNCTION_GREATER);
}

//........................................................................

namespace osgEarth { namespace Composite
{
    struct ImageInfo
    {
        ImageInfo()
        {
            image = 0;
            opacity = 1;
        }

        TileKey bestAvailableKey;
        //bool mayHaveData;
        float opacity;
        osg::ref_ptr<const osg::Image> image;
    };

    // some helper types.    
    typedef std::vector<ImageInfo> ImageMixVector;   
} }

REGISTER_OSGEARTH_LAYER(compositeimage, CompositeImageLayer);

void
CompositeImageLayer::addLayer(ImageLayer* layer)
{
    if (isOpen())
    {
        OE_WARN << LC << "Illegal call to addLayer when layer is already open" << std::endl;
    }
    else if (layer)
    {
        _layers.push_back(layer);
    }
}

const ImageLayerVector&
CompositeImageLayer::getLayers() const
{
    return _layers;
}

void
CompositeImageLayer::init()
{
    ImageLayer::init();
}

osg::Node*
CompositeImageLayer::getNode() const
{
    return _layerNodes.get();
}

void
CompositeImageLayer::addedToMap(const Map* map)
{
    bool dataExtentsValid = true;

    osg::ref_ptr<const Profile> profile = getProfile();

    DataExtentList outputExtents;

    for (auto& layer : _layers)
    {
        if (layer->isOpen())
        {
            layer->addedToMap(map);

            // gather extents                        
            DataExtentList inputExtents;
            layer->getDataExtents(inputExtents);

            // If even one of the layers' data extents is unknown, the entire composite
            // must have unknown data extents:
            if (inputExtents.empty())
            {
                dataExtentsValid = false;
            }

            if (dataExtentsValid)
            {
                for (DataExtentList::const_iterator j = inputExtents.begin(); j != inputExtents.end(); ++j)
                {
                    // Convert the data extent to the profile that is actually used by this TileSource
                    DataExtent dataExtent = *j;
                    GeoExtent ext = dataExtent.transform(profile->getSRS());
                    unsigned int minLevel = 0;
                    unsigned int maxLevel = profile->getEquivalentLOD(layer->getProfile(), *dataExtent.maxLevel());
                    outputExtents.push_back(DataExtent(ext, minLevel, maxLevel));
                }
            }
        }
    }
    if (dataExtentsValid)
    {
        setDataExtents(outputExtents);
    }
}

void
CompositeImageLayer::removedFromMap(const Map* map)
{
    for(ImageLayerVector::iterator i = _layers.begin();
        i != _layers.end();
        ++i)
    {
        i->get()->removedFromMap(map);
    }
}

Status
CompositeImageLayer::openImplementation()
{
    Status parent = ImageLayer::openImplementation();
    if (parent.isError())
        return parent;

    // If we're in cache-only mode, do not attempt to open the component layers!
    if (getCacheSettings()->cachePolicy()->isCacheOnly())
        return Status::NoError;

    osg::ref_ptr<const Profile> profile = getProfile();

    // If the user didn't call addLayer(), try to read them from the options.
    if (_layers.empty())
    {
        for(auto& conf : options().layers())
        {
            osg::ref_ptr<Layer> newLayer = Layer::create(conf);
            ImageLayer* layer = dynamic_cast<ImageLayer*>(newLayer.get());
            if (layer)
            {
                // Add to the list
                _layers.push_back(layer);
            }
            else
            {
                OE_WARN << LC << "This composite can only contains ImageLayers; discarding a layer" << std::endl;
            }
        }
    }

    else
    {
        // the user added layers through the API, so store each layer's options
        // for serialization
        for(auto& layer : _layers)
        {
            options().layers().push_back(layer->getConfig());
        }
    }

    for(auto& layer : _layers)
    {
        if (!layer->isOpen() && !layer->getOpenAutomatically())
            continue;

        layer->setReadOptions(getReadOptions());

        Status status = layer->open();

        if (status.isOK())
        {
            OE_DEBUG << LC << "...opened " << layer->getName() << " OK" << std::endl;

            // If no profile is specified assume they want to use the profile of the first layer in the list
            if (!profile.valid())
            {
                profile = layer->getProfile();
                if (!profile.valid())
                {
                    return Status(
                        Status::ResourceUnavailable,
                        Stringify() << "Cannot establish profile for layer " << layer->getName());
                }
                else if (profile->getSRS()->getVerticalDatum() != NULL)
                {
                    // Remove the VDATUM! Otherwise we will process it twice,
                    // once in the component layer and again in the composite.
                    ProfileOptions po = profile->toProfileOptions();
                    po.vsrsString().unset();
                    profile = Profile::create(po);
                }
            }            

            // If the sublayer has a Node, add it to the group.
            if (layer->getNode())
            {
                if (!_layerNodes.valid())
                    _layerNodes = new osg::Group();

                _layerNodes->addChild(layer->getNode());
            }
        }

        else
        {
            OE_WARN << LC << "...failed to open " << layer->getName() << ": " << status.message() << std::endl;
            if (getCacheSettings()->isCacheEnabled())
            {
                OE_WARN << LC << "...cache writes will be DISABLED for this layer" << std::endl;
                getCacheSettings()->integrateCachePolicy(CachePolicy(CachePolicy::USAGE_READ_ONLY));
            }
        }
    }

    if (!getProfile())
    {
        // If there is no profile set by the user or by a component, fall back
        // on a default profile. This will allow the Layer to continue to operate
        // off the cache even if all components fail to initialize for some reason.
        if (profile.valid() == false)
        {
            if (getCacheSettings()->isCacheEnabled())
            {
                profile = Profile::create(Profile::GLOBAL_GEODETIC);
            }
            else
            {
                return Status(Status::ResourceUnavailable, "Unable to open any component layers");
            }
        }

        setProfile(profile.get());
    }

    return Status::NoError;
}

Status
CompositeImageLayer::closeImplementation()
{
    for(auto& layer : _layers)
    {
        layer->close();
    }

    if (_layerNodes.valid())
    {
        _layerNodes->removeChildren(0, _layerNodes->getNumChildren());
    }

    return Status::OK();
}

GeoImage
CompositeImageLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress) const
{
    unsigned size = getTileSize();

    Composite::ImageMixVector images;
    images.reserve(_layers.size());

    // Try to get an image from each of the layers for the given key.
    for(auto& layer : _layers)
    {
        if (!layer->isOpen())
            continue;

        Composite::ImageInfo imageInfo;
        imageInfo.opacity = layer->getOpacity();
        imageInfo.bestAvailableKey = layer->getBestAvailableTileKey(key);
        
        // if there is possibly actual data for this key...
        if (imageInfo.bestAvailableKey == key)
        {
            GeoImage image = layer->createImage(key, progress);
            if (image.valid())
            {
                imageInfo.image = image.getImage();
            }

            // If the progress got cancelled or it needs a retry then return NULL to prevent this tile from being built and cached with incomplete or partial data.
            if (progress && progress->isCanceled())
            {
                OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl;
                return GeoImage::INVALID;
            }
        }

        images.push_back(imageInfo);
    }

    // Compute the number of valid images
    unsigned numValidImages = 0;
    for (unsigned int i = 0; i < images.size(); i++)
    {
        Composite::ImageInfo& info = images[i];
        if (info.image.valid())
        {
            numValidImages++;
        }
    }

    // Create fallback images if we have some valid data but not for all the layers
    if (numValidImages > 0 && numValidImages < images.size())
    {
        for (unsigned int i = 0; i < images.size(); i++)
        {
            Composite::ImageInfo& info = images[i];
            ImageLayer* layer = _layers[i].get();
            if (info.image.valid() == false && info.bestAvailableKey.valid())
            {
                TileKey currentKey = info.bestAvailableKey; //key.createParentKey();

                GeoImage image;
                while (!image.valid() && currentKey.valid())
                {
                    image = layer->createImage(currentKey, progress);
                    if (image.valid())
                    {
                        break;
                    }

                    // If the progress got cancelled or it needs a retry then return INVALID
                    // to prevent this tile from being built and cached with incomplete or partial data.
                    if (progress && progress->isCanceled())
                    {
                        OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl;
                        return GeoImage::INVALID;
                    }

                    currentKey = currentKey.createParentKey();
                }

                if (image.valid())
                {
                    bool bilinear = layer->isCoverage() ? false : true;
                    GeoImage cropped = image.crop( key.getExtent(), true, size, size, bilinear);
                    info.image = cropped.getImage();
                }                    
            }
        }
    }

    // Now finally create the output image.
    // Recompute the number of valid images and make sure they are all the correct size
    numValidImages = 0;
    for (unsigned int i = 0; i < images.size(); i++)
    {
        Composite::ImageInfo& info = images[i];
        if (info.image.valid())
        {
            numValidImages++;
            if (info.image->s() != size || info.image->t() != size)
            {
                osg::ref_ptr< osg::Image > resized;
                ImageUtils::resizeImage(info.image.get(), size, size, resized);
                info.image = resized;
            }
        }
    }    

    if ( progress && progress->isCanceled() )
    {
        return GeoImage::INVALID;
    }

    else if ( numValidImages == 0 )
    {
        return GeoImage::INVALID;
    }

    else if ( numValidImages == 1 )
    {
        //We only have one valid image, so just return it and don't bother with compositing
        for (unsigned int i = 0; i < images.size(); i++)
        {
            Composite::ImageInfo& info = images[i];
            if (info.image.valid())
            {
                return GeoImage(info.image.release(), key.getExtent());
            }
        }
        return GeoImage::INVALID;
    }

    else
    {
        osg::Image* result = 0;
        for (unsigned int i = 0; i < images.size(); i++)
        {
            Composite::ImageInfo& imageInfo = images[i];
            if (!result)
            {
                if (imageInfo.image.valid())
                {
                    result = new osg::Image( *imageInfo.image.get());
                }
            }
            else
            {
                if (imageInfo.image.valid())
                {
                    if (options().function() == options().FUNCTION_BLEND)
                    {
                        ImageUtils::mix(result, imageInfo.image.get(), imageInfo.opacity);
                    }
                    else
                    {
                        ImageUtils::PixelReader readOne(result);
                        ImageUtils::PixelReader readTwo(imageInfo.image.get());
                        ImageUtils::PixelWriter writeOne(result);
                        osg::Vec4 pixelOne, pixelTwo;

                        std::function<bool(float, float)> compare;
                        if (options().function() == options().FUNCTION_LESS)
                            compare = [](float a, float b) { return a < b; };
                        else // FUNCTION_MORE
                            compare = [](float a, float b) { return a > b; };
                            
                        readOne.forEachPixel([&](auto& iter)
                            {
                                readOne(pixelOne, iter);
                                readTwo(pixelTwo, iter);
                                if (compare(pixelTwo.r(), pixelOne.r()))
                                    writeOne(pixelTwo, iter);
                            });
                    }
                }
            }            
        }        
        return GeoImage(result, key.getExtent());
    }
}

//........................................................................

Config
CompositeElevationLayer::Options::getConfig() const
{
    Config conf = ElevationLayer::Options::getConfig();
    if (_layers.empty() == false)
    {
        Config layersConf("layers");
        for(auto& options : _layers)
        {
            layersConf.add(options.getConfig());
        }
        conf.set(layersConf);
    }
    return conf;
}

void
CompositeElevationLayer::Options::fromConfig(const Config& conf)
{
    const ConfigSet& layers = conf.child("layers").children();
    for(auto& conf : layers)
    {
        _layers.push_back(ConfigOptions(conf));
    }
}


REGISTER_OSGEARTH_LAYER(compositeelevation, CompositeElevationLayer);

void
CompositeElevationLayer::addLayer(ElevationLayer* layer)
{
    if (isOpen())
    {
        OE_WARN << LC << "Illegal call to addLayer when layer is already open" << std::endl;
    }
    else if (layer)
    {
        _layers.push_back(layer);
    }
}

const ElevationLayerVector&
CompositeElevationLayer::getLayers() const
{
    return _layers;
}

void
CompositeElevationLayer::init()
{
    ElevationLayer::init();
}

osg::Node*
CompositeElevationLayer::getNode() const
{
    return _layerNodes.get();
}

void
CompositeElevationLayer::addedToMap(const Map* map)
{
    bool dataExtentsValid = true;
    osg::ref_ptr<const Profile> profile = getProfile();

    DataExtentList outputExtents;

    for (auto& layer : _layers)
    {
        if (layer->isOpen())
        {
            layer->addedToMap(map);

            // gather extents     
            DataExtentList inputExtents;
            layer->getDataExtents(inputExtents);

            // If even one of the layers' data extents is unknown, the entire composite
            // must have unknown data extents:
            if (inputExtents.empty())
            {
                dataExtentsValid = false;
            }

            if (dataExtentsValid)
            {
                for (DataExtentList::const_iterator j = inputExtents.begin(); j != inputExtents.end(); ++j)
                {
                    // Convert the data extent to the profile that is actually used by this TileSource
                    DataExtent dataExtent = *j;
                    GeoExtent ext = dataExtent.transform(profile->getSRS());
                    unsigned int minLevel = 0;
                    unsigned int maxLevel = profile->getEquivalentLOD(layer->getProfile(), *dataExtent.maxLevel());
                    outputExtents.push_back(DataExtent(ext, minLevel, maxLevel));
                }
            }
        }
    }
    if (dataExtentsValid)
    {
        setDataExtents(outputExtents);
    }
}

void
CompositeElevationLayer::removedFromMap(const Map* map)
{
    for (auto& layer : _layers)
    {
        if (layer->isOpen())
            layer->removedFromMap(map);
    }
}

Status
CompositeElevationLayer::openImplementation()
{
    Status parent = super::openImplementation();
    if (parent.isError())
        return parent;    
    
    // If we're in cache-only mode, do not attempt to open the component layers!
    if (getCacheSettings() && getCacheSettings()->cachePolicy()->isCacheOnly())
        return Status::NoError;

    osg::ref_ptr<const Profile> profile;

    // If the user didn't call addLayer(), try to read them from the options.
    if (_layers.empty())
    {
        for(auto& conf : options().layers())
        {
            osg::ref_ptr<Layer> newLayer = Layer::create(conf);
            ElevationLayer* layer = dynamic_cast<ElevationLayer*>(newLayer.get());
            if (layer)
            {
                // Add to the list
                _layers.push_back(layer);
            }
            else
            {
                OE_WARN << LC << "This composite can only contains ElevationLayers; discarding a layer" << std::endl;
            }
        }
    }

    // otherwise, we need to add these layers to the options in case
    // someone calls getConfig.
    else
    {
        for(auto& layer : _layers)
        {
            options().layers().push_back(layer->getConfig());
        }
    }

    for (auto& layer : _layers)
    {
        if (!layer->getOpenAutomatically())
            continue;

        layer->setReadOptions(getReadOptions());

        Status status = layer->open();

        if (status.isOK())
        {
            OE_DEBUG << LC << "...opened " << layer->getName() << " OK" << std::endl;

            // If no profile is specified assume they want to use the profile of the first layer in the list.
            if (!profile.valid())
            {
                profile = layer->getProfile();
                if (!profile.valid())
                {
                    return Status(Status::ResourceUnavailable, 
                        "Cannot establish profile for layer " + layer->getName());
                }
                else if (profile->getSRS()->getVerticalDatum() != NULL)
                {
                    // Remove the VDATUM! Otherwise we will process it twice,
                    // once in the component layer and again in the composite.
                    ProfileOptions po = profile->toProfileOptions();
                    po.vsrsString().unset();
                    profile = Profile::create(po);
                }
            }            

            // If the sublayer has a Node, add it to the group.
            if (layer->getNode())
            {
                if (!_layerNodes.valid())
                    _layerNodes = new osg::Group();

                _layerNodes->addChild(layer->getNode());
            }
        }

        else
        {
            OE_WARN << LC << "...failed to open " << layer->getName() << ": " << status.message() << std::endl;
            if (getCacheSettings()->isCacheEnabled())
            {
                OE_WARN << LC << "...cache writes will be DISABLED for this layer" << std::endl;
                getCacheSettings()->integrateCachePolicy(CachePolicy(CachePolicy::USAGE_READ_ONLY));
            }
        }
    }

    // If there is no profile set by the user or by a component, fall back
    // on a default profile. This will allow the Layer to continue to operate
    // off the cache even if all components fail to initialize for some reason.
    if (profile.valid() == false)
    {
        if (getCacheSettings()->isCacheEnabled())
        {
            profile = Profile::create(Profile::GLOBAL_GEODETIC);
        }
        else
        {
            return Status(Status::ResourceUnavailable, "Unable to open any component layers");
        }
    }

    setProfile( profile.get() );

    return Status::NoError;
}

Status
CompositeElevationLayer::closeImplementation()
{
    for(auto& layer : _layers)
        layer->close();

    if (_layerNodes.valid())
    {
        _layerNodes->removeChildren(0, _layerNodes->getNumChildren());
    }

    return Status::OK();
}

GeoHeightField
CompositeElevationLayer::createHeightFieldImplementation(const TileKey& key, ProgressCallback* progress) const
{
    auto hf = HeightFieldUtils::createReferenceHeightField(
        key.getExtent(), getTileSize(), getTileSize(), 0, false, NO_DATA_VALUE);

    // Populate the heightfield and return it if it's valid
    if (_layers.populateHeightField(hf.get(), NULL, key, 0, INTERP_BILINEAR, progress))
    {                
        return GeoHeightField(hf.release(), key.getExtent());
    }
    else
    {        
        return GeoHeightField::INVALID;
    }
}


#undef  LC
#define LC "[CompositeLandCoverLayer] "

//........................................................................

Config
CompositeLandCoverLayer::Options::getConfig() const
{
    Config conf = LandCoverLayer::Options::getConfig();
    if (_layers.empty() == false)
    {
        Config layersConf("layers");
        for(auto& options : _layers)
        {
            layersConf.add(options.getConfig());
        }
        conf.set(layersConf);
    }
    return conf;
}

void
CompositeLandCoverLayer::Options::fromConfig(const Config& conf)
{
    const ConfigSet& layers = conf.child("layers").children();
    for(auto& conf : layers)
    {
        _layers.push_back(ConfigOptions(conf));
    }
}

//........................................................................

REGISTER_OSGEARTH_LAYER(compositelandcover, CompositeLandCoverLayer);

void
CompositeLandCoverLayer::addLayer(LandCoverLayer* layer)
{
    if (isOpen())
    {
        OE_WARN << LC << "Illegal call to addLayer when layer is already open" << std::endl;
    }
    else if (layer)
    {
        _layers.push_back(layer);
    }
}

const LandCoverLayerVector&
CompositeLandCoverLayer::getLayers() const
{
    return _layers;
}

void
CompositeLandCoverLayer::init()
{
    LandCoverLayer::init();
}

osg::Node*
CompositeLandCoverLayer::getNode() const
{
    return _layerNodes.get();
}

void
CompositeLandCoverLayer::addedToMap(const Map* map)
{
    bool dataExtentsValid = true;

    osg::ref_ptr<const Profile> profile = getProfile();

    DataExtentList outputExtents;

    for (auto& layer : _layers)
    {
        if (layer->isOpen())
        {
            layer->addedToMap(map);
        }

        // gather extents                        
        DataExtentList inputExtents;
        layer->getDataExtents(inputExtents);

        // If even one of the layers' data extents is unknown, the entire composite
        // must have unknown data extents:
        if (inputExtents.empty())
        {
            dataExtentsValid = false;
        }

        if (dataExtentsValid)
        {
            for (DataExtentList::const_iterator j = inputExtents.begin(); j != inputExtents.end(); ++j)
            {
                // Convert the data extent to the profile that is actually used by this TileSource
                DataExtent dataExtent = *j;
                GeoExtent ext = dataExtent.transform(profile->getSRS());
                unsigned int minLevel = 0;
                unsigned int maxLevel = profile->getEquivalentLOD(layer->getProfile(), *dataExtent.maxLevel());
                outputExtents.push_back(DataExtent(ext, minLevel, maxLevel));                
            }
        }
    }

    if (dataExtentsValid)
    {
        setDataExtents(outputExtents);
    }
}

void
CompositeLandCoverLayer::removedFromMap(const Map* map)
{
    for (auto& layer : _layers)
    {
        layer->removedFromMap(map);
    }
}

Status
CompositeLandCoverLayer::openImplementation()
{
    Status parent = ImageLayer::openImplementation(); // skipping LandCoverLayer::open...
    if (parent.isError())
        return parent;

    // If we're in cache-only mode, do not attempt to open the component layers!
    if (getCacheSettings()->cachePolicy()->isCacheOnly())
        return Status::NoError;

    osg::ref_ptr<const Profile> profile;

    bool dataExtentsValid = true;

    // You may not call addLayers() and also put layers in the options.
    if (_layers.empty() == false && options().layers().empty() == false)
    {
        return Status(Status::ConfigurationError, 
            "Illegal to add layers both by options and by API");
    }

    // If the user didn't call addLayer(), try to read them from the options.
    if (_layers.empty())
    {
        for(auto& conf : options().layers())
        {
            osg::ref_ptr<Layer> newLayer = Layer::create(conf);
            LandCoverLayer* layer = dynamic_cast<LandCoverLayer*>(newLayer.get());
            if (layer)
            {
                // Add to the list
                _layers.push_back(layer);
            }
            else
            {
                OE_WARN << LC << "This composite can only contains LandCoverLayers; discarding a layer" << std::endl;
            }
        }
    }

    else
    {
        // the user added layers through the API, so store each layer's options
        // for serialization
        for(auto& layer : _layers)
        {
            options().layers().push_back(layer->getConfig());
        }
    }

    for(auto& layer : _layers)
    {
        if (!layer->getOpenAutomatically())
            continue;

        layer->setReadOptions(getReadOptions());

        Status status = layer->open();

        if (status.isOK())
        {
            OE_DEBUG << LC << "...opened " << layer->getName() << " OK" << std::endl;

            // If no profile is specified assume they want to use the profile of the first layer in the list.
            if (!profile.valid())
            {
                profile = layer->getProfile();
                if (!profile.valid())
                {
                    return Status(
                        Status::ResourceUnavailable, 
                        Stringify()<<"Cannot establish profile for layer " << layer->getName());
                }
                else if (profile->getSRS()->getVerticalDatum() != NULL)
                {
                    // Remove the VDATUM! Otherwise we will process it twice,
                    // once in the component layer and again in the composite.
                    ProfileOptions po = profile->toProfileOptions();
                    po.vsrsString().unset();
                    profile = Profile::create(po);
                }
            }            

            // If the sublayer has a Node, add it to the group.
            if (layer->getNode())
            {
                if (!_layerNodes.valid())
                    _layerNodes = new osg::Group();

                _layerNodes->addChild(layer->getNode());
            }
        }

        else
        {
            OE_WARN << LC << "...failed to open " << layer->getName() << ": " << status.message() << std::endl;
            if (getCacheSettings()->isCacheEnabled())
            {
                OE_WARN << LC << "...cache writes will be DISABLED for this layer" << std::endl;
                getCacheSettings()->integrateCachePolicy(CachePolicy(CachePolicy::USAGE_READ_ONLY));
            }
        }
    }

    // If there is no profile set by the user or by a component, fall back
    // on a default profile. This will allow the Layer to continue to operate
    // off the cache even if all components fail to initialize for some reason.
    if (profile.valid() == false)
    {
        if (getCacheSettings()->isCacheEnabled())
        {
            profile = Profile::create(Profile::GLOBAL_GEODETIC);
        }
        else
        {
            return Status(Status::ResourceUnavailable, "Unable to open any component layers");
        }
    }

    setProfile( profile.get() );

    return Status::NoError;
}

Status
CompositeLandCoverLayer::closeImplementation()
{
    for(auto& layer : _layers)
    {
        layer->close();
    }

    if (_layerNodes.valid())
    {
        _layerNodes->removeChildren(0, _layerNodes->getNumChildren());
    }

    return Status::OK();
}

GeoImage
CompositeLandCoverLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress) const
{
    unsigned size = getTileSize();

    osg::ref_ptr<osg::Image> image;

    // Populate the heightfield and return it if it's valid
    if (_layers.populateLandCoverImage(image, key, progress))
    {                
        return GeoImage(image.get(), key.getExtent());
    }
    else
    {        
        return GeoImage::INVALID;
    }
}
