OSGEarth学习笔记(1)加载基本地理数据

发布于 2024-09-15  5866 次阅读


环境配置

首先编译OSGEarth

用vcpkg编译很方便,如果源码编译的话前置依赖库太多了很复杂。

C++第三方库管理工具vcpkg使用教程-CSDN博客

OSG编程指南<二>:利用Vcpkg编译OSG(OSGEarth)_OSG三维引擎入门及进阶-CSDN专栏

首先要安装powershell 7,然后用git clone vcpkg,安装就行。之后一键配置osgearth,最后要和vs环境同步。

运行测试代码,踩了几个坑。

一个是osgearth库里math头文件用的std::max和std::min和windows.h的max、min宏冲突,要在引入windows.h前define NOMINMAX。第二个是opengl头文件重复引入的问题,这在之前学习opengl遇到过的。第三是要把osg plugins的dll放到编译输出目录,此外debug模式下osg plugins的dll版本不一样

加载TMS(影像&高程)及GeoTIFF

最简单的加载在线地图层 (TMS 瓦片地图)或者是本地geotiff,3.5版本api改了很多,不是原来osg::ref_ptr <osgEarth::Map>、osgdriver那一套了

#define NOMINMAX
#include <windows.h>
#include <GL/gl.h>
#include <osgViewer/Viewer>
#include <osgEarth/Notify>
#include <osgEarth/EarthManipulator>
#include <osgEarth/MapNode>
#include <osgEarth/Threading>
#include <osgEarth/ShaderGenerator>
#include <osgDB/ReadFile>
#include <osgGA/TrackballManipulator>
#include <osgUtil/Optimizer>
#include <iostream>
#include <osgEarth/Metrics>
#include <osgEarth/TMS>
#include <osgEarth/EarthManipulator>
#include <osg/ArgumentParser>
#include <osgEarth/GDAL>
using namespace osgEarth;
using namespace osgEarth::Util;


int main(int argc, char** argv)
{
    // 初始化osgEarth
    osgEarth::initialize();

    // 解析命令行参数
    osg::ArgumentParser args(&argc, argv);

    // 创建Viewer
    osgViewer::Viewer viewer(args);
    viewer.setUpViewInWindow(0, 100, 1200, 800);
    viewer.setReleaseContextAtEndOfFrameHint(false); 

    // 创建一个地图节点
    //osgEarth::MapNode* mapNode = new osgEarth::MapNode();
    osgEarth::Map* map = new osgEarth::Map();

    // 添加在线地图层 (TMS 瓦片地图)
    //osgEarth::TMSImageLayer* imagery = new osgEarth::TMSImageLayer();
    //imagery->setURL("https://readymap.org/readymap/tiles/1.0.0/7/");
    //osgEarth::Map* map = new osgEarth::Map();

    //加载本地geotiff
    std::string myTifFilename = "D:/coding/testdata/world.tif"; 
    auto gdalLayer = new osgEarth::GDALImageLayer();
    gdalLayer->setURL(osgEarth::URI(myTifFilename));
    map->addLayer(gdalLayer);

    auto mapNode = new osgEarth::MapNode(map);

    // 设置场景数据
    viewer.setSceneData(mapNode);

    // 设置地球的操纵器
    viewer.setCameraManipulator(new osgEarth::EarthManipulator(args));

    // 运行viewer
    return viewer.run();
}

但是我在试图加载本地高分影像时却不能正常显示,有可能是图像太大却没做预处理,按照文档尝试进行Build internal tiles&Build overviews

Working with Data — osgEarth 3.5 documentation

相关脚本在.\vcpkg\installed\x64-windows\share\bash-completion\completions目录下(COMPRESS=JPEG,不是JPG,不知道这官方文档为什么不更新,压缩完小了很多)

gdal_translate -of GTiff -co TILED=YES -co COMPRESS=JPEG input.tif output.tif

做完tiles是能显示了,但内存爆炸,而且加载巨慢,再做个Build overviews

gdaladdo --config BIGTIFF YES -r nearest D:\coding\testdata\output.tif 2 4 8 16

这样就能正常读取了

显示高程的话比较简单,一样的加载TMS就行

auto elevLayer2 = new TMSElevationLayer;
    elevLayer2->setURL(osgEarth::URI("http://readymap.org/readymap/tiles/1.0.0/116/"));
    elevLayer2->setVerticalDatum("egm96");
    map->addLayer(elevLayer2);

加载mbtiles和shp

加载mbtiles

#include <osgEarth/EarthManipulator>
#include <osgEarth/MapNode>
#include <osgEarth/Threading>
#include <osgEarth/ShaderGenerator>
#include <osgDB/ReadFile>
#include <osgGA/TrackballManipulator>
#include <osgUtil/Optimizer>
#include <iostream>
#include <osgEarth/Metrics>
#include <osgEarth/TMS>
#include <osgEarth/EarthManipulator>
#include <osg/ArgumentParser>
#include <osgEarth/GDAL>
#include <osgEarth/Notify>
#include <osgEarth/MBTiles>
using namespace osgEarth;
using namespace osgEarth::Util;
int main()
{
	_putenv_s("PROJ_LIB", "D:\\coding\\vcpkg\\installed\\x64-windows\\share\\proj");
	osgEarth::initialize();
	//create a viewer
	osgViewer::Viewer viewer;
	viewer.setUpViewInWindow(0, 100, 800, 600);
	// ? why
	viewer.setReleaseContextAtEndOfFrameHint(false);
	//set camera manipulator
	viewer.setCameraManipulator(new EarthManipulator);
	// Map is datamodel for collection of layers.
	auto rootMap = new osgEarth::Map;
	//GeoTiff Layer
	std::string worldTifFilename = "D:/coding/testdata/world.tif";
	auto gdalLayer = new GDALImageLayer();
	gdalLayer->setURL(osgEarth::URI(worldTifFilename));
	//mbtiles layer
	std::string mbtilesFilename = "D:/coding/testdata/world_countries.mbtiles";
	auto mbtLayer = new MBTilesImageLayer();
	mbtLayer->setURL(osgEarth::URI(mbtilesFilename));
	osgEarth::GeoExtent mbtExtent = mbtLayer->getExtent();
	// MapNode is the render or visualization of Map.
	osg::ref_ptr<osgEarth::MapNode> rootMapNode = new osgEarth::MapNode(rootMap);
	rootMap->addLayer(gdalLayer);
	rootMap->addLayer(mbtLayer);
	viewer.setSceneData(rootMapNode);
	//? what
	return viewer.run();
}

显示shp花了老大劲了,一个是map得改成osg::ref_ptr<map> = new Map();不知道为什么必须是这种指针。第二个是老问题了,proj.db的版本问题,妈的这个问题纠缠我很久了,每次新用一个地理相关的库都会有新的proj版本出来,我一开始看到报错了,但是没在意,因为我觉得新版本的proj.db能兼容老的,结果花了六个小时才排查这点。。。解决方法很简单,设置环境变量把原来postgresql占用的proj库的地址顶掉。

现在代码长这样

#define NOMINMAX
#include <osgViewer/Viewer>
#include <osgEarth/Notify>
#include <osgEarth/EarthManipulator>
#include <osgEarth/MapNode>
#include <osgEarth/Threading>
#include <osgEarth/ShaderGenerator>
#include <osgDB/ReadFile>
#include <osgGA/TrackballManipulator>
#include <osgUtil/Optimizer>
#include <iostream>
#include <osgEarth/Metrics>
#include <osgEarth/TMS>
#include <osgEarth/EarthManipulator>
#include <osg/ArgumentParser>
#include <osgEarth/ExampleResources>
#include <osgEarth/GDAL>
#include <osgEarth/Style>
#include <osgEarth/OGRFeatureSource>
#include <osgEarth/FeatureModelLayer>
#include <osgEarth/FeatureImageLayer>
#include <osgEarth/MBTiles>
#include <osgEarth/ContourMap>
#include <osgEarth/GLUtils>
#include <osg/LineWidth>
using namespace osgEarth;
using namespace osgEarth::Util;

int main(int argc, char** argv)
{
    _putenv_s("PROJ_LIB", "D:\\coding\\vcpkg\\installed\\x64-windows\\share\\proj");
    // 初始化osgEarth
    osgEarth::initialize();

    // 解析命令行参数
    osg::ArgumentParser args(&argc, argv);

    //查看详细的日志
    //osg::setNotifyLevel(osg::INFO);

    // 创建Viewer
    osgViewer::Viewer viewer(args);
    // 设置地球的操纵器
    viewer.setCameraManipulator(new osgEarth::EarthManipulator(args));
    viewer.setUpViewInWindow(0, 100, 1200, 800);
    viewer.setReleaseContextAtEndOfFrameHint(false); 
    //读取shp必须用osg::ref_ptr<Map>指针接map对象
    osg::ref_ptr<Map> map = new Map();
    
    // 添加在线地图层 (TMS 瓦片地图)
    osgEarth::TMSImageLayer* imagery = new osgEarth::TMSImageLayer();
    imagery->setURL("https://readymap.org/readymap/tiles/1.0.0/7/");
    map->addLayer(imagery);

    //添加在线高程
    auto elevLayer2 = new TMSElevationLayer;
    elevLayer2->setURL(osgEarth::URI("http://readymap.org/readymap/tiles/1.0.0/116/"));
    elevLayer2->setVerticalDatum("egm96");
    map->addLayer(elevLayer2);
    //绘制高程图(色彩化)
    /*ContourMapLayer::Options eOpts;
    ContourMapLayer::Options::Stop stop0, stop1, stop2, stop3, stop4;
    stop0.elevation = -625;
    stop0.color = osgEarth::Color(0, 0, 0.8, 1);
    stop1.elevation = 0.0;
    stop1.color = osgEarth::Color(0, 0.4, 1, 1);
    stop2.elevation = 10.0;
    stop2.color = osgEarth::Color(0, 1, 0, 1);
    stop3.elevation = 1000.0;
    stop3.color = osgEarth::Color(1, 1, 0, 1);
    stop4.elevation = 2000.0;
    stop4.color = osgEarth::Color(1, 0, 0, 1);
    eOpts.stops().push_back(stop0);
    eOpts.stops().push_back(stop1);
    eOpts.stops().push_back(stop2);
    eOpts.stops().push_back(stop3);
    eOpts.stops().push_back(stop4);
    auto contour = new ContourMapLayer(eOpts);*/
    
    //添加本地局部高分影像(geotiff)
    /*std::string myTifFilename = "D:/coding/testdata/kenli.tif"; 
    auto gdalLayer = new osgEarth::GDALImageLayer();
    gdalLayer->setURL(osgEarth::URI(myTifFilename));
    map->addLayer(gdalLayer);*/

    //添加mbtiles
    //std::string mbtilesFilename = "D:/coding/testdata/world.mbtiles"; 
    //osg::ref_ptr<MBTilesImageLayer> mbtLayer = new MBTilesImageLayer(); 
    //mbtLayer->setURL(osgEarth::URI(mbtilesFilename));
    //map->addLayer(mbtLayer);

    //添加shp文件
    OGRFeatureSource* features = new OGRFeatureSource();
    features->setURL("D:/coding/testdata/world.shp");
    map->addLayer(features);
    //设置style
    Style style;
    //设置线style
    LineSymbol* ls = style.getOrCreateSymbol<LineSymbol>();
    ls->stroke()->color() = Color::Yellow; //线色
    ls->stroke()->width() = 4;// 线宽,默认单位是像素
    ls->tessellationSize()->set(10, Units::KILOMETERS);//Tessellate the line geometry such that no segment is longer than this value
    // 设置填充符号(如果是多边形)
    //PolygonSymbol* polygonSymbol = style.getOrCreate<PolygonSymbol>();
    //polygonSymbol->fill()->color() = Color(Color::Green, 0.5);  // 绿色填充,50%透明度
    //设置高程style
    AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>();
    alt->clamping() = alt->CLAMP_TO_TERRAIN;// 贴紧地面
    alt->technique() = alt->TECHNIQUE_DRAPE;

    //读取shp,建立图层并设置style
    osg::ref_ptr<FeatureModelLayer> shpLayer = new FeatureModelLayer();
    shpLayer->setName("Shapefile Layer");
    shpLayer->setFeatureSource(features);
    StyleSheet* styleSheet = new StyleSheet();
    styleSheet->addStyle(style);
    shpLayer->setStyleSheet(styleSheet);
    shpLayer->getOrCreateStateSet()->addUniform(new osg::Uniform("oe_GL_LineStipplePattern", (int)0xffff), osg::StateAttribute::ON);//这行代码很重要必须设置oe_GL_LineStipplePattern 为 0xffff
    map->addLayer(shpLayer);

    osg::ref_ptr<osgEarth::MapNode> mapNode = new osgEarth::MapNode(map.get());
    // 设置场景数据
    viewer.setSceneData(mapNode);
    // 运行viewer
    //return Metrics::run(viewer);
    return viewer.run();
}

显示WFS图层

关于postgis、geoserver发布wfs图层之前博客有记录

使用geoserver的全球地图示例

    //加载wfs图层
    std::string source = "http://localhost:8084/geoserver/ne/ows?";
    osg::ref_ptr<WFSFeatureSource> wfsSource = new WFSFeatureSource;
    wfsSource->setURL(osgEarth::URI(source));
    wfsSource->setTypeName("countries");
    //wfsSource->setOutputFormat("gml2");
    wfsSource->setOutputFormat("json");

    osg::ref_ptr<FeatureModelLayer> wfsLayer = new FeatureModelLayer();
    wfsLayer->setName("wfs Layer");
    wfsLayer->setFeatureSource(wfsSource);
    map->addLayer(wfsLayer);

遗憾的是并不能成功读取,wfsSource这步没能读取到任何信息,看了源码四个小时也不知道问题出在哪。检查了源码中url的拼接方法,我能确认这样设置url、typename和outputFormat是能正确拼接的,但不知道为什么不能正常读取。

去github上提了个discussion希望有好心人救救。话说我目前需要的是一个能高性能加载大规模矢量数据并渲染的模式,明天试一试直接读取postgis或SQLite/SpatiaLite。

届ける言葉を今は育ててる
最后更新于 2024-09-21