TileBuilder::Job* TileBuilder::createJob( const TileKey& key, Threading::MultiEvent& semaphore ) { Job* job = new Job( key, _map ); // create the image layer tasks: for( ImageLayerVector::const_iterator i = job->_mapf.imageLayers().begin(); i != job->_mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() && layer->isKeyValid(key) ) { ParallelTask<BuildColorLayer>* j = new ParallelTask<BuildColorLayer>( &semaphore ); j->init( key, layer, job->_mapf.getMapInfo(), _terrainOptions, job->_repo ); j->setPriority( -(float)key.getLevelOfDetail() ); job->_tasks.push_back( j ); } } // If we have elevation layers, start an elevation job as well. Otherwise just create an // empty one while we're waiting for the images to load. if ( job->_mapf.elevationLayers().size() > 0 ) { ParallelTask<BuildElevLayer>* ej = new ParallelTask<BuildElevLayer>( &semaphore ); ej->init( key, job->_mapf, _terrainOptions, job->_repo ); ej->setPriority( -(float)key.getLevelOfDetail() ); job->_tasks.push_back( ej ); } return job; }
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; }
void TileBuilder::createTile(const TileKey& key, bool parallelize, osg::ref_ptr<Tile>& out_tile, bool& out_hasRealData, bool& out_hasLodBlendedLayers ) { MapFrame mapf( _map, Map::MASKED_TERRAIN_LAYERS ); SourceRepo repo; // 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; const MapInfo& mapInfo = mapf.getMapInfo(); // If we need more than one layer, fetch them in parallel. // TODO: change the test based on isKeyValid total. if ( parallelize && (mapf.imageLayers().size() + mapf.elevationLayers().size() > 1) ) { // count the valid layers. int jobCount = 0; for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { if ( i->get()->isKeyValid( key ) ) ++jobCount; if ( i->get()->getImageLayerOptions().lodBlending() == true ) out_hasLodBlendedLayers = true; } if ( mapf.elevationLayers().size() > 0 ) ++jobCount; // A thread job monitoring event: Threading::MultiEvent semaphore( jobCount ); // Start the image layer jobs: for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->isKeyValid(key) ) { ParallelTask<BuildColorLayer>* j = new ParallelTask<BuildColorLayer>( &semaphore ); j->init( key, layer, mapInfo, _terrainOptions, repo ); j->setPriority( -(float)key.getLevelOfDetail() ); _service->add( j ); } } // If we have elevation layers, start an elevation job as well. Otherwise just create an // empty one while we're waiting for the images to load. if ( mapf.elevationLayers().size() > 0 ) { ParallelTask<BuildElevLayer>* ej = new ParallelTask<BuildElevLayer>( &semaphore ); ej->init( key, mapf, _terrainOptions, repo ); ej->setPriority( -(float)key.getLevelOfDetail() ); _service->add( ej ); } else { BuildElevLayer build; build.init( key, mapf, _terrainOptions, repo ); build.execute(); } // Wait for all the jobs to finish. semaphore.wait(); } // Fetch the image data serially: else { // gather all the image layers serially. for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); //if ( layer->isKeyValid(key) ) // Wrong. no guarantee key is in the same profile. if ( layer->getEnabled() ) { BuildColorLayer build; build.init( key, layer, mapInfo, _terrainOptions, repo ); build.execute(); if ( layer->getImageLayerOptions().lodBlending() == true ) out_hasLodBlendedLayers = true; } } // make an elevation layer. BuildElevLayer build; build.init( key, mapf, _terrainOptions, repo ); build.execute(); } // Bail out now if there's no data to be had. if ( repo._colorLayers.size() == 0 && !repo._elevLayer.getHFLayer() ) { return; } // OK we are making a tile, so if there's no heightfield yet, make an empty one. if ( !repo._elevLayer.getHFLayer() ) { osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 8, 8 ); //osg::HeightField* hf = key.getProfile()->getVerticalSRS()->createReferenceHeightField( key.getExtent(), 8, 8 ); osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf ); hfLayer->setLocator( GeoLocator::createForKey(key, mapInfo) ); repo._elevLayer = CustomElevLayer( hfLayer, true ); } // Now, if there are any color layers that did not get built, create them with an empty // image so the shaders have something to draw. osg::ref_ptr<osg::Image> emptyImage; osgTerrain::Locator* locator = repo._elevLayer.getHFLayer()->getLocator(); for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() && !layer->isKeyValid(key) ) { if ( !emptyImage.valid() ) emptyImage = ImageUtils::createEmptyImage(); repo.add( CustomColorLayer( layer, emptyImage.get(), locator, key.getLevelOfDetail(), key, true ) ); } } //osg::Vec3dArray* maskBounds = 0L; //osgEarth::MaskLayer* mask = mapf.getTerrainMaskLayer(); //if (mask) // maskBounds = mask->getOrCreateBoundary(); // Ready to create the actual tile. AssembleTile assemble; assemble.init( key, mapInfo, _terrainOptions, repo, mapf.terrainMaskLayers() ); assemble.execute(); if (!out_hasRealData) { // Check the results and see if we have any real data. for( ColorLayersByUID::const_iterator i = repo._colorLayers.begin(); i != repo._colorLayers.end(); ++i ) { if ( !i->second.isFallbackData() ) { out_hasRealData = true; break; } } } if ( !out_hasRealData && !repo._elevLayer.isFallbackData() ) { out_hasRealData = true; } out_tile = assemble._tile; }
void TileBuilder::finalizeJob(TileBuilder::Job* job, osg::ref_ptr<Tile>& out_tile, bool& out_hasRealData, bool& out_hasLodBlending) { SourceRepo& repo = job->_repo; out_hasRealData = false; out_hasLodBlending = false; // Bail out now if there's no data to be had. if ( repo._colorLayers.size() == 0 && !repo._elevLayer.getHFLayer() ) { return; } const TileKey& key = job->_key; const MapInfo& mapInfo = job->_mapf.getMapInfo(); // OK we are making a tile, so if there's no heightfield yet, make an empty one. if ( !repo._elevLayer.getHFLayer() ) { osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 8, 8 ); osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf ); hfLayer->setLocator( GeoLocator::createForKey(key, mapInfo) ); repo._elevLayer = CustomElevLayer( hfLayer, true ); } // Now, if there are any color layers that did not get built, create them with an empty // image so the shaders have something to draw. osg::ref_ptr<osg::Image> emptyImage; osgTerrain::Locator* locator = repo._elevLayer.getHFLayer()->getLocator(); for( ImageLayerVector::const_iterator i = job->_mapf.imageLayers().begin(); i != job->_mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() ) { if ( !layer->isKeyValid(key) ) { if ( !emptyImage.valid() ) emptyImage = ImageUtils::createEmptyImage(); repo.add( CustomColorLayer( i->get(), emptyImage.get(), locator, key.getLevelOfDetail(), key, true ) ); } if ( i->get()->getImageLayerOptions().lodBlending() == true ) out_hasLodBlending = true; } } // Ready to create the actual tile. AssembleTile assemble; assemble.init( key, mapInfo, _terrainOptions, repo ); assemble.execute(); // Check the results and see if we have any real data. for( ColorLayersByUID::const_iterator i = repo._colorLayers.begin(); i != repo._colorLayers.end(); ++i ) { if ( !i->second.isFallbackData() ) { out_hasRealData = true; break; } } if ( !out_hasRealData && !repo._elevLayer.isFallbackData() ) { out_hasRealData = true; } out_tile = assemble._tile; }
void TileModelFactory::createTileModel(const TileKey& key, osg::ref_ptr<TileModel>& out_model, bool& out_hasRealData, bool& out_hasLodBlendedLayers ) { MapFrame mapf( _map, Map::MASKED_TERRAIN_LAYERS ); const MapInfo& mapInfo = mapf.getMapInfo(); osg::ref_ptr<TileModel> model = new TileModel(); 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; // Fetch the image data and make color layers. for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() ) { BuildColorData build; build.init( key, layer, mapInfo, _terrainOptions, model.get() ); build.execute(); if ( layer->getImageLayerOptions().lodBlending() == true ) { out_hasLodBlendedLayers = true; } } } // make an elevation layer. BuildElevationData build; build.init( key, mapf, _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() ) { 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 ); } // Now, if there are any color layers that did not get built, create them with an empty // image so the shaders have something to draw. osg::ref_ptr<osg::Image> emptyImage; osgTerrain::Locator* locator = model->_elevationData.getHFLayer()->getLocator(); for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() && !layer->isKeyValid(key) ) { if ( !emptyImage.valid() ) emptyImage = ImageUtils::createEmptyImage(); model->_colorData[i->get()->getUID()] = TileModel::ColorData( layer, emptyImage.get(), locator, key.getLevelOfDetail(), key, true ); } } // Ready to create the actual tile. //AssembleTile assemble; //assemble.init( key, mapInfo, _terrainOptions, model.get(), mapf.terrainMaskLayers() ); //assemble.execute(); // 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) { // 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; } } } if ( !out_hasRealData && !model->_elevationData.isFallbackData() ) { out_hasRealData = true; } out_model = model.release(); //out_tile = assemble._node; }
osg::Node* OSGTileFactory::createPopulatedTile(const MapFrame& mapf, Terrain* terrain, const TileKey& key, bool wrapInPagedLOD, bool fallback, bool& validData ) { const MapInfo& mapInfo = mapf.getMapInfo(); bool isPlateCarre = !mapInfo.isGeocentric() && mapInfo.isGeographicSRS(); typedef std::vector<GeoImageData> GeoImageDataVector; GeoImageDataVector image_tiles; // Collect the image layers bool empty_map = mapf.imageLayers().size() == 0 && mapf.elevationLayers().size() == 0; // Create the images for the tile for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); GeoImageData imageData; // Only try to create images if the key is valid if ( layer->isKeyValid( key ) ) { imageData._image = layer->createImage( key ); imageData._layerUID = layer->getUID(); imageData._imageTileKey = key; } // always push images, even it they are empty, so that the image_tiles vector is one-to-one // with the imageLayers() vector. image_tiles.push_back( imageData ); } bool hasElevation = false; //Create the heightfield for the tile osg::ref_ptr<osg::HeightField> hf; if ( mapf.elevationLayers().size() > 0 ) { mapf.getHeightField( key, false, hf, 0L, _terrainOptions.elevationInterpolation().value()); } //If we are on the first LOD and we couldn't get a heightfield tile, just create an empty one. Otherwise you can run into the situation //where you could have an inset heightfield on one hemisphere and the whole other hemisphere won't show up. if ( mapInfo.isGeocentric() && key.getLevelOfDetail() <= 1 && !hf.valid()) { hf = createEmptyHeightField( key ); } hasElevation = hf.valid(); //Determine if we've created any images unsigned int numValidImages = 0; for (unsigned int i = 0; i < image_tiles.size(); ++i) { if (image_tiles[i]._image.valid()) numValidImages++; } //If we couldn't create any imagery or heightfields, bail out if (!hf.valid() && (numValidImages == 0) && !empty_map) { OE_DEBUG << LC << "Could not create any imagery or heightfields for " << key.str() <<". Not building tile" << std::endl; validData = false; //If we're not asked to fallback on previous LOD's and we have no data, return NULL if (!fallback) { return NULL; } } else { validData = true; } //Try to interpolate any missing image layers from parent tiles for (unsigned int i = 0; i < mapf.imageLayers().size(); i++ ) { if (!image_tiles[i]._image.valid()) { if (mapf.getImageLayerAt(i)->isKeyValid(key)) { //If the key was valid and we have no image, then something possibly went wrong with the image creation such as a server being busy. createValidGeoImage(mapf.getImageLayerAt(i), key, image_tiles[i]._image, image_tiles[i]._imageTileKey); } //If we still couldn't create an image, either something is really wrong or the key wasn't valid, so just create a transparent placeholder image if (!image_tiles[i]._image.valid()) { //If the image is not valid, create an empty texture as a placeholder image_tiles[i]._image = GeoImage(ImageUtils::createEmptyImage(), key.getExtent()); image_tiles[i]._imageTileKey = key; } } } //Fill in missing heightfield information from parent tiles if (!hf.valid()) { //We have no heightfield sources, if ( mapf.elevationLayers().size() == 0 ) { hf = createEmptyHeightField( key ); } else { //Try to get a heightfield again, but this time fallback on parent tiles if ( mapf.getHeightField( key, true, hf, 0L, _terrainOptions.elevationInterpolation().value() ) ) { hasElevation = true; } else { //We couldn't get any heightfield, so just create an empty one. hf = createEmptyHeightField( key ); } } } // In a Plate Carre tesselation, scale the heightfield elevations from meters to degrees if ( isPlateCarre ) { HeightFieldUtils::scaleHeightFieldToDegrees( hf.get() ); } osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( key, mapInfo ); osgTerrain::HeightFieldLayer* hf_layer = new osgTerrain::HeightFieldLayer(); hf_layer->setLocator( locator.get() ); hf_layer->setHeightField( hf.get() ); bool isStreaming = _terrainOptions.loadingPolicy()->mode() == LoadingPolicy::MODE_SEQUENTIAL || _terrainOptions.loadingPolicy()->mode() == LoadingPolicy::MODE_PREEMPTIVE; Tile* tile = terrain->createTile( key, locator.get() ); tile->setTerrainTechnique( terrain->cloneTechnique() ); tile->setVerticalScale( _terrainOptions.verticalScale().value() ); //tile->setLocator( locator.get() ); tile->setElevationLayer( hf_layer ); //tile->setRequiresNormals( true ); tile->setDataVariance(osg::Object::DYNAMIC); #if 0 //Attach an updatecallback to normalize the edges of TerrainTiles. if (hasElevation && _terrainOptions.normalizeEdges().get() ) { tile->setUpdateCallback(new TerrainTileEdgeNormalizerUpdateCallback()); tile->setDataVariance(osg::Object::DYNAMIC); } #endif //Assign the terrain system to the TerrainTile. //It is very important the terrain system is set while the MapConfig's sourceMutex is locked. //This registers the terrain tile so that adding/removing layers are always in sync. If you don't do this //you can end up with a situation where the database pager is waiting to merge a tile, then a layer is added, then //the tile is finally merged and is out of sync. double min_units_per_pixel = DBL_MAX; #if 0 // create contour layer: if (map->getContourTransferFunction() != NULL) { osgTerrain::ContourLayer* contourLayer(new osgTerrain::ContourLayer(map->getContourTransferFunction())); contourLayer->setMagFilter(_terrainOptions.getContourMagFilter().value()); contourLayer->setMinFilter(_terrainOptions.getContourMinFilter().value()); tile->setCustomColorLayer(layer,contourLayer); //TODO: need layerUID, not layer index here -GW ++layer; } #endif for (unsigned int i = 0; i < image_tiles.size(); ++i) { if (image_tiles[i]._image.valid()) { const GeoImage& geo_image = image_tiles[i]._image; double img_xmin, img_ymin, img_xmax, img_ymax; geo_image.getExtent().getBounds( img_xmin, img_ymin, img_xmax, img_ymax ); //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 = key.getProfile()->getSRS()->createLocator( img_xmin, img_ymin, img_xmax, img_ymax, isPlateCarre ); if ( mapInfo.isGeocentric() ) img_locator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); tile->setCustomColorLayer( CustomColorLayer( mapf.getImageLayerAt(i), geo_image.getImage(), img_locator.get(), key.getLevelOfDetail(), key) ); double upp = geo_image.getUnitsPerPixel(); // Scale the units per pixel to degrees if the image is mercator (and the key is geo) if ( geo_image.getSRS()->isMercator() && key.getExtent().getSRS()->isGeographic() ) upp *= 1.0f/111319.0f; min_units_per_pixel = osg::minimum(upp, min_units_per_pixel); } } osg::BoundingSphere bs = tile->getBound(); double max_range = 1e10; double radius = bs.radius(); #if 1 double min_range = radius * _terrainOptions.minTileRangeFactor().get(); //osg::LOD::RangeMode mode = osg::LOD::DISTANCE_FROM_EYE_POINT; #else double width = key.getExtent().width(); if (min_units_per_pixel == DBL_MAX) min_units_per_pixel = width/256.0; double min_range = (width / min_units_per_pixel) * _terrainOptions.getMinTileRangeFactor(); //osg::LOD::RangeMode mode = osg::LOD::PIXEL_SIZE_ON_SCREEN; #endif // a skirt hides cracks when transitioning between LODs: hf->setSkirtHeight(radius * _terrainOptions.heightFieldSkirtRatio().get() ); // for now, cluster culling does not work for CUBE rendering //bool isCube = mapInfo.isCube(); //map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE; if ( mapInfo.isGeocentric() && !mapInfo.isCube() ) { //TODO: Work on cluster culling computation for cube faces osg::ClusterCullingCallback* ccc = createClusterCullingCallback(tile, locator->getEllipsoidModel() ); tile->setCullCallback( ccc ); } // Wait until now, when the tile is fully baked, to assign the terrain to the tile. // Placeholder tiles might try to locate this tile as an ancestor, and access its layers // and locators...so they must be intact before making this tile available via setTerrain. // // If there's already a placeholder tile registered, this will be ignored. If there isn't, // this will register the new tile. tile->attachToTerrain( terrain ); //tile->setTerrain( terrain ); //terrain->registerTile( tile ); if ( isStreaming && key.getLevelOfDetail() > 0 ) { static_cast<StreamingTile*>(tile)->setHasElevationHint( hasElevation ); } osg::Node* result = 0L; if (wrapInPagedLOD) { // create a PLOD so we can keep subdividing: osg::PagedLOD* plod = new osg::PagedLOD(); plod->setCenter( bs.center() ); plod->addChild( tile, min_range, max_range ); std::string filename = createURI( _engineId, key ); //map->getId(), key ); //Only add the next tile if it hasn't been blacklisted bool isBlacklisted = osgEarth::Registry::instance()->isBlacklisted( filename ); if (!isBlacklisted && key.getLevelOfDetail() < (unsigned int)getTerrainOptions().maxLOD().value() && validData ) { plod->setFileName( 1, filename ); plod->setRange( 1, 0.0, min_range ); } else { plod->setRange( 0, 0, FLT_MAX ); } #if USE_FILELOCATIONCALLBACK osgDB::Options* options = new osgDB::Options; options->setFileLocationCallback( new FileLocationCallback() ); plod->setDatabaseOptions( options ); #endif result = plod; if ( isStreaming ) result->addCullCallback( new PopulateStreamingTileDataCallback( _cull_thread_mapf ) ); } else { result = tile; } return result; }
bool MapFrame::isCached( const osgEarth::TileKey& key ) const { const Profile* mapProfile = getProfile(); //Check the imagery layers for( ImageLayerVector::const_iterator i = imageLayers().begin(); i != imageLayers().end(); i++ ) { ImageLayer* layer = i->get(); osg::ref_ptr< Cache > cache = layer->getCache(); if ( !cache.valid() || !layer->getProfile() ) return false; std::vector< TileKey > keys; if ( mapProfile->isEquivalentTo( layer->getProfile() ) ) { keys.push_back( key ); } else { layer->getProfile()->getIntersectingTiles( key, keys ); } for (unsigned int j = 0; j < keys.size(); ++j) { if ( layer->isKeyValid( keys[j] ) ) { if ( !cache->isCached( keys[j], layer->getCacheSpec() ) ) { return false; } } } } for( ElevationLayerVector::const_iterator i = elevationLayers().begin(); i != elevationLayers().end(); ++i ) { ElevationLayer* layer = i->get(); osg::ref_ptr< Cache > cache = layer->getCache(); if ( !cache.valid() || !layer->getProfile() ) return false; std::vector<TileKey> keys; if ( mapProfile->isEquivalentTo( layer->getProfile() ) ) { keys.push_back( key ); } else { layer->getProfile()->getIntersectingTiles( key, keys ); } for (unsigned int j = 0; j < keys.size(); ++j) { if ( layer->isKeyValid( keys[j] ) ) { if ( !cache->isCached( keys[j], layer->getCacheSpec() ) ) { return false; } } } } return true; }