1. 说明
在《warlock框架的platform显示流程梳理》中已经知道warlock在地图上的显示基本就是将数据从wsf获取后转发到wkf的MapDisplay插件,由MapDisplay插件完成在地图上的展示,包括模型、标签、绘制等,地图数据的显示都在MapDisplay插件中,为了实现二三维同步,本文就来研究通过魔改MapDisplay插件来实现。
afsim本身是基于osgearth来展示三维的数字地球,通过配置earth文件可以从三维切换成二维,但不能同时显示,除非再创建一个插件来单独展示二维,并经过一定的逻辑可以实现二三维的同步。原本想基于MapDiaplay做一个二三维同步展示的功能,结果在实现过程中发现基于MapDisplay还可以实现其他的显示效果,比如局部战场、特写展示等,因此做着做着就对MapDisplay做了全面的改造,包括代码结构、继承关系等,让其不只是显示三维地球,还可以支持添加不同的Viewer来展示二维、局部、特写等不同的效果。因此本文可能无法全面展示所有改造内容,仅能把关键的地方提示出来,具体的实现需要大家根据开发经验自行补充。
已知问题暂未修复:
(已解决)1、如果二三维同时显示,二维地图无法拖动,但只要有一个是浮动的窗口,就能拖动。
2、标签显隐选中后,双击地图其他地方,选中状态会变为未选中状态;
3、Elevation Lines(等高线?)二三维显示不一致(在二维中我去掉了显示);
4、Lakes_and_Rivers无法在二维显示,原因未知(但不是太重要,可通过earth文件配置)先看看效果:
2. 抽象接口
在MapDisplay中,视图是通过DockWidget内部创建并返回的。这里我也沿用这种机制,但因为wkf中DockWidget有两种类型,一种是中心窗口嵌入型的(wkf::DockWidget,它继承至QDockWidget),一种是四周嵌入型的(QDockWidget),为了同时调度这两种类型,需要再往上抽象一层DockWidget接口,用于封装统一接口和多视图基础实现:
// IDockWidgt.h
namespace Map {
class IDockWidget
{
public:
IDockWidget(QDockWidget *dockWidget);
virtual ~IDockWidget();
wkf::Viewer* GetViewer() const { return mViewerPtr; }
QWidget* widget() const { return mDockWidget->widget(); }
virtual QList<wkf::Action*> GetActions() const = 0;
// 用于创建自定义的CameraMontion
virtual vespa::VaCameraMotion* GetCustomCameraMontion(vespa::VaCameraBase *camera);
void init();
protected:
QDockWidget* mDockWidget = nullptr;
wkf::Viewer* mViewerPtr = nullptr;
vespa::VaCameraMotion* mCameraMotionPtr = nullptr;
const PrefObject* mMapPrefObject = nullptr;
QMutex mEntitySelectionMutex;
};
}// IDockWidget.cpp
Map::IDockWidget::IDockWidget(QDockWidget* dockWidget)
: mDockWidget(dockWidget)
{
}
Map::IDockWidget::~IDockWidget()
{
}
void Map::IDockWidget::init()
{
wkfEnv.GetMainWindow()->AddDockWidgetToViewMenu(mDockWidget);
wkf::VtkEnvironment::InitializeOSG_Path();
mViewerPtr = new wkf::Viewer(mDockWidget, vespa::VaViewer::HighlightViewer, 0);
vespa::VaWidget* widget = new vespa::VaWidget(mViewerPtr, mDockWidget, true);
widget->BlockRightClick(true);
mViewerPtr->SetChooser(widget->GetChooser());
// 获取自定义的CameraMontion
mCameraMotionPtr = GetCustomCameraMontion(mViewerPtr->GetCamera());
mViewerPtr->GetCamera()->SetCustomCameraMotion(mCameraMotionPtr);
mDockWidget->setWidget(widget);
mDockWidget->setAcceptDrops(true);
}
vespa::VaCameraMotion* Map::IDockWidget::GetCustomCameraMontion(vespa::VaCameraBase *camera)
{
return new CameraMotion(camera);
}3. 修改三维MapDockWidget
基于上面定义的接口,使用多继承的方式让MapDockWidget进行继承:

然后删除已经在IDockWidget中实现的GetViewer方法,同时删除成员变量,这些变量都统一定义在父类IDockWidget中,如下:

然后在MapDockWidget的构造函数中调用父类的init方法用于创建Viewer并初始化,init里的实现就是MapDockWidget构造函数的实现,所以也需要将MapDockWidget中相关代码进行删除,并且创建Viewer需要的窗口指针是通过IDockWidget的构造函数传入保存备用的:

删除后的MapDockWidget构造函数如下:

此时编译运行,与原始的效果是一样的还看不出区别:

4. 创建二维地图
我们知道在MapDisplay的Preference中可以切换地图,所选择的地图是resource/map目录下对应的earth文件:

其中带后缀flat的是二维模式的地图。这里其实可以想到通过在代码中动态设置选择的地图名称来切换二三维,所以首先就是复制MapDockWidget为Map2DockWidget,并把类名修改为Map2DockWidget,构造函数的内容与三维的MapDockWidget一样。

最后在MapPlugin中创建Map2DockWidget界面对象(这里先采用硬编码添加Viewer,后面修改为动态添加):

这里因为是直接复制MapDockWidget的代码,如果不做任何修改,默认加载的是三维地球,并且也是因为复制的MapDockWidgt,因此在warlock加载场景后,也会显示出平台,点击平台同样会触发选中事件,右键菜单与原本的三维操作是一样的,并且通过Preferenc修改的部分效果(如颜色)会同步在两个球上更新:
上面是两个三维球的显示,但我们这里的目的是显示二维地图,因此需要先将三维球切换为二维。在之前的《影像及高程等数据加载》文章中,讲过afsim会根据Preference里面配置的地图名称添加后缀" - flat"加载同名xxx_flat.earth文件来将osgearth的三维球展开为二维。我也是沿用此机制,根据三维球选择的地图名称,在我们新建的Map2DockWidget中将SetPreferences修改为三维同名带" - flat"的地图名称的代码:
void Map2DockWidget::SetPreferences(const PrefObject* aPrefObjectPtr)
{
// we don't set mActiveMap right away, because SetMap compares to avoid redundant "changes"
std::string newMap = aPrefObjectPtr->GetPreferences().mActiveMap;
QString mapName = QString(newMap.c_str());
if (!mapName.contains("flat"))
{
newMap += " - flat";
}
mActiveMap = wkfEnv.GetPreferenceObject<wkf::SharedMapPreferencesObject>()->SetMap(newMap, mViewerPtr, mActiveMap);
mCameraMotionPtr->SetZoomOnCursor(aPrefObjectPtr->GetZoomOnCursor());
}这里的newMap就是Preference中选择的地图文件,通过判断后缀是否含 - flat,来动态添加到二维地图名称的_flat后缀,这样做的好处是,二三维可以配置不同的地图数据,修改后编译,运行就可以看到新建的Map2DockWidget显示二维地图,并且就算通过Preference切换,也并不影响我们自建的二维地图:

上面虽然能够同时显示二三维,但会发现这样创建还是有不一样的地方:
两个地图没有同步;
右键菜单中居中等针对平台的操作,会作用到第一个三维球中;
标签显隐对新加的地图上的平台无效;
二维地图上的platform对Perference的大部分修改无响应等等;
这些都是因为没有在MapPlugin文件中将事件的触发分发到我们新建的DockWidget中造成的。下面通过魔改MapPlugin来管理多DockWidget的方式解决这些问题。
5. 多DockWidget支持
为了让代码逻辑更清晰一点,我这里首先对继承关系进行改造。阅读MapPlugin的代码可知,它内部的很多操作,都是对它内部保存的mDockWidget变量来进行的,这个变量是三维地球MapDockWidget的对象。我们想要支持多DockWidget,那么就需要修改MapPlugin让其支持对多个DockWidget进行操作,即只需要将其改为遍历IDockWidget并调用相应的方法,就能让子类自己处理相关的内容了。上面抽象IDockWidget的目的就在此。所以这里我们首先在MapPlugin中添加一个QList静态变量来保存IDockWidget指针:
// 这里将其设置为static,后面有用
// plugin只会初始化一次,所有这里设置为static不会有问题
static QList<IDockWidget*> mDockWidgetPtrList; 这里再进一步思考一下代码的复用性:上面我们通过拷贝三维DockWidget的实现,仅修改了SetPreferences的实现就完成了二维地图的创建和加载,那么其实说明此时二三维中的很多代码都是重复的。因此这里考虑将原本的MapDockWidget处理为二三维的父类,二三维只需要重新实现一下SetPreferences即可,DockWidget的继承关系应大致为下图所示:

注:这里之所以设计为多继承关系,是因为不是所有DockWidget都需要继承wkf::DockWidget,也可以继承QDockWidget,因此只有多继承方式才能让Map::Plugin来按共同的接口进行统一管理。
下面来按步骤进行魔改:
1、首先将MapDockWidget的SetPreferences函数移动到接口类IDockWidget中,并在接口类中添加OnSetPreferences纯虚函数,让子类实现:
virtual void SetPreferences(const PrefObject * aPrefObjectPtr);
virtual void OnSetPreferences(const Map::PrefObject* aPrefObjectPtr) = 0;SetPreferences的实现是将MapDockWidget中的代码移动过来,并添加OnSetPreferences的调用:
void Map::IDockWidget::SetPreferences(const Map::PrefObject* aPrefObjectPtr)
{
mMapPrefObject = aPrefObjectPtr;
wkf::Scenario* scenPtr = vaEnv.GetStandardScenario();
if (scenPtr)
{
const std::map<unsigned int, wkf::Platform*>& platMap = scenPtr->GetIndexPlatformMap();
for (auto& it : platMap)
{
wkf::AttachmentLabel* labelPtr = dynamic_cast<wkf::AttachmentLabel*>(it.second->FindAttachment(mUniqueId));
if (labelPtr)
{
labelPtr->SetFont(mMapPrefObject->GetLabelFont());
}
}
}
OnSetPreferences(mMapPrefObject);
}2、修改MapPlugin的PreferencesChanged为调用DockWidget列表的SetPreferences方法,并将PrefObject作为参数传递:
void Map::Plugin::PreferencesChanged()
{
PrefObject* prefObj = dynamic_cast<PrefObject*>(QObject::sender());
for (auto dockWidget : mDockWidgetPtrList)
{
dockWidget->SetPreferences(prefObj);
}
mCursorStatusPtr->SetShowAltitude(prefObj->GetShowTerrainAltitudeAtCursor());
}3、将MapDockWidget的成员变量设置为protected,以供子类使用;
4、创建Map3DockWidget继承MapDockWidget并实现OnSetPreferences接口:
// Map3DockWidget.h
#pragma once
#include "MapDockWidget.hpp"
class Map3DockWidget : public Map::DockWidget
{
Q_OBJECT
public:
Map3DockWidget(QWidget* parent = nullptr);
~Map3DockWidget();
protected:
virtual void OnSetPreferences(const Map::PrefObject* aPrefObjectPtr) override;
};// Map3DockWidget.cpp
#include "Map3DockWidget.h"
#include "WkfEnvironment.hpp"
#include "WkfMainWindow.hpp"
#include "WkfSharedMapPreferencesObject.hpp"
#include "WkfVtkEnvironment.hpp"
Map3DockWidget::Map3DockWidget(QWidget* parent)
: Map::DockWidget(wkfEnv.GetMainWindow()->centralWidget())
{
this->setWindowTitle("Map3D");
}
Map3DockWidget::~Map3DockWidget()
{
}
void Map3DockWidget::OnSetPreferences(const Map::PrefObject* aPrefObjectPtr)
{
std::string newMap = aPrefObjectPtr->GetPreferences().mActiveMap;
QString mapName = QString(newMap.c_str());
mActiveMap = wkfEnv.GetPreferenceObject<wkf::SharedMapPreferencesObject>()->SetMap(newMap, mViewerPtr, mActiveMap);
dynamic_cast<Map::CameraMotion*>(mCameraMotionPtr)->SetZoomOnCursor(aPrefObjectPtr->GetZoomOnCursor());
}5、将之前创建的Map2DockWidget多余的代码删除,与Map3DockWidget大致相同,仅OnSetPreferences的实现不一样:
void Map2DockWidget::OnSetPreferences(const Map::PrefObject* aPrefObjectPtr)
{
// we don't set mActiveMap right away, because SetMap compares to avoid redundant "changes"
std::string newMap = aPrefObjectPtr->GetPreferences().mActiveMap;
QString mapName = QString(newMap.c_str());
if (!mapName.contains("flat"))
{
newMap += " - flat";
}
mActiveMap = wkfEnv.GetPreferenceObject<wkf::SharedMapPreferencesObject>()->SetMap(newMap, mViewerPtr, mActiveMap);
dynamic_cast<Map::CameraMotion*>(mCameraMotionPtr)->SetZoomOnCursor(aPrefObjectPtr->GetZoomOnCursor());
}6、在MapPlugin的构造函数中添加二三维DockWidget的创建,并添加进mDockWidgetPtrList变量中:
// 创建三维球
IDockWidget* map3DockWidget = new Map3DockWidget(wkfEnv.GetMainWindow()->centralWidget());
// 按原本的逻辑将三维球设置为主球
wkf::Viewer* viewerPtr = map3DockWidget->GetViewer();
vaEnv.SetStandardViewer(viewerPtr);
vespa::VaWidget* widgetPtr = dynamic_cast<vespa::VaWidget*>(map3DockWidget->widget());
vaEnv.SetStandardWidget(widgetPtr);
mDockWidgetPtrList.append(map3DockWidget);
// 创建二维地图
IDockWidget* map2DockWidget = new Map2DockWidget(wkfEnv.GetMainWindow()->centralWidget());
mDockWidgetPtrList.append(map2DockWidget);7、最后将MapPlugin中所有与mDockWidget相关的报错代码都先注释掉。此时编译运行,不出意外的话,应该跟之前复制MapDockWidget代码生成二三维相同的显示了。

6. 二三维同步问题修改
二三维同步需要处理的大概有以下几个问题:
1、三维球和二维地图在界面中心点的位置大致是相同的;
2、鼠标拖动三维球旋转,二维地图应该同步平移;
3、鼠标滚轮放大或缩小三维球,二维地图同步放大或缩小
4、鼠标拖动二维地图平移,三维球应该同步旋转;
5、鼠标滚轮放大或缩小二维地图,三维球同步放大或缩小
6、在二维或三维上选中某个平台,另一方同步高亮;
以及上面提到的几个其他问题:
7、右键菜单中居中等针对平台的操作,只会会作用到主三维球中;
8、标签显隐对新加的地图上的平台无效;
9、二维地图上的platform对Perference的大部分修改无响应等等。
1)二三维操作同步问题
熟悉osgearth的朋友都知道,osgearth的每个view、camera、manipulator的关系如下图:

View是渲染的顶层容器,管理着一个或多个Camera以及用户的交互、Camera定义了观察场景的视角,包含视图矩阵和投影矩阵、Manipulator(特别是CameraManipulator)负责处理用户输入并更新Camera的矩阵;并且在osgearth中还有一个专门的EarthManipulator,用于处理地球相关的数据。
有了上面的基础,并且在afsim的VTK框架中通过VaViewer、VaCamera、VaCameraMotion将osgeath的三个核心内容进行了封装,因此理论上我们可以不用直接操作底层的osgearth,而只需要通过封装的类来实现我们对地球的操作。查看代码后,我发现有一个地方跟上图不太一样,就是在VaViewer中只能获取到一个VaCamera,而osgearth的Viewer却可以获取多个Camera:

我也不是太熟悉osgearth,因此也没有细究代码实现,但afsim提供了另外一种方式来实现多个视图观察地球:就是在创建VaWidget时将构造函数传入的VaViewer进行了管理。

所以我们上面创建的IDockWidget的init方法执行后就能在窗口中显示出二维和三维地图。
上面说了这么多,终究一点是我们可以通过afsim封装的类来实现功能,而不用再去学习osgearth。回到正题:通过阅读代码,发现鼠标在地图上进行视角的操作时,通过继承实现的Map::CameraMotion的BuildViewMatrix向外发送了一个ViewMatrixChanged通知:

查看这个代码的实现,可以知道它处理的结果就是鼠标操作后对应的Viewer的矩阵变换结果,即Viewer的位置和朝向。这里就能想到:其他的Viewer就能够通过监听这个事件来做对应的处理了。这里啰嗦一下:通过全文搜索BuildViewMatrix,发现afsim提供了下图几种CameraMotion:

上图不多说了,主要用于实现不同视角的功能,再次回到正题:就是监听ViewMatrixChanged事件,然后获取发送事件的Viewer的位置和朝向,根据需要设置自身的CameraMontion的位置和朝向。因为vespa::VaObserver只能Connect非成员函数,所以先在MapPlugin中定义一个回调函数:
void ViewMatrixChanged(vespa::VaViewer* aViewerPtr)
{
}然后在构造函数中添加监听:

由于之前我们将QList<IDockWidget*>设置为了静态成员变量,因此ViewMatrixChanged函数的实现就只需要循环这个变量即可,不过得先在IDockWidget添加一个回调函数,就命名为OnViewMatrixChanged吧,然后就是将这个通知分发到具体实现类去处理了:
virtual void OnViewMatrixChanged(vespa::VaViewer* aViewerPtr) = 0;由于MapDockWidget是二三维DockWidget共同的基类,因此就只需要在MapDockWidget中实现OnViewMatrixChanged即可实现二三维同步了:
void Map::DockWidget::OnViewMatrixChanged(vespa::VaViewer* aViewerPtr)
{
if (aViewerPtr == mViewerPtr) return;
auto matrix1 = aViewerPtr->GetViewMatrix();
auto matrix2 = mViewerPtr->GetViewMatrix();
if (matrix1 != matrix2)
{
auto motion = dynamic_cast<vespa::VaCameraMotionGeocentric*>(mViewerPtr->GetCamera()->GetCameraMotion());
auto aData = aViewerPtr->GetCamera()->GetCameraMotion()->GetMotionData();
motion->SetPosition(aData->mLLA);
motion->SetAzElDist(aData->mAED[0], aData->mAED[1], aData->mAED[2]);
mViewerPtr->RequestUpdate();
}
}编译后就可以实现二三维同步了:
2)平台标签显隐不同步问题
标签不同步的问题只涉及到MapPlugin中的两个方法ResetOptionStates和SetPlatformOptionState,其中ResetOptionStates的实现中调用了vaEnv.GetStandardScenario()表示只针对主三维球,而SetPlatformOptionState的实现中只针对原本的mDockWidget对象,它也是主三维球,并且这两个函数是重写的父类Plugin虚函数实现的,所以不能像上面一样通过修改为纯虚函数让子类实现,而需要将实现往后移动到二三维的DockWidget中去实现,在MapPlugin类中仅做通知。所以第一步是在抽象类IDockWidget中声明纯虚函数:
virtual void OnResetOptionStates() = 0;
virtual void OnSetPlatformOptionState(int aOptionId, bool aState, wkf::Platform* aPlatformPtr) = 0;同时,还需要将MapPlugin中对原本mDockWidget操作的方法也提取到接口类中,但可以不是纯虚函数,而是默认实现的虚函数,由子类决定是否要进行重载:
// 这几个是地图响应函数,可以默认实现
virtual void CenterCamera(wkf::Entity* aEntityPtr) {}
virtual void Follow(wkf::Entity* aEntityPtr) {}
virtual bool HasActiveRuler() const { return false; }
virtual void MeasureFrom(const vespa::VaLocationReference& aLocRef) {}同样的道理,可以将上面声明的纯虚函数放到二三维共同的基类MapDockWidget中去实现,然后修改MapPlugin中的代码改为通知:
void Map::Plugin::ResetOptionStates()
{
for (auto dockWidget : Map::Plugin::mDockWidgetPtrList)
{
dockWidget->OnResetOptionStates();
}
}
void Map::Plugin::SetPlatformOptionState(int aOptionId, bool aState, wkf::Platform* aPlatformPtr)
{
for (auto dockWidget : Map::Plugin::mDockWidgetPtrList)
{
dockWidget->OnSetPlatformOptionState(aOptionId, aState, aPlatformPtr);
}
}将原本的代码挪到这两个函数的实现中去:
void Map::DockWidget::OnResetOptionStates()
{
wkf::Scenario* scenPtr = vaEnv.GetStandardScenario();
if (scenPtr)
{
const std::map<unsigned int, wkf::Platform*>& platMap = scenPtr->GetIndexPlatformMap();
for (auto& it : platMap)
{
wkf::AttachmentLabel* labelPtr = dynamic_cast<wkf::AttachmentLabel*>(it.second->FindAttachment(mUniqueId));
if (labelPtr)
{
it.second->RemoveAttachment(labelPtr->GetUniqueId());
}
}
}
}
void Map::DockWidget::OnSetPlatformOptionState(int aOptionId, bool aState, wkf::Platform* aPlatformPtr)
{
if (aPlatformPtr)
{
wkf::AttachmentLabel* labelPtr = dynamic_cast<wkf::AttachmentLabel*>(aPlatformPtr->FindAttachment("label" + mUniqueId));
// if label attachment does not exist, create it
if (!labelPtr)
{
if (aState)
{
try
{
if (!labelPtr)
{
labelPtr = vespa::VaEnvironment::CreateAttachment<wkf::AttachmentLabel>(
"label",
*aPlatformPtr,
mViewerPtr,
true);
labelPtr->SetName("label" + mUniqueId);
}
}
catch (const vespa::UnknownAttachmentTypeError& e)
{
ut::log::error() << e.what() << "in Map::Plugin::SetPlatformOptionState()";
return;
}
// Fortify still warns even if an exception is thrown.
if (!labelPtr)
{
return;
}
if (mMapPrefObject != nullptr)
{
labelPtr->Setup(QString::fromStdString(aPlatformPtr->GetName()),
wkfEnv.GetPreferenceObject<wkf::TeamVisibilityPrefObject>()->GetTeamColor(
QString::fromStdString(aPlatformPtr->GetSide())), mMapPrefObject->GetLabelFont());
}
}
}
if (labelPtr)
{
labelPtr->SetStateVisibility(aState);
}
}
}注:上述代码中的mUniqueId,是用来做唯一标识的,用任意唯一标识生成即可。
编译后,通过PlatformOption即能够同时控制二三维标签显隐:
3)MapPreference不同步问题
这个问题主要是在PrefObject的Applay函数中引起的,查看代码可知,它只对StandardViewer生效,即我们在MapPlugin的构造函数中设置的三维球Map3DockWidget中的Viewer:

所以这里就比较容易修改了,很容易想到的做法是遍历MapPlugin中创建的IDockWidget后获取Viewer来实现,如下:
void Map::PrefObject::Apply()
{
PrefData& pd = mCurrentPrefs;
for (auto dockWidget : Map::Plugin::mDockWidgetPtrList)
{
auto viewer = dockWidget->GetViewer();
//vespa::VaViewer* viewer = vaEnv.GetStandardViewer();
if (viewer && viewer->IsInitialized())
{
...
}
else
{
mCallbacks.Add(vespa::VaObserver::ViewerInitialized.Connect(&Map::PrefObject::ViewerInitializedCB, this));
}
}
}但是这样实现的勉强能够让MapOption中的下图选项同步,而Overlays选项仍然不会同步:

原因在于Apply的实现中,它将这几个对象保存为了成员变量,且只初始化一次,后面就只会通过SetVisible来控制显隐:

我们创建了二维和三维两个视图,应该需要两套上述成员变量。所以这里应该将这类成员变量移动到具体的实现类(即IDockWidget的子类)中去。这里首先在IDockWidget中声明接口和成员变量(以mGridLines为例)
virtual void OnPrefObjectApply(const Map::PrefObject* aPrefObjectPtr) = 0;
vespa::VaOverlayMapGrid* mMapGridPtr{nullptr};然后在MapDockWidget中实现OnPrefObjectApply接口:
void Map::DockWidget::OnPrefObjectApply(const Map::PrefData& pd)
{
if (pd.mGridLines)
{
if (nullptr == mMapGridPtr)
{
mMapGridPtr = dynamic_cast<vespa::VaOverlayMapGrid*>(vaEnv.GetFactory()->CreateOverlay("graticule", mUniqueId));
mViewerPtr->AddOverlay(mMapGridPtr);
}
float color[4] = { static_cast<float>(pd.mGridColor.redF()),
static_cast<float>(pd.mGridColor.greenF()),
static_cast<float>(pd.mGridColor.blueF()),
static_cast<float>(pd.mGridColor.alphaF()) };
float density = pd.mGridDensity;
mMapGridPtr->SetColor(color);
mMapGridPtr->SetDensityFactor(density);
mMapGridPtr->SetVisible(true);
}
else if (nullptr != mMapGridPtr)
{
mMapGridPtr->SetVisible(false);
}
}最后再删除PrefObject的mGridLine实现并调用IDockWidget的接口:

其他成员变量按照上述代码修改后编译,即可实现Overlays的二三维同步:
1、Lakes_and_Rivers无法在二维显示,原因未知(但不是太重要,可通过earth配置)
2、Elevation Lines二三维显示不一致,因为在二维上显示效果也不明显,建议二维上不做显示,如下图:

7. 后记
上面呢把基本的框架搭建好了,以上即是我的方式实现的二三维同步的大概思路和主要的实现内容,说不上好,但勉强能用。当然肯定还有其他更好的方式来实现,这就需要大家自己根据自己掌握的其他技术来研究实现了。其中也还有一些比如传感器包络、航线显示、标绘等暂时没有实现同步,这些呢基于上面的框架后面慢慢加吧,但可能不会专门针对这个去做修复。
往期推荐
飞腾D2000麒麟V10国防版下编译
基于wsf插件扩展内置platform
服务端仿真引擎框架
arm64版的麒麟V10服务器docker容器集成后台仿真引擎

评论