diff --git a/data/splat/splat_catalog.xml b/data/splat/splat_catalog.xml index da0e398cc2..8d869e58a5 100644 --- a/data/splat/splat_catalog.xml +++ b/data/splat/splat_catalog.xml @@ -1,42 +1,15 @@ Default texture splatting catalog - + cropland2.jpg - - cropland5.jpg - - - cropland6.jpg - - - cropland6.jpg - forest5.jpg - - forest5.jpg - - - forest6.jpg - - - forest7.jpg - - - forest8.jpg - - - forest9.jpg - grass5.jpg - - grass6.jpg - savanna1.jpg @@ -52,7 +25,7 @@ 9.0 - + rock_dark.jpg diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt index d432f06060..f6e3d6cf6a 100644 --- a/src/osgEarth/CMakeLists.txt +++ b/src/osgEarth/CMakeLists.txt @@ -106,6 +106,8 @@ SET(LIB_PUBLIC_HEADERS IntersectionPicker IOTypes JsonUtils + LandCover + LandCoverLayer Layer LayerListener Lighting @@ -279,6 +281,8 @@ set(TARGET_SRC IntersectionPicker.cpp IOTypes.cpp JsonUtils.cpp + LandCover.cpp + LandCoverLayer.cpp Layer.cpp Lighting.cpp Locators.cpp diff --git a/src/osgEarth/LandCover b/src/osgEarth/LandCover new file mode 100644 index 0000000000..5152b4ca4d --- /dev/null +++ b/src/osgEarth/LandCover @@ -0,0 +1,129 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2016 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#ifndef OSGEARTH_LAND_COVER_H +#define OSGEARTH_LAND_COVER_H 1 + +#include +#include + +namespace osgEarth +{ + /** + * A single classification definition of land cover. + * For example, "forest" or "water". + * A set of these makes up a land cover dictionary. + */ + class OSGEARTH_EXPORT LandCoverClass : public osg::Object + { + public: + META_Object(osgEarth, LandCoverClass); + + LandCoverClass(); + LandCoverClass(const Config& conf); + LandCoverClass(const LandCoverClass& rhs, const osg::CopyOp& op); + + public: + void fromConfig(const Config& conf); + Config getConfig() const; + }; + typedef std::vector< osg::ref_ptr > LandCoverClassVector; + + + /** + * A complete set of available land cover classes. + */ + class OSGEARTH_EXPORT LandCoverDictionary : public osg::Object + { + public: + META_Object(osgEarth, LandCoverDictionary); + + LandCoverDictionary(); + LandCoverDictionary(const LandCoverDictionary& rhs, const osg::CopyOp& op); + + public: + LandCoverClassVector& getClasses() { return _landCoverClasses; } + const LandCoverClassVector& getClasses() const { return _landCoverClasses; } + + public: + void fromConfig(const Config& conf); + Config getConfig() const; + + private: + LandCoverClassVector _landCoverClasses; + }; + + + /** + * Maps an integral value from a land cover data source to one of the + * land cover classes in the dictionary. + * For example, 42 --> "tundra". + */ + class OSGEARTH_EXPORT LandCoverValueMapping : public osg::Object + { + public: + META_Object(osgEarth, LandCoverValueMapping); + + LandCoverValueMapping(); + LandCoverValueMapping(const Config& conf); + LandCoverValueMapping(const LandCoverValueMapping& rhs, const osg::CopyOp& op); + + public: + void setValue(int value) { _value = value; } + int getValue() const { return _value.get(); } + + void setLandCoverClass(LandCoverClass* lcClass) { _lcClass = lcClass; } + const LandCoverClass* getLandCoverClass() const { return _lcClass.get(); } + + void fromConfig(const Config&); + Config getConfig() const; + + private: + optional _value; + osg::ref_ptr _lcClass; + }; + typedef std::vector< osg::ref_ptr > LandCoverValueMappingVector; + + + /** + * Defines a particular data source for land cover data, and stores + * the set of mappings from the source's values to the dictionary's + * classes. + */ + class OSGEARTH_EXPORT LandCoverDataSource : public osg::Object + { + public: + META_Object(osgEarth, LandCoverDataSource); + + LandCoverDataSource(); + LandCoverDataSource(const Config& conf); + LandCoverDataSource(const LandCoverDataSource& rhs, const osg::CopyOp& op); + + LandCoverValueMappingVector& getMappings() { return _valueMappings; } + const LandCoverValueMappingVector& getMappings() const { return _valueMappings; } + + void fromConfig(const Config& conf); + Config getConfig() const; + + private: + LandCoverValueMappingVector _valueMappings; + }; + +} // namespace osgEarth + +#endif // OSGEARTH_LAND_COVER_H diff --git a/src/osgEarth/LandCover.cpp b/src/osgEarth/LandCover.cpp new file mode 100644 index 0000000000..89464c5b88 --- /dev/null +++ b/src/osgEarth/LandCover.cpp @@ -0,0 +1,200 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2016 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#include + +#define LC "[LandCover] " + +using namespace osgEarth; + +LandCoverClass::LandCoverClass() : +osg::Object() +{ + //nop +} + +LandCoverClass::LandCoverClass(const Config& conf) : +osg::Object() +{ + fromConfig(conf); +} + +LandCoverClass::LandCoverClass(const LandCoverClass& rhs, const osg::CopyOp& op) : +osg::Object(rhs, op) +{ + //nop +} + +void +LandCoverClass::fromConfig(const Config& conf) +{ + setName(conf.value("name")); +} + +Config +LandCoverClass::getConfig() const +{ + Config conf("class"); + conf.set("name", getName()); + return conf; +} + +//............................................................................ + +LandCoverDictionary::LandCoverDictionary() : +osg::Object() +{ + //nop +} + +LandCoverDictionary::LandCoverDictionary(const LandCoverDictionary& rhs, const osg::CopyOp& op) : +osg::Object(rhs, op) +{ + //nop +} + +void +LandCoverDictionary::fromConfig(const Config& conf) +{ + setName(conf.value("name")); + const Config* classes = conf.child_ptr("classes"); + if (classes) + { + for(ConfigSet::const_iterator i = classes->children().begin(); + i != classes->children().end(); + ++i) + { + osg::ref_ptr lcc = new LandCoverClass(*i); + if (!lcc->getName().empty()) + _landCoverClasses.push_back(lcc.get()); + } + } +} + +Config +LandCoverDictionary::getConfig() const +{ + Config conf("land_cover_dictionary"); + if (!_landCoverClasses.empty()) + { + Config classes("classes"); + conf.add(classes); + for (LandCoverClassVector::const_iterator i = _landCoverClasses.begin(); + i != _landCoverClasses.end(); + ++i) + { + const LandCoverClass* lcc = i->get(); + if (lcc && !lcc->getName().empty()) + { + classes.add(lcc->getConfig()); + } + } + } + return conf; +} + +//........................................................................... + +LandCoverValueMapping::LandCoverValueMapping() : +osg::Object() +{ + //nop +} + +LandCoverValueMapping::LandCoverValueMapping(const Config& conf) : +osg::Object() +{ + fromConfig(conf); +} + +LandCoverValueMapping::LandCoverValueMapping(const LandCoverValueMapping& rhs, const osg::CopyOp& op) : +osg::Object(rhs, op) +{ + _value = rhs._value; + _lcClass = rhs._lcClass.get(); +} + +void +LandCoverValueMapping::fromConfig(const Config& conf) +{ + conf.getIfSet("value", _value); + _lcClass = new LandCoverClass(conf.child("class")); +} + +Config +LandCoverValueMapping::getConfig() const +{ + Config conf("mapping"); + conf.addIfSet("value", _value); + if (_lcClass.valid()) + conf.add(_lcClass->getConfig()); + return conf; +} + +//........................................................................... + +LandCoverDataSource::LandCoverDataSource() : +osg::Object() +{ + //nop +} + +LandCoverDataSource::LandCoverDataSource(const Config& conf) : +osg::Object() +{ + fromConfig(conf); +} + +LandCoverDataSource::LandCoverDataSource(const LandCoverDataSource& rhs, const osg::CopyOp& op) : +osg::Object(rhs, op) +{ + _valueMappings = rhs._valueMappings; +} + +void +LandCoverDataSource::fromConfig(const Config& conf) +{ + const Config* mappings = conf.child_ptr("mappings"); + if (mappings) + { + for (ConfigSet::const_iterator i = mappings->children().begin(); + i != mappings->children().end(); + ++i) + { + osg::ref_ptr mapping = new LandCoverValueMapping(*i); + _valueMappings.push_back(mapping.get()); + } + } +} + +Config +LandCoverDataSource::getConfig() const +{ + Config conf("land_cover_data_source"); + Config mappings("mappings"); + conf.add(mappings); + for(LandCoverValueMappingVector::const_iterator i = _valueMappings.begin(); + i != _valueMappings.end(); + ++i) + { + LandCoverValueMapping* mapping = i->get(); + if (mapping) + mappings.add(mapping->getConfig()); + } + return conf; +} diff --git a/src/osgEarth/LandCoverLayer b/src/osgEarth/LandCoverLayer new file mode 100644 index 0000000000..f50ece2d7a --- /dev/null +++ b/src/osgEarth/LandCoverLayer @@ -0,0 +1,97 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2016 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#ifndef OSGEARTH_LAND_COVER_LAYER +#define OSGEARTH_LAND_COVER_LAYER 1 + +#include + +namespace osgEarth +{ + class OSGEARTH_EXPORT LandCoverLayerOptions : public ImageLayerOptions + { + public: + LandCoverLayerOptions(const ConfigOptions& co = ConfigOptions()); + + /** + * Images layer from which to read source coverage data. + */ + std::vector& imageLayers() { return _imageLayers; } + const std::vector& imageLayers() const { return _imageLayers; } + + /** + * Amount by which to warp texture coordinates of coverage data. + * Try 0.01 as a starting point. + */ + //optional& warpFactor() { return _warp; } + //const optional& warpFactor() const { return _warp; } + + /** + * LOD at which to calculate the noise function for warping. + */ + //optional& baseLOD() { return _baseLOD; } + //const optional& baseLOD() const { return _baseLOD; } + + /** + * Bit size of the encoded data. The default is 32 (for a 32-bit signed + * floating point value) but you can set it to 16 if you know your data + * values are all within the range of a signed 16-bit float. + */ + optional& bits() { return _bits; } + const optional& bits() const { return _bits; } + + public: + virtual Config getConfig() const; + + protected: + void fromConfig(const Config& conf); + virtual void mergeConfig(const Config& conf) { + ImageLayerOptions::mergeConfig(conf); + fromConfig(conf); + } + + optional _warp; + optional _baseLOD; + optional _bits; + std::vector _imageLayers; + }; + + + class OSGEARTH_EXPORT LandCoverLayer : public ImageLayer + { + public: + META_Layer(osgEarth, LandCoverLayer, LandCoverLayerOptions); + + /** Construct an emptry land cover layer. Use options() to configure */ + LandCoverLayer(); + + /** Construct a land cover layer from options. */ + LandCoverLayer(const LandCoverLayerOptions& options); + + protected: // Layer + + virtual void init(); + + protected: // TerrainLayer + + virtual TileSource* createTileSource(); + }; + +} // namespace osgEarth + +#endif // OSGEARTH_LAND_COVER_LAYER diff --git a/src/osgEarth/LandCoverLayer.cpp b/src/osgEarth/LandCoverLayer.cpp new file mode 100644 index 0000000000..74bdb304c2 --- /dev/null +++ b/src/osgEarth/LandCoverLayer.cpp @@ -0,0 +1,376 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2016 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#include +#include +#include + +using namespace osgEarth; + +REGISTER_OSGEARTH_LAYER(land_cover, LandCoverLayer); + +namespace +{ + osg::Vec2 getSplatCoords(const TileKey& key, float baseLOD, const osg::Vec2& covUV) + { + osg::Vec2 out; + + float dL = (float)key.getLOD() - baseLOD; + float factor = pow(2.0f, dL); + float invFactor = 1.0/factor; + out.set( covUV.x()*invFactor, covUV.y()*invFactor ); + + // For upsampling we need to calculate an offset as well + if ( factor >= 1.0 ) + { + unsigned wide, high; + key.getProfile()->getNumTiles(key.getLOD(), wide, high); + + float tileX = (float)key.getTileX(); + float tileY = (float)(wide-1-key.getTileY()); // swap Y. (not done in the shader version.) + + osg::Vec2 a( floor(tileX*invFactor), floor(tileY*invFactor) ); + osg::Vec2 b( a.x()*factor, a.y()*factor ); + osg::Vec2 c( (a.x()+1.0f)*factor, (a.y()+1.0f)*factor ); + osg::Vec2 offset( (tileX-b.x())/(c.x()-b.x()), (tileY-b.y())/(c.y()-b.y()) ); + + out += offset; + } + + return out; + } + + struct ILayer + { + GeoImage image; + float scale; + osg::Vec2 bias; + bool valid; + float warp; + ImageUtils::PixelReader* read; + + ILayer() : valid(true), read(0L), scale(1.0f), warp(0.0f) { } + + ~ILayer() { if (read) delete read; } + + void load(const TileKey& key, ImageLayer* sourceLayer, float sourceWarp, ProgressCallback* progress) + { + if ( sourceLayer->getEnabled() && sourceLayer->getVisible() && sourceLayer->isKeyInRange(key) ) + { + for(TileKey k = key; k.valid() && !image.valid(); k = k.createParentKey()) + { + image = sourceLayer->createImage(k, progress); + } + } + + valid = image.valid(); + + if ( valid ) + { + scale = key.getExtent().width() / image.getExtent().width(); + bias.x() = (key.getExtent().xMin() - image.getExtent().xMin()) / image.getExtent().width(); + bias.y() = (key.getExtent().yMin() - image.getExtent().yMin()) / image.getExtent().height(); + + read = new ImageUtils::PixelReader(image.getImage()); + + // cannot interpolate coverage data: + read->setBilinear( false ); + + warp = sourceWarp; + } + } + }; + + class LandCoverTileSource : public TileSource + { + public: + LandCoverTileSource(const LandCoverLayerOptions& options); + + Status initialize(const osgDB::Options* readOptions); + + osg::Image* createImage(const TileKey& key, ProgressCallback* progress); + + CachePolicy getCachePolicyHint() const { + return CachePolicy::NO_CACHE; + } + + const LandCoverLayerOptions* _options; + const LandCoverLayerOptions& options() const { return *_options; } + + ImageLayerVector _imageLayers; + std::vector _warps; + osg::ref_ptr _readOptions; + //osg::ref_ptr _imageLayer; + //osgEarth::Util::SimplexNoise _noiseGen; + + }; + + + LandCoverTileSource::LandCoverTileSource(const LandCoverLayerOptions& options) : + TileSource(options), + _options(&options) + { + //nop + } + + Status + LandCoverTileSource::initialize(const osgDB::Options* readOptions) + { + const Profile* profile = getProfile(); + if ( !profile ) + { + profile = osgEarth::Registry::instance()->getGlobalGeodeticProfile(); + setProfile( profile ); + } + + // load all the image layers: + _imageLayers.assign( options().imageLayers().size(), 0L ); + _warps.assign( options().imageLayers().size(), 0.0f ); + + for(unsigned i=0; isetTargetProfileHint( profile ); + layer->setReadOptions(readOptions); + const Status& s = layer->open(); + if (s.isOK()) + { + _imageLayers[i] = layer; + OE_INFO << "Opened layer \"" << layer->getName() << std::endl; + } + else + { + OE_WARN << "Layer \"" << layer->getName() << "\": " << s.toString() << std::endl; + } + + //Config conf = ilo.getConfig(); + //_warps[i] = conf.value("warp", options().warpFactor().get()); + } + +#if 0 + // set up the noise generator. + const float F[4] = { 4.0f, 16.0f, 4.0f, 8.0f }; + const float P[4] = { 0.8f, 0.6f, 0.8f, 0.9f }; + const float L[4] = { 2.2f, 1.7f, 3.0f, 4.0f }; + + // Configure the noise function: + _noiseGen.setNormalize ( true ); + _noiseGen.setRange ( 0.0, 1.0 ); + _noiseGen.setFrequency ( F[0] ); + _noiseGen.setPersistence( P[0] ); + _noiseGen.setLacunarity ( L[0] ); + _noiseGen.setOctaves ( 8 ); +#endif + + return STATUS_OK; + } + + osg::Image* + LandCoverTileSource::createImage(const TileKey& key, ProgressCallback* progress) + { + if ( _imageLayers.empty() ) + return 0L; + + std::vector layers(_imageLayers.size()); + + // Allocate the new coverage image; it will contain unnormalized values. + osg::Image* out = new osg::Image(); + ImageUtils::markAsUnNormalized(out, true); + + // Allocate a suitable format: + GLenum dataType; + GLint internalFormat; + + if ( options().bits().isSetTo(16u) ) + { + // 16-bit float: + dataType = GL_FLOAT; + internalFormat = GL_LUMINANCE16F_ARB; + } + else //if ( _options.bits().isSetTo(32u) ) + { + // 32-bit float: + dataType = GL_FLOAT; + internalFormat = GL_LUMINANCE32F_ARB; + } + + int tilesize = getPixelsPerTile(); + + out->allocateImage(tilesize, tilesize, 1, GL_LUMINANCE, dataType); + out->setInternalTextureFormat(internalFormat); + + //float noiseLOD = options().baseLOD().get(); + //float warp = options().warpFactor().get(); + + osg::Vec2 cov; // coverage coordinates + //float noise; // noise value + //osg::Vec2 noiseCoords; + + ImageUtils::PixelWriter write( out ); + + float du = 1.0f / (float)(out->s()-1); + float dv = 1.0f / (float)(out->t()-1); + + osg::Vec4 nodata; + if (internalFormat == GL_LUMINANCE16F_ARB) + nodata.set(-32768, -32768, -32768, -32768); + else + nodata.set(NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE, NO_DATA_VALUE); + + for(float u=0.0f; u<=1.0f; u+=du) + { + for(float v=0.0f; v<=1.0f; v+=dv) + { + bool wrotePixel = false; + for(int L = layers.size()-1; L >= 0 && !wrotePixel; --L) + { + ILayer& layer = layers[L]; + if ( !layer.valid ) + continue; + + if ( !layer.image.valid() ) + layer.load(key, _imageLayers[L], _warps[L], progress); + + if ( !layer.valid ) + continue; + + osg::Vec2 cov(layer.scale*u + layer.bias.x(), layer.scale*v + layer.bias.y()); + + if ( cov.x() >= 0.0f && cov.x() <= 1.0f && cov.y() >= 0.0f && cov.y() <= 1.0f ) + { + // Noise is like a repeating overlay at the noiseLOD. So sample it using + // straight U/V tile coordinates. + //noiseCoords = getSplatCoords( key, noiseLOD, osg::Vec2(u,v) ); + //noise = getNoise( _noiseGen, noiseCoords ); + //cov = warpCoverageCoords(cov, noise, layer.warp); + + osg::Vec4 texel = (*layer.read)(cov.x(), cov.y()); + if ( texel.r() != NO_DATA_VALUE ) + { + write.f(texel, u, v); + wrotePixel = true; + } + } + } + + if ( !wrotePixel ) + { + write.f(nodata, u, v); + } + } + } + + return out; + } +} + +//........................................................................ + +LandCoverLayerOptions::LandCoverLayerOptions(const ConfigOptions& options) : +ImageLayerOptions(options) +{ + fromConfig(_conf); +} + +void +LandCoverLayerOptions::fromConfig(const Config& conf) +{ + conf.getIfSet("warp", _warp); + conf.getIfSet("base_lod", _baseLOD); + conf.getIfSet("bits", _bits); + + ConfigSet layerConfs = conf.child("images").children("image"); + for (ConfigSet::const_iterator i = layerConfs.begin(); i != layerConfs.end(); ++i) + { + _imageLayers.push_back(ImageLayerOptions(*i)); + } +} + +Config +LandCoverLayerOptions::getConfig() const +{ + Config conf = ImageLayerOptions::getConfig(); + conf.key() = "land_cover"; + + conf.addIfSet("warp", _warp); + conf.addIfSet("base_lod", _baseLOD); + conf.addIfSet("bits", _bits); + + // multiple + if (_imageLayers.size() > 0) + { + Config images("images"); + for (std::vector::const_iterator i = _imageLayers.begin(); + i != _imageLayers.end(); + ++i) + { + images.add("image", i->getConfig()); + } + conf.add(images); + } + + return conf; +} + +//........................................................................... + +#undef LC +#define LC "[LandCoverLayer] " + + +LandCoverLayer::LandCoverLayer() : +ImageLayer(&_optionsConcrete), +_options(&_optionsConcrete) +{ + init(); +} + +LandCoverLayer::LandCoverLayer(const LandCoverLayerOptions& options) : +ImageLayer(&_optionsConcrete), +_options(&_optionsConcrete), +_optionsConcrete(options) +{ + init(); +} + +void +LandCoverLayer::init() +{ + options().coverage() = true; + options().visible() = false; + options().shared() = true; + ImageLayer::init(); +} + +TileSource* +LandCoverLayer::createTileSource() +{ + OE_WARN << LC << "LandCoverLayer::createTileSource\n"; + + osg::ref_ptr ts = new LandCoverTileSource(options()); + + Status tsStatus = ts->initialize(getReadOptions()); + if (tsStatus.isError()) + { + setStatus(tsStatus); + return 0L; + } + return ts.release(); +}