Qt/C++離線讀取全國任意經緯度高程海拔值/無任何依賴/純原創程式碼解析

飞扬青云發表於2024-12-09

一、前言說明

做地圖開發會遇到一個常規需求,就是獲取當前經緯度對應的海拔高度,也叫做高程值,很遺憾各大地圖廠商都未提供介面獲取,可能是有明文規定,不能地圖中提供對應的海拔高度值,於是需要另想他法,儘管谷歌地圖線上的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));
}

三、相關地址

  1. 檔案地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取碼:o05q 檔名:bin_map.zip
  2. 國內站點:https://gitee.com/feiyangqingyun
  3. 國際站點:https://github.com/feiyangqingyun
  4. 線上文件:http://www.qtcdev.com/map/

四、效果圖

五、功能特點

5.1 地圖功能

  1. 支援多種地圖核心,預設採用百度地圖,可選高德地圖、天地圖、騰訊地圖、谷歌地圖等。
  2. 同時支援線上地圖和離線地圖兩種模式,離線地圖方便在不聯網的場景中使用。
  3. 支援各種地圖控制元件的啟用,比如地圖導航、地圖型別、縮圖、比例尺、全景導航、實時路況、繪圖工具、結果皮膚等。
  4. 支援多種地圖功能的動態啟用禁用,比如地圖拖曳、鍵盤操作、滾輪縮放、雙擊放大、連續縮放、地圖測距等。
  5. 提供眾多js函式介面用於互動,引數極其豐富,能夠想到的應用場景需求都有。
  6. 統一的訊號槽機制,地圖中的結果統一訊號傳送出去,收到後根據type型別區分。
  7. 支援地圖互動,比如滑鼠按下獲取對應位置的經緯度。單擊標註點彈出對應點的資訊。
  8. 支援新增標註、刪除標註、移動標註、清空標註。
  9. 標註點可以指定圖示圖片和尺寸,支援gif動圖,支援指定以圖片中心對齊還是底部中心對齊。可以設定旋轉角度,帶富文字提示資訊。
  10. 標註點事件支援單擊發訊號通知和自己彈框顯示資訊。
  11. 提供地址轉座標和座標轉地址介面。
  12. 支援各種圖形繪製,包括折線圖、多邊形、矩形、圓形、弧線等。
  13. 可顯示懸浮的繪圖工具欄,直接在地圖上劃線、標註點、矩形、圓形等。
  14. 支援各種區域搜尋,比如矩形區域、圓形區域,可以按照關鍵字匹配將搜尋結果顯示在地圖中。
  15. 可動態新增離線的行政區邊界點資料。可以搜尋行政區劃並獲取該區域的邊界點資料。資料可以儲存到檔案以便離線使用。
  16. 支援點聚合功能,多個小標註點合併到一個大標註點,防止點密集導致互動不友好。
  17. 可以新增海量點,每個點都可以單擊獲取對應座標和資訊。
  18. 所有的覆蓋物資訊比如標註點、矩形、多邊形、折線圖等,都可以主動獲取對應的資訊比如座標點和路徑等。
  19. 支援路徑規劃,支援公交路線、自駕路線、步行路線、騎行路線,不同查詢支援不同策略,可選最少時間、最少換乘、不走高架等。
  20. 路徑規劃結果可以顯示在地圖中,也可以獲取到路徑點座標集合。這個資料可以儲存到檔案,以便發給機器人或者無人機做導航用來軌跡移動。
  21. 可以設定不同的地圖檢視比如街道圖、衛星圖、混合圖。
  22. 可以設定不同的樣式,比如午夜藍、青草綠等樣式風格。
  23. 可以設定地圖的旋轉角度和傾斜角度。
  24. 提供經緯度座標糾偏轉換功能,比如傳入的GPS座標需要轉換到百度地圖座標或者高德地圖座標。各種座標系轉換全部離線函式,支援地球座標系WGS-84、火星座標系GCJ-02、百度座標系BD-09之間的互相轉換,涵蓋了各種地圖的座標系。
  25. 提供動態軌跡點移動功能,按照給定的經緯度座標集合平滑移動。
  26. 同時支援qwidget和qml,支援編譯到安卓系統執行。

5.2 其他功能

  1. 提供離線地圖下載模組,可以選擇不同的地圖核心比如百度地圖或者谷歌地圖,不同的地圖型別比如下載街道圖還是衛星圖,不同的地圖層級,多執行緒極速下載。
  2. 表格行實時顯示對應的瓦片下載進度,有下載超時時間,重試次數,每個瓦片下載完成都傳送訊號通知,引數包括下載用時。
  3. 提供省市輪廓圖下載模組,自動下載各個地區的輪廓圖,儲存到指令碼檔案或者文字檔案。
  4. 支援手動調整不同區域的輪廓邊界,調整後可以主動獲取調整後的邊界點集合。
  5. 提供動態點位示例,手動在地圖上選點並新增標註,附帶自定義的資訊比如速度和時間等。
  6. 提供海量點位示例,批次新增標註點、點聚合、海量點。用於測試環境中支援的最大點位效能。
  7. 提供動態軌跡示例,在地圖上滑鼠按下選擇起點和終點後,查詢路線,獲取路徑軌跡點,模擬軌跡平滑移動。可以篩選資料將過多的路徑點篩選到設定的點數。
  8. 提供軌跡回放示例,按照指定的軌跡點列表回放,也可以匯入軌跡點資料進行回放。同時支援在街道圖、衛星圖、混合圖中回放軌跡。
  9. 提供省市區域地圖示例,採用echart元件,同時支援閃爍點圖、遷徙圖、區域地圖、世界地圖、儀表盤等。可以設定標題、提示資訊、背景顏色、文字顏色、線條顏色、區域顏色等各種顏色。
  10. 省市區域地圖示例,內建世界地圖、全國地圖、省份地圖、地區地圖,可以精確到縣,所有地圖全部離線使用。可設定城市的名稱、值、經緯度集合。
  11. 內建通用瀏覽器元件,同時支援webkit/webengine/miniblink等核心。提供網頁控制元件示例,演示開啟網頁和本地網頁檔案。
  12. 支援任意Qt版本、任意系統、任意編譯器。

相關文章