osgEarth 2.1.1

/home/cube/sources/osgearth/src/osgEarthUtil/Graticule.cpp

Go to the documentation of this file.
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 #include <osgEarthUtil/Graticule>
00020 #include <osgEarthUtil/AutoClipPlaneHandler>
00021 #include <osgEarthFeatures/BuildGeometryFilter>
00022 #include <osgEarthFeatures/TransformFilter>
00023 #include <osgEarthFeatures/ResampleFilter>
00024 #include <osgEarthSymbology/Geometry>
00025 #include <osgEarth/Registry>
00026 #include <osgEarth/FindNode>
00027 #include <osgEarth/Utils>
00028 #include <OpenThreads/Mutex>
00029 #include <OpenThreads/ScopedLock>
00030 #include <osg/PagedLOD>
00031 #include <osg/ProxyNode>
00032 #include <osg/MatrixTransform>
00033 #include <osg/Depth>
00034 #include <osg/Program>
00035 #include <osg/LineStipple>
00036 #include <osg/ClusterCullingCallback>
00037 #include <osgDB/FileNameUtils>
00038 #include <osgUtil/Optimizer>
00039 #include <osgText/Text>
00040 #include <sstream>
00041 #include <iomanip>
00042 
00043 using namespace osgEarth;
00044 using namespace osgEarth::Util;
00045 using namespace osgEarth::Features;
00046 using namespace osgEarth::Symbology;
00047 using namespace OpenThreads;
00048 
00049 static Mutex s_graticuleMutex;
00050 typedef std::map<unsigned int, osg::ref_ptr<Graticule> > GraticuleRegistry;
00051 static GraticuleRegistry s_graticuleRegistry;
00052 
00053 #define GRATICLE_EXTENSION "osgearthutil_graticule"
00054 #define TEXT_MARKER "t"
00055 #define GRID_MARKER "g"
00056 
00057 //---------------------------------------------------------------------------
00058 
00059 namespace
00060 {
00061     char s_vertexShader[] =
00062         "varying vec3 Normal; \n"
00063         "void main(void) \n"
00064         "{ \n"
00065         "    Normal = normalize( gl_NormalMatrix * gl_Normal ); \n"
00066         "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
00067         "    gl_FrontColor = gl_Color; \n"
00068         "} \n";
00069 
00070     char s_fragmentShader[] =
00071         "varying vec3 Normal; \n"
00072         "void main(void) \n"
00073         "{ \n"
00074         "    gl_FragColor = gl_Color; \n"
00075         "} \n";
00076 }
00077 
00078 //---------------------------------------------------------------------------
00079 
00080 Graticule::Graticule( const Map* map ) :
00081 _autoLevels( true ),
00082 _map( map ),
00083 _textColor( 1,1,0,1 )
00084 {
00085     // safely generate a unique ID for this graticule:
00086     _id = Registry::instance()->createUID();
00087     {
00088         ScopedLock<Mutex> lock( s_graticuleMutex );
00089         s_graticuleRegistry[_id] = this;
00090     }
00091 
00092     setLineColor( osg::Vec4f(1,1,1,0.7) );
00093     setTextColor( osg::Vec4f(1,1,0,1) );
00094 
00095     if ( _map->isGeocentric() )
00096     {
00097         double r = map->getProfile()->getSRS()->getEllipsoid()->getRadiusEquator();
00098 
00099         int x=8, y=4;
00100         double d = 3.5*r;
00101         double lw=0.15;
00102         addLevel( FLT_MAX, x, y, lw );
00103         for(int i=0; i<9; i++)
00104         {
00105             x *= 2, y *= 2;
00106             lw *= 0.5;
00107             d *= 0.5;
00108             addLevel( r+d, x, y, lw );
00109         }
00110     }
00111 
00112     // Prime the grid:
00113     {
00114         std::stringstream buf;
00115         buf << "0_" << _id << "." << GRID_MARKER << "." << GRATICLE_EXTENSION;
00116         std::string bufStr = buf.str();
00117         osg::ProxyNode* proxy = new osg::ProxyNode();
00118         proxy->setFileName( 0, bufStr );
00119         proxy->setCenterMode( osg::ProxyNode::USER_DEFINED_CENTER );
00120         proxy->setCenter( osg::Vec3(0,0,0) );
00121         proxy->setRadius( 1e10 );
00122         this->addChild( proxy );
00123     }
00124 
00125     // Prime the text:
00126     {
00127         std::stringstream buf;
00128         buf << "0_" << _id << "." << TEXT_MARKER << "." << GRATICLE_EXTENSION;
00129         std::string bufStr = buf.str();
00130 
00131         osg::ProxyNode* proxy = new osg::ProxyNode();
00132         proxy->setFileName( 0, bufStr );
00133         proxy->setCenterMode( osg::ProxyNode::USER_DEFINED_CENTER );
00134         proxy->setCenter( osg::Vec3(0,0,0) );
00135         proxy->setRadius( 1e10 );
00136 
00137         this->addChild( proxy );
00138     }
00139 
00140     osg::StateSet* set = this->getOrCreateStateSet();
00141     set->setRenderBinDetails( 9999, "RenderBin" );
00142     set->setAttributeAndModes( 
00143         new osg::Depth( osg::Depth::ALWAYS ), 
00144         osg::StateAttribute::ON | osg::StateAttribute::PROTECTED );
00145     set->setMode( GL_LIGHTING, 0 );
00146 
00147     //osg::Program* program = new osg::Program();
00148     //program->addShader( new osg::Shader( osg::Shader::VERTEX, s_vertexShader ) );
00149     //program->addShader( new osg::Shader( osg::Shader::FRAGMENT, s_fragmentShader ) );
00150     //set->setAttributeAndModes( program, osg::StateAttribute::ON );
00151 
00152     this->addEventCallback( new AutoClipPlaneCallback( _map.get() ) );
00153 }
00154 
00155 void
00156 Graticule::addLevel( float maxRange, unsigned int cellsX, unsigned int cellsY, double lineWidth )
00157 {
00158     if ( _autoLevels )
00159     {
00160         _autoLevels = false;
00161         _levels.clear();
00162     }
00163 
00164     Level level;
00165     level._maxRange = maxRange;
00166     level._cellsX = cellsX;
00167     level._cellsY = cellsY;
00168     level._lineWidth = lineWidth;
00169 
00170     for( std::vector<Level>::iterator i = _levels.begin(); i != _levels.end(); ++i ) 
00171     {
00172         if ( maxRange > i->_maxRange )
00173         {
00174             _levels.insert( i, level );
00175             return;
00176         }
00177     }
00178     _levels.push_back( level );
00179 }
00180 
00181 bool
00182 Graticule::getLevel( unsigned int level, Graticule::Level& out_level ) const
00183 {
00184     if ( level < _levels.size() )
00185     {
00186         out_level = _levels[level];
00187         return true;
00188     }
00189     else
00190     {
00191         return false;
00192     }
00193 }
00194 
00195 namespace
00196 {
00197     Geometry*
00198     createCellGeometry( const GeoExtent& tex, double lw, const GeoExtent& profEx, bool isGeocentric )
00199     {            
00200         LineString* geom = 0L;
00201 
00202         if ( tex.yMin() == profEx.yMin() )
00203         {
00204             geom = new LineString(2);
00205             geom->push_back( osg::Vec3d( tex.xMin(), tex.yMax(), 0 ) );
00206             geom->push_back( osg::Vec3d( tex.xMin(), tex.yMin(), 0 ) );
00207         }
00208         else
00209         {
00210             geom = new LineString(3);
00211             geom->push_back( osg::Vec3d( tex.xMin(), tex.yMax(), 0 ) );
00212             geom->push_back( osg::Vec3d( tex.xMin(), tex.yMin(), 0 ) );
00213             geom->push_back( osg::Vec3d( tex.xMax(), tex.yMin(), 0 ) );
00214         }
00215 
00216         return geom;
00217     }
00218 
00219     struct HardCodeCellBoundCB : public osg::Node::ComputeBoundingSphereCallback
00220     {
00221         HardCodeCellBoundCB( const osg::BoundingSphere& bs ) : _bs(bs) { }
00222         virtual osg::BoundingSphere computeBound(const osg::Node&) const { return _bs; }
00223         osg::BoundingSphere _bs;
00224     };
00225 
00226     osg::Node*
00227     createTextTransform(double x, double y, double value, 
00228                         const osg::EllipsoidModel* ell, 
00229                         float size, const osg::Vec4f& color,
00230                         float rotation =0.0f )
00231     {    
00232         osg::Vec3d pos;
00233         if ( ell ) // is geocentric
00234         {
00235             ell->convertLatLongHeightToXYZ(
00236                 osg::DegreesToRadians( y ),
00237                 osg::DegreesToRadians( x ),
00238                 0,
00239                 pos.x(), pos.y(), pos.z() );
00240         }
00241         else
00242         {
00243             pos.set( x, y, 0 );
00244         }
00245 
00246         osgText::Text* t = new osgText::Text();
00247         t->setFont( "fonts/arial.ttf" );
00248         t->setAlignment( osgText::Text::CENTER_BOTTOM );
00249         t->setCharacterSizeMode( osgText::Text::SCREEN_COORDS );
00250         t->setCharacterSize( size );
00251         t->setBackdropType( osgText::Text::OUTLINE );
00252         t->setBackdropColor( osg::Vec4f(0,0,0,1) );
00253         t->setColor( color );
00254 
00255         std::stringstream buf;
00256         buf << std::fixed << std::setprecision(3) << value;
00257         std::string bufStr = buf.str();
00258         t->setText( bufStr );
00259 
00260         if ( rotation != 0.0f ) 
00261         {
00262             osg::Quat rot;
00263             rot.makeRotate( osg::DegreesToRadians(rotation), 0, 0, 1 );
00264             t->setRotation( rot );
00265         }
00266 
00267         osg::Geode* geode = new osg::Geode();
00268         geode->addDrawable( t );
00269 
00270         osg::Matrixd L2W;
00271         ell->computeLocalToWorldTransformFromXYZ(
00272             pos.x(), pos.y(), pos.z(), L2W );
00273 
00274         osg::MatrixTransform* xform = new osg::MatrixTransform();
00275         xform->setMatrix( L2W );
00276         xform->addChild( geode );     
00277 
00278         // in geocentric mode, install a plane culler
00279         if ( ell )
00280             xform->setCullCallback( new CullNodeByNormal(pos) );
00281 
00282         return xform;
00283     }
00284 }
00285 
00286 void
00287 Graticule::setLineColor( const osg::Vec4f& color )
00288 {
00289     _lineStyle.getOrCreateSymbol<LineSymbol>()->stroke()->color() = color;
00290 }
00291 
00292 osg::Node*
00293 Graticule::createGridLevel( unsigned int levelNum ) const
00294 {
00295     if ( !_map->isGeocentric() )
00296     {
00297         OE_WARN << "Graticule: only supports geocentric maps" << std::endl;
00298         return 0L;
00299     }
00300 
00301     Graticule::Level level;
00302     if ( !getLevel( levelNum, level ) )
00303         return 0L;
00304 
00305     OE_DEBUG << "Graticule: creating grid level " << levelNum << std::endl;
00306 
00307     osg::Group* group = new osg::Group();
00308 
00309     const Profile* mapProfile = _map->getProfile();
00310     const GeoExtent& pex = mapProfile->getExtent();
00311 
00312     double tw = pex.width() / (double)level._cellsX;
00313     double th = pex.height() / (double)level._cellsY;
00314 
00315     for( unsigned int x=0; x<level._cellsX; ++x )
00316     {
00317         for( unsigned int y=0; y<level._cellsY; ++y )
00318         {
00319             GeoExtent tex(
00320                 mapProfile->getSRS(),
00321                 pex.xMin() + tw * (double)x,
00322                 pex.yMin() + th * (double)y,
00323                 pex.xMin() + tw * (double)(x+1),
00324                 pex.yMin() + th * (double)(y+1) );
00325 
00326             Geometry* geom = createCellGeometry( tex, level._lineWidth, pex, _map->isGeocentric() );
00327 
00328             Feature* feature = new Feature();
00329             feature->setGeometry( geom );
00330             FeatureList features;
00331             features.push_back( feature );
00332 
00333             FilterContext cx;
00334             cx.profile() = new FeatureProfile( tex );
00335             cx.isGeocentric() = _map->isGeocentric();
00336 
00337             if ( _map->isGeocentric() )
00338             {
00339                 // We need to make sure that on a round globe, the points are sampled such that
00340                 // long segments follow the curvature of the earth.
00341                 ResampleFilter resample;
00342                 resample.maxLength() = tex.width() / 10.0;
00343                 cx = resample.push( features, cx );
00344             }
00345 
00346             TransformFilter xform( mapProfile->getSRS() );
00347             xform.setMakeGeocentric( _map->isGeocentric() );
00348             xform.setLocalizeCoordinates( true );
00349             cx = xform.push( features, cx );
00350 
00351             osg::ref_ptr<osg::Node> output;
00352             BuildGeometryFilter bg;
00353             bg.setStyle( _lineStyle );
00354             //cx = bg.push( features, cx );
00355             output = bg.push( features, cx ); //.getNode();
00356 
00357             if ( cx.isGeocentric() )
00358             {
00359                 // get the geocentric control point:
00360                 double cplon, cplat, cpx, cpy, cpz;
00361                 tex.getCentroid( cplon, cplat );
00362                 tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(
00363                     osg::DegreesToRadians( cplat ), osg::DegreesToRadians( cplon ), 0.0, cpx, cpy, cpz );
00364                 osg::Vec3 controlPoint(cpx, cpy, cpz);
00365 
00366                 // get the horizon point:
00367                 tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ(
00368                     osg::DegreesToRadians( tex.yMin() ), osg::DegreesToRadians( tex.xMin() ), 0.0,
00369                     cpx, cpy, cpz );
00370                 osg::Vec3 horizonPoint(cpx, cpy, cpz);
00371 
00372                 // the deviation is the dot product of the control vector and the vector from the
00373                 // control point to the horizon point.
00374                 osg::Vec3 controlPointNorm = controlPoint; controlPointNorm.normalize();
00375                 osg::Vec3 horizonVecNorm = horizonPoint - controlPoint; horizonVecNorm.normalize();                
00376                 float deviation = controlPointNorm * horizonVecNorm;
00377 
00378                 // construct the culling callback using the deviation.
00379                 osg::ClusterCullingCallback* ccc = new osg::ClusterCullingCallback();
00380                 ccc->set( controlPoint, controlPointNorm, deviation, (controlPoint-horizonPoint).length() );
00381 
00382                 // need a new group, because never put a cluster culler on a matrixtransform (doesn't work)
00383                 osg::Group* me = new osg::Group();
00384                 me->setCullCallback( ccc );
00385                 me->addChild( output.get() );
00386                 output = me;
00387             }
00388 
00389             group->addChild( output.get() );
00390         }
00391     }
00392 
00393     // organize it for better culling
00394     osgUtil::Optimizer opt;
00395     opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS );
00396 
00397     osg::Node* result = group;
00398 
00399     if ( levelNum < getNumLevels() )
00400     {
00401         Graticule::Level nextLevel;
00402         if ( getLevel( levelNum+1, nextLevel ) )
00403         {
00404             osg::PagedLOD* plod = new osg::PagedLOD();
00405             plod->addChild( group, nextLevel._maxRange, level._maxRange );
00406             std::stringstream buf;
00407             buf << levelNum+1 << "_" << getID() << "." << GRID_MARKER << "." << GRATICLE_EXTENSION;
00408             std::string bufStr = buf.str();
00409             plod->setFileName( 1, bufStr );
00410             plod->setRange( 1, 0, nextLevel._maxRange );
00411             result = plod;
00412         }
00413     }
00414 
00415     return result;
00416 }
00417 
00418 osg::Node*
00419 Graticule::createTextLevel( unsigned int levelNum ) const
00420 {
00421     if ( !_map->isGeocentric() )
00422     {
00423         OE_WARN << "Graticule: only supports geocentric maps" << std::endl;
00424         return 0L;
00425     }
00426 
00427     Graticule::Level level;
00428     if ( !getLevel( levelNum, level ) )
00429         return 0L;
00430 
00431     OE_DEBUG << "Graticule: creating text level " << levelNum << std::endl;
00432 
00433     osg::Group* group = new osg::Group();
00434 
00435     const Profile* mapProfile = _map->getProfile();
00436     const GeoExtent& pex = mapProfile->getExtent();
00437 
00438     double tw = pex.width() / (double)level._cellsX;
00439     double th = pex.height() / (double)level._cellsY;
00440 
00441     const osg::EllipsoidModel* ell = _map->getProfile()->getSRS()->getEllipsoid();
00442 
00443     for( unsigned int x=0; x<level._cellsX; ++x )
00444     {
00445         for( unsigned int y=0; y<level._cellsY; ++y )
00446         {
00447             GeoExtent tex(
00448                 mapProfile->getSRS(),
00449                 pex.xMin() + tw * (double)x,
00450                 pex.yMin() + th * (double)y,
00451                 pex.xMin() + tw * (double)(x+1),
00452                 pex.yMin() + th * (double)(y+1) );
00453 
00454             double offset = 2.0 * level._lineWidth;
00455 
00456             double cx, cy;
00457             tex.getCentroid( cx, cy );
00458 
00459             // y value on the x-axis:
00460             group->addChild( createTextTransform(
00461                 cx,
00462                 tex.yMin() + offset,
00463                 tex.yMin(),
00464                 ell,
00465                 20.0f,
00466                 _textColor ) );
00467 
00468             // x value on the y-axis:
00469             group->addChild( createTextTransform(
00470                 tex.xMin() + offset,
00471                 cy,
00472                 tex.xMin(),
00473                 ell, 
00474                 20.0f,
00475                 _textColor,
00476                 -90.0f ) );
00477         }
00478     }
00479 
00480     // organize it for better culling
00481     osgUtil::Optimizer opt;
00482     opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS );
00483 
00484     osg::Node* result = group;
00485 
00486     if ( levelNum+1 < getNumLevels() )
00487     {
00488         Graticule::Level nextLevel;
00489         if ( getLevel( levelNum+1, nextLevel ) )
00490         {
00491             osg::PagedLOD* plod = new osg::PagedLOD();
00492             plod->addChild( group, nextLevel._maxRange, level._maxRange );
00493             std::stringstream buf;
00494             buf << levelNum+1 << "_" << getID() << "." << TEXT_MARKER << "." << GRATICLE_EXTENSION;
00495             std::string bufStr = buf.str();
00496             plod->setFileName( 1, bufStr );
00497             plod->setRange( 1, 0, nextLevel._maxRange );
00498             result = plod;
00499         }
00500     }
00501     return result;
00502 }
00503 
00504 /**************************************************************************/
00505 
00506 namespace osgEarth { namespace Util
00507 {
00508     // OSG Plugin for loading subsequent graticule levels
00509     class GraticuleFactory : public osgDB::ReaderWriter
00510     {
00511     public:
00512         virtual const char* className()
00513         {
00514             supportsExtension( GRATICLE_EXTENSION, "osgEarth graticule" );
00515             return "osgEarth graticule LOD loader";
00516         }
00517 
00518         virtual bool acceptsExtension(const std::string& extension) const
00519         {
00520             return osgDB::equalCaseInsensitive(extension, GRATICLE_EXTENSION);
00521         }
00522 
00523         virtual ReadResult readNode(const std::string& uri, const Options* options) const
00524         {        
00525             std::string ext = osgDB::getFileExtension( uri );
00526             if ( !acceptsExtension( ext ) )
00527                 return ReadResult::FILE_NOT_HANDLED;
00528 
00529             // the graticule definition is formatted: LEVEL_ID.MARKER.EXTENSION
00530             std::string def = osgDB::getNameLessExtension( uri );
00531             
00532             std::string marker = osgDB::getFileExtension( def );
00533             def = osgDB::getNameLessExtension( def );
00534 
00535             int levelNum, id;
00536             sscanf( def.c_str(), "%d_%d", &levelNum, &id );
00537 
00538             // look up the graticule referenced in the location name:
00539             Graticule* graticule = 0L;
00540             {
00541                 ScopedLock<Mutex> lock( s_graticuleMutex );
00542                 GraticuleRegistry::iterator i = s_graticuleRegistry.find(id);
00543                 if ( i != s_graticuleRegistry.end() )
00544                     graticule = i->second.get();
00545             }
00546 
00547             if ( marker == GRID_MARKER )
00548             {
00549                 osg::Node* result = graticule->createGridLevel( levelNum );
00550                 return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE;
00551             }
00552             else if ( marker == TEXT_MARKER )
00553             {
00554                 osg::Node* result = graticule->createTextLevel( levelNum );
00555                 return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE;
00556             }
00557             else
00558             {
00559                 OE_NOTICE << "oh no! no markers" << std::endl;
00560                 return ReadResult::FILE_NOT_HANDLED;
00561             }
00562         }
00563     };
00564     REGISTER_OSGPLUGIN(osgearthutil_graticule, GraticuleFactory)
00565 
00566 } } // namespace osgEarth::Util
00567 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines