文章目录

  • 1.简述
  • 2.百度地图
    • 2.1百度地图准备
    • 2.2 百度地图开发
      • 2.2.1实时绘制轨迹
      • 2.2.2设定航点
  • 3 QT软件开发
    • 3.1使用QWebEngineView显示Html
    • 3.2 MapChannel实现两者交互
  • 4.关于地图坐标
  • 5.轨迹回放软件
  • 6.其他事项
    • 6.1源码
    • 6.2离线地图
  • 7.参考资料

1.简述

小伙伴们在做一些空中、陆地、水下机器人时,通常会为其配套开发一个岸基监控软件,用于显示机器人状态、配置一些参数、下达任务等。通常为了让这个软件更加便于使用(看起来更加高B格),通常会嵌入一个地理地图在其中。在监控软件中嵌入地图有下述好处:

  1. 可视化机器人的运动轨迹;
  2. 在地图上选取位置(航点)用于设置机器人的任务。

那么,如何简单而优雅的实现上述好处呢。本文将结合自己的研究经历,描述和分享出我设计出的小玩意,希望对有需求的小伙伴以帮助。
开发岸基监控软件的软件有很多Qt、C#、MFC,目前最流行的应该是Qt了,本人使用的Qt版本为5.12.1。(QWebEngine组件好像要用MSVC编译器,我使用的是MSVC2017)Qt开发的岸基监控软件通过QWebEngine组件加载html形式地图,两者使用QWebChannel进行交互,地图上的功能通过html/JavaScript来实现。地图可以选择百度地图、高德地图、谷歌地图,本人认为百度地图提高的API开发资料是最好的,因此选用百度地图。

最后结合百度地图和Qt写了个轨迹回放软件,并将源码分享给大家。
先看效果:

2.百度地图

2.1百度地图准备

以嵌入百度在线地图为例进行描述。我们使用的是百度地图开放平台的Web开发中的JavaScript API

点进去后是百度地图的三维GL版本,我们选择平面的图就行。使用百度在线地图,首先需要申请账号和密钥,按照开发指南中一步一步进行下去就可以使用自己的密钥来加载百度在线地图地图。然后非常重要的是示例Demo,里面提供了很多很好的示例。可以直接复制示例代码,在本地新建一个html文件,然后粘贴进行,修改密钥,就可以用浏览器打开了(如果打不开,可以使用下面提供的html模板代码)。示例中地图基础覆盖物对我们入门有很大的帮助,想要使用更多的用法,还得去看类参考。完成这一步可以先跳到


<!DOCTYPE html>
<html>
<head><meta name="viewport" content="initial-scale=1.0, user-scalable=no" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>BDMAP</title><style type="text/css">html {height: 100%}body {height: 100%;margin: 0px;padding: 0px}#container {height: 100%}</style><script type="text/javascript" src="http://api.map.baidu.com/getscript?v=3.0&ak=您的密钥"></script>
</head>
<body><div id="container"></div>
</body>
</html><script type="text/javascript">var map = new BMap.Map("container");  // 创建地图实例map.centerAndZoom(new BMap.Point(100.939631,33.078577), 5);  // 初始化地图,设置中心点坐标和地图级别map.enableScrollWheelZoom(); //开启滚轮缩放map.disableDoubleClickZoom();map.addControl(new BMap.NavigationControl({ type: BMAP_NAVIGATION_CONTROL_LARGE, showZoomInfo: true }));map.addControl(new BMap.MapTypeControl({ mapTypes: [BMAP_NORMAL_MAP, BMAP_HYBRID_MAP], anchor: BMAP_ANCHOR_TOP_LEFT, offset: new BMap.Size(80, 50)}));map.addControl(new BMap.ScaleControl({ anchor: BMAP_ANCHOR_TOP_LEFT }));
</script>

2.2 百度地图开发

本节只针对岸基监控软件的需求开发进行描述。一个是接收岸基监控软件传来的机器人位置,实时显示在地图上,并将其绘制出轨迹。另一个是在地图上标出航点,将航点的经纬度传递给岸基监控软件用以设置机器人任务。

2.2.1实时绘制轨迹

首先声明一个BMap.Marker的覆盖物变量(本人专业领域是海洋机器人,所以就命名了boatMarker)由于实时表示机器人的位置。Marker的图标Icon可以是矢量图标BMap.Symbol,可以形象表示机器人的艏向。矢量图标有多种选择。然后初始化了一个Label跟随Marker显示一些附加信息。boatPoints用于保存实时轨迹的航点序列。boatTrackPolyline则是表示轨迹的折线。

var boatMarker = new BMap.Marker;  //艇的marker
var boatIcon = new BMap.Symbol(BMap_Symbol_SHAPE_FORWARD_CLOSED_ARROW, { //BMap_Symbol_SHAPE_PLANEscale: 1.5,strokeWeight: 1,fillColor: "red",fillOpacity: 0.8
});
var boatLabel = new BMap.Label("0mD,0mH", { offset: new BMap.Size(20, -20) });
boatLabel.setStyle({ color: "black", fontSize: "15px" });
var boatPoints = new Array();  //保存艇的轨迹点
var boatTrackPolyline = new BMap.Polyline([], { strokeColor: "green", strokeWeight: 2, strokeOpacity: 0.5 });


实现接收经纬度,更新机器人位置和绘制轨迹的代码见下。接收到经纬度后,首先通过wgs84tobd09()函数将wgs84坐标系下的位置转换到bd09坐标系下(详细见第4章)。然后使用course来选择矢量图标,最后更新boatMarker,机器人位置更新就实现了。将机器人当前位置push到boatPoints中,然后将最新的轨迹点序列通过setPath覆给折线boatTrackPolyline,然后显示出来就实现了绘制轨迹功能。

function showBoatPosition(lng, lat, course) {var currentPosition = wgs84tobd09(lng, lat);var currentPoint = new BMap.Point(currentPosition[0], currentPosition[1]);boatIcon.setRotation(course);//boatLabel.setContent(depth.toFixed(1) + "mD,"+ height.toFixed(1) + "mH");boatMarker.setIcon(boatIcon);//boatMarker.setLabel(boatLabel);boatMarker.setPosition(currentPoint);map.addOverlay(boatMarker);boatPoints.push(currentPoint);if (boatPoints.length == 1) {boatTrackPolyline.setPath(boatPoints);map.addOverlay(boatTrackPolyline);                }else if(boatPoints.length > 1)boatTrackPolyline.setPath(boatPoints);}

那么函数showBoatPosition()由谁调用呢?
首先注册一个通道,passId为mapWidgetInit()函数里这行代码注册的通道明相同channel -> registerObject(“passId”,mapChannel);
boatPosUpdated()是类MapChannel定义的信号成员;
showBoatPosition()是上述html里写的javascript函数。
connect将MapChannel的信号与html里的函数连接起来,就实现了qt->html的数据传递。

new QWebChannel(qt.webChannelTransport,function(channel){window.bridge = channel.objects.passId;//注册一个通道,将window.bridge连接到qt下的passId,形成通路channel.objects.passId.boatPosUpdated.connect(showBoatPosition);channel.objects.passId.clearTrackClicked.connect(clearTrack);
});

当然,html本身不具备这样的属性,需要辅助工具实现,该工具由qt公司提供:qwebchannel.js可以在 QT安装目录/Examples/Qt-5.x.x/webchannel/shared内找到。将qwebchannel.js放到html同目录下,然后添加下面代码。

<script type="text/javascript" src="qwebchannel.js"></script>

在开始绘制新轨迹前,对之前的轨迹进行清除。

 function clearTrack() {boatPoints = [];map.removeOverlay(boatTrackPolyline);}

2.2.2设定航点

给机器人设置任务,总得给他几个经纬度形式的航点,那么在地图上点出航点,然后把这些点的经纬度获取到再发给机器人岂不美哉。下面咱们就实现起来。
添加航点我们使用鼠标绘制工具条库,首先head部分添加下列引用代码。

<script type="text/javascript" src="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
<link rel="stylesheet" href="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.css" />

首先定义一些变量来保存绘制出的东西,实例化鼠标绘制工具。最后效果见下图。

var wayPoints = new Array();
var wayPointsPolyline = new BMap.Polyline([], { strokeColor: "red", strokeWeight: 2, strokeOpacity: 0.5, strokeStyle: "dashed"});
var wayPointsMarker = new Array();  //保存点覆盖物
var polylines = new Array();  //保存折线覆盖物
var circles = new Array();  //保存圆覆盖物
var polygon = new Array();  //保存多边形覆盖物var styleOptions = {strokeColor: "red",    //边线颜色。fillColor: "red",      //填充颜色。当参数为空时,圆形将没有填充效果。strokeWeight: 2,       //边线的宽度,以像素为单位。strokeOpacity: 0.8,       //边线透明度,取值范围0 - 1。fillOpacity: 0.2,      //填充的透明度,取值范围0 - 1。strokeStyle: 'solid' //边线的样式,solid或dashed。
}
var dashedStyleOptions = {strokeColor: "red",    //边线颜色。fillColor: "red",      //填充颜色。当参数为空时,圆形将没有填充效果。strokeWeight: 2,       //边线的宽度,以像素为单位。strokeOpacity: 0.5,    //边线透明度,取值范围0 - 1。fillOpacity: 0.2,      //填充的透明度,取值范围0 - 1。strokeStyle: 'dashed' //边线的样式,solid或dashed。
}//实例化鼠标绘制工具
var drawingManager = new BMapLib.DrawingManager(map, {isOpen: false, //是否开启绘制模式enableDrawingTool: true, //是否显示工具栏enableCalculate: true,drawingToolOptions: {anchor: BMAP_ANCHOR_TOP_RIGHT, //位置offset: new BMap.Size(5, 5), //偏离值},circleOptions: styleOptions, //圆的样式polylineOptions: dashedStyleOptions, //线的样式polygonOptions: styleOptions, //多边形的样式rectangleOptions: styleOptions //矩形的样式
});


我们下达航点,最适合用点覆盖物表示,默认的点覆盖物过于单调,做一些优化:对标出的点做编号0,1,2…;将航点依次用虚线连接起来,表示航线;已经标出的航点,可以支持拖拽修改位置;为每个航点设置一个title,内容为该航点的序号+wgs84经纬度。

实现方案:监听DrawingManager的overlaycomplete()事件,绑定响应函数taskOverlaysComplete(),在函数里判断绘制了什么东西,然后做相应的处理。主要优化点绘制:

  1. 将Marker先push到wayPointsMarker数组中,为每个Marker设置一个Label,Label里写编号;
  2. 通过调用bd09towgs84函数将bd09坐标转化为wgs84坐标,然后设置为title内容,那么鼠标移动到航点上是,会弹出title内容;
  3. 启用Marker的拖拽功能,并监听拖拽事件,绑定函数markerDragendEvent()
  4. 将Marker的bd09位置push到wayPoints的数组中,然后绘制折线。

航点拖拽相应函数markerDragendEvent主要更新wayPoints里存的被拖拽航点的坐标和进而更新虚线。

其他绘制完成就只是将绘制出的覆盖物push到对应的数组里,并绑定了一个“click”相应函数来显示覆盖物的相关信息。

function overlay_style(e) {var p = e.target;if (p instanceof BMap.Marker) {alert("该覆盖物是点,点的坐标是:" + p.getPosition().lng + "," + p.getPosition().lat);}else if (p instanceof BMap.Circle) {var bdCenter = p.getCenter();var wgsCenter = bd09towgs84(bdCenter.lng, bdCenter.lat);alert("圆心: " + wgsCenter[0].toFixed(7) + ", " + wgsCenter[1].toFixed(7) + "\n" + "半径: " + p.getRadius().toFixed(1) + "m");}else if (p instanceof BMap.Polyline) {var polylinePoints = p.getPath();var str = "航线路径点:\n";for (var i = 0; i < polylinePoints.length; i++) {var bdPos = polylinePoints[i];var wgsPos = bd09towgs84(bdPos.lng, bdPos.lat);str += ("ID" + i + ": " + wgsPos[0].toFixed(7) + ", " + wgsPos[1].toFixed(7) + "\n"); }alert(str);} else if (p instanceof BMap.Polygon){var polygonPoints = p.getPath();var str = "区域边界点:\n";for (var i = 0; i < polygonPoints.length; i++) {var bdPos = polygonPoints[i];var wgsPos = bd09towgs84(bdPos.lng, bdPos.lat);str += ("ID" + i + ": " + wgsPos[0].toFixed(7) + ", " + wgsPos[1].toFixed(7) + "\n");}alert(str);}
}function markerDragendEvent(e) {var bdPos = e.point;var wgsPos = bd09towgs84(bdPos.lng, bdPos.lat);var ID = e.target.getLabel().content;e.target.setTitle("ID" + ID + ":" + wgsPos[0].toFixed(7) + "," +             wgsPos[1].toFixed(7));wayPoints.splice(ID,1,bdPos);if(wayPoints.length > 1)wayPointsPolyline.setPath(wayPoints);loadWaypoints();
}function taskOverlaysComplete(e) {if (e.overlay instanceof BMap.Marker) {wayPointsMarker.push(e.overlay);var bdPos = e.overlay.getPosition();var wgsPos = bd09towgs84(bdPos.lng, bdPos.lat);var lastIndex = wayPointsMarker.length - 1;wayPointsMarker[lastIndex].setTitle("ID" + lastIndex + ":" + wgsPos[0].toFixed(7) + "," + wgsPos[1].toFixed(7));var label = new BMap.Label(wayPointsMarker.length - 1, { offset: new BMap.Size(5, 3) });label.setStyle({color: "white",backgroundColor: "0",border: "0",fontsize: "13px"});wayPointsMarker[lastIndex].setLabel(label);wayPointsMarker[lastIndex].enableDragging();wayPointsMarker[lastIndex].addEventListener("dragend", markerDragendEvent);wayPoints.push(bdPos);if(wayPoints.length == 1){wayPointsPolyline.setPath(wayPoints);map.addOverlay(wayPointsPolyline);}else if(wayPoints.length > 1)wayPointsPolyline.setPath(wayPoints);}else if (e.overlay instanceof BMap.Polyline) {e.overlay.addEventListener("click", overlay_style);e.overlay.enableEditing();polylines.push(e.overlay);}else if (e.overlay instanceof BMap.Circle) {e.overlay.addEventListener("click", overlay_style);//e.overlay.enableEditing();circles.push(e.overlay);}            else if (e.overlay instanceof BMap.Polygon) {e.overlay.addEventListener("click", overlay_style);//e.overlay.enableEditing();polygon.push(e.overlay);}
}
drawingManager.addEventListener("overlaycomplete", taskOverlaysComplete);


可以发现,地图上点击的航点都被push到wayPointsMarker中,那么怎么将其传递给qt呢?
下面函函数实现将wayPoints传递给qt。先将航点的个数通过transTask()传递出去,然后再每个航点都通过transPoints()传递出去。

function loadWaypoints() {window.bridge.transTask(0, wayPointsMarker.length);for (var i = 0; i < wayPointsMarker.length; i++) {var wgsPos = bd09towgs84(wayPointsMarker[i].getPosition().lng, wayPointsMarker[i].getPosition().lat);window.bridge.transPoints(i, wgsPos[0], wgsPos[1]);}
}

谁来调用loadWaypoints这个函数呢?
有两种方案:1.qt界面上布置一个按钮,然后将触发事件传递过来,就像绘制轨迹那样;2.可以再html上布置按钮或右键菜单来点击触发。显然后者更加方便,那就搞起来。
下面为添加的右键菜单代码,其中loadWaypoints为上述定义的函数。如果在测试该功能是用浏览器打开发现点击鼠标右键出现不了菜单,可以使用qt打开测试,(我的浏览器出现不了,但是qt上嵌入功能正常)。

var menu = new BMap.ContextMenu();
var toolMenuItem = [{text: '加载航点',callback: loadWaypoints},{text: '清除航点',callback: clearWaypoints},{text: '清除轨迹',callback: clearTrack}
];
for (var i = 0; i < toolMenuItem.length; i++) {menu.addItem(new BMap.MenuItem(toolMenuItem[i].text, toolMenuItem[i].callback, 100));
}
map.addContextMenu(menu);function clearWaypoints() {var len = wayPointsMarker.length;for (var i = 0; i < len; i++)map.removeOverlay(wayPointsMarker.shift());wayPoints = [];map.removeOverlay(wayPointsPolyline);
}


html准备好了,qt那边怎么办呢?
上面提到的transTask()和transPoints()函数是在MapChannel中定义的public slots,可以由html/js直接调用。请移步3.2。

3 QT软件开发

Qt开发的岸基监控软件通过QWebEngine组件加载html形式地图,两者使用QWebChannel进行交互,地图上的功能通过html/JavaScript来实现。

3.1使用QWebEngineView显示Html

使用QWebEngineView需要现在.pro文件中添加webenginewidgets组件。

 QT       += webenginewidgets

然后再mainwindow.ui拖出一个Widget控件,提升为QWebEngineView(先添加,再提升)。


然后在构造函数中添加出事化代码。其中MapChannel为继承自QObject的类,用于和实现Qt和html/js的交互,该类的实现详细见。

void MainWindow::mapWidgetInit()
{channel = new QWebChannel(this);mapChannel = new MapChannel(this);channel -> registerObject("passId",mapChannel);this -> ui -> widget_map -> page() -> setWebChannel(channel);this -> ui -> widget_map -> load(QUrl("file:///./onlinemap/map.html"));connect(mapChannel,&MapChannel::reloadMapClicked,this,&MainWindow::reloadMap);
}

3.2 MapChannel实现两者交互

两者使用QWebChannel进行交互,地图上的功能通过html/JavaScript来实现。使用QWebChannel时,用来向JS注册的类应该额外编写一个继承自QObject的类,使其只包含交互必要的属性和方法,否则虽然也可以编译运行,但是会在控制台出现很多类似Property xxxx of object xxxx has no notify signal and is not constant…的信息。意思大概是xxxx类的xxxx属性没有通知信号,当属性值在HTML中被修改时qt将无法更新属性值。
可以认为QWebChannel会默认所注册的类中的所有属性都需要进行交互,对其是否有合适的信号和槽函数进行了检测并给出了警告。从这个角度来说也应该让负责交互的类只包含必要的内容,否则可能会在交互时传递很多无用的信息增加程序负担。

下面提供了本人编写的比较全面的一个MapChannel,传递的参数都是double和int基本类型,也可满足应用需求。当然还可以传递Json复杂数据。

以绘制机器人实时轨迹为例,MapChannel定义了一个public属性的updateBoatPos()函数供岸基监控软件应用调用;updateBoatPos()函数里只进行唯一的动作:emit boatPosUpdated(),发射信号;boatPosUpdated()信号在html中被绑定到showBoatPosition()函数上,是不是很绕?因此在监控软件中调用updateBoatPos()函数,就相当于html执行了showBoatPosition()函数,数据也从qt传递到了地图里。

#ifndef MAPCHANNEL_H
#define MAPCHANNEL_H#include <QObject>class MapChannel : public QObject
{Q_OBJECT
public:explicit MapChannel(QObject *parent = nullptr);/*MainWindow 调用*/void addPoint(double lng, double lat);void movePoint(int id, double lng, double lat);void updateBoatPos(double lng, double lat, double course);void setOrigin(double lng, double lat);void addFencePoint(double,double);void addFence();void clearFence();void clearWaypoints();void clearTrack();void panTo(double,double);signals:/*MapChannel -> MainWindow*/void mousePointChanged(double, double);void reloadMapClicked();void taskCome(int,int);void pointsCome(int,double,double);/*MapChannel -> html*/void addPointClicked(double,double);void movePointClicked(int,double,double);void setOriginPoint(double,double);void boatPosUpdated(double,double,double);void addFencePointClicked(double,double);void addFenceClicked();void clearFenceClicked();void clearWaypointsClicked();void clearAllClicked();void clearTrackClicked();void panToClicked(double,double);public slots:/*html调用*/void getMousePoint(double lng, double lat);void reloadMap();void transTask(int type, int len);void transPoints(int id, double lng, double lat);/*MainWindow 调用*/void clearAll();
};#endif // MAPCHANNEL_H
#include "mapchannel.h"MapChannel::MapChannel(QObject *parent) : QObject(parent)
{}void MapChannel::getMousePoint(double lng, double lat)
{emit mousePointChanged(lng,lat);
}void MapChannel::addPoint(double lng, double lat)
{emit addPointClicked(lng,lat);
}void MapChannel::movePoint(int id, double lng, double lat)
{emit movePointClicked(id,lng,lat);
}void MapChannel::transTask(int type, int len)
{emit taskCome(type,len);
}void MapChannel::transPoints(int id, double lng, double lat)
{emit pointsCome(id,lng,lat);
}void MapChannel::updateBoatPos(double lng, double lat, double course)
{emit boatPosUpdated(lng,lat,course);
}void MapChannel::reloadMap()
{emit reloadMapClicked();
}void MapChannel::setOrigin(double lng, double lat)
{emit setOriginPoint(lng,lat);
}void MapChannel::clearWaypoints()
{emit clearWaypointsClicked();
}void MapChannel::clearAll()
{emit clearAllClicked();
}void MapChannel::addFencePoint(double lng, double lat)
{emit addFencePointClicked(lng,lat);
}void MapChannel::addFence()
{emit addFenceClicked();
}void MapChannel::clearFence()
{emit clearFenceClicked();
}void MapChannel::panTo(double lng, double lat)
{emit panToClicked(lng,lat);
}void MapChannel::clearTrack()
{emit clearTrackClicked();
}

qt接收html航点,MapChannel了几个public slots,可以直接在html/js中直接以window.bridge.XXX的形式调用。transPoints()函数里只做一件事:emit pointsCome(id,lng,lat),发射信号。因此在外部可以连接&MapChannel::pointsCome和定义好的槽函数来接收由html传来的经纬度。实现了由html->qt的数据传递。

    connect(mapChannel,&MapChannel::pointsCome,[](int index, double lng, double lat){qDebug()<<index<<QString::number(lng,'f',6)<<QString::number(lat,'f',6);});

4.关于地图坐标

常用的gnss定位模块得到的经纬度都是WGS84系(地心坐标系,GPS原始坐标体系)下的坐标。国内,任何一个地图产品都不允许直接使用GPS坐标,据说是为了保密。

国家规定,国内地图坐标必须至少使用GCJ-02(国测局02年发布的坐标体系,它是一种对经纬度数据的加密算法,即加入随机的偏差)进行首次加密。高德、谷歌地图在国内采用的为GCJ-02加密后的坐标。

而国内的百度地图在GCJ-02的基础上又进行了二次非线性加偏得到BD09坐标。
因此需要WGS84与BD09之间相互转换。百度API提供精确的WGS84->BD09的转换服务,但是逆向不提供。该服务有请求次数,且需要联网。

因此网上搜索了一些大佬的资料,总结出以下转换公式,放在CoordinateTransform.js文件中,经测试精度在5m以内。

// CoordinateTransform.jsvar x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var PI = 3.1415926535897932384626;
var a = 6378245.0;
var ee = 0.00669342162296594323;function wgs84tobd09(lng, lat) {var mid = wgs84togcj02(lng, lat);mid = gcj02tobd09(mid[0], mid[1]);return mid
}function bd09towgs84(lng, lat) {var mid = bd09togcj02(lng, lat);mid = gcj02towgs84(mid[0], mid[1]);return mid
}function bd09togcj02(bd_lon, bd_lat) {var x_pi = 3.14159265358979324 * 3000.0 / 180.0;var x = bd_lon - 0.0065;var y = bd_lat - 0.006;var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);var gg_lng = z * Math.cos(theta);var gg_lat = z * Math.sin(theta);return [gg_lng, gg_lat]
}function gcj02towgs84(lng, lat) {if (out_of_china(lng, lat)) {return [lng, lat]}else {var dlat = transformlat(lng - 105.0, lat - 35.0);var dlng = transformlng(lng - 105.0, lat - 35.0);var radlat = lat / 180.0 * PI;var magic = Math.sin(radlat);magic = 1 - ee * magic * magic;var sqrtmagic = Math.sqrt(magic);dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);mglat = lat + dlat;mglng = lng + dlng;return [lng * 2 - mglng, lat * 2 - mglat]}
}function wgs84togcj02(lng, lat) {if (out_of_china(lng, lat)) {return [lng, lat]}else {var dlat = transformlat(lng - 105.0, lat - 35.0);var dlng = transformlng(lng - 105.0, lat - 35.0);var radlat = lat / 180.0 * PI;var magic = Math.sin(radlat);magic = 1 - ee * magic * magic;var sqrtmagic = Math.sqrt(magic);dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);var mglat = lat + dlat;var mglng = lng + dlng;return [mglng, mglat]}
}function gcj02tobd09(lng, lat) {var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);var bd_lng = z * Math.cos(theta) + 0.0065;var bd_lat = z * Math.sin(theta) + 0.006;return [bd_lng, bd_lat]
}function transformlat(lng, lat) {var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;return ret
}function transformlng(lng, lat) {var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;return ret
}function out_of_china(lng, lat) {return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);
}

5.轨迹回放软件

使用上面开发的地图,和测试用的qt程序,最后整理整理写了个轨迹回放软件,实际效果为本页最上方的gif。

目前这个软件只能回放csv格式的机器人航行数据。
1.点击“打开文件”按钮进行CSV文件选择;
2.在“数据结构”中调整时间戳、经纬度、艏向数据所在的列索引,索引从0开始;
3.在“实时数据”中可观察对应数字是否正确;
4.点击“开始回放”可在地图上动态显示轨迹;
5.播放速度在回放过程中可随时调整,默认1秒一行;
实现起来非常简单,如果有需要可以自行下载源码。

6.其他事项

6.1源码

在线百度地图map.html、坐标转换文件CoordinateTransform.js、qwebchannel.js和mapchannel.cpp和mapchannel.h上传到的github上,点击此处。里面已经实现的功能比文中描述的要丰富些,可自行探索。

轨迹回放软件,github点击此处。
大家觉得有用给个star或fork,谢谢!

同时,放一份在csdn上。

6.2离线地图

在线地图需要在可访问公网时才能可用,有时外场试验不方便联网,在线地图就不可行了。但是,网上已有大佬将百度地图API的功能全部离线化,做成离线的包,仅需简单替换上述功能都可实现。

7.参考资料

在学习过程中,浏览到很了多大佬总结的经验和资料,受益匪浅,有些直接交流为我答疑解惑。受到他们无私奉献精神的鼓舞,我也写了这篇博客。再次感谢他们:

  1. QT实现加载百度离线地图
  2. Qt加载百度地图,鼠标实时位置信息显示
  3. qt使用高德地图并与之简单交互
  4. 最清晰Qt与JS通过qwebchannel交互例子
  5. 各种国内地图坐标系总结
  6. 火星坐标、百度坐标、WGS84坐标转换代码(JS、python版)

使用QT嵌入百度地图开发高B格岸基监控软件相关推荐

  1. QT5百度地图开发学习——qt调用JavaScript函数并传参

    文章目录 前言 一.通信桥梁bridge 二.QT与JS相互通信(调用函数) 1.QT调用js函数 前言 在上文<QT5百度地图开发学习--控件提升展示地图>中,我们通过控件提升的方式在同 ...

  2. QT5百度地图开发学习——qt调用输入位置坐标位置进行百度地图定位

    系列文章目录 上一章: QT5百度地图开发学习--JavaScript调用qt函数并传参给qt 文章目录 系列文章目录 前言 一.界面设计 二.槽函数 总结 前言 本文实现输入坐标然后在地图上显示位置 ...

  3. web前端JavaScript嵌入百度地图API的方法 最详细

    web前端JavaScript嵌入百度地图API最详细的方法 一.申请成为百度开发者,获得使用地图API接口的权限,获取(AK)码. 二.实现首图效果(可以参考开发者指南) 1. 根据Hello Wo ...

  4. [android] 百度地图开发 (二).定位城市位置和城市POI搜索

    一. 百度地图城市定位和POI搜索知识       上一篇文章"百度地图开发(一)"中讲述了如何申请百度APIKey及解决显示空白网格的问题.该篇文章主要讲述如何定位城市位置.定位 ...

  5. php 嵌入手机百度地图,C# 程序中嵌入百度地图

    本例是对WinForm中使用百度地图的简要介绍.百度地图目前支持Android开发,IOS开发,Web开发,服务接口,具体可以参照'百度地图开放平台'. [动态加载百度地图]涉及到的知识点:WebBr ...

  6. 在网页中嵌入百度地图的步骤

    百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口,可帮助您在网站中构建功能丰富.交互性强的地图应用,支持PC端和移动端基于浏览器的地图应用开发,且支持HTML5特 ...

  7. 百度地图开发技术方案及解决办法

    技术方案及解决办法 文章目录 技术方案及解决办法 总体方案 基础框架 前端网页 后端程序 结合搭建方法 跨域解决 python处理excel 问题描述 解决办法 网页结构 vue实例 内容引入 ele ...

  8. [android] 百度地图开发 (三).定位当前位置及getLastKnownLocation获取location总为空问题

           前一篇百度地图开发讲述"(二).定位城市位置和城市POI搜索",主要通过监听对象MKSearchListener类实现城市兴趣点POI(Point of Intere ...

  9. 百度地图开发(二)之添加覆盖物 地理编码和反地理编码

    转载请注明出处: http://blog.csdn.net/crazy1235/article/details/43377545 之前写过一篇关于百度地图开发的blog,主要介绍了百度地图的基本地图的 ...

最新文章

  1. Java程序员面试中的多线程问题1
  2. python怎么导入文件-Python模块导入详解
  3. PMP知识点(四、进度管理)
  4. html表格高度适应屏幕,Table的自适应高度
  5. 什么是基金净值、单位净值、累计净值
  6. 读书笔记--Android Gradle权威指南(上)
  7. Linux more命令、Linux rhmask命令
  8. 在Win7系统下, 使用VS2015 打开带有日文注释程序出现乱码的解决方案
  9. IP与以太网的包收发操作
  10. linux孟庆昌第六章课后题_周三多管理学第5版课后答案资料笔记和课后习题含考研真题详解...
  11. 用Java发起HTTP请求与获取状态码(含状态码列表)
  12. sql语句日期格式转换
  13. adb 查看浏览器内核版本
  14. NavigationDuplicated: Avoided redundant
  15. 工业设计算计算机类专业吗,北大工学院工业设计工程数一计算机方向经验贴
  16. MATLAB-APP编程
  17. zznuoj-1003
  18. 2022年氧化工艺考试练习题模拟考试平台操作
  19. 什么是Cloud Computing?
  20. vs code常用的插件

热门文章

  1. FreeCAD宏代码建模 老式地漏
  2. phpadmin安装到mysql中_apach+mysql+php+phpadmin安装!(for windows)
  3. k-vim安装及The ycmd server SHUT DOWN (restart with ‘:YcmRestartServer‘)这种错误的解决方法
  4. 菜鸟程序员的成长之路(二)——时间都去哪儿了
  5. Windows按键映射、替换、禁用
  6. atompark:外贸工具-Atomic 6合一系列产品-Crack
  7. 三坐标测量基础知识之建立坐标系的必要性
  8. Python3 Unittest+HTMLTestRunner.py文件
  9. kvm虚拟化学习笔记(二十一)之KVM性能优化学习笔记
  10. 『phphot』【SD2.0大会】达内创始人韩少云:创业就像买股票