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 <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