osgearth加载mapbox在线高程数据
加载mapbox在线高程数据
mapbox的高程数据数据是rgba四通道的png格式栅格图像数据,而一般的高程数据HeightField是单通道的。
因此,在请求到源数据之后只需要做一次osg::Image -> osg::HeightField 的转换即可。
官网提供的转换公式:
height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
通过继承 TileSource 重写 createImage 和 createHeightField 即可实现功能。
实现效果:
下图为我国台湾省花莲机场傍山效果图。
头文件
重新定义配置选项
#include <osgEarth/Common>
#include <osgEarth/TileSource>
#include <osgEarth/URI>
using namespace osgEarth;
class CgMapboxTerrainOptions : public TileSourceOptions
{
public:
optional<URI>& url() { return _url; }
const optional<URI>& url() const { return _url; }
optional<bool>& invertY() { return _invertY; }
const optional<bool>& invertY() const { return _invertY; }
optional<std::string>& format() { return _format; }
const optional<std::string>& format() const { return _format; }
public:
CgMapboxTerrainOptions(const TileSourceOptions& opt = TileSourceOptions()) : TileSourceOptions(opt)
{
setDriver("mapboxTer");
fromConfig(_conf);
}
CgMapboxTerrainOptions(const std::string& inUrl) : TileSourceOptions()
{
setDriver("mapboxTer");
fromConfig(_conf);
url() = inUrl;
}
virtual ~CgMapboxTerrainOptions() { }
public:
Config getConfig() const {
Config conf = TileSourceOptions::getConfig();
conf.updateIfSet("url", _url);
conf.updateIfSet("format", _format);
conf.updateIfSet("invert_y", _invertY);
return conf;
}
protected:
void mergeConfig(const Config& conf) {
TileSourceOptions::mergeConfig(conf);
fromConfig(conf);
}
private:
void fromConfig(const Config& conf) {
conf.getIfSet("url", _url);
conf.getIfSet("format", _format);
conf.getIfSet("invert_y", _invertY);
}
optional<URI> _url;
optional<std::string> _format;
optional<bool> _invertY;
};
TileSource 的实现
通过继承 TileSource 重写 createImage 和 createHeightField,并自定义 TileSourceDriver 使用新的TileSource。
#include "CgMapboxTerrainOptions.h"
#include <osgEarth/ImageUtils>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgEarth/Registry>
class MapBoxTerrainSource : public TileSource
{
public:
MapBoxTerrainSource(const TileSourceOptions& options)
:TileSource(options), _options(options), _rotate_iter(0u)
{
}
Status initialize(const osgDB::Options* dbOptions)
{
_dbOptions = Registry::instance()->cloneOrCreateOptions(dbOptions);
URI xyzURI = _options.url().value();
if (xyzURI.empty())
{
return Status::Error("Fail: driver requires a valid \"url\" property");
}
// driver requires a profile.
if (!getProfile())
{
return Status::Error("An explicit profile definition is required by the XYZ driver.");
}
_template = xyzURI.full();
_rotateStart = _template.find("[");
_rotateEnd = _template.find("]");
if (_rotateStart != std::string::npos && _rotateEnd != std::string::npos && _rotateEnd - _rotateStart > 1)
{
_rotateString = _template.substr(_rotateStart, _rotateEnd - _rotateStart + 1);
_rotateChoices = _template.substr(_rotateStart + 1, _rotateEnd - _rotateStart - 1);
}
_format = _options.format().isSet()
? *_options.format()
: osgDB::getLowerCaseFileExtension(xyzURI.base());
return STATUS_OK;
}
osg::Image* createImage(const TileKey& key, ProgressCallback* progress)
{
unsigned x, y;
key.getTileXY(x, y);
if (_options.invertY() == true)
{
unsigned cols = 0, rows = 0;
key.getProfile()->getNumTiles(key.getLevelOfDetail(), cols, rows);
y = rows - y - 1;
}
std::string location = _template;
// support OpenLayers template style:
replaceIn(location, "${x}", Stringify() << x);
replaceIn(location, "${y}", Stringify() << y);
replaceIn(location, "${z}", Stringify() << key.getLevelOfDetail());
// failing that, legacy osgearth style:
replaceIn(location, "{x}", Stringify() << x);
replaceIn(location, "{y}", Stringify() << y);
replaceIn(location, "{z}", Stringify() << key.getLevelOfDetail());
std::string cacheKey;
if (!_rotateChoices.empty())
{
cacheKey = location;
unsigned index = (++_rotate_iter) % _rotateChoices.size();
replaceIn(location, _rotateString, Stringify() << _rotateChoices[index]);
}
URI uri(location, _options.url()->context());
if (!cacheKey.empty())
uri.setCacheKey(cacheKey);
return uri.getImage(_dbOptions.get(), progress);
}
virtual std::string getExtension() const
{
return _format;
}
osg::HeightField* createHeightField(const TileKey& key, ProgressCallback* progress);
private:
const CgMapboxTerrainOptions _options;
std::string _format;
std::string _template;
std::string _rotateChoices;
std::string _rotateString;
std::string::size_type _rotateStart, _rotateEnd;
OpenThreads::Atomic _rotate_iter;
osg::ref_ptr<osgDB::Options> _dbOptions;
};
osg::HeightField* MapBoxTerrainSource::createHeightField(const TileKey& key, ProgressCallback* progress)
{
// height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
// MapBox encoded elevation PNG.
// https://www.mapbox.com/blog/terrain-rgb/
if (1/*_options.elevationEncoding().value() == "mapbox"*/)
{
if (getStatus().isError())
return 0L;
osg::HeightField *hf = 0;
osg::ref_ptr<osg::Image> image = createImage(key, progress);
if (image.valid())
{
// Allocate the heightfield.
hf = new osg::HeightField();
hf->allocate(image->s(), image->t());
ImageUtils::PixelReader reader(image.get());
for (unsigned int c = 0; c < image->s(); c++)
{
for (unsigned int r = 0; r < image->t(); r++)
{
osg::Vec4 pixel = reader(c, r);
pixel.r() *= 255.0;
pixel.g() *= 255.0;
pixel.b() *= 255.0;
float h = -10000.0f + ((pixel.r() * 65536.0f + pixel.g() * 256.0f + pixel.b()) * 0.1f);
hf->setHeight(c, r, h);
}
}
}
return hf;
}
else
{
return TileSource::createHeightField(key, progress);
}
}
class MapBoxTerrainTileSourceDriver : public TileSourceDriver
{
public:
MapBoxTerrainTileSourceDriver()
{
supportsExtension("osgearth_mapboxTer", "mapboxTer Driver");
}
virtual const char* className()
{
return "mapboxTer Driver";
}
virtual ReadResult readObject(const std::string& file_name, const Options* options) const
{
if (!acceptsExtension(osgDB::getLowerCaseFileExtension(file_name)))
{
return ReadResult::FILE_NOT_HANDLED;
}
return new MapBoxTerrainSource(getTileSourceOptions(options));
}
};
REGISTER_OSGPLUGIN(osgearth_mapboxTer, MapBoxTerrainTileSourceDriver)
使用
CgMapboxTerrainOptions mbTerOpt;
mbTerOpt.url() = "http://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=...";
mbTerOpt.profile()->namedProfile() = "spherical-mercator";
ElevationLayerOptions eoptions("mapboxEle", mbTerOpt);
osg::ref_ptr<osgEarth::ElevationLayer> rpEleLayer = new osgEarth::ElevationLayer(eoptions);
osg::ref_ptr<osgEarth::Map> rpMap = new osgEarth::Map(mapOpts);
rpMap->addImageLayer(rpImagLayer.get());
rpMap->addElevationLayer(rpEleLayer.get());
注意:access_token可在mapbox官网自行申请。
参考
本人在参考大佬的文章后自行实践,未能按照大佬的代码实现相关效果,一部分的原因是大佬给出的代码并不全。
在重新定义配置选项 CgMapboxTerrainOptions 和 TileSourceDriver 后,最终将mapbox在线高程数据加载出来了。
参考文章:
osgearth加载mapbox在线高程数据