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 #include <limits.h> 00020 #include <iomanip> 00021 00022 #include <osgEarth/Caching> 00023 #include <osgEarth/ImageToHeightFieldConverter> 00024 #include <osgEarth/FileUtils> 00025 #include <osgEarth/ImageUtils> 00026 #include <osgEarth/ThreadingUtils> 00027 00028 #include <osgDB/FileUtils> 00029 #include <osgDB/FileNameUtils> 00030 #include <osgDB/ReadFile> 00031 #include <osgDB/WriteFile> 00032 00033 #include <OpenThreads/ScopedLock> 00034 00035 using namespace osgEarth; 00036 00037 #define LC "[Cache] " 00038 00039 //------------------------------------------------------------------------ 00040 00041 Cache::Cache( const CacheOptions& options ) : 00042 osg::Object( true ), 00043 _options(options) 00044 { 00045 //NOP 00046 } 00047 00048 Cache::Cache(const Cache& rhs, const osg::CopyOp& op) : 00049 osg::Object(rhs), 00050 _refURI( rhs._refURI ) 00051 { 00052 //NOP 00053 } 00054 00055 bool 00056 Cache::getHeightField( const TileKey& key, const CacheSpec& spec, osg::ref_ptr<const osg::HeightField>& out_hf ) 00057 { 00058 //Try to get an image from the cache 00059 osg::ref_ptr<const osg::Image> image; 00060 if ( getImage( key, spec, image ) ) 00061 { 00062 OE_DEBUG << LC << "Read cached heightfield " << std::endl; 00063 ImageToHeightFieldConverter conv; 00064 out_hf = conv.convert(image.get()); 00065 return out_hf.valid(); 00066 } 00067 else return false; 00068 } 00069 00070 void 00071 Cache::setHeightField( const TileKey& key, const CacheSpec& spec, const osg::HeightField* hf) 00072 { 00073 ImageToHeightFieldConverter conv; 00074 //Take a reference to the returned image so it gets deleted properly 00075 osg::ref_ptr<osg::Image> image = conv.convert(hf); 00076 setImage( key, spec, image.get() ); 00077 } 00078 00079 //------------------------------------------------------------------------ 00080 00081 #undef LC 00082 #define LC "[DiskCache] " 00083 00084 static Threading::ReadWriteMutex s_mutex; 00085 00086 DiskCache::DiskCache( const DiskCacheOptions& options ) : 00087 Cache( options ), 00088 _options( options ) 00089 { 00090 setName( "tilecache" ); 00091 _writeWorldFilesOverride = getenv("OSGEARTH_WRITE_WORLD_FILES") != 0L; 00092 } 00093 00094 DiskCache::DiskCache( const DiskCache& rhs, const osg::CopyOp& op ) : 00095 Cache( rhs, op ), 00096 _layerPropertiesCache( rhs._layerPropertiesCache ), 00097 _writeWorldFilesOverride( rhs._writeWorldFilesOverride ), 00098 _options( rhs._options ) 00099 { 00100 //NOP 00101 } 00102 00103 bool 00104 DiskCache::isCached(const osgEarth::TileKey& key, const CacheSpec& spec ) const 00105 { 00106 //Check to see if the file for this key exists 00107 std::string filename = getFilename( key, spec ); 00108 return osgDB::fileExists(filename); 00109 } 00110 00111 std::string 00112 DiskCache::getPath() const 00113 { 00114 if ( _options.path().empty() || _refURI.empty() ) 00115 return _options.path(); 00116 else 00117 { 00118 if (osgDB::containsServerAddress( _refURI )) return _options.path(); 00119 return osgEarth::getFullPath( _refURI, _options.path() ); 00120 } 00121 } 00122 00123 std::string 00124 DiskCache::getFilename(const osgEarth::TileKey& key, const CacheSpec& spec ) const 00125 { 00126 unsigned int level, x, y; 00127 level = key.getLevelOfDetail() +1; 00128 key.getTileXY( x, y ); 00129 00130 unsigned int numCols, numRows; 00131 key.getProfile()->getNumTiles(level, numCols, numRows); 00132 00133 // need to invert the y-tile index 00134 y = numRows - y - 1; 00135 00136 char buf[2048]; 00137 sprintf( buf, "%s/%s/%02d/%03d/%03d/%03d/%03d/%03d/%03d.%s", 00138 getPath().c_str(), 00139 spec.cacheId().c_str(), 00140 level, 00141 (x / 1000000), 00142 (x / 1000) % 1000, 00143 (x % 1000), 00144 (y / 1000000), 00145 (y / 1000) % 1000, 00146 (y % 1000), 00147 spec.format().c_str()); 00148 00149 return buf; 00150 } 00151 00152 bool 00153 DiskCache::getImage( const TileKey& key, const CacheSpec& spec, osg::ref_ptr<const osg::Image>& out_image ) 00154 { 00155 std::string filename = getFilename(key, spec); 00156 00157 //If the path doesn't contain a zip file, check to see that it actually exists on disk 00158 if (!osgEarth::isZipPath(filename)) 00159 { 00160 if (!osgDB::fileExists(filename)) 00161 return false; 00162 } 00163 00164 { 00165 Threading::ScopedReadLock lock(s_mutex); 00166 out_image = osgDB::readImageFile( filename ); 00167 } 00168 00169 return out_image.valid(); 00170 } 00171 00175 void 00176 DiskCache::setImage( const TileKey& key, const CacheSpec& spec, const osg::Image* image) 00177 { 00178 std::string filename = getFilename( key, spec ); 00179 std::string path = osgDB::getFilePath(filename); 00180 std::string extension = spec.format(); 00181 00182 //If the format is empty, see if we can glean an extension from the image filename 00183 if (extension.empty()) 00184 { 00185 if (!image->getFileName().empty()) 00186 { 00187 extension = osgDB::getFileExtension( image->getFileName() ); 00188 } 00189 } 00190 00191 //If the format is STILL empty, just use PNG 00192 if (extension.empty()) 00193 { 00194 extension = "png"; 00195 } 00196 00197 // serialize cache writes. 00198 Threading::ScopedWriteLock lock(s_mutex); 00199 00200 //If the path doesn't currently exist or we can't create the path, don't cache the file 00201 if (!osgDB::fileExists(path) && !osgEarth::isZipPath(path) && !osgDB::makeDirectory(path)) 00202 { 00203 OE_WARN << LC << "Couldn't create path " << path << std::endl; 00204 } 00205 00206 std::string ext = osgDB::getFileExtension(filename); 00207 00208 if ( _options.writeWorldFiles() == true || _writeWorldFilesOverride ) 00209 { 00210 //Write out the world file along side the image 00211 double minx, miny, maxx, maxy; 00212 key.getExtent().getBounds(minx, miny, maxx, maxy); 00213 00214 std::string baseFilename = osgDB::getNameLessExtension(filename); 00215 00216 /* 00217 Determine the correct extension for the file type. Typically, world file extensions 00218 consist of the first letter of the extension, followed by the third, then the letter "w". 00219 For instance a jpg file's world file would be a jgw file. 00220 */ 00221 std::string worldFileExt = "wld"; 00222 if (ext.size() >= 3) 00223 { 00224 worldFileExt[0] = ext[0]; 00225 worldFileExt[1] = ext[2]; 00226 worldFileExt[2] = 'w'; 00227 } 00228 std::string worldFileName = baseFilename + std::string(".") + worldFileExt; 00229 std::ofstream worldFile; 00230 worldFile.open(worldFileName.c_str()); 00231 00232 double x_units_per_pixel = (maxx - minx) / (double)image->s(); 00233 double y_units_per_pixel = -(maxy - miny) / (double)image->t(); 00234 worldFile << std::fixed << std::setprecision(10) 00235 //X direction units per pixel 00236 << x_units_per_pixel << std::endl 00237 //Rotation about the y axis, in our case 0 00238 << "0" << std::endl 00239 //Rotation about the x axis, in our case 0 00240 << "0" << std::endl 00241 //Y direction units per pixel, typically negative 00242 << y_units_per_pixel << std::endl 00243 //X coordinate of the center of the upper left pixel 00244 << minx + 0.5 * x_units_per_pixel << std::endl 00245 //Y coordinate of the upper left pixel 00246 << maxy + 0.5 * y_units_per_pixel; 00247 worldFile.close(); 00248 } 00249 00250 bool writingJpeg = (ext == "jpg" || ext == "jpeg"); 00251 00252 osg::ref_ptr<osgDB::ReaderWriter::Options> op = new osgDB::ReaderWriter::Options(); 00253 op->setOptionString(_options.imageWriterPluginOptions().value()); 00254 00255 //If we are trying to write a non RGB image to JPEG, convert it to RGB before we write it 00256 if ((image->getPixelFormat() != GL_RGB) && writingJpeg) 00257 { 00258 //Take a reference so the converted image will be deleted 00259 osg::ref_ptr<osg::Image> rgb = ImageUtils::convertToRGB8( image ); 00260 if (rgb.valid()) 00261 { 00262 osgDB::writeImageFile(*rgb.get(), filename, op.get()); 00263 } 00264 } 00265 else 00266 { 00267 osgDB::writeImageFile(*image, filename, op.get()); 00268 } 00269 } 00270 00271 std::string 00272 DiskCache::getTMSPath(const std::string& cacheId) const 00273 { 00274 return getPath() + std::string("/") + cacheId + std::string("/tms.xml"); 00275 } 00276 00277 void DiskCache::storeProperties(const CacheSpec& spec, 00278 const Profile* profile, 00279 unsigned int tile_size) 00280 { 00281 osg::ref_ptr<TileMap> tileMap = TileMap::create("", profile, spec.format(), tile_size, tile_size); 00282 tileMap->setTitle( spec.name() ); 00283 std::string path = getTMSPath( spec.cacheId() ); 00284 OE_DEBUG << LC << "Writing TMS file to " << path << std::endl; 00285 TileMapReaderWriter::write( tileMap.get(), path ); 00286 } 00287 00288 bool 00289 DiskCache::loadProperties(const std::string& cacheId, 00290 CacheSpec& out_spec, 00291 osg::ref_ptr<const Profile>& out_profile, 00292 unsigned int& out_tileSize) 00293 { 00294 //Try to get it from the cache. 00295 LayerPropertiesCache::const_iterator i = _layerPropertiesCache.find(cacheId); 00296 if ( i != _layerPropertiesCache.end()) 00297 { 00298 out_spec = CacheSpec(cacheId, i->second._format); 00299 out_tileSize = i->second._tile_size; 00300 out_profile = i->second._profile.get(); 00301 return true; 00302 } 00303 00304 osg::ref_ptr<TileMap> tileMap; 00305 00306 std::string path = getTMSPath( cacheId ); 00307 OE_INFO << LC << "Metadata file is " << path << std::endl; 00308 00309 if (osgDB::fileExists( path ) ) 00310 { 00311 osg::ref_ptr<TileMap> tileMap = TileMapReaderWriter::read(path, NULL); 00312 if (tileMap.valid()) 00313 { 00314 LayerProperties props; 00315 props._format = tileMap->getFormat().getExtension(); 00316 props._tile_size = tileMap->getFormat().getWidth(); 00317 props._profile = tileMap->createProfile(); 00318 00319 _layerPropertiesCache[cacheId] = props; 00320 00321 out_spec = CacheSpec( cacheId, props._format ); 00322 out_profile = props._profile.get(); 00323 out_tileSize = props._tile_size; 00324 return true; 00325 } 00326 else 00327 { 00328 OE_WARN << LC << "Failed to load cache metadata from " << path << std::endl; 00329 } 00330 } 00331 return false; 00332 } 00333 00334 //------------------------------------------------------------------------ 00335 00336 #undef LC 00337 #define LC "[MemCache] " 00338 00339 MemCache::MemCache( int maxSize ): 00340 _maxNumTilesInCache( maxSize ) 00341 { 00342 setName( "mem" ); 00343 } 00344 00345 MemCache::MemCache( const MemCache& rhs, const osg::CopyOp& op ) : 00346 _maxNumTilesInCache( rhs._maxNumTilesInCache ) 00347 { 00348 } 00349 00350 unsigned int 00351 MemCache::getMaxNumTilesInCache() const 00352 { 00353 return _maxNumTilesInCache; 00354 } 00355 00356 void 00357 MemCache::setMaxNumTilesInCache(unsigned int max) 00358 { 00359 _maxNumTilesInCache = max; 00360 } 00361 00362 bool 00363 MemCache::getImage(const osgEarth::TileKey& key, const CacheSpec& spec, osg::ref_ptr<const osg::Image>& out_image ) 00364 { 00365 osg::ref_ptr<const osg::Object> result; 00366 if ( getObject(key, spec, result) ) 00367 { 00368 out_image = dynamic_cast<const osg::Image*>( result.get() ); 00369 return out_image.valid(); 00370 } 00371 else return false; 00372 } 00373 00374 void 00375 MemCache::setImage(const osgEarth::TileKey& key, const CacheSpec& spec, const osg::Image* image) 00376 { 00377 setObject( key, spec, ImageUtils::cloneImage(image) ); 00378 } 00379 00380 bool 00381 MemCache::getHeightField( const TileKey& key,const CacheSpec& spec, osg::ref_ptr<const osg::HeightField>& out_hf ) 00382 { 00383 osg::ref_ptr<const osg::Object> result; 00384 if ( getObject(key, spec, result) ) 00385 { 00386 out_hf = dynamic_cast<const osg::HeightField*>(result.get()); 00387 return out_hf.valid(); 00388 } 00389 else return false; 00390 } 00391 00392 void 00393 MemCache::setHeightField( const TileKey& key, const CacheSpec& spec, const osg::HeightField* hf) 00394 { 00395 setObject( key, spec, new osg::HeightField(*hf) ); 00396 } 00397 00398 bool 00399 MemCache::purge( const std::string& cacheId, int olderThan, bool async ) 00400 { 00401 //TODO: can this be a ReadWriteLock instead? 00402 00403 // MemCache does not support timestamps or async, so just clear it out altogether. 00404 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); 00405 00406 // MemCache does not support cacheId... 00407 _keyToIterMap.clear(); 00408 _objects.clear(); 00409 00410 return true; 00411 } 00412 00413 bool 00414 MemCache::getObject( const TileKey& key, const CacheSpec& spec, osg::ref_ptr<const osg::Object>& output ) 00415 { 00416 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); 00417 00418 std::string id = key.str() + spec.cacheId(); 00419 KeyToIteratorMap::iterator itr = _keyToIterMap.find(id); 00420 if (itr != _keyToIterMap.end()) 00421 { 00422 CachedObject entry = *itr->second; 00423 _objects.erase(itr->second); 00424 _objects.push_front(entry); 00425 itr->second = _objects.begin(); 00426 output = itr->second->_object.get(); 00427 return output.valid(); 00428 } 00429 return false; 00430 } 00431 00432 void 00433 MemCache::setObject( const TileKey& key, const CacheSpec& spec, const osg::Object* referenced ) 00434 { 00435 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); 00436 00437 std::string id = key.str() + spec.cacheId(); 00438 00439 _objects.push_front(CachedObject()); 00440 CachedObject& entry = _objects.front(); 00441 00442 //CachedObject entry; 00443 entry._object = referenced; 00444 entry._key = id; 00445 //_objects.push_front(entry); 00446 00447 _keyToIterMap[id] = _objects.begin(); 00448 00449 if (_objects.size() > _maxNumTilesInCache) 00450 { 00451 _keyToIterMap.erase( _objects.back()._key ); 00452 _objects.pop_back(); 00453 //CachedObject toRemove = _objects.back(); 00454 //_objects.pop_back(); 00455 //_keyToIterMap.erase(toRemove._key); 00456 } 00457 } 00458 00459 bool 00460 MemCache::isCached(const osgEarth::TileKey& key, const CacheSpec& spec) const 00461 { 00462 OpenThreads::ScopedLock<OpenThreads::Mutex> lock( const_cast<MemCache*>(this)->_mutex); 00463 std::string id = key.str() + spec.cacheId(); 00464 return _keyToIterMap.find(id) != _keyToIterMap.end(); 00465 //osg::ref_ptr<osg::Image> image = getImage(key,spec); 00466 //return image.valid(); 00467 } 00468 00469 //------------------------------------------------------------------------ 00470 00471 #undef LC 00472 #define LC "[TMSCache] " 00473 00474 TMSCache::TMSCache( const TMSCacheOptions& options ) : 00475 DiskCache( options ), 00476 _options( options ) 00477 { 00478 setName( "tms" ); 00479 } 00480 00481 TMSCache::TMSCache( const TMSCache& rhs, const osg::CopyOp& op ) : 00482 DiskCache( rhs, op ), 00483 _options( rhs._options ) 00484 { 00485 //nop 00486 } 00487 00488 std::string 00489 TMSCache::getFilename( const TileKey& key,const CacheSpec& spec ) const 00490 { 00491 unsigned int x,y; 00492 key.getTileXY(x, y); 00493 00494 unsigned int lod = key.getLevelOfDetail(); 00495 00496 unsigned int numCols, numRows; 00497 key.getProfile()->getNumTiles(lod, numCols, numRows); 00498 if ( _options.invertY() == false ) 00499 { 00500 y = numRows - y - 1; 00501 } 00502 00503 std::stringstream buf; 00504 buf << getPath() << "/" << spec.cacheId() << "/" << lod << "/" << x << "/" << y << "." << spec.format(); 00505 std::string bufStr; 00506 bufStr = buf.str(); 00507 return bufStr; 00508 } 00509 00510 //------------------------------------------------------------------------ 00511 00512 #undef LC 00513 #define LC "[CacheFactory] " 00514 #define CACHE_OPTIONS_TAG "__osgEarth::CacheOptions" 00515 00516 Cache* 00517 CacheFactory::create( const CacheOptions& options) 00518 { 00519 osg::ref_ptr<Cache> result =0L; 00520 OE_INFO << LC << "Initializing cache of type \"" << options.getDriver() << "\"" << std::endl; 00521 00522 if ( options.getDriver().empty() ) 00523 { 00524 OE_WARN << LC << "ILLEGAL: no driver set in cache options" << std::endl; 00525 } 00526 else if ( options.getDriver() == "tms" ) 00527 { 00528 result = new TMSCache( options ); 00529 } 00530 else if ( options.getDriver() == "tilecache" ) 00531 { 00532 result = new DiskCache( options ); 00533 } 00534 else // try to load from a plugin 00535 { 00536 osg::ref_ptr<osgDB::ReaderWriter::Options> rwopt = new osgDB::ReaderWriter::Options(); 00537 rwopt->setPluginData( CACHE_OPTIONS_TAG, (void*)&options ); 00538 00539 std::string driverExt = ".osgearth_cache_" + options.getDriver(); 00540 osgDB::ReaderWriter::ReadResult rr = osgDB::readObjectFile( driverExt, rwopt.get() ); 00541 result = dynamic_cast<Cache*>( rr.getObject() ); 00542 if ( !result ) 00543 { 00544 OE_WARN << LC << "Failed to load cache plugin for type \"" << options.getDriver() << "\"" << std::endl; 00545 } 00546 } 00547 return result.release(); 00548 } 00549 00550 //------------------------------------------------------------------------ 00551 00552 const CacheOptions& 00553 CacheDriver::getCacheOptions( const osgDB::ReaderWriter::Options* rwopt ) const 00554 { 00555 return *static_cast<const CacheOptions*>( rwopt->getPluginData( CACHE_OPTIONS_TAG ) ); 00556 } 00557