osgEarth 2.1.1
|
00001 /* --*-c++-*-- */ 00002 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph 00003 * Copyright 2008-2010 Pelican Mapping 00004 * http://osgearth.org 00005 * 00006 * osgEarth is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU Lesser General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU Lesser General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Lesser General Public License 00017 * along with this program. If not, see <http://www.gnu.org/licenses/> 00018 */ 00019 00020 #include <osgEarthFeatures/FeatureModelGraph> 00021 #include <osgEarthFeatures/CropFilter> 00022 #include <osgEarth/ThreadingUtils> 00023 #include <osgEarth/NodeUtils> 00024 #include <osgEarth/ElevationQuery> 00025 #include <osg/PagedLOD> 00026 #include <osg/ProxyNode> 00027 #include <osgDB/FileNameUtils> 00028 #include <osgDB/ReaderWriter> 00029 #include <osgDB/WriteFile> 00030 #include <osgUtil/Optimizer> 00031 00032 #define LC "[FeatureModelGraph] " 00033 00034 using namespace osgEarth; 00035 using namespace osgEarth::Features; 00036 using namespace osgEarth::Symbology; 00037 00038 #undef USE_PROXY_NODE_FOR_TESTING 00039 00040 //--------------------------------------------------------------------------- 00041 00042 // pseudo-loader for paging in feature tiles for a FeatureModelGraph. 00043 00044 namespace 00045 { 00046 UID _uid = 0; 00047 Threading::ReadWriteMutex _fmgMutex; 00048 std::map<UID, FeatureModelGraph*> _fmgRegistry; 00049 00050 static std::string s_makeURI( UID uid, unsigned lod, unsigned x, unsigned y ) 00051 { 00052 std::stringstream buf; 00053 buf << uid << "." << lod << "_" << x << "_" << y << ".osgearth_pseudo_fmg"; 00054 std::string str = buf.str(); 00055 return str; 00056 } 00057 00058 osg::Group* createPagedNode( const osg::BoundingSphered& bs, const std::string& uri, float minRange, float maxRange ) 00059 { 00060 #ifdef USE_PROXY_NODE_FOR_TESTING 00061 osg::ProxyNode* p = new osg::ProxyNode(); 00062 p->setCenter( bs.center() ); 00063 p->setRadius( bs.radius() ); 00064 p->setFileName( 0, uri ); 00065 #else 00066 osg::PagedLOD* p = new osg::PagedLOD(); 00067 p->setCenter( bs.center() ); 00068 p->setRadius( bs.radius() ); 00069 p->setFileName( 0, uri ); 00070 p->setRange( 0, minRange, maxRange ); 00071 #endif 00072 return p; 00073 } 00074 } 00075 00076 struct osgEarthFeatureModelPseudoLoader : public osgDB::ReaderWriter 00077 { 00078 osgEarthFeatureModelPseudoLoader() 00079 { 00080 supportsExtension( "osgearth_pseudo_fmg", "Feature model pseudo-loader" ); 00081 } 00082 00083 const char* className() 00084 { // override 00085 return "osgEarth Feature Model Pseudo-Loader"; 00086 } 00087 00088 ReadResult readNode(const std::string& uri, const Options* options) const 00089 { 00090 if ( !acceptsExtension( osgDB::getLowerCaseFileExtension(uri) ) ) 00091 return ReadResult::FILE_NOT_HANDLED; 00092 00093 UID uid; 00094 unsigned lod, x, y; 00095 sscanf( uri.c_str(), "%u.%d_%d_%d.%*s", &uid, &lod, &x, &y ); 00096 00097 FeatureModelGraph* graph = getGraph(uid); 00098 if ( graph ) 00099 return ReadResult( graph->load( lod, x, y, uri ) ); 00100 else 00101 return ReadResult::ERROR_IN_READING_FILE; 00102 } 00103 00104 static UID registerGraph( FeatureModelGraph* graph ) 00105 { 00106 Threading::ScopedWriteLock lock( _fmgMutex ); 00107 UID key = ++_uid; 00108 _fmgRegistry[key] = graph; 00109 return key; 00110 } 00111 00112 static void unregisterGraph( UID uid ) 00113 { 00114 Threading::ScopedWriteLock lock( _fmgMutex ); 00115 _fmgRegistry.erase( uid ); 00116 } 00117 00118 static FeatureModelGraph* getGraph( UID uid ) 00119 { 00120 Threading::ScopedReadLock lock( _fmgMutex ); 00121 std::map<UID, FeatureModelGraph*>::const_iterator i = _fmgRegistry.find( uid ); 00122 return i != _fmgRegistry.end() ? i->second : 0L; 00123 } 00124 }; 00125 00126 REGISTER_OSGPLUGIN(osgearth_pseudo_fmg, osgEarthFeatureModelPseudoLoader); 00127 00128 namespace 00129 { 00130 GeoExtent 00131 s_getTileExtent( unsigned lod, unsigned tileX, unsigned tileY, const GeoExtent& fullExtent ) 00132 { 00133 double w = fullExtent.width(); 00134 double h = fullExtent.height(); 00135 for( unsigned i=0; i<lod; ++i ) { 00136 w *= 0.5; 00137 h *= 0.5; 00138 } 00139 return GeoExtent( 00140 fullExtent.getSRS(), 00141 fullExtent.xMin() + w * (double)tileX, 00142 fullExtent.yMin() + h * (double)tileY, 00143 fullExtent.xMin() + w * (double)(tileX+1), 00144 fullExtent.yMin() + h * (double)(tileY+1) ); 00145 } 00146 } 00147 00148 00149 //--------------------------------------------------------------------------- 00150 00151 FeatureModelGraph::FeatureModelGraph(FeatureSource* source, 00152 const FeatureModelSourceOptions& options, 00153 FeatureNodeFactory* factory, 00154 Session* session) : 00155 _source ( source ), 00156 _options ( options ), 00157 _factory ( factory ), 00158 _session ( session ), 00159 _dirty ( false ) 00160 { 00161 _uid = osgEarthFeatureModelPseudoLoader::registerGraph( this ); 00162 00163 // install the stylesheet in the session if it doesn't already have one. 00164 if ( !session->styles() ) 00165 session->setStyles( _options.styles().get() ); 00166 00167 // initialize lighting on the graph, if necessary. 00168 osg::StateSet* stateSet = getOrCreateStateSet(); 00169 00170 if ( _options.enableLighting().isSet() ) 00171 stateSet->setMode( GL_LIGHTING, *_options.enableLighting() ? 1 : 0 ); 00172 00173 // Calculate the usable extent (in both feature and map coordinates) and bounds. 00174 const Profile* mapProfile = session->getMapInfo().getProfile(); 00175 00176 // the part of the feature extent that will fit on the map (in map coords): 00177 _usableMapExtent = mapProfile->clampAndTransformExtent( 00178 _source->getFeatureProfile()->getExtent(), 00179 &_featureExtentClamped ); 00180 00181 // same, back into feature coords: 00182 _usableFeatureExtent = _usableMapExtent.transform( _source->getFeatureProfile()->getSRS() ); 00183 00184 // world-space bounds of the feature layer 00185 _fullWorldBound = getBoundInWorldCoords( _usableMapExtent, 0L ); 00186 00187 // whether to request tiles from the source (if available). if the source is tiled, but the 00188 // user manually specified schema levels, don't use the tiles. 00189 _useTiledSource = _source->getFeatureProfile()->getTiled(); 00190 00191 if ( options.levels().isSet() && options.levels()->getNumLevels() > 0 ) 00192 { 00193 // the user provided a custom levels setup, so don't use the tiled source (which 00194 // provides its own levels setup) 00195 _useTiledSource = false; 00196 00197 // for each custom level, calculate the best LOD match and store it in the level 00198 // layout data. We will use this information later when constructing the SG in 00199 // the pager. 00200 for( unsigned i = 0; i < options.levels()->getNumLevels(); ++i ) 00201 { 00202 const FeatureLevel* level = options.levels()->getLevel( i ); 00203 unsigned lod = options.levels()->chooseLOD( *level, _fullWorldBound.radius() ); 00204 _lodmap.resize( lod+1, 0L ); 00205 _lodmap[lod] = level; 00206 00207 OE_INFO << LC << source->getName() 00208 << ": F.Level max=" << level->maxRange() << ", min=" << level->minRange() 00209 << ", LOD=" << lod << std::endl; 00210 } 00211 } 00212 00213 setNumChildrenRequiringUpdateTraversal( 1 ); 00214 00215 redraw(); 00216 } 00217 00218 FeatureModelGraph::~FeatureModelGraph() 00219 { 00220 osgEarthFeatureModelPseudoLoader::unregisterGraph( _uid ); 00221 } 00222 00223 void 00224 FeatureModelGraph::dirty() 00225 { 00226 _dirty = true; 00227 } 00228 00229 osg::BoundingSphered 00230 FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent, 00231 const MapFrame* mapf ) const 00232 { 00233 osg::Vec3d center, corner; 00234 double z = 0.0; 00235 GeoExtent workingExtent; 00236 00237 if ( extent.getSRS()->isEquivalentTo( _usableMapExtent.getSRS() ) ) 00238 { 00239 workingExtent = extent; 00240 } 00241 else 00242 { 00243 workingExtent = extent.transform( _usableMapExtent.getSRS() ); // safe. 00244 } 00245 00246 workingExtent.getCentroid( center.x(), center.y() ); 00247 00248 if ( mapf ) 00249 { 00250 // note: use the lowest possible resolution to speed up queries 00251 ElevationQuery query( *mapf ); 00252 query.getElevation( center, mapf->getProfile()->getSRS(), center.z(), DBL_MAX ); 00253 } 00254 00255 corner.x() = workingExtent.xMin(); 00256 corner.y() = workingExtent.yMin(); 00257 corner.z() = z; 00258 00259 if ( _session->getMapInfo().isGeocentric() ) 00260 { 00261 workingExtent.getSRS()->transformToECEF( center, center ); 00262 workingExtent.getSRS()->transformToECEF( corner, corner ); 00263 } 00264 00265 return osg::BoundingSphered( center, (center-corner).length() ); 00266 } 00267 00268 void 00269 FeatureModelGraph::setupPaging() 00270 { 00271 // calculate the bounds of the full data extent: 00272 MapFrame mapf = _session->createMapFrame(); 00273 osg::BoundingSphered bs = getBoundInWorldCoords( _usableMapExtent, &mapf ); 00274 00275 // calculate the max range for the top-level PLOD: 00276 float maxRange = bs.radius() * _options.levels()->tileSizeFactor().value(); 00277 00278 // build the URI for the top-level paged LOD: 00279 std::string uri = s_makeURI( _uid, 0, 0, 0 ); 00280 00281 // bulid the top level Paged LOD: 00282 osg::Group* pagedNode = createPagedNode( bs, uri, 0.0f, maxRange ); 00283 this->addChild( pagedNode ); 00284 } 00285 00286 osg::Node* 00287 FeatureModelGraph::load( unsigned lod, unsigned tileX, unsigned tileY, const std::string& uri ) 00288 { 00289 OE_DEBUG << LC 00290 << "load: " << lod << "_" << tileX << "_" << tileY << std::endl; 00291 00292 osg::Group* result = 0L; 00293 00294 if ( _useTiledSource ) 00295 { 00296 // A "tiled" source has a pre-generted tile hierarchy, but no range information. 00297 // We will be calculating the LOD ranges here. 00298 00299 // The extent of this tile: 00300 GeoExtent tileExtent = s_getTileExtent( lod, tileX, tileY, _usableFeatureExtent ); 00301 00302 // Calculate the bounds of this new tile: 00303 MapFrame mapf = _session->createMapFrame(); 00304 osg::BoundingSphered tileBound = getBoundInWorldCoords( tileExtent, &mapf ); 00305 00306 // Apply the tile range multiplier to calculate a max camera range. The max range is 00307 // the geographic radius of the tile times the multiplier. 00308 float tileFactor = _options.levels().isSet() ? _options.levels()->tileSizeFactor().get() : 15.0f; 00309 double maxRange = tileBound.radius() * tileFactor; 00310 FeatureLevel level( 0, maxRange ); 00311 00312 // Construct a tile key that will be used to query the source for this tile. 00313 TileKey key(lod, tileX, tileY, _source->getFeatureProfile()->getProfile()); 00314 osg::Group* geometry = build( level, tileExtent, &key ); 00315 result = geometry; 00316 00317 if (lod < _source->getFeatureProfile()->getMaxLevel()) 00318 { 00319 // see if there are any more levels. If so, build some pagedlods to bring the 00320 // next level in. 00321 FeatureLevel nextLevel(0, maxRange/2.0); 00322 00323 osg::ref_ptr<osg::Group> group = new osg::Group(); 00324 00325 // calculate the LOD of the next level: 00326 if ( lod+1 != ~0 ) 00327 { 00328 MapFrame mapf = _session->createMapFrame(); 00329 buildSubTilePagedLODs( lod, tileX, tileY, &mapf, group.get() ); 00330 00331 // slap the geometry in there afterwards, if there is any 00332 if ( geometry ) 00333 group->addChild( geometry ); 00334 00335 result = group.release(); 00336 } 00337 } 00338 } 00339 00340 else if ( !_options.levels().isSet() || _options.levels()->getNumLevels() == 0 ) 00341 { 00342 // This is a non-tiled data source that has NO level details. In this case, 00343 // we simply want to load all features at once and make them visible at 00344 // maximum camera range. 00345 FeatureLevel all( 0.0f, FLT_MAX ); 00346 result = build( all, GeoExtent::INVALID, 0 ); 00347 } 00348 00349 else if ( lod < _lodmap.size() ) 00350 { 00351 // This path computes the SG for a model graph with explicity-defined levels of 00352 // detail. We already calculated the LOD level map in setupPaging(). If the 00353 // current LOD points to an actual FeatureLevel, we build the geometry for that 00354 // level in the tile. 00355 00356 osg::Group* geometry = 0L; 00357 const FeatureLevel* level = _lodmap[lod]; 00358 if ( level ) 00359 { 00360 // There exists a real data level at this LOD. So build the geometry that will 00361 // represent this tile. 00362 GeoExtent tileExtent = 00363 lod > 0 ? 00364 s_getTileExtent( lod, tileX, tileY, _usableFeatureExtent ) : 00365 _usableFeatureExtent; 00366 00367 geometry = build( *level, tileExtent, 0 ); 00368 result = geometry; 00369 } 00370 00371 if ( lod < _lodmap.size()-1 ) 00372 { 00373 // There are more populated levels below this one. So build the subtile 00374 // PagedLODs that will load them. 00375 osg::ref_ptr<osg::Group> group = new osg::Group(); 00376 00377 MapFrame mapf = _session->createMapFrame(); 00378 buildSubTilePagedLODs( lod, tileX, tileY, &mapf, group.get() ); 00379 00380 if ( geometry ) 00381 group->addChild( geometry ); 00382 00383 result = group.release(); 00384 } 00385 } 00386 00387 if ( !result ) 00388 { 00389 // If the read resulting in nothing, create an empty group so that the read 00390 // (technically) succeeds and the pager won't try to load the null child 00391 // over and over. 00392 result = new osg::Group(); 00393 } 00394 else 00395 { 00396 RemoveEmptyGroupsVisitor::run( result ); 00397 } 00398 00399 if ( result->getNumChildren() == 0 ) 00400 { 00401 // if the result group contains no data, blacklist it so we never try to load it again. 00402 Threading::ScopedWriteLock exclusiveLock( _blacklistMutex ); 00403 _blacklist.insert( uri ); 00404 OE_DEBUG << LC << "Blacklisting: " << uri << std::endl; 00405 } 00406 00407 return result; 00408 } 00409 00410 00411 void 00412 FeatureModelGraph::buildSubTilePagedLODs(unsigned parentLOD, 00413 unsigned parentTileX, 00414 unsigned parentTileY, 00415 const MapFrame* mapf, 00416 osg::Group* parent) 00417 { 00418 unsigned subtileLOD = parentLOD + 1; 00419 unsigned subtileX = parentTileX * 2; 00420 unsigned subtileY = parentTileY * 2; 00421 00422 // make a paged LOD for each subtile: 00423 for( unsigned u = subtileX; u <= subtileX + 1; ++u ) 00424 { 00425 for( unsigned v = subtileY; v <= subtileY + 1; ++v ) 00426 { 00427 GeoExtent subtileFeatureExtent = s_getTileExtent( subtileLOD, u, v, _usableFeatureExtent ); 00428 osg::BoundingSphered subtile_bs = getBoundInWorldCoords( subtileFeatureExtent, mapf ); 00429 00430 // Camera range for the PLODs. This should always be sufficient because 00431 // the max range of a FeatureLevel below this will, by definition, have a max range 00432 // less than or equal to this number -- based on how the LODs were chosen in 00433 // setupPaging. 00434 float maxRange = subtile_bs.radius() * _options.levels()->tileSizeFactor().value(); 00435 00436 std::string uri = s_makeURI( _uid, subtileLOD, u, v ); 00437 00438 // check the blacklist to make sure we haven't unsuccessfully tried 00439 // this URI before 00440 bool blacklisted = false; 00441 { 00442 Threading::ScopedReadLock sharedLock( _blacklistMutex ); 00443 blacklisted = _blacklist.find( uri ) != _blacklist.end(); 00444 } 00445 00446 if ( !blacklisted ) 00447 { 00448 OE_DEBUG << LC << " " << uri 00449 << std::fixed 00450 << "; center = " << subtile_bs.center().x() << "," << subtile_bs.center().y() << "," << subtile_bs.center().z() 00451 << "; radius = " << subtile_bs.radius() 00452 << std::endl; 00453 00454 osg::Group* pagedNode = createPagedNode( subtile_bs, uri, 0.0f, maxRange ); 00455 parent->addChild( pagedNode ); 00456 } 00457 } 00458 } 00459 } 00460 00461 osg::Group* 00462 FeatureModelGraph::build( const FeatureLevel& level, const GeoExtent& extent, const TileKey* key ) 00463 { 00464 osg::ref_ptr<osg::Group> group = new osg::Group(); 00465 00466 // form the baseline query, which does a spatial query based on the working extent. 00467 Query query; 00468 if ( extent.isValid() ) 00469 query.bounds() = extent.bounds(); 00470 00471 // add a tile key to the query if there is one, to support TFS-style queries 00472 if ( key ) 00473 query.tileKey() = *key; 00474 00475 // now, go through any level-based selectors. 00476 const StyleSelectorVector& levelSelectors = level.selectors(); 00477 00478 // if there are none, just build once with the default style and query. 00479 if ( levelSelectors.size() == 0 ) 00480 { 00481 // attempt to glean the style from the feature source name: 00482 const Style style = *_session->styles()->getStyle( *_source->getFeatureSourceOptions().name() ); 00483 00484 osg::Node* node = build( style, query, extent ); 00485 if ( node ) 00486 group->addChild( node ); 00487 } 00488 00489 else 00490 { 00491 for( StyleSelectorVector::const_iterator i = levelSelectors.begin(); i != levelSelectors.end(); ++i ) 00492 { 00493 const StyleSelector& selector = *i; 00494 00495 // fetch the selector's style: 00496 const Style* selectorStyle = _session->styles()->getStyle( selector.getSelectedStyleName() ); 00497 00498 // combine the selector's query, if it has one: 00499 Query selectorQuery = 00500 selector.query().isSet() ? query.combineWith( *selector.query() ) : query; 00501 00502 osg::Node* node = build( *selectorStyle, selectorQuery, extent ); 00503 if ( node ) 00504 group->addChild( node ); 00505 } 00506 } 00507 00508 if ( group->getNumChildren() > 0 ) 00509 { 00510 // account for a min-range here. 00511 if ( level.minRange() > 0.0f ) 00512 { 00513 osg::LOD* lod = new osg::LOD(); 00514 lod->addChild( group.get(), level.minRange(), FLT_MAX ); 00515 group = lod; 00516 } 00517 00518 if ( _session->getMapInfo().isGeocentric() && _options.clusterCulling() == true ) 00519 { 00520 const GeoExtent& ccExtent = extent.isValid() ? extent : _source->getFeatureProfile()->getExtent(); 00521 if ( ccExtent.isValid() ) 00522 { 00523 // if the extent is more than 90 degrees, bail 00524 GeoExtent geodeticExtent = ccExtent.transform( ccExtent.getSRS()->getGeographicSRS() ); 00525 if ( geodeticExtent.width() < 90.0 && geodeticExtent.height() < 90.0 ) 00526 { 00527 #if 1 00528 // get the geocentric tile center: 00529 osg::Vec3d tileCenter; 00530 ccExtent.getCentroid( tileCenter.x(), tileCenter.y() ); 00531 osg::Vec3d centerECEF; 00532 ccExtent.getSRS()->transformToECEF( tileCenter, centerECEF ); 00533 00534 osg::NodeCallback* ccc = ClusterCullerFactory::create( group.get(), centerECEF ); 00535 if ( ccc ) 00536 group->addCullCallback( ccc ); 00537 #endif 00538 } 00539 } 00540 } 00541 00542 return group.release(); 00543 } 00544 00545 else 00546 { 00547 return 0L; 00548 } 00549 } 00550 00551 osg::Group* 00552 FeatureModelGraph::build( const Style& baseStyle, const Query& baseQuery, const GeoExtent& workingExtent ) 00553 { 00554 osg::ref_ptr<osg::Group> group = new osg::Group(); 00555 00556 if ( _source->hasEmbeddedStyles() ) 00557 { 00558 const FeatureProfile* profile = _source->getFeatureProfile(); 00559 00560 // each feature has its own style, so use that and ignore the style catalog. 00561 osg::ref_ptr<FeatureCursor> cursor = _source->createFeatureCursor( baseQuery ); 00562 while( cursor->hasMore() ) 00563 { 00564 Feature* feature = cursor->nextFeature(); 00565 if ( feature ) 00566 { 00567 FeatureList list; 00568 list.push_back( feature ); 00569 osg::ref_ptr<FeatureCursor> cursor = new FeatureListCursor(list); 00570 00571 FilterContext context( _session.get(), _source->getFeatureProfile(), workingExtent ); 00572 00573 // note: gridding is not supported for embedded styles. 00574 osg::ref_ptr<osg::Node> node; 00575 00576 // Get the Group that parents all features of this particular style. Note, this 00577 // might be NULL if the factory does not support style groups. 00578 osg::Group* styleGroup = _factory->getOrCreateStyleGroup(*feature->style(), _session.get()); 00579 if ( styleGroup ) 00580 { 00581 if ( !group->containsNode( styleGroup ) ) 00582 group->addChild( styleGroup ); 00583 } 00584 00585 if ( _factory->createOrUpdateNode( cursor.get(), *feature->style(), context, node ) ) 00586 { 00587 if ( node.valid() ) 00588 { 00589 if ( styleGroup ) 00590 styleGroup->addChild( node.get() ); 00591 else 00592 group->addChild( node.get() ); 00593 } 00594 } 00595 } 00596 } 00597 } 00598 00599 else 00600 { 00601 const StyleSheet* styles = _session->styles(); 00602 00603 // if we have selectors, sort the features into style groups and create a node for each group. 00604 if ( styles->selectors().size() > 0 ) 00605 { 00606 for( StyleSelectorList::const_iterator i = styles->selectors().begin(); i != styles->selectors().end(); ++i ) 00607 { 00608 // pull the selected style... 00609 const StyleSelector& sel = *i; 00610 00611 // combine the selection style with the incoming base style: 00612 Style selectedStyle = *styles->getStyle( sel.getSelectedStyleName() ); 00613 Style combinedStyle = baseStyle.combineWith( selectedStyle ); 00614 00615 // .. and merge it's query into the existing query 00616 Query combinedQuery = baseQuery.combineWith( *sel.query() ); 00617 00618 // then create the node. 00619 osg::Group* styleGroup = createNodeForStyle( combinedStyle, combinedQuery ); 00620 if ( styleGroup && !group->containsNode(styleGroup) ) 00621 group->addChild( styleGroup ); 00622 } 00623 } 00624 00625 // otherwise, render all the features with a single style 00626 else 00627 { 00628 Style combinedStyle = baseStyle; 00629 00630 // if there's no base style defined, choose a "default" style from the stylesheet. 00631 if ( baseStyle.empty() ) 00632 combinedStyle = *styles->getDefaultStyle(); 00633 00634 osg::Group* styleGroup = createNodeForStyle( combinedStyle, baseQuery ); 00635 if ( styleGroup && !group->containsNode(styleGroup) ) 00636 group->addChild( styleGroup ); 00637 } 00638 } 00639 00640 return group->getNumChildren() > 0 ? group.release() : 0L; 00641 } 00642 00643 osg::Group* 00644 FeatureModelGraph::createNodeForStyle(const Style& style, const Query& query) 00645 { 00646 osg::Group* styleGroup = 0L; 00647 00648 // the profile of the features 00649 const FeatureProfile* profile = _source->getFeatureProfile(); 00650 00651 // get the extent of the full set of feature data: 00652 const GeoExtent& extent = profile->getExtent(); 00653 00654 // query the feature source: 00655 osg::ref_ptr<FeatureCursor> cursor = _source->createFeatureCursor( query ); 00656 00657 if ( cursor->hasMore() ) 00658 { 00659 Bounds cellBounds = 00660 query.bounds().isSet() ? *query.bounds() : extent.bounds(); 00661 00662 FilterContext context( _session.get(), profile, GeoExtent(profile->getSRS(), cellBounds) ); 00663 00664 // start by culling our feature list to the working extent. By default, this is done by 00665 // checking feature centroids. But the user can override this to crop feature geometry to 00666 // the cell boundaries. 00667 FeatureList workingSet; 00668 cursor->fill( workingSet ); 00669 00670 CropFilter crop( 00671 _options.levels().isSet() && _options.levels()->cropFeatures() == true ? 00672 CropFilter::METHOD_CROPPING : CropFilter::METHOD_CENTROID ); 00673 context = crop.push( workingSet, context ); 00674 00675 // next, if the usable extent is less than the full extent (i.e. we had to clamp the feature 00676 // extent to fit on the map), calculate the extent of the features in this tile and 00677 // crop to the map extent if necessary. (Note, if cropFeatures was set to true, this is 00678 // already done) 00679 if ( _featureExtentClamped && _options.levels().isSet() && _options.levels()->cropFeatures() == false ) 00680 { 00681 context.extent() = _usableFeatureExtent; 00682 CropFilter crop2( CropFilter::METHOD_CROPPING ); 00683 context = crop2.push( workingSet, context ); 00684 } 00685 00686 if ( workingSet.size() > 0 ) 00687 { 00688 // next ask the implementation to construct OSG geometry for the cell features. 00689 osg::ref_ptr<osg::Node> node; 00690 00691 osg::ref_ptr<FeatureCursor> newCursor = new FeatureListCursor(workingSet); 00692 00693 if ( _factory->createOrUpdateNode( newCursor.get(), style, context, node ) ) 00694 { 00695 if ( !styleGroup ) 00696 styleGroup = _factory->getOrCreateStyleGroup( style, _session.get() ); 00697 00698 // if it returned a node, add it. (it doesn't necessarily have to) 00699 if ( node.valid() ) 00700 styleGroup->addChild( node.get() ); 00701 } 00702 } 00703 00704 CacheStats stats = context.resourceCache()->getSkinStats(); 00705 OE_DEBUG << LC << "Resource Cache skins: " 00706 << " num=" << stats._entries << ", max=" << stats._maxEntries 00707 << ", queries=" << stats._queries << ", hits=" << (100.0f*stats._hitRatio) << "%" 00708 << std::endl; 00709 00710 } 00711 00712 00713 return styleGroup; 00714 } 00715 00716 void 00717 FeatureModelGraph::traverse(osg::NodeVisitor& nv) 00718 { 00719 if ( nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) 00720 { 00721 if (_source->outOfSyncWith(_revision) || _dirty) 00722 { 00723 redraw(); 00724 } 00725 } 00726 osg::Group::traverse(nv); 00727 } 00728 00729 void 00730 FeatureModelGraph::redraw() 00731 { 00732 removeChildren( 0, getNumChildren() ); 00733 // if there's a display schema in place, set up for quadtree paging. 00734 if ( _options.levels().isSet() || _useTiledSource ) //_source->getFeatureProfile()->getTiled() ) 00735 { 00736 setupPaging(); 00737 } 00738 else 00739 { 00740 FeatureLevel defaultLevel( 0.0f, FLT_MAX ); 00741 00742 //Remove all current children 00743 osg::Node* node = build( defaultLevel, GeoExtent::INVALID, 0 ); 00744 if ( node ) 00745 addChild( node ); 00746 } 00747 00748 _source->sync( _revision ); 00749 _dirty = false; 00750 } 00751 00752 void 00753 FeatureModelGraph::setStyles( StyleSheet* styles ) 00754 { 00755 _session->setStyles( styles ); 00756 dirty(); 00757 }