[osg][osgEarth][原]基于OE自定义自由飞行漫游器(初级版)

由于受够了OE的漫游器,想搞个可以在全球飞行的漫游器,所以就做了一个:

请无视我的起名规则······

类头文件:EarthWalkManipulator.h

#pragma once
//南水之源  20180101
#include <osgGA/CameraManipulator>
#include <osgEarth/MapNode>
#include <osgEarth/Viewpoint>
#include <osgEarth/GeoData>

class EarthWalkManipulator :public osgGA::CameraManipulator
{
public:
    EarthWalkManipulator();
    ~EarthWalkManipulator();

    //所有漫游器都必须实现的4个纯虚函数
    virtual void setByMatrix(const osg::Matrixd& matrix) {}  //设置相机的位置姿态矩阵
    virtual void setByInverseMatrix(const osg::Matrixd& matrix) {}  //设置相机的视图矩阵
    virtual osg::Matrixd getMatrix() const;  //获取相机的姿态矩阵
    virtual osg::Matrixd getInverseMatrix() const;   //获取相机的视图矩阵 

    //所有操作在这里响应
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);

    // Attach a node to the manipulator.
    virtual void setNode(osg::Node*);
    virtual osg::Node* getNode();
    bool established();

    /**
    * Sets the camera position, optionally moving it there over time.
    */
    //virtual void setViewpoint(const osgEarth::Viewpoint& vp, double duration_s = 0.0);
    virtual void home(double /*unused*/);
    virtual void home(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us);

    void addMouseEvent(const osgGA::GUIEventAdapter& ea);
    bool calcMovement(const osgGA::GUIEventAdapter& ea);
protected:
    osg::Vec3   _eye;               //视点位置
    osg::Quat    _rotate;            //旋转姿态
    osg::ref_ptr<osg::Node>  _root;

    osg::observer_ptr<osg::Node> _node;
    osg::observer_ptr<osgEarth::MapNode>   _mapNode;

    osg::ref_ptr<const osgEarth::SpatialReference> _srs;

    float        _speed;                //速度

    // Internal event stack comprising last two mouse events.
    osg::ref_ptr<const osgGA::GUIEventAdapter> _ga_t1;
    osg::ref_ptr<const osgGA::GUIEventAdapter> _ga_t0;

};

类实现:EarthWalkManipulator.cpp

//南水之源  20180101
#include "EarthWalkManipulator.h"
#include <osgViewer\Viewer>

#include <osgDB\ReadFile>
#include <osg\MatrixTransform>

using namespace osgEarth;

EarthWalkManipulator::EarthWalkManipulator()
{
    _eye = osg::Vec3d(0, 0, 0);
    //_rotate = osg::Quat(-osg::PI_2, osg::X_AXIS);
    _speed = 1.0;
}

EarthWalkManipulator::~EarthWalkManipulator()
{
}

//获取相机的姿态矩阵
osg::Matrixd EarthWalkManipulator::getMatrix() const
{
    osg::Matrix mat;
    mat.setRotate(_rotate);//先旋转
    mat.postMultTranslate(_eye);//再平移
    return mat;
}

osg::Matrixd EarthWalkManipulator::getInverseMatrix() const
{
    osg::Matrix mat;
    mat.setRotate(-_rotate);
    mat.preMultTranslate(-_eye);
    return mat;
    //return osg::Matrixd::inverse(getMatrix());
}

void
EarthWalkManipulator::home(double unused)
{
    _eye = osg::Vec3d(0, 0, 0);
    _speed = 1.0;
}

void
EarthWalkManipulator::home(const osgGA::GUIEventAdapter&, osgGA::GUIActionAdapter& us)
{
    home(0.0);
    us.requestRedraw();
}

void
EarthWalkManipulator::setNode(osg::Node* node)
{
    // you can only set the node if it has not already been set, OR if you are setting
    // it to NULL. (So to change it, you must first set it to NULL.) This is to prevent
    // OSG from overwriting the node after you have already set on manually.
    if (node == 0L || !_node.valid())
    {
        _root = node;
        _node = node;
        _mapNode = 0L;
        _srs = 0L;

        established();

        osg::Matrix matrixGood1;
        GeoPoint point1(_srs, 0, 0, 10000.0);
        point1.createLocalToWorld(matrixGood1);

        _eye = matrixGood1.getTrans();

        osg::Vec3d worldup;
        point1.createWorldUpVector(worldup);

        osg::Matrix mat;
        matrixGood1.getRotate().get(mat);
        osg::Vec3d eye, center, up;
        mat.getLookAt(eye, center, up);
        mat.makeLookAt(eye, -worldup, up);

        _rotate = mat.getRotate();

    }
}

osg::Node*
EarthWalkManipulator::getNode()
{
    return _node.get();
}

bool EarthWalkManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
{
    switch (ea.getEventType())
    {
    case(osgGA::GUIEventAdapter::FRAME):
    {
        if (calcMovement(ea))//根据鼠标在屏幕中的位置调整相机转向
            us.requestRedraw();
        return true;
    }break;
    case(osgGA::GUIEventAdapter::SCROLL):
    {
        osg::Quat qat;
        osg::Matrix mat;
        _rotate.get(mat);
        osg::Vec3d eye, center, up;
        mat.getLookAt(eye, center, up);

        osg::Vec3d dirction = center - eye;
        dirction.normalize();
        up.normalize();
        osg::Vec3d cross = dirction^up;
        cross.normalize();

        cross *= 0.01;
        switch (ea.getScrollingMotion())
        {
        case osgGA::GUIEventAdapter::ScrollingMotion::SCROLL_UP://逆时针旋转相机
        {
            mat = osg::Matrix::lookAt(eye, center, up + cross);
            _rotate = mat.getRotate();
        }break;
        case osgGA::GUIEventAdapter::ScrollingMotion::SCROLL_DOWN://顺时针旋转相机
        {
            mat = osg::Matrix::lookAt(eye, center, up - cross);
            _rotate = mat.getRotate();
        }break;
        }
        return true;
    }break;
    case (osgGA::GUIEventAdapter::KEYDOWN):
    {
        osg::Vec3   v3Direction;         //视点方向
        osg::Matrix mCameraQuat;
        osg::Vec3d  v3Eye, v3Center, v3Up;
        _rotate.get(mCameraQuat);
        mCameraQuat.getLookAt(v3Eye, v3Center, v3Up);//这里的v3Eye不是实际相机的位置,而是0,0,0
        v3Direction = v3Center - v3Eye;
        v3Direction.normalize();
        osg::Vec3d v3CrossVector = v3Up^v3Direction;
        v3CrossVector.normalize();
        if (ea.getKey() == ‘w‘ || ea.getKey() == ‘W‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)//前进
        {
            _eye += v3Direction * _speed;
        }
        if (ea.getKey() == ‘s‘ || ea.getKey() == ‘S‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)//后退
        {
            _eye -= v3Direction * _speed;
        }
        if (ea.getKey() == ‘a‘ || ea.getKey() == ‘A‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)//左移
        {
            _eye += v3CrossVector * _speed;
        }
        if (ea.getKey() == ‘d‘ || ea.getKey() == ‘D‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)//右移
        {
            _eye -= v3CrossVector * _speed;
        }
        if (ea.getKey() == ‘-‘ || ea.getKey() == ‘_‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Control_L)//减10倍移动速度
        {
            _speed /= 10.0;
            if (_speed < 1.0)
            {
                _speed = 1.0;
            }
        }
        if (ea.getKey() == ‘=‘ || ea.getKey() == ‘+‘ || ea.getKey() == osgGA::GUIEventAdapter::KEY_Shift_L)//加10倍移动速度
        {
            _speed *= 10.0;
            if (_speed > 100000.0)
            {
                _speed = 100000.0;
            }
        }

        if (ea.getKey() == ‘h‘ || ea.getKey() == ‘H‘)//在当前经纬度,姿态回正:1.视点向地面 2.头部向正北
        {
            v3Eye = _eye;//使用相机实际位置
            osg::Vec3d v3EyeLonLat;
            _srs->transformFromWorld(v3Eye, v3EyeLonLat);
            //先获取当前位置的经纬度,再获取当前正上,正北
            osg::Matrix mRealAttitude;

            if (v3EyeLonLat.z() < 0)//v3EyeLonLat.z()是眼点实际海拔
                v3EyeLonLat.z() = 100;//将海拔0以下的物体拉到海拔100米

            GeoPoint gEyeGeo(_srs, v3EyeLonLat.x(), v3EyeLonLat.y(), v3EyeLonLat.z());
            gEyeGeo.createLocalToWorld(mRealAttitude);

            osg::Vec3d v3HorizonUp;//指天向量
            gEyeGeo.createWorldUpVector(v3HorizonUp);

            _eye = mRealAttitude.getTrans();

            mRealAttitude.getLookAt(v3Eye, v3Center, v3Up);//获取新的位置和姿态

            osg::Matrix mDeviationAttitude;//向北位置偏移0.00001纬度,为了计算正北方向
            GeoPoint gDeviationEyeGeo(_srs, v3EyeLonLat.x(), v3EyeLonLat.y() + 0.00001, v3EyeLonLat.z());
            gDeviationEyeGeo.createLocalToWorld(mDeviationAttitude);
            osg::Vec3d v3DeviationNorthPoint = mDeviationAttitude.getTrans();
            osg::Vec3d v3NorthHeadUp = v3DeviationNorthPoint - v3Eye;
            v3NorthHeadUp.normalize();//指北向量

            //if (v3EyeLonLat.y() < 90.0 && v3EyeLonLat.y() > 0.0)//没研究出为什么北半球和南半球需要相反,但实际使用没问题
            //{
            //    mRealAttitude.makeLookAt(osg::Vec3d(0,0,0), -v3HorizonUp, -v3NorthHeadUp);
            //}
            if (v3EyeLonLat.y() < 89.99999  && v3EyeLonLat.y() > -90.0)
            {
                mRealAttitude.makeLookAt(osg::Vec3d(0, 0, 0), -v3HorizonUp, v3NorthHeadUp);
            }
            _rotate = mRealAttitude.getRotate();
        }
    }break;
    default:
        return false;
    }
}

bool
EarthWalkManipulator::established()
{
    if (_srs.valid() && _mapNode.valid() && _node.valid())
        return true;

    // lock down the observed node:
    osg::ref_ptr<osg::Node> safeNode;
    if (!_node.lock(safeNode))
        return false;

    // find a map node or fail:
    _mapNode = osgEarth::MapNode::findMapNode(safeNode.get());
    if (!_mapNode.valid())
        return false;

    // Cache the SRS.
    _srs = _mapNode->getMapSRS();
    return true;
}

void EarthWalkManipulator::addMouseEvent(const osgGA::GUIEventAdapter& ea)
{
    _ga_t1 = _ga_t0;
    _ga_t0 = &ea;
}

bool EarthWalkManipulator::calcMovement(const osgGA::GUIEventAdapter& ea)
{
    osg::Quat qat;
    osg::Matrix mat;
    _rotate.get(mat);
    osg::Vec3d eye, center, up;
    mat.getLookAt(eye, center, up);

    osg::Vec3d dirction = center - eye;
    dirction.normalize();
    up.normalize();
    osg::Vec3d cross = dirction^up;
    cross.normalize();

    double x1 = ea.getXnormalized();
    double y1 = ea.getYnormalized();

    osg::Vec3d deviation(0, 0, 0);
    if (x1 > 0.1)
    {
        deviation += cross * 0.001;
    }
    else if (x1 < -0.1)
    {
        deviation -= cross * 0.001;
    }
    if (y1 > 0.1)
    {
        deviation += up * 0.001;
    }
    else if (y1 < -0.1)
    {
        deviation -= up * 0.001;
    }

    mat = osg::Matrix::lookAt(eye, deviation + center, up);
    _rotate = mat.getRotate();

    return true;
}

使用:main.cpp

#include <osg/Image>
#include <osgGA/StateSetManipulator>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgEarth/Map>
#include <osgEarth/MapNode>
#include <osgEarth/Registry>
#include <osgEarthSymbology/Geometry>
#include <osgEarthSymbology/GeometryRasterizer>
#include <osgEarthUtil/EarthManipulator>
#include <osgEarthUtil/AutoClipPlaneHandler>
#include <osgEarth/ImageToHeightFieldConverter>
#include <osgEarth/ImageUtils>
#include <osgEarth/FileUtils>
#include <osgEarth/Registry>
#include <osgEarth/MapFrame>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgEarthUtil/ExampleResources>
#include <math.h>

#include <osgGA/NodeTrackerManipulator>
#include <osgGA/AnimationPathManipulator>
#include <osgGA/KeySwitchMatrixManipulator>
#include "EarthWalkManipulator.h"

using namespace osgEarth;
using namespace osgEarth::Util;
using namespace osgEarth::Symbology;

int main(int argc, char** argv)
{
    //正常的.earth文件加载
    osg::ArgumentParser arguments(&argc, argv);
    osgViewer::Viewer viewer;
    MapNode* s_mapNode = 0L;
    osg::Node* earthFile = MapNodeHelper().load(arguments, &viewer);
    if (earthFile)
        s_mapNode = MapNode::get(earthFile);
    if (!s_mapNode)
    {
        OE_WARN << "Unable to load earth file." << std::endl;
        return -1;
    }
    osg::Group* root = new osg::Group();
    root->addChild(earthFile);

    osg::Matrix matrixGood1;
    osg::Vec3d geopoint1, geopoint2, geopoint3;
    GeoPoint point1(s_mapNode->getMapSRS(), 0, 0, 1000);
    point1.createLocalToWorld(matrixGood1);
    //matrixGood1.getLookAt(geopoint1, geopoint2, geopoint3);
    //osg::Vec3 _vector = geopoint1 - geopoint2;

    //添加一头牛,查看位置是否正确
    //osg::Node* cow = osgDB::readNodeFile("D:\\temp\\node\\cow.osg");
    //
    //osg::ref_ptr<osg::MatrixTransform> pat = new osg::MatrixTransform;
    //pat->addChild(cow);
    //pat->setMatrix(osg::Matrix::scale(200000, 200000, 200000));

    //osg::Matrix maaat2;
    //osg::ref_ptr<osg::MatrixTransform> pat2 = new osg::MatrixTransform;
    //pat2->setMatrix(osg::Matrix::rotate(matrixGood1.getRotate())*
    //    osg::Matrix::translate(matrixGood1.getTrans()));
    //pat2->addChild(pat);
    //root->addChild(pat2);

    viewer.setSceneData(root);

    //模型漫游器
    osgGA::NodeTrackerManipulator* nodeTrack = new osgGA::NodeTrackerManipulator();
    nodeTrack->setTrackNode(root);

    /*************************************动画漫游器**下*********************************/
    GeoPoint gPoint1(s_mapNode->getMap()->getSRS(), 32, 118, 400);
    osg::Matrix gMatrix1;
    gPoint1.createLocalToWorld(gMatrix1);//获取当前地球上的正确姿态
    //由于相机,自身向下看,所以在当前姿态基础上抬起60度,注意是前乘!
    gMatrix1.preMultRotate(osg::Quat(osg::DegreesToRadians(60.0), osg::X_AXIS));
    osg::Quat q1;    gMatrix1.get(q1);//获取当前矩阵姿态
    osg::Vec3d vPos1 = gMatrix1.getTrans();//获取当前矩阵位置

    GeoPoint gPoint2(s_mapNode->getMap()->getSRS(), 32.01, 118.01, 400);
    osg::Matrix gMatrix2;
    gPoint2.createLocalToWorld(gMatrix2);
    gMatrix2.preMultRotate(osg::Quat(osg::DegreesToRadians(60.0), osg::X_AXIS));
    osg::Quat q2;
    gMatrix2.get(q2);
    osg::Vec3d vPos2 = gMatrix2.getTrans();

    GeoPoint gPoint3(s_mapNode->getMap()->getSRS(), 32.02, 118.02, 400);
    osg::Matrix gMatrix3;
    gPoint3.createLocalToWorld(gMatrix3);
    osg::Quat q3;
    gMatrix3.get(q3);
    osg::Vec3d vPos3 = gMatrix3.getTrans();
    //获取相机之后再顺旋转,其实是错误的姿态
    osg::Quat qbuf(osg::DegreesToRadians(60.0), osg::X_AXIS);
    q3    *= qbuf;

    //使用动画漫游器
    osgGA::AnimationPathManipulator *animationPathMp = new osgGA::AnimationPathManipulator();
    //给动画漫游器添加关键帧
    osg::AnimationPath* _animationPath = new osg::AnimationPath;
    _animationPath->insert(0.0, osg::AnimationPath::ControlPoint(vPos1, q1));//姿态正确
    _animationPath->insert(3.0, osg::AnimationPath::ControlPoint(vPos2, q2));//姿态正确
    _animationPath->insert(6.0, osg::AnimationPath::ControlPoint(vPos3, q3));//姿态错误!
    _animationPath->setLoopMode(osg::AnimationPath::SWING);//设置路径是回摆的
    animationPathMp->setAnimationPath(_animationPath);
    /*************************************动画漫游器**上*********************************/

    //这里添加三个漫游器,使用一个控制漫游器选择,按键盘‘3’就切换到路径动画漫游器了
    osgGA::KeySwitchMatrixManipulator* keyPtr = new osgGA::KeySwitchMatrixManipulator();
    keyPtr->addMatrixManipulator(‘1‘, "earthMan", new EarthManipulator());
    keyPtr->addMatrixManipulator(‘2‘, "trakerMan", nodeTrack);
    keyPtr->addMatrixManipulator(‘3‘, "animationPathMan", animationPathMp);
    keyPtr->addMatrixManipulator(‘4‘, "earthWalkMan", new EarthWalkManipulator());
    viewer.setCameraManipulator(keyPtr);
    //viewer.setUpViewOnSingleScreen(0);

    {
        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
        traits->x = 40;
        traits->y = 40;
        traits->width = 600;
        traits->height = 480;
        traits->windowDecoration = true;
        traits->doubleBuffer = true;
        traits->sharedContext = 0;

        osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());

        osg::ref_ptr<osg::Camera> camera = new osg::Camera;
        camera->setGraphicsContext(gc.get());
        camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
        GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
        camera->setDrawBuffer(buffer);
        camera->setReadBuffer(buffer);

        // add this slave camera to the viewer, with a shift left of the projection matrix
        viewer.addSlave(camera.get());
    }

    while(!viewer.done())
        viewer.frame();

    return 1;
}

由于是初级版,所以有些操作还是比较反人类···

运行起来是用其他漫游器告诉你:能看到个地球

开始操作:

1.按‘4’切换到自定义的漫游器,现在你的视点在球心,啥也看不到

2.按‘s’或者‘下’,再按‘h’现在应该是在地球表面了(别问为什么会需要这么SB的操作···)

3.然后自己体验吧(不要晕了···)

后期会更新一个有良好操作的版本

原文地址:https://www.cnblogs.com/lyggqm/p/8336810.html

时间: 2024-10-08 02:19:04

[osg][osgEarth][原]基于OE自定义自由飞行漫游器(初级版)的相关文章

[osg][osgEarth][原]基于OE自定义自由飞行漫游器(第二版)

在初级版上,进行新的漫游方式调整 头文件: #pragma once //南水之源 20180101 #include <osgGA/CameraManipulator> #include <osgEarth/MapNode> #include <osgEarth/Viewpoint> #include <osgEarth/GeoData> class EarthWalkManipulator :public osgGA::CameraManipulator

基于jQuery仿QQ音乐播放器网页版代码

基于jQuery仿QQ音乐播放器网页版代码是一款黑色样式风格的网页QQ音乐播放器样式代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class="m_player" id="divplayer" role="application" onselectstart="return false" style="left: 0px;"> <div class=&

[原][osg][osgEarth]关于在OE中使用物理引擎的调研

关于物理引擎旋转的一些整理 参考文档 http://blog.wolfire.com/2010/03/Comparing-ODE-and-Bullet 介绍ODE和bullet的利弊 http://stackoverflow.com/questions/6408198/bullet-vs-newton-game-dynamics-vs-ode-physics-engines 讨论作者想要哪款物理引擎 https://www.ibm.com/developerworks/cn/opensource

iOS开发--XLVideoPlayer——基于AVFoundation自定义的视频播放器

本文聊点关于最近写的这个自定义播放器.支持UITableViewCell上小屏.全屏播放,手动及屏幕旋转切换,包括右下角的小窗悬停播放,不依赖于视图控制器和第三方,尽量的让使用起来更简单,具体代码详情请戳Github,先看看效果如何! 这是基于AVFoundation下自定义的一个播放器,先简单介绍几个用到的类. 介绍: AVPlayer:可以理解为播放器对象,灵活性好,可以高度化的自定义UI,但它本身不能显示视频,显示需要另一个类AVPlayerLayer来显示,继承于CALayer,下面是摘

基于 HtmlHelper 自定义扩展Container

基于 HtmlHelper 自定义扩展Container Intro 基于 asp.net mvc 的权限控制系统的一部分,适用于对UI层数据呈现的控制,基于 HtmlHelper 的扩展组件 Code 基于 asp.net mvc 的权限控制系统示例代码:https://github.com/WeihanLi/AccessControlDemo 权限控制核心代码:https://github.com/WeihanLi/AccessControlDemo/tree/master/AccessCo

Java 基于数组自定义实现容量不可变向量Vector

背景:假定集合 S 由 n 个元素组成,它们按照线性次序存放,于是我们就可以直接访问其中的第一个元素.第二个元素.第三个元素--.也就是说,通过[0, n-1]之间的每一个整数,都可以直接访问到唯一的元素 e,而这个整数就等于 S 中位于 e 之前的元素个数??在此,我们称之为该元素的秩( Rank).不难看出,若元素 e 的秩为 r,则只要 e 的直接前驱(或直接后继)存在,其秩就是 r-1(或 r+1).这一定义与 Java. C++之类的程序语言中关于数组元素的编号规则是一致的.支持通过秩

JVM系列六(自定义插入式注解器).

一.概述 从前面 文章 中我们可以了解到,javac 的三个步骤中,程序员唯一能干预的就是注解处理器部分,注解处理器类似于编译器的插件,在这些插件里面,可以读取.修改.添加抽象语法树中的任意元素.因此,只要有足够的创意,程序员可以通过自定义插入式注解处理器来实现许多原本只能在编码中完成的事情.我们常见的 Lombok.Hibernate Validator 等都是基于自定义插入式注解器来实现的. 要实现注解处理器首先要做的就是继承抽象类 javax.annotation.processing.A

SpringMVC经典系列-14自定义SpringMVC的拦截器---【LinusZhu】

注意:此文章是个人原创,希望有转载需要的朋友们标明文章出处,如果各位朋友们觉得写的还好,就给个赞哈,你的鼓励是我创作的最大动力,LinusZhu在此表示十分感谢,当然文章中如有纰漏,请联系[email protected],敬请朋友们斧正,谢谢. 这部分主要讲解SpringMVC的拦截器的部分,会带着大家完成定义拦截器的两种方式的实例,不多说了,开始-- SpringMVC的拦截器主要是用于拦截用户的请求,并且进行相应的处理,如:权限验证.判断登录等. 定义拦截器的两种方式,如下: 1. 实现接

Qt 插件综合编程-基于插件的OpenStreetMap瓦片查看器client(5) 小结

经过不断试用与改动,这个查看器终于还是完毕了设计.实现.查看器,顾名思义,没有编辑功能:说的白一点,仅仅是一个以OpenStreetMap为底图的显示装置罢了.和专业GIS相比,这款基于插件的OpenStreetMap瓦片查看器client显得单薄.和消费类GIS(如面向浏览器的地图API)相比,它是本地的.似乎和Native应用更加合拍. 事实上一直以来想利用业余时间做这样一个工具,使得非计算机专业的project师们能够拥有一款支持漫游.标图.动画效果的轻量级地理信息插件,特别是掌握C++.