osgEarth 2.1.1

/home/cube/sources/osgearth/src/osgEarthDrivers/wms/ReaderWriterWMS.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 
00020 #include <osgEarth/TileSource>
00021 #include <osgEarth/ImageToHeightFieldConverter>
00022 #include <osgEarth/Registry>
00023 #include <osgEarth/XmlUtils>
00024 #include <osgEarthUtil/WMS>
00025 #include <osgDB/FileNameUtils>
00026 #include <osgDB/FileUtils>
00027 #include <osgDB/Registry>
00028 #include <osgDB/ReadFile>
00029 #include <osgDB/WriteFile>
00030 #include <osg/ImageSequence>
00031 #include <sstream>
00032 #include <stdlib.h>
00033 #include <string.h>
00034 #include <limits.h>
00035 #include <iomanip>
00036 
00037 #include "TileService"
00038 #include "WMSOptions"
00039 
00040 #define LC "[WMS] "
00041 
00042 using namespace osgEarth;
00043 using namespace osgEarth::Util;
00044 using namespace osgEarth::Drivers;
00045 
00046 // All looping ImageSequences deriving from this class will by in sync due to
00047 // a shared reference time.
00048 class SyncImageSequence : public osg::ImageSequence {
00049 public:
00050     SyncImageSequence()
00051     { 
00052     }
00053 
00054     virtual void update(osg::NodeVisitor* nv) {
00055         setReferenceTime( 0.0 );
00056         osg::ImageSequence::update( nv );
00057     }
00058 };
00059 
00060 
00061 class WMSSource : public TileSource
00062 {
00063 public:
00064         WMSSource( const TileSourceOptions& options ) : TileSource( options ), _options(options)
00065     {
00066         if ( _options.times().isSet() )
00067         {
00068             StringTokenizer( *_options.times(), _timesVec, ",", "", false, true );
00069             OE_INFO << LC << "WMS-T: found " << _timesVec.size() << " times." << std::endl;
00070         }
00071 
00072         // localize it since we might override them:
00073         _formatToUse = _options.format().value();
00074         _srsToUse = _options.srs().value();
00075     }
00076 
00078     void initialize( const std::string& referenceURI, const Profile* overrideProfile)
00079     {
00080         osg::ref_ptr<const Profile> result;
00081 
00082         char sep = _options.url()->full().find_first_of('?') == std::string::npos? '?' : '&';
00083 
00084         URI capUrl = _options.capabilitiesUrl().value();
00085         if ( capUrl.empty() )
00086         {
00087             capUrl = URI(
00088                 _options.url()->full() + 
00089                 sep + 
00090                 "SERVICE=WMS" +
00091                 "&VERSION=" + _options.wmsVersion().value() +
00092                 "&REQUEST=GetCapabilities" );
00093         }
00094 
00095         //Try to read the WMS capabilities
00096         osg::ref_ptr<WMSCapabilities> capabilities = WMSCapabilitiesReader::read( capUrl.full(), 0L ); //getOptions() );
00097         if ( !capabilities.valid() )
00098         {
00099             OE_WARN << "[osgEarth::WMS] Unable to read WMS GetCapabilities." << std::endl;
00100             //return;
00101         }
00102         else
00103         {
00104             OE_INFO << "[osgEarth::WMS] Got capabilities from " << capUrl.full() << std::endl;
00105         }
00106 
00107         if ( _formatToUse.empty() && capabilities.valid() )
00108         {
00109             _formatToUse = capabilities->suggestExtension();
00110             OE_NOTICE << "[osgEarth::WMS] No format specified, capabilities suggested extension " << _formatToUse << std::endl;
00111         }
00112 
00113         if ( _formatToUse.empty() )
00114             _formatToUse = "png";
00115        
00116         if ( _srsToUse.empty() )
00117             _srsToUse = "EPSG:4326";
00118 
00119         std::string wmsFormatToUse = _options.wmsFormat().value();
00120 
00121         //Initialize the WMS request prototype
00122         std::stringstream buf;
00123 
00124         // first the mandatory keys:
00125         buf
00126             << std::fixed << _options.url()->full() << sep
00127             << "SERVICE=WMS"
00128             << "&VERSION=" << _options.wmsVersion().value()
00129             << "&REQUEST=GetMap"
00130             << "&LAYERS=" << _options.layers().value()
00131             << "&FORMAT=" << ( wmsFormatToUse.empty() ? std::string("image/") + _formatToUse : wmsFormatToUse )
00132             << "&STYLES=" << _options.style().value()
00133             << "&SRS=" << _srsToUse
00134             << "&WIDTH="<< _options.tileSize().value()
00135             << "&HEIGHT="<< _options.tileSize().value()
00136             << "&BBOX=%lf,%lf,%lf,%lf";
00137 
00138         // then the optional keys:
00139         if ( _options.transparent().isSet() )
00140             buf << "&TRANSPARENT=" << (_options.transparent() == true ? "TRUE" : "FALSE");
00141             
00142 
00143                 _prototype = "";
00144         _prototype = buf.str();
00145 
00146         osg::ref_ptr<SpatialReference> wms_srs = SpatialReference::create( _srsToUse );
00147 
00148         // check for spherical mercator:
00149         if ( wms_srs.valid() && wms_srs->isEquivalentTo( osgEarth::Registry::instance()->getGlobalMercatorProfile()->getSRS() ) )
00150         {
00151             result = osgEarth::Registry::instance()->getGlobalMercatorProfile();
00152         }
00153                 else if (wms_srs.valid() && wms_srs->isEquivalentTo( osgEarth::Registry::instance()->getGlobalGeodeticProfile()->getSRS()))
00154                 {
00155                         result = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
00156                 }
00157 
00158         // Next, try to glean the extents from the layer list
00159         if ( capabilities.valid() )
00160         {
00161             //TODO: "layers" mights be a comma-separated list. need to loop through and
00162             //combine the extents?? yes
00163             WMSLayer* layer = capabilities->getLayerByName( _options.layers().value() );
00164             if ( layer )
00165             {
00166                 double minx, miny, maxx, maxy;                
00167                 minx = miny = maxx = maxy = 0;
00168 
00169                 //Check to see if the profile is equivalent to global-geodetic
00170                 if (wms_srs->isGeographic())
00171                 {
00172                                         //Try to get the lat lon extents if they are provided
00173                                     layer->getLatLonExtents(minx, miny, maxx, maxy);
00174 
00175                                         //If we still don't have any extents, just default to global geodetic.
00176                                         if (!result.valid() && minx == 0 && miny == 0 && maxx == 0 && maxy == 0)
00177                                         {
00178                                                 result = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
00179                                         }
00180                 }       
00181 
00182                 if (minx == 0 && miny == 0 && maxx == 0 && maxy == 0)
00183                 {
00184                     layer->getExtents(minx, miny, maxx, maxy);
00185                 }
00186 
00187 
00188                 if (!result.valid())
00189                 {
00190                     result = Profile::create( _srsToUse, minx, miny, maxx, maxy );
00191                 }
00192 
00193                 //Add the layer extents to the list of valid areas
00194                 if (minx != 0 || maxx != 0 || miny != 0 || maxy != 0)
00195                 {
00196                     GeoExtent extent( result->getSRS(), minx, miny, maxx, maxy);                    
00197                     getDataExtents().push_back( DataExtent(extent, 0, INT_MAX) );
00198                 }
00199             }
00200         }
00201 
00202         // Last resort: create a global extent profile (only valid for global maps)
00203         if ( !result.valid() && wms_srs->isGeographic())
00204         {
00205             result = osgEarth::Registry::instance()->getGlobalGeodeticProfile();
00206         }
00207         
00208 
00209         // JPL uses an experimental interface called TileService -- ping to see if that's what
00210         // we are trying to read:
00211         URI tsUrl = _options.tileServiceUrl().value();
00212         if ( tsUrl.empty() )
00213         {
00214             tsUrl = URI(
00215                 _options.url()->full() + sep + std::string("request=GetTileService") );
00216         }
00217 
00218         OE_INFO << "[osgEarth::WMS] Testing for JPL/TileService at " << tsUrl.full() << std::endl;
00219         _tileService = TileServiceReader::read(tsUrl.full(), 0L); //getOptions());
00220         if (_tileService.valid())
00221         {
00222             OE_INFO << "[osgEarth::WMS] Found JPL/TileService spec" << std::endl;
00223             TileService::TilePatternList patterns;
00224             _tileService->getMatchingPatterns(
00225                 _options.layers().value(),
00226                 _formatToUse,
00227                 _options.style().value(),
00228                 _srsToUse,
00229                 _options.tileSize().value(),
00230                 _options.tileSize().value(),
00231                 patterns );
00232 
00233             if (patterns.size() > 0)
00234             {
00235                 result = _tileService->createProfile( patterns );
00236                 _prototype = _options.url()->full() + sep + patterns[0].getPrototype();
00237             }
00238         }
00239         else
00240         {
00241             OE_INFO << "[osgEarth::WMS] No JPL/TileService spec found; assuming standard WMS" << std::endl;
00242         }
00243 
00244         //Use the override profile if one is passed in.
00245         if (overrideProfile)
00246         {
00247             result = overrideProfile;
00248         }
00249 
00250         //TODO: won't need this for OSG 2.9+, b/c of mime-type support
00251         _prototype = _prototype + std::string("&.") + _formatToUse;
00252 
00253         // populate the data metadata:
00254         // TODO
00255 
00256                 setProfile( result.get() );
00257     }
00258 
00259     /* override */
00260     bool isDynamic() const
00261     {
00262         // return TRUE if we are reading WMS-T.
00263         return _timesVec.size() > 1;
00264     }
00265 
00266 public:
00267 
00268     // fetch a tile from the WMS service and report any exceptions.
00269     osgDB::ReaderWriter* fetchTileAndReader( 
00270         const TileKey&     key, 
00271         const std::string& extraAttrs,
00272         ProgressCallback*  progress, 
00273         HTTPResponse&      out_response )
00274     {
00275         osgDB::ReaderWriter* result = 0L;
00276 
00277         std::string uri = createURI(key);
00278         if ( !extraAttrs.empty() )
00279         {
00280             std::string delim = uri.find("?") == std::string::npos ? "?" : "&";
00281             uri = uri + delim + extraAttrs;
00282         }
00283 
00284 
00285         out_response = HTTPClient::get( uri, 0L, progress ); //getOptions(), progress );
00286 
00287         if ( out_response.isOK() )
00288         {
00289             const std::string& mt = out_response.getMimeType();
00290 
00291             if ( mt == "application/vnd.ogc.se_xml" || mt == "text/xml" )
00292             {
00293                 // an XML result means there was a WMS service exception:
00294                 Config se;
00295                 if ( se.loadXML( out_response.getPartStream(0) ) )
00296                 {
00297                     Config ex = se.child("serviceexceptionreport").child("serviceexception");
00298                     if ( !ex.empty() ) {
00299                         OE_NOTICE << "WMS Service Exception: " << ex.value() << std::endl;
00300                     }
00301                     else {
00302                         OE_NOTICE << "WMS Response: " << se.toString() << std::endl;
00303                     }
00304                 }
00305                 else {
00306                     OE_NOTICE << "WMS: unknown error." << std::endl;
00307                 }
00308             }
00309             else
00310             {
00311                 // really ought to use mime-type support here -GW
00312                 std::string typeExt = mt.substr( mt.find_last_of("/")+1 );
00313                 result = osgDB::Registry::instance()->getReaderWriterForExtension( typeExt );
00314                 if ( !result ) {
00315                     OE_NOTICE << "WMS: no reader registered; URI=" << createURI(key) << std::endl;
00316                 }
00317             }
00318         }
00319         return result;
00320     }
00321 
00322 
00324     osg::Image* createImage( const TileKey& key, ProgressCallback* progress )
00325     {
00326         osg::ref_ptr<osg::Image> image;
00327 
00328         if ( _timesVec.size() > 1 )
00329         {
00330             image = createImageSequence( key, progress );
00331         }
00332         else
00333         {
00334             std::string extras;
00335             if ( _timesVec.size() == 1 )
00336                 extras = "TIME=" + _timesVec[0];
00337 
00338             HTTPResponse response;
00339             osgDB::ReaderWriter* reader = fetchTileAndReader( key, extras, progress, response );
00340             if ( reader )
00341             {
00342                 osgDB::ReaderWriter::ReadResult readResult = reader->readImage( response.getPartStream( 0 ), 0L ); //getOptions() );
00343                 if ( readResult.error() ) {
00344                     OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
00345                 }
00346                 else {
00347                     image = readResult.getImage();
00348                 }
00349             }
00350         }
00351 
00352         return image.release();
00353     }
00354 
00356     osg::Image* createImage3D( const TileKey& key, ProgressCallback* progress )
00357     {
00358         osg::ref_ptr<osg::Image> image;
00359 
00360         for( unsigned int r=0; r<_timesVec.size(); ++r )
00361         {
00362             std::string extraAttrs = "TIME=" + _timesVec[r];
00363             HTTPResponse response;
00364             osgDB::ReaderWriter* reader = fetchTileAndReader( key, extraAttrs, progress, response );
00365             if ( reader )
00366             {
00367                 osgDB::ReaderWriter::ReadResult readResult = reader->readImage( response.getPartStream( 0 ), 0L ); //getOptions() );
00368                 if ( readResult.error() ) {
00369                     OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
00370                 }
00371                 else
00372                 {
00373                     osg::ref_ptr<osg::Image> timeImage = readResult.getImage();
00374 
00375                     if ( !image.valid() )
00376                     {
00377                         image = new osg::Image();
00378                         image->allocateImage(
00379                             timeImage->s(), timeImage->t(), _timesVec.size(),
00380                             timeImage->getPixelFormat(),
00381                             timeImage->getDataType(),
00382                             timeImage->getPacking() );
00383                         image->setInternalTextureFormat( timeImage->getInternalTextureFormat() );
00384                     }
00385 
00386                     memcpy( 
00387                         image->data(0,0,r), 
00388                         timeImage->data(), 
00389                         osg::minimum(image->getImageSizeInBytes(), timeImage->getImageSizeInBytes()) );
00390                 }
00391             }
00392         }
00393 
00394         return image.release();
00395     }
00396     
00398     //osg::Image* createImageSequence( const TileKey& key, ProgressCallback* progress )
00399     //{
00400     //    osg::ImageSequence* seq = new osg::ImageSequence();
00401 
00402     //    for( int r=0; r<_timesVec.size(); ++r )
00403     //    {
00404     //        std::string extraAttrs = "TIME=" + _timesVec[r];
00405 
00406     //        std::string uri = createURI(key);
00407     //        std::string delim = uri.find("?") == std::string::npos ? "?" : "&";
00408     //        uri = uri + delim + extraAttrs;
00409     //        uri = uri + "&." + _formatToUse;
00410 
00411     //        seq->addImageFile( uri );
00412     //    }
00413 
00414     //    seq->play();
00415     //    seq->setLength( (double)_timesVec.size() );
00416     //    seq->setLoopingMode( osg::ImageStream::LOOPING );
00417     //    
00418     //    return seq;
00419     //}
00420 
00422     osg::Image* createImageSequence( const TileKey& key, ProgressCallback* progress )
00423     {
00424         osg::ImageSequence* seq = new SyncImageSequence(); //osg::ImageSequence();
00425 
00426         seq->setLoopingMode( osg::ImageStream::LOOPING );
00427         seq->setLength( _options.secondsPerFrame().value() * (double)_timesVec.size() );
00428         seq->play();
00429 
00430         for( unsigned int r=0; r<_timesVec.size(); ++r )
00431         {
00432             std::string extraAttrs = "TIME=" + _timesVec[r];
00433 
00434             HTTPResponse response;
00435             osgDB::ReaderWriter* reader = fetchTileAndReader( key, extraAttrs, progress, response );
00436             if ( reader )
00437             {
00438                 osgDB::ReaderWriter::ReadResult readResult = reader->readImage( response.getPartStream( 0 ), 0L ); //getOptions() );
00439                 if ( !readResult.error() )
00440                 {
00441                     seq->addImage( readResult.getImage() );
00442                 }
00443                 else
00444                 {
00445                     OE_WARN << "WMS: image read failed for " << createURI(key) << std::endl;
00446                 }
00447             }
00448         }
00449 
00450         return seq;
00451     }
00452 
00453 
00455     osg::HeightField* createHeightField( const TileKey& key,
00456                                          ProgressCallback* progress)
00457     {
00458         osg::Image* image = createImage(key, progress);
00459         if (!image)
00460         {
00461             OE_INFO << "[osgEarth::WMS] Failed to read heightfield from " << createURI(key) << std::endl;
00462         }
00463 
00464         float scaleFactor = 1;
00465 
00466         //Scale the heightfield to meters
00467         if ( _options.elevationUnit() == "ft")
00468         {
00469             scaleFactor = 0.3048;
00470         }
00471 
00472         ImageToHeightFieldConverter conv;
00473         return conv.convert( image, scaleFactor );
00474     }
00475 
00476 
00477     std::string createURI( const TileKey& key ) const
00478     {
00479         double minx, miny, maxx, maxy;
00480         key.getExtent().getBounds( minx, miny, maxx, maxy);
00481         
00482         char buf[2048];
00483         sprintf(buf, _prototype.c_str(), minx, miny, maxx, maxy);
00484         
00485         std::string uri(buf);
00486 
00487         // url-ize the uri before returning it
00488         if ( osgDB::containsServerAddress( uri ) )
00489             uri = replaceIn(uri, " ", "%20");
00490 
00491         return uri;
00492     }
00493 
00494     virtual int getPixelsPerTile() const
00495     {
00496         return _options.tileSize().value();
00497     }
00498 
00499     virtual std::string getExtension()  const 
00500     {
00501         return _formatToUse;
00502     }
00503 
00504 private:
00505     const WMSOptions _options;
00506     std::string _formatToUse;
00507     std::string _srsToUse;
00508     osg::ref_ptr<TileService> _tileService;
00509     osg::ref_ptr<const Profile> _profile;
00510     std::string _prototype;
00511     std::vector<std::string> _timesVec;
00512 };
00513 
00514 
00515 class WMSSourceFactory : public TileSourceDriver
00516 {
00517     public:
00518         WMSSourceFactory() {}
00519 
00520         virtual const char* className()
00521         {
00522             return "WMS Reader";
00523         }
00524         
00525         virtual bool acceptsExtension(const std::string& extension) const
00526         {
00527             return osgDB::equalCaseInsensitive( extension, "osgearth_wms" );
00528         }
00529 
00530         virtual ReadResult readObject(const std::string& file_name, const Options* opt) const
00531         {
00532             std::string ext = osgDB::getFileExtension( file_name );
00533             if ( !acceptsExtension( ext ) )
00534             {
00535                 return ReadResult::FILE_NOT_HANDLED;
00536             }
00537 
00538             return new WMSSource( getTileSourceOptions(opt) );
00539         }
00540 };
00541 
00542 REGISTER_OSGPLUGIN(osgearth_wms, WMSSourceFactory)
00543 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines