// override // Creates an image. osg::Image* createImage(const TileKey& key, ProgressCallback* progress ) { if ( !_imageLayer.valid() || !_featureSource.valid() ) return 0L; // fetch the image for this key: GeoImage image = _imageLayer->createImage(key, progress); if ( !image.valid() ) return 0L; // fetch a set of features for this key. The features are in their // own SRS, so we need to transform: const SpatialReference* featureSRS = _featureSource->getFeatureProfile()->getSRS(); GeoExtent extentInFeatureSRS = key.getExtent().transform( featureSRS ); // assemble a spatial query. It helps if your features have a spatial index. Query query; query.bounds() = extentInFeatureSRS.bounds(); //query.expression() = ... // SQL expression compatible with data source osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query); // create a new image to return. osg::Image* output = new osg::Image(); //output->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE); // do your magic here. return output; }
/** return the maximum resolution in the list of images */ float GeoImageList::maxResolution() { if (!minMaxResUptodate_) { minRes_ = -1; maxRes_ = 500; qDebug("GeoImageList::maxResolution"); QDictIterator < GeoImage > it = QDictIterator < GeoImage > (*this); for (; it.current(); ++it) { GeoImage *img = it.current(); qDebug("%s: xres: %f yres: %f", (img->filename()).latin1(), img->resolutionX(), img->resolutionY()); float res = it.current()->resolutionX(); if (res > minRes_) minRes_ = res; if (res < maxRes_) maxRes_ = res; res = it.current()->resolutionY(); if (res > minRes_) minRes_ = res; if (res < maxRes_) maxRes_ = res; } minMaxResUptodate_ = true; } return maxRes_; }
bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv) { ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() ); ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() ); // Just call createImage or createHeightField on the layer and the it will be cached! if (imageLayer) { GeoImage image = imageLayer->createImage( key ); if (image.valid()) { return true; } } else if (elevationLayer ) { GeoHeightField hf = elevationLayer->createHeightField(key, 0L); if (hf.valid()) { return true; } } // If we didn't produce a result but the key isn't within range then we should continue to // traverse the children b/c a min level was set. if (!_layer->isKeyInLegalRange(key)) { return true; } return false; }
/** Prepare the result image with the given instantiated net and the corresponding label image tiles */ void Analysis::prepareResultImage() { iNodeRoot_->setNewID(1); emit message("Preparing result map"); if (!iNodeRoot_->labelImage()) { qDebug("Analysis::prepareResultImage: no labelimage"); return; } int size_x = int ((geoImageList_->geoEast() - geoImageList_->geoWest()) / labelImageList_->maxResolution()); int size_y = int ((geoImageList_->geoNorth() - geoImageList_->geoSouth()) / labelImageList_->maxResolution()); GeoImage *img = new GeoImage("result.plm", "result", size_x, size_y, geoImageList_->geoWest(), geoImageList_->geoNorth(), geoImageList_->geoEast(), geoImageList_->geoSouth()); if (!img->mergeInto(*(iNodeRoot_->labelImage()), 0, iNodeRoot_->attributeInt("id"), iNodeRoot_->attributeInt("IDStart"))) { iNodeRoot_->attribute("status", "deleted"); } iNodeRoot_->mergeResultImage(*img); img->write(); map_ = img; emit sigMapView(iNodeRoot_, map_); #ifdef DEBUGMSG qDebug("Analysis::prepareResultImage: image=(%dx%d)", size_x, size_y); #endif }
GeoImage TextureCompositorTexArray::prepareImage( const GeoImage& layerImage, const GeoExtent& tileExtent, unsigned textureSize ) const { const osg::Image* image = layerImage.getImage(); if (!image) return GeoImage::INVALID; if (image->getPixelFormat() != GL_RGBA || image->getInternalTextureFormat() != GL_RGBA8 || image->s() != textureSize || image->t() != textureSize ) { // Because all tex2darray layers must be identical in format, let's use RGBA. osg::ref_ptr<osg::Image> newImage = ImageUtils::convertToRGBA8( image ); // TODO: revisit. For now let's just settle on 256 (again, all layers must be the same size) if ( image->s() != textureSize || image->t() != textureSize ) { osg::ref_ptr<osg::Image> resizedImage; if ( ImageUtils::resizeImage( newImage.get(), textureSize, textureSize, resizedImage ) ) newImage = resizedImage.get(); } return GeoImage( newImage.get(), layerImage.getExtent() ); } else { return layerImage; } }
bool CacheSeed::cacheTile(const MapFrame& mapf, const TileKey& key ) const { bool gotData = false; for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); i++ ) { ImageLayer* layer = i->get(); if ( layer->isKeyValid( key ) ) { GeoImage image = layer->createImage( key ); if ( image.valid() ) gotData = true; } } if ( mapf.elevationLayers().size() > 0 ) { osg::ref_ptr<osg::HeightField> hf; mapf.getHeightField( key, false, hf ); if ( hf.valid() ) gotData = true; } return gotData; }
bool handleTile(const TileKey& key) { bool ok = false; GeoImage image = _source->createImage(key); if ( image.valid() ) ok = _dest->storeImage(key, image.getImage(), 0L); return ok; }
osg::Image* createImage( const TileKey& key, ProgressCallback* progress ) { if ( !_image.valid() || key.getLevelOfDetail() > getMaxDataLevel() ) return NULL; GeoImage cropped = _image.crop( key.getExtent(), true, getPixelsPerTile(), getPixelsPerTile() ); return cropped.valid() ? cropped.takeImage() : 0L; }
/** return a list of included images*/ QStringList GeoImageList::list(QString type) { if (type.isEmpty()) return list_; QStringList list; QStringList::ConstIterator it; for (it = list_.begin(); it != list_.end(); ++it) { GeoImage *im = geoImage(*it); if (QString::compare(im->type(), type, Qt::CaseInsensitive) == 0) list += (*it); qDebug("GeoImageList::list(item=%s)", it->toLatin1().constData()); } return list; }
/** return a list of included images*/ QStringList GeoImageList::list(QString type) { if (type.isEmpty()) return list_; QStringList list; QStringList::ConstIterator it; for (it = list_.begin(); it != list_.end(); ++it) { GeoImage *im = geoImage(*it); if (qstricmp(im->type(), type) == 0) list += (*it); qDebug("GeoImageList::list(item=%s)", (const char *) (*it)); } return list; }
bool OSGTileFactory::createValidGeoImage(ImageLayer* layer, const TileKey& key, GeoImage& out_image, TileKey& out_actualTileKey, ProgressCallback* progress) { //TODO: Redo this to just grab images from the parent TerrainTiles //Try to create the image with the given key out_actualTileKey = key; while (out_actualTileKey.valid()) { if ( layer->isKeyValid(out_actualTileKey) ) { out_image = layer->createImage( out_actualTileKey, progress ); if ( out_image.valid() ) { return true; } } out_actualTileKey = out_actualTileKey.createParentKey(); } return false; }
void OceanCompositor::applyLayerUpdate(osg::StateSet* stateSet, UID layerUID, const GeoImage& preparedImage, const TileKey& tileKey, const TextureLayout& layout, osg::StateSet* parentStateSet) const { osg::Texture2D* tex = s_getTexture( stateSet, layerUID, layout, parentStateSet); if ( tex ) { osg::Image* image = preparedImage.getImage(); image->dirty(); // required for ensure the texture recognizes the image as new data tex->setImage( image ); // set up proper mipmapping filters: if (ImageUtils::isPowerOfTwo( image ) && !(!image->isMipmap() && ImageUtils::isCompressed(image)) ) { if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR_MIPMAP_LINEAR ) tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR ); } else if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR ) { tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR ); } } }
void TextureCompositorMultiTexture::applyLayerUpdate(osg::StateSet* stateSet, UID layerUID, const GeoImage& preparedImage, const TileKey& tileKey, const TextureLayout& layout, osg::StateSet* parentStateSet) const { osg::Texture2D* tex = s_getTexture( stateSet, layerUID, layout, parentStateSet, _minFilter, _magFilter); if ( tex ) { osg::Image* image = preparedImage.getImage(); image->dirty(); // required for ensure the texture recognizes the image as new data tex->setImage( image ); // set up proper mipmapping filters: if (_enableMipmapping && _enableMipmappingOnUpdatedTextures && ImageUtils::isPowerOfTwo( image ) && !(!image->isMipmap() && ImageUtils::isCompressed(image)) ) { if ( tex->getFilter(osg::Texture::MIN_FILTER) != _minFilter ) tex->setFilter( osg::Texture::MIN_FILTER, _minFilter ); } else if ( tex->getFilter(osg::Texture::MIN_FILTER) != osg::Texture::LINEAR ) { tex->setFilter( osg::Texture::MIN_FILTER, osg::Texture::LINEAR ); } bool lodBlending = layout.getSlot(layerUID, 1) >= 0; if (_enableMipmapping && _enableMipmappingOnUpdatedTextures && lodBlending ) { int slot = layout.getSlot(layerUID, 0); // update the timestamp on the image layer to support blending. float now = (float)osg::Timer::instance()->delta_s( osg::Timer::instance()->getStartTick(), osg::Timer::instance()->tick() ); ArrayUniform stampUniform( "osgearth_SlotStamp", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot() + 1 ); stampUniform.setElement( slot, now ); // set the texture matrix to properly position the blend (parent) texture osg::Matrix mat; if ( parentStateSet != 0L ) { unsigned tileX, tileY; tileKey.getTileXY(tileX, tileY); mat(0,0) = 0.5f; mat(1,1) = 0.5f; mat(3,0) = (float)(tileX % 2) * 0.5f; mat(3,1) = (float)(1 - tileY % 2) * 0.5f; } ArrayUniform texMatUniform( "osgearth_TexBlendMatrix", osg::Uniform::FLOAT_MAT4, stateSet, layout.getMaxUsedSlot() + 1 ); texMatUniform.setElement( slot, mat ); } } }
/** loads the given labelimage fname and returns a pointer to the new image. If the image was already requested, only a pointer to the image is returned and the reference counter of the image is incremented. */ GeoImage *GeoImageList::loadLabelImage(QString fname, QString key, float west, float north, float east, float south) { minMaxResUptodate_ = false; GeoImage *img = find(fname); if (img) return img->shallowCopy(); GeoImage *gi = new GeoImage(fname, key, west, north, east, south); #ifdef WIN32 if (gi == 0){ cout << "Out of Memory..7"; exit(1); } #endif insert(fname, gi); //insert read geoimage list_ += fname; //fill additional list of image names return gi; }
/** return the maximum resolution in the list of images */ float GeoImageList::maxResolution() { if (!minMaxResUptodate_) { minRes_ = -1; maxRes_ = 500; qDebug("GeoImageList::maxResolution"); Iterator it = begin(); for (; it!=end(); ++it) { GeoImage *img = it.value(); qDebug("%s: xres: %f yres: %f", img->filename().toLatin1().constData(), img->resolutionX(), img->resolutionY()); float res = img->resolutionX(); if (res > minRes_) minRes_ = res; if (res < maxRes_) maxRes_ = res; res = img->resolutionY(); if (res > minRes_) minRes_ = res; if (res < maxRes_) maxRes_ = res; } minMaxResUptodate_ = true; } return maxRes_; }
CustomColorLayerRef* OSGTileFactory::createImageLayer(const MapInfo& mapInfo, ImageLayer* layer, const TileKey& key, ProgressCallback* progress) { if ( !layer ) return 0L; GeoImage geoImage; //If the key is valid, try to get the image from the MapLayer bool keyValid = layer->isKeyValid( key ); if ( keyValid ) { geoImage = layer->createImage(key, progress); } else { //If the key is not valid, simply make a transparent tile geoImage = GeoImage(ImageUtils::createEmptyImage(), key.getExtent()); } if (geoImage.valid()) { osg::ref_ptr<GeoLocator> imgLocator = GeoLocator::createForKey( key, mapInfo ); if ( mapInfo.isGeocentric() ) imgLocator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); return new CustomColorLayerRef( CustomColorLayer( layer, geoImage.getImage(), imgLocator.get(), key.getLevelOfDetail(), key) ); } return NULL; }
bool CacheTileHandler::handleTile( const TileKey& key ) { ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() ); ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() ); // Just call createImage or createHeightField on the layer and the it will be cached! if (imageLayer) { GeoImage image = imageLayer->createImage( key ); if (image.valid()) { return true; } } else if (elevationLayer ) { GeoHeightField hf = elevationLayer->createHeightField( key ); if (hf.valid()) { return true; } } return false; }
/** merge inodes labelimage into the result image */ void INode::mergeResultImage(GeoImage & resultImg, RunLengthLabelImage &rlelabelimage) { qDebug("INode::mergeResultImage: name=%s", name_.latin1()); QListIterator < INode > it = QListIterator < INode > (children()); for (; it.current(); ++it) { INode *node = it.current(); if (node->status() == TRASH) continue; RunLengthLabelImage copylabelimage = rlelabelimage; if (node->labelImage() && node->attributeInt("id")) { // id==0 is background and should not be merged into the result image if (!resultImg.mergeInto(*(node->labelImage()), attributeInt("IDStart"), node->attributeInt("id"), node->attributeInt("IDStart"), copylabelimage)) node->attribute("status", "deleted"); } else { node->attribute("status", "no labelimage"); qDebug("INode::mergeResultImage: %s ommitted", (const char *) node->name()); } if (node->attributeInt("id")) { // id==0 is background and should not be merged into the result image node->mergeResultImage(resultImg, copylabelimage); } else { qDebug("INode::mergeResultImage: %s ommitted", (const char *) node->name()); } } }
void execute() { GeoImage geoImage; bool isFallbackData = false; // fetch the image from the layer, falling back on parent keys utils we are // able to find one that works. TileKey imageKey( _key ); while( !geoImage.valid() && imageKey.valid() && _layer->isKeyValid(imageKey) ) { geoImage = _layer->createImage( imageKey, 0L ); // TODO: include a progress callback? if ( !geoImage.valid() ) { imageKey = imageKey.createParentKey(); isFallbackData = true; } } GeoLocator* locator = 0L; if ( !geoImage.valid() ) { // no image found, so make an empty one (one pixel alpha). geoImage = GeoImage( ImageUtils::createEmptyImage(), _key.getExtent() ); locator = GeoLocator::createForKey( _key, *_mapInfo ); isFallbackData = true; } else { locator = GeoLocator::createForExtent(geoImage.getExtent(), *_mapInfo); } // add the color layer to the repo. _repo->add( CustomColorLayer( _layer, geoImage.getImage(), locator, _key.getLevelOfDetail(), _key, isFallbackData ) ); }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { TileKey key = *query.tileKey(); #if 0 // Debug Polygon* poly = new Polygon(); poly->push_back(key.getExtent().xMin(), key.getExtent().yMin()); poly->push_back(key.getExtent().xMax(), key.getExtent().yMin()); poly->push_back(key.getExtent().xMax(), key.getExtent().yMax()); poly->push_back(key.getExtent().xMin(), key.getExtent().yMax()); FeatureList features; Feature* feature = new Feature(poly, SpatialReference::create("wgs84")); features.push_back( feature ); return new FeatureListCursor( features ); #else osg::ref_ptr< osgEarth::ImageLayer > layer = query.getMap()->getImageLayerByName(*_options.layer()); if (layer.valid()) { GeoImage image = layer->createImage( key ); FeatureList features; if (image.valid()) { double pixWidth = key.getExtent().width() / (double)image.getImage()->s(); double pixHeight = key.getExtent().height() / (double)image.getImage()->t(); ImageUtils::PixelReader reader(image.getImage()); for (unsigned int r = 0; r < image.getImage()->t(); r++) { double y = key.getExtent().yMin() + (double)r * pixHeight; double minX = 0; double maxX = 0; float value = 0.0; for (unsigned int c = 0; c < image.getImage()->s(); c++) { double x = key.getExtent().xMin() + (double)c * pixWidth; osg::Vec4f color = reader(c, r); // Starting a new row. Initialize the values. if (c == 0) { minX = x; maxX = x + pixWidth; value = color.r(); } // Ending a row, finish the polygon. else if (c == image.getImage()->s() -1) { // Increment the maxX to finish the row. maxX = x + pixWidth; Polygon* poly = new Polygon(); poly->push_back(minX, y); poly->push_back(maxX, y); poly->push_back(maxX, y+pixHeight); poly->push_back(minX, y+pixHeight); Feature* feature = new Feature(poly, SpatialReference::create("wgs84")); feature->set(*_options.attribute(), value); features.push_back( feature ); minX = x; maxX = x + pixWidth; value = color.r(); } // The value is different, so complete the polygon and start a new one. else if (color.r() != value) { Polygon* poly = new Polygon(); poly->push_back(minX, y); poly->push_back(maxX, y); poly->push_back(maxX, y+pixHeight); poly->push_back(minX, y+pixHeight); Feature* feature = new Feature(poly, SpatialReference::create("wgs84")); feature->set(*_options.attribute(), value); features.push_back( feature ); minX = x; maxX = x + pixWidth; value = color.r(); } // The value is the same as the previous value, continue the polygon by increasing the maxX. else if (color.r() == value) { maxX = x + pixWidth; } } } if (!features.empty()) { //OE_NOTICE << LC << "Returning " << features.size() << " features" << std::endl; return new FeatureListCursor( features ); } } } else { OE_NOTICE << LC << "Couldn't get layer " << *_options.layer() << std::endl; } return 0; #endif }
osg::Image* CompositeTileSource::createImage(const TileKey& key, ProgressCallback* progress ) { ImageMixVector images; images.reserve(_imageLayers.size()); // Try to get an image from each of the layers for the given key. for (ImageLayerVector::const_iterator itr = _imageLayers.begin(); itr != _imageLayers.end(); ++itr) { ImageLayer* layer = itr->get(); ImageInfo imageInfo; imageInfo.dataInExtents = layer->getTileSource()->hasDataInExtent( key.getExtent() ); imageInfo.opacity = layer->getOpacity(); if (imageInfo.dataInExtents) { 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() || progress->needsRetry())) { OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl; return 0L; } } images.push_back(imageInfo); } // Determine the output texture size to use based on the image that were creatd. unsigned numValidImages = 0; osg::Vec2s textureSize; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& info = images[i]; if (info.image.valid()) { if (numValidImages == 0) { textureSize.set( info.image->s(), info.image->t()); } 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++) { ImageInfo& info = images[i]; ImageLayer* layer = _imageLayers[i].get(); if (!info.image.valid() && info.dataInExtents) { TileKey parentKey = key.createParentKey(); GeoImage image; while (!image.valid() && parentKey.valid()) { image = layer->createImage(parentKey, progress); if (image.valid()) { break; } // If the progress got cancelled or it needs a retry then return NULL to prevent this tile from being built and cached with incomplete or partial data. if (progress && (progress->isCanceled() || progress->needsRetry())) { OE_DEBUG << LC << " createImage was cancelled or needs retry for " << key.str() << std::endl; return 0L; } parentKey = parentKey.createParentKey(); } if (image.valid()) { // TODO: Bilinear options? bool bilinear = layer->isCoverage() ? false : true; GeoImage cropped = image.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), bilinear); info.image = cropped.getImage(); } } } } // Now finally create the output image. //Recompute the number of valid images numValidImages = 0; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& info = images[i]; if (info.image.valid()) numValidImages++; } if ( progress && progress->isCanceled() ) { return 0L; } else if ( numValidImages == 0 ) { return 0L; } 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++) { ImageInfo& info = images[i]; if (info.image.valid()) return info.image.release(); } return 0L; } else { osg::Image* result = 0; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& imageInfo = images[i]; if (!result) { if (imageInfo.image.valid()) { result = new osg::Image( *imageInfo.image.get()); } } else { if (imageInfo.image.valid()) { ImageUtils::mix( result, imageInfo.image.get(), imageInfo.opacity ); } } } return result; } }
osg::Image* CompositeTileSource::createImage(const TileKey& key, ProgressCallback* progress ) { ImageMixVector images; images.reserve( _options._components.size() ); for(CompositeTileSourceOptions::ComponentVector::const_iterator i = _options._components.begin(); i != _options._components.end(); ++i ) { if ( progress && progress->isCanceled() ) return 0L; ImageInfo imageInfo; imageInfo.dataInExtents = false; TileSource* source = i->_tileSourceInstance.get(); if ( source ) { //TODO: This duplicates code in ImageLayer::isKeyValid. Maybe should move that to TileSource::isKeyValid instead int minLevel = 0; int maxLevel = INT_MAX; if (i->_imageLayerOptions->minLevel().isSet()) { minLevel = i->_imageLayerOptions->minLevel().value(); } else if (i->_imageLayerOptions->minResolution().isSet()) { minLevel = source->getProfile()->getLevelOfDetailForHorizResolution( i->_imageLayerOptions->minResolution().value(), source->getPixelsPerTile()); } if (i->_imageLayerOptions->maxLevel().isSet()) { maxLevel = i->_imageLayerOptions->maxLevel().value(); } else if (i->_imageLayerOptions->maxResolution().isSet()) { maxLevel = source->getProfile()->getLevelOfDetailForHorizResolution( i->_imageLayerOptions->maxResolution().value(), source->getPixelsPerTile()); } // check that this source is within the level bounds: if (minLevel > (int)key.getLevelOfDetail() || maxLevel < (int)key.getLevelOfDetail() ) { continue; } //Only try to get data if the source actually has data if (source->hasDataInExtent( key.getExtent() ) ) { //We have data within these extents imageInfo.dataInExtents = true; if ( !source->getBlacklist()->contains( key.getTileId() ) ) { osg::ref_ptr< ImageLayerPreCacheOperation > preCacheOp; if ( i->_imageLayerOptions.isSet() ) { preCacheOp = new ImageLayerPreCacheOperation(); preCacheOp->_processor.init( i->_imageLayerOptions.value(), _dbOptions.get(), true ); } imageInfo.image = source->createImage( key, preCacheOp.get(), progress ); imageInfo.opacity = 1.0f; //If the image is not valid and the progress was not cancelled, blacklist if (!imageInfo.image.valid() && (!progress || !progress->isCanceled())) { //Add the tile to the blacklist OE_DEBUG << LC << "Adding tile " << key.str() << " to the blacklist" << std::endl; source->getBlacklist()->add( key.getTileId() ); } imageInfo.opacity = i->_imageLayerOptions.isSet() ? i->_imageLayerOptions->opacity().value() : 1.0f; } } else { OE_DEBUG << LC << "Source has no data at " << key.str() << std::endl; } } //Add the ImageInfo to the list images.push_back( imageInfo ); } unsigned numValidImages = 0; osg::Vec2s textureSize; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& info = images[i]; if (info.image.valid()) { if (numValidImages == 0) { textureSize.set( info.image->s(), info.image->t()); } numValidImages++; } } //Try to fallback on any empty images if we have some valid images but not valid images for ALL layers if (numValidImages > 0 && numValidImages < images.size()) { for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& info = images[i]; if (!info.image.valid() && info.dataInExtents) { TileKey parentKey = key.createParentKey(); TileSource* source = _options._components[i]._tileSourceInstance; if (source) { osg::ref_ptr< ImageLayerPreCacheOperation > preCacheOp; if ( _options._components[i]._imageLayerOptions.isSet() ) { preCacheOp = new ImageLayerPreCacheOperation(); preCacheOp->_processor.init( _options._components[i]._imageLayerOptions.value(), _dbOptions.get(), true ); } osg::ref_ptr< osg::Image > image; while (!image.valid() && parentKey.valid()) { image = source->createImage( parentKey, preCacheOp.get(), progress ); if (image.valid()) { break; } parentKey = parentKey.createParentKey(); } if (image.valid()) { //We got an image, but now we need to crop it to match the incoming key's extents GeoImage geoImage( image.get(), parentKey.getExtent()); GeoImage cropped = geoImage.crop( key.getExtent(), true, textureSize.x(), textureSize.y(), *source->_options.bilinearReprojection()); image = cropped.getImage(); } info.image = image.get(); } } } } //Recompute the number of valid images numValidImages = 0; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& info = images[i]; if (info.image.valid()) numValidImages++; } if ( progress && progress->isCanceled() ) { return 0L; } else if ( numValidImages == 0 ) { return 0L; } 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++) { ImageInfo& info = images[i]; if (info.image.valid()) return info.image.release(); } return 0L; } else { osg::Image* result = 0; for (unsigned int i = 0; i < images.size(); i++) { ImageInfo& imageInfo = images[i]; if (!result) { if (imageInfo.image.valid()) { result = new osg::Image( *imageInfo.image.get()); } } else { if (imageInfo.image.valid()) { ImageUtils::mix( result, imageInfo.image, imageInfo.opacity ); } } } return result; } }
GeoImage ImageLayer::createImageInNativeProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } GeoImage result; if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. result = createImageInKeyProfile( key, progress ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key, nativeKeys); // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { bool isFallback = false; GeoImage image = createImageInKeyProfile( *k, progress ); if ( image.valid() ) { mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // if we get EVEN ONE invalid tile, we have to abort because there will be // empty spots in the mosaic. (By "invalid" we mean a tile that could not // even be resolved through the fallback procedure.) return GeoImage::INVALID; //TODO: probably need to change this so the mosaic uses alpha. } } // bail out if we got nothing. if ( mosaic.getImages().size() > 0 ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); result = GeoImage( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); } } return result; }
/** read a list of GeoImage and the attributes through parser */ void GeoImageList::read(MLParser & parser) { minMaxResUptodate_ = false; qDebug("GeoImageList::read(parser)"); list_.clear(); QString keywords[] = { "geoimage", "geoimagelist", "" }; const MLTagTable nodeTagTable(keywords); const int TOK_GEOIMAGE = 1; const int TOK_GEOIMAGE_LIST = 2; int tag; #ifdef WIN32 geoNorth_ = -1e38F; geoSouth_ = 1e38F; geoWest_ = 1e38F; geoEast_ = -1e38F; #else geoNorth_ = -1e36; geoSouth_ = +1e36; geoWest_ = +1e36; geoEast_ = -1e36; #endif do { tag = parser.tag(nodeTagTable); switch (tag) { case TOK_GEOIMAGE:{ GeoImage *gi = new GeoImage(parser); //parse geoimage #ifdef WIN32 if (gi == 0){ cout << "Out of Memory..6"; exit(1); } #endif if (gi->geoNorth() > geoNorth_) geoNorth_ = gi->geoNorth(); if (gi->geoSouth() < geoSouth_) geoSouth_ = gi->geoSouth(); if (gi->geoEast() > geoEast_) geoEast_ = gi->geoEast(); if (gi->geoWest() < geoWest_) geoWest_ = gi->geoWest(); insert(*((*gi)["key"]), gi); //insert read geoimage list_ += *((*gi)["key"]); //fill additional list of image names break; } default:{ ArgDict *args = parser.args(); delete args; break; } } } while ((tag != MLParser::END_OF_FILE) && (tag != -TOK_GEOIMAGE_LIST)); //test images and create a structure for the images QString key; for (QStringList::Iterator it = list_.begin(); it != list_.end(); ++it) { key = *it; GeoImage *img = find(key); qDebug("## GeoImageList::read(parser)" + img->filename()); img->load(); } }
void OSGTerrainEngineNode::addImageLayer( ImageLayer* layerAdded ) { if ( !layerAdded ) return; if (!_isStreaming) { refresh(); } else { // visit all existing terrain tiles and inform each one of the new image layer: TileVector tiles; _terrain->getTiles( tiles ); for( TileVector::iterator itr = tiles.begin(); itr != tiles.end(); ++itr ) { Tile* tile = itr->get(); StreamingTile* streamingTile = 0L; GeoImage geoImage; bool needToUpdateImagery = false; int imageLOD = -1; if ( !_isStreaming || tile->getKey().getLevelOfDetail() == 1 ) { // in standard mode, or at the first LOD in seq/pre mode, fetch the image immediately. TileKey geoImageKey = tile->getKey(); _tileFactory->createValidGeoImage( layerAdded, tile->getKey(), geoImage, geoImageKey ); imageLOD = tile->getKey().getLevelOfDetail(); } else { // in seq/pre mode, set up a placeholder and mark the tile as dirty. geoImage = GeoImage(ImageUtils::createEmptyImage(), tile->getKey().getExtent() ); needToUpdateImagery = true; streamingTile = static_cast<StreamingTile*>(tile); } if (geoImage.valid()) { const MapInfo& mapInfo = _update_mapf->getMapInfo(); double img_min_lon, img_min_lat, img_max_lon, img_max_lat; geoImage.getExtent().getBounds(img_min_lon, img_min_lat, img_max_lon, img_max_lat); //Specify a new locator for the color with the coordinates of the TileKey that was actually used to create the image osg::ref_ptr<GeoLocator> img_locator = tile->getKey().getProfile()->getSRS()->createLocator( img_min_lon, img_min_lat, img_max_lon, img_max_lat, !mapInfo.isGeocentric() ); //Set the CS to geocentric if we are dealing with a geocentric map if ( mapInfo.isGeocentric() ) { img_locator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); } tile->setCustomColorLayer( CustomColorLayer( layerAdded, geoImage.getImage(), img_locator.get(), imageLOD, tile->getKey() ) ); // if necessary, tell the tile to queue up a new imagery request (since we // just installed a placeholder) if ( needToUpdateImagery ) { streamingTile->updateImagery( layerAdded, *_update_mapf, _tileFactory.get() ); } } else { // this can happen if there's no data in the new layer for the given tile. // we will rely on the driver to dump out a warning if this is an error. } tile->applyImmediateTileUpdate( TileUpdate::ADD_IMAGE_LAYER, layerAdded->getUID() ); } updateTextureCombining(); } }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Make sure the request is in range. if ( !isKeyInRange(key) ) { return GeoImage::INVALID; } GeoImage result; OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // the cache key combines the Key and the horizontal profile. std::string cacheKey = Stringify() << key.str() << "_" << key.getProfile()->getHorizSignature(); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); // Check the layer L2 cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult result = bin->readObject(cacheKey, 0L); if ( result.succeeded() ) return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent()); } // locate the cache bin for the target profile for this layer: CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if (getTileSource() || (cacheBin && policy.isCacheOnly())) { //nop = OK. } else { disable("Error: layer does not have a valid TileSource, cannot create image"); return GeoImage::INVALID; } // validate the existance of a valid layer profile (unless we're in cache-only mode, in which // case there is no layer profile) if ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile"); return GeoImage::INVALID; } osg::ref_ptr< osg::Image > cachedImage; // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readImage(cacheKey, 0L); if ( r.succeeded() ) { cachedImage = r.releaseImage(); ImageUtils::fixInternalFormat( cachedImage.get() ); bool expired = policy.isExpired(r.lastModifiedTime()); if (!expired) { OE_DEBUG << "Got cached image for " << key.str() << std::endl; return GeoImage( cachedImage.get(), key.getExtent() ); } else { OE_DEBUG << "Expired image for " << key.str() << std::endl; } } } // The data was not in the cache. If we are cache-only, fail sliently if ( policy.isCacheOnly() ) { // If it's cache only and we have an expired but cached image, just return it. if (cachedImage.valid()) { return GeoImage( cachedImage.get(), key.getExtent() ); } else { return GeoImage::INVALID; } } // Get an image from the underlying TileSource. result = createImageFromTileSource( key, progress ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::fixInternalFormat( result.getImage() ); } // memory cache first: if ( result.valid() && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getImage(), 0L); } // If we got a result, the cache is valid and we are caching in the map profile, // write to the map cache. if (result.valid() && cacheBin && policy.isCacheWriteable()) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write(cacheKey, result.getImage(), 0L); } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; // We couldn't get an image from the source. So see if we have an expired cached image if (cachedImage.valid()) { OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl; result = GeoImage( cachedImage.get(), key.getExtent()); } } return result; }
GeoImage ImageLayer::assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress) { GeoImage mosaicedImage, result; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( _runtimeOptions.edgeBufferRatio().isSet() ) { double ratio = _runtimeOptions.edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( key, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool retry = false; ImageMosaic mosaic; // keep track of failed tiles. std::vector<TileKey> failedKeys; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { GeoImage image = createImageFromTileSource( *k, progress ); if ( image.valid() ) { if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels. // This is not the smarter choice (in some case RGB would be sufficient) but // it ensure consistency between all images / layers. // // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images. // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint // if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // the tile source did not return a tile, so make a note of it. failedKeys.push_back( *k ); if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail. OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // We got at least one good tile, so go through the bad ones and try to fall back on // lower resolution data to fill in the gaps. The entire mosaic must be populated or // this qualifies as a bad tile. for(std::vector<TileKey>::iterator k = failedKeys.begin(); k != failedKeys.end(); ++k) { GeoImage image; for(TileKey parentKey = k->createParentKey(); parentKey.valid() && !image.valid(); parentKey = parentKey.createParentKey()) { image = createImageFromTileSource( parentKey, progress ); if ( image.valid() ) { GeoImage cropped; if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } cropped = image.crop( k->getExtent(), false, image.getImage()->s(), image.getImage()->t() ); } else { // TODO: may not work.... test; tilekey extent will <> cropped extent cropped = image.crop( k->getExtent(), true, image.getImage()->s(), image.getImage()->t(), false ); } // and queue it. mosaic.getImages().push_back( TileImage(cropped.getImage(), *k) ); } } if ( !image.valid() ) { // a tile completely failed, even with fallback. Eject. OE_DEBUG << LC << "Couldn't fallback on tiles for ImageMosaic" << std::endl; // let it go. The empty areas will be filled with alpha by ImageMosaic. } } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); } else { OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.driver()->bilinearReprojection() ); } // Process images with full alpha to properly support MP blending. if ( result.valid() && *_runtimeOptions.featherPixels() && !isCoverage() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
void TerrainTileModelFactory::addColorLayers(TerrainTileModel* model, const Map* map, const TerrainEngineRequirements* reqs, const TileKey& key, const CreateTileModelFilter& filter, ProgressCallback* progress) { OE_START_TIMER(fetch_image_layers); int order = 0; LayerVector layers; map->getLayers(layers); for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i) { Layer* layer = i->get(); if (layer->getRenderType() != layer->RENDERTYPE_TERRAIN_SURFACE) continue; if (!layer->getEnabled()) continue; if (!filter.accept(layer)) continue; ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer); if (imageLayer) { osg::Texture* tex = 0L; osg::Matrixf textureMatrix; if (imageLayer->isKeyInLegalRange(key) && imageLayer->mayHaveDataInExtent(key.getExtent())) { if (imageLayer->createTextureSupported()) { tex = imageLayer->createTexture( key, progress, textureMatrix ); } else { GeoImage geoImage = imageLayer->createImage( key, progress ); if ( geoImage.valid() ) { if ( imageLayer->isCoverage() ) tex = createCoverageTexture(geoImage.getImage(), imageLayer); else tex = createImageTexture(geoImage.getImage(), imageLayer); } } } // if this is the first LOD, and the engine requires that the first LOD // be populated, make an empty texture if we didn't get one. if (tex == 0L && _options.firstLOD() == key.getLOD() && reqs && reqs->fullDataAtFirstLodRequired()) { tex = _emptyTexture.get(); } if (tex) { tex->setName(model->getKey().str()); TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel(); layerModel->setImageLayer(imageLayer); layerModel->setTexture(tex); layerModel->setMatrix(new osg::RefMatrixf(textureMatrix)); model->colorLayers().push_back(layerModel); if (imageLayer->isShared()) { model->sharedLayers().push_back(layerModel); } if (imageLayer->isDynamic()) { model->setRequiresUpdateTraverse(true); } } } else // non-image kind of TILE layer: { TerrainTileColorLayerModel* colorModel = new TerrainTileColorLayerModel(); colorModel->setLayer(layer); model->colorLayers().push_back(colorModel); } } if (progress) progress->stats()["fetch_imagery_time"] += OE_STOP_TIMER(fetch_image_layers); }
void execute() { GeoImage geoImage; bool isFallbackData = false; bool useMercatorFastPath = _opt->enableMercatorFastPath() != false && _mapInfo->isGeocentric() && _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. bool autoFallback = _key.getLevelOfDetail() <= 1; TileKey imageKey( _key ); TileSource* tileSource = _layer->getTileSource(); const Profile* layerProfile = _layer->getProfile(); //Only try to get data from the source if it actually intersects the key extent bool hasDataInExtent = true; if (tileSource && layerProfile) { GeoExtent ext = _key.getExtent(); if (!layerProfile->getSRS()->isEquivalentTo( ext.getSRS())) { ext = layerProfile->clampAndTransformExtent( ext ); } hasDataInExtent = ext.isValid() && tileSource->hasDataInExtent( ext ); } if (hasDataInExtent) { while( !geoImage.valid() && imageKey.valid() && _layer->isKeyValid(imageKey) ) { if ( useMercatorFastPath ) { bool mercFallbackData = false; geoImage = _layer->createImageInNativeProfile( imageKey, 0L, autoFallback, mercFallbackData ); if ( geoImage.valid() && mercFallbackData ) { isFallbackData = true; } } else { geoImage = _layer->createImage( imageKey, 0L, autoFallback ); } if ( !geoImage.valid() ) { imageKey = imageKey.createParentKey(); isFallbackData = true; } } } GeoLocator* locator = 0L; if ( !geoImage.valid() ) { // no image found, so make an empty one (one pixel alpha). geoImage = GeoImage( ImageUtils::createEmptyImage(), _key.getExtent() ); locator = GeoLocator::createForKey( _key, *_mapInfo ); isFallbackData = true; } else { if ( useMercatorFastPath ) locator = new MercatorLocator(geoImage.getExtent()); else locator = GeoLocator::createForExtent(geoImage.getExtent(), *_mapInfo); } bool isStreaming = _opt->loadingPolicy()->mode() == LoadingPolicy::MODE_PREEMPTIVE || _opt->loadingPolicy()->mode() == LoadingPolicy::MODE_SEQUENTIAL; if (geoImage.getImage() && isStreaming) { // protected against multi threaded access. This is a requirement in sequential/preemptive mode, // for example. This used to be in TextureCompositorTexArray::prepareImage. // TODO: review whether this affects performance. geoImage.getImage()->setDataVariance( osg::Object::DYNAMIC ); } // add the color layer to the repo. _repo->add( CustomColorLayer( _layer, geoImage.getImage(), locator, _key.getLevelOfDetail(), _key, isFallbackData ) ); }
void TextureCompositorTexArray::applyLayerUpdate(osg::StateSet* stateSet, UID layerUID, const GeoImage& preparedImage, const TileKey& tileKey, const TextureLayout& layout, osg::StateSet* parentStateSet) const { GeoExtent tileExtent(tileKey.getExtent()); int slot = layout.getSlot( layerUID ); if ( slot < 0 ) return; // means the layer no longer exists // access the texture array, creating or growing it if necessary: osg::Texture2DArray* texture = s_getTexture( stateSet, layout, 0, textureSize() ); ensureSampler( stateSet, 0 ); // assign the new image at the proper position in the texture array. osg::Image* image = preparedImage.getImage(); assignImage(texture, slot, image); // update the region uniform to reflect the geo extent of the image: const GeoExtent& imageExtent = preparedImage.getExtent(); osg::Vec4 tileTransform; getImageTransform(tileExtent, imageExtent, tileTransform); // access the region uniform, creating or growing it if necessary: ArrayUniform regionUni( "region", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 ); if ( regionUni.isValid() ) { int layerOffset = slot * 8; for (int i = 0; i < 4; ++i) regionUni.setElement( layerOffset + i, tileTransform[i]); //region->dirty(); } if ( layout.isBlendingEnabled( layerUID ) && regionUni.isValid() ) { osg::Uniform* secondarySampler = ensureSampler( stateSet, 1 ); osg::Texture2DArray* parentTexture = 0; const unsigned parentLayerOffset = slot * 8 + 4; if ( parentStateSet ) { ArrayUniform parentRegion( "region", osg::Uniform::FLOAT, parentStateSet, layout.getMaxUsedSlot()+1 ); //osg::Uniform* parentRegion = s_getRegionUniform( parentStateSet, // layout ); GeoExtent parentExtent(tileKey.createParentKey().getExtent()); float widthRatio, heightRatio; parentRegion.getElement(slot * 8 + 2, widthRatio); parentRegion.getElement(slot * 8 + 3, heightRatio); float parentImageWidth = parentExtent.width() / widthRatio; float parentImageHeight = parentExtent.height() / heightRatio; float xRatio, yRatio; parentRegion.getElement(slot * 8, xRatio); parentRegion.getElement(slot * 8 + 1, yRatio); float ParentImageXmin = parentExtent.xMin() - xRatio * parentImageWidth; float ParentImageYmin = parentExtent.yMin() - yRatio * parentImageHeight; regionUni.setElement(parentLayerOffset, static_cast<float>((tileExtent.xMin() - ParentImageXmin) / parentImageWidth)); regionUni.setElement(parentLayerOffset + 1, static_cast<float>((tileExtent.yMin() - ParentImageYmin) / parentImageHeight)); regionUni.setElement(parentLayerOffset + 2, static_cast<float>(tileExtent.width() / parentImageWidth)); regionUni.setElement(parentLayerOffset + 3, static_cast<float>(tileExtent.height() / parentImageHeight)); //regionUni.dirty(); parentTexture = static_cast<osg::Texture2DArray*>(parentStateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE)); } else { // setting the parent transform values to -1 disabled blending for this layer. #hack -gw for (int i = 0; i < 4; ++i) regionUni.setElement(parentLayerOffset + i, tileTransform[i]); } if (parentTexture) stateSet->setTextureAttribute(1, parentTexture, osg::StateAttribute::ON); else secondarySampler->set(0); // update the timestamp on the image layer to support fade-in blending. float now = (float)osg::Timer::instance()->delta_s( osg::Timer::instance()->getStartTick(), osg::Timer::instance()->tick() ); ArrayUniform stampUniform( "osgearth_SlotStamp", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 ); stampUniform.setElement( slot, now ); } }