Qt/C++离线读取全国任意经纬度高程海拔值/无任何依赖/纯原创代码解析
一、前言说明
做地图开发会遇到一个常规需求,就是获取当前经纬度对应的海拔高度,也叫做高程值,很遗憾各大地图厂商都未提供接口获取,可能是有明文规定,不能地图中提供对应的海拔高度值,于是需要另想他法,尽管谷歌地图在线的api接口是提供了海拔高度值,但是懂得都懂,国内哪里还能用谷歌地图?完全用不了啊,就算你开发者能用,99.99%的用户也是用不了,而且必须是在线,没有离线也不行。
通过查阅资料得知有个gdal的开源库,支持读取tif文件获取高程值,使用过了是可以,但是编译复杂,尝试过很多次直接集成源码的方式,终归失败,源码数量太多了,两千多个,也有不少的依赖,比如依赖zip和sqlite啥的,所以最终放弃这个方案,后面又找了一些类似tinytiff的开源库,都是用来读取tif文件的,但是没有看到获取高程值接口,而且只支持普通的tif文件。
在折腾的快要放弃的时候,往往就是离成功最近的时候,思索着有没有更简便的方式,而且跨平台,毕竟只是想获取个高程值,引入个这么大的库完全没有必要,有点杀鸡用牛刀的感觉。在经过使用gdal函数接口的过程中,规则都是从图片的像素坐标获取灰度值,然后这个灰度值对应的就是海拔,网上很少有人提到这点,其实这点极其重要,没搞过的人一直云里雾里的折腾编译gdal等开源库。后面还发现对应tif的还有个txt的文本文件,打开内容看到最前面写着左上角的经纬度坐标和水平和垂直比例尺等参数值,然后就是逐行的每个像素点的海拔值,这就非常美妙了,要的就是这种文件呢,自己写个算法去处理也是分分分钟的事情。一气呵成打完收工,自此不仅离线使用,速度纳秒级别,还支持所有平台,就一个类文件100行左右,不要太完美啊。
二、相关代码
#include "frmmain.h"
#include "ui_frmmain.h"
#include "qthelper.h"
#include "gdalobj.h"
#include "demread.h"
frmMain::frmMain(QWidget *parent) : QWidget(parent), ui(new Ui::frmMain)
{
ui->setupUi(this);
this->initForm();
//on_btnOpen_clicked();
QMetaObject::invokeMethod(this, "on_btnOpen_clicked", Qt::QueuedConnection);
}
frmMain::~frmMain()
{
delete ui;
}
bool frmMain::eventFilter(QObject *watched, QEvent *event)
{
if (watched == ui->labImage) {
if (event->type() == QEvent::MouseButtonPress) {
pressed = true;
lastPos = ((QMouseEvent *)event)->globalPos();
} else if (event->type() == QEvent::MouseButtonRelease) {
pressed = false;
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = (QMouseEvent *)event;
if (pressed) {
QPoint pos = mouseEvent->globalPos();
QScrollBar *scrollBarx = ui->scrollArea->horizontalScrollBar();
QScrollBar *scrollBary = ui->scrollArea->verticalScrollBar();
int offsetx = pos.x() - lastPos.x();
int offsety = pos.y() - lastPos.y();
scrollBarx->setValue(scrollBarx->value() - offsetx);
scrollBary->setValue(scrollBary->value() - offsety);
lastPos = pos;
} else {
QPoint pos = mouseEvent->pos();
ui->txtPos->setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
int value;
QSize size;
QPointF lnglat;
if (ui->cboxType->currentIndex() == 0) {
if (ui->btnOpen->isEnabled()) {
value = DemRead::getAltitude(pos);
size = QSize(DemRead::width, DemRead::height);
lnglat = DemRead::getLngLat(pos);
}
} else {
value = gdal->getAltitude(pos);
size = gdal->getSize();
lnglat = gdal->getLngLat(pos);
}
if (size.width() > 0) {
ui->txtSize->setText(QString("%1 x %2").arg(size.width()).arg(size.height()));
ui->txtLnglat->setText(QString("%1, %2").arg(lnglat.x()).arg(lnglat.y()));
ui->txtAltitude->setText(QString("%1 米").arg(value));
}
}
}
}
return QWidget::eventFilter(watched, event);
}
void frmMain::initForm()
{
pressed = false;
lastPos = QPoint();
txtFile = QtHelper::appPath() + "/data/dem001.txt";
tifFile = QtHelper::appPath() + "/data/dem001.tif";
gdal = new GdalObj(this);
#ifndef Q_CC_MSVC
ui->cboxType->setCurrentIndex(0);
ui->cboxType->setEnabled(false);
#else
ui->cboxType->setCurrentIndex(1);
#endif
ui->cboxInput->addItem("89.2883, 42.7083");
ui->cboxInput->addItem("121.4703, 31.2339");
ui->cboxInput->addItem("121.5527, 25.0557");
ui->cboxInput->addItem("121.1792, 26.4213");
ui->cboxInput->setCurrentIndex(0);
QPixmap pixmap(tifFile);
ui->labImage->installEventFilter(this);
ui->labImage->setPixmap(pixmap);
ui->labImage->setAttribute(Qt::WA_MouseTracking);
ui->txtSize->setText(QString("%1 x %2").arg(pixmap.width()).arg(pixmap.height()));
}
void frmMain::readFinsh()
{
ui->btnOpen->setEnabled(true);
ui->btnGet->setEnabled(true);
ui->txtSize->setText(QString("%1 x %2").arg(DemRead::width).arg(DemRead::height));
}
void frmMain::on_btnOpen_clicked()
{
if (ui->cboxType->currentIndex() == 0) {
//这里线程执行/防止卡主界面
ui->btnOpen->setEnabled(false);
ui->btnGet->setEnabled(false);
static QFutureWatcher<void> watcher;
connect(&watcher, SIGNAL(finished()), this, SLOT(readFinsh()));
watcher.setFuture(QtConcurrent::run(DemRead::readFile, txtFile));
} else {
if (gdal->open(tifFile)) {
QSize size = gdal->getSize();
ui->txtSize->setText(QString("%1 x %2").arg(size.width()).arg(size.height()));
}
}
}
void frmMain::on_btnGet_clicked()
{
QString text = ui->cboxInput->lineEdit()->text();
QStringList list = text.split(",");
QPointF lnglat(list.at(0).toDouble(), list.at(1).toDouble());
int value;
QPoint pos;
if (ui->cboxType->currentIndex() == 0) {
pos = DemRead::getPos(lnglat);
value = DemRead::getAltitude(lnglat);
} else {
pos = gdal->getPos(lnglat);
value = gdal->getAltitude(lnglat);
}
ui->txtLnglat->setText(text);
ui->txtAltitude->setText(QString("%1 米").arg(value));
ui->txtPos->setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
//移动到对应图片中心点
ui->scrollArea->horizontalScrollBar()->setValue(pos.x() - (ui->scrollArea->width() / 2));
ui->scrollArea->verticalScrollBar()->setValue(pos.y() - (ui->scrollArea->height() / 2));
}
三、相关地址
- 文件地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_map.zip
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 在线文档:http://www.qtcdev.com/map/
四、效果图
五、功能特点
5.1 地图功能
- 支持多种地图内核,默认采用百度地图,可选高德地图、天地图、腾讯地图、谷歌地图等。
- 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。
- 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。
- 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。
- 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有。
- 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。
- 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。
- 支持添加标注、删除标注、移动标注、清空标注。
- 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。
- 标注点事件支持单击发信号通知和自己弹框显示信息。
- 提供地址转坐标和坐标转地址接口。
- 支持各种图形绘制,包括折线图、多边形、矩形、圆形、弧线等。
- 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。
- 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。
- 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。
- 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。
- 可以添加海量点,每个点都可以单击获取对应坐标和信息。
- 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。
- 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。
- 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。
- 可以设置不同的地图视图比如街道图、卫星图、混合图。
- 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。
- 可以设置地图的旋转角度和倾斜角度。
- 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。
- 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动。
- 同时支持qwidget和qml,支持编译到安卓系统运行。
5.2 其他功能
- 提供离线地图下载模块,可以选择不同的地图内核比如百度地图或者谷歌地图,不同的地图类型比如下载街道图还是卫星图,不同的地图层级,多线程极速下载。
- 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。
- 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。
- 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。
- 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。
- 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。
- 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。
- 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。
- 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。
- 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。
- 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。
- 支持任意Qt版本、任意系统、任意编译器。