Qt/C++地圖高階繪圖/指定唯一標識新增刪除修改/動態顯示和隱藏/支援天地圖高德地圖百度地圖

飞扬青云發表於2024-11-15

一、前言說明

已經有了最基礎的介面用來新增覆蓋物,而且還有透過進入覆蓋物模式動態新增覆蓋物的功能,為什麼還要來個高階繪圖?因為又有新的需求,給錢就搞,一點底線都沒有。無論哪個地圖廠家,提供的介面都是沒有唯一標識引數的,也就類似於學號,這就是需要自己主動定一個屬性用來儲存唯一標識,這樣方便後面刪除和修改。比如之前的刪除覆蓋物,只能指定一種型別的覆蓋物,指定圓形則刪除所有圓形覆蓋物,這樣還是完全不夠的,很多時候需要指定唯一標識來刪除和修改。

經過大量的模擬測試發現,對覆蓋物的刪除和清空clearoverlay等,高德地圖和谷歌地圖會在合適的時機釋放記憶體,而其他地圖幾乎不會去主動釋放,所以如果遇到需要很多覆蓋物的場景,建議生成一次後,後面只是去改變該覆蓋物的屬性比如座標位置和路徑,而不是清空後再次去生成。這樣就可以極力避免記憶體洩漏,這可能也是web的缺陷,沒有手動釋放機制,說是內部有垃圾自動回收,但是內部很可能判斷失敗,導致一直無法釋放。所以需要重新調整新增和刪除修改覆蓋物的函式介面,增加唯一標識引數,需要搞個動態的雷達掃描,只需要不斷更新覆蓋物經緯度座標即可。

二、相關程式碼

#include "frmmapdraw.h"
#include "ui_frmmapdraw.h"
#include "qthelper.h"
#include "maphelper.h"
#include "webview.h"

#include "frmmapdrawradar.h"
#include "frmmapdrawairline.h"

frmMapDraw::frmMapDraw(QWidget *parent) : QWidget(parent), ui(new Ui::frmMapDraw)
{
    ui->setupUi(this);
    this->initForm();
    this->initConfig();
}

frmMapDraw::~frmMapDraw()
{
    delete ui;
}

void frmMapDraw::showEvent(QShowEvent *)
{
    //只需要載入一次/避免重複初始化
    static bool isLoad = false;
    if (!isLoad) {
        isLoad = true;
        QMetaObject::invokeMethod(this, "on_btnLoadMap_clicked", Qt::QueuedConnection);
    }
}

void frmMapDraw::initForm()
{
    //設定右側固定寬度
    ui->widgetRight->setFixedWidth(AppData::RightWidth - 50);

    mapObj = NULL;

    //例項化瀏覽器控制元件並加入到佈局
    webView = new WebView(this);
    webView->setLayout(ui->gridLayout);
    connect(webView, SIGNAL(receiveDataFromJs(QString, QVariant)), this, SLOT(receiveDataFromJs(QString, QVariant)));
}

void frmMapDraw::initConfig()
{
    MapHelper::loadMapCore(ui->cboxMapCore, AppConfig::MapDrawCore);
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(on_btnLoadMap_clicked()));

    ui->cboxMapLocal->setCurrentIndex(AppConfig::MapDrawLocal ? 1 : 0);
    connect(ui->cboxMapLocal, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapLocal, SIGNAL(currentIndexChanged(int)), this, SLOT(on_btnLoadMap_clicked()));

    ui->cboxMapType->setCurrentIndex(AppConfig::MapDrawType);
    connect(ui->cboxMapType, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    ui->cboxOverlay->addItem("標註", "marker");
    ui->cboxOverlay->addItem("折線", "polyline");
    ui->cboxOverlay->addItem("多邊形", "polygon");
    ui->cboxOverlay->addItem("矩形", "rectangle");
    ui->cboxOverlay->addItem("圓形", "circle");
    ui->cboxOverlay->setCurrentIndex(AppConfig::MapDrawOverlay);
    connect(ui->cboxOverlay, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
}

void frmMapDraw::saveConfig()
{
    AppConfig::MapDrawCore = ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    AppConfig::MapDrawLocal = (ui->cboxMapLocal->currentIndex() == 1);
    AppConfig::MapDrawType = ui->cboxMapType->currentIndex();
    AppConfig::MapDrawOverlay = ui->cboxOverlay->currentIndex();
    AppConfig::writeConfig();
}

void frmMapDraw::runJs(const QString &js)
{
    mapObj->runJs(js);
}

void frmMapDraw::receiveDataFromJs(const QString &type, const QVariant &data)
{
    QString result = data.toString();
    if (type == "marker" || type == "polyline" || type == "polygon" || type == "rectangle" || type == "circle") {
        //填入當前單擊的覆蓋物的標識
        ui->txtFlag->setText(result);
        //啟動編輯狀態/方便對比當前按下了哪個
        on_btnEditOverlay_clicked();
    }
}

void frmMapDraw::on_btnLoadMap_clicked()
{
    //根據不同地圖核心例項化地圖類
    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    bool mapLocal = (ui->cboxMapLocal->currentIndex() == 1);
    int mapType = ui->cboxMapType->currentIndex();
    int zoom = MapHelper::getMapZoom(mapCore, this->objectName());
    MapHelper::initMapObj(this, &mapObj, mapCore);
    mapObj->setWebView(webView);
    mapObj->setSaveFile(SaveFile);
    mapObj->setMapLocal(mapLocal);
    mapObj->setMapType(mapType);
    mapObj->setZoom(zoom);
    mapObj->load();

    //天地圖專用繪圖
    ui->frameTian->setEnabled(mapCore == MapCore_Tian);
    ui->txtFlag->setText("overlay1");
}

void frmMapDraw::on_btnDrawRadar_clicked()
{
    frmMapDrawRadar *form = new frmMapDrawRadar;
    form->setAttribute(Qt::WA_DeleteOnClose);
    QtHelper::setFormInCenter(form);
    form->show();
}

void frmMapDraw::on_btnDrawAirline_clicked()
{
    frmMapDrawAirline *form = new frmMapDrawAirline;
    form->setAttribute(Qt::WA_DeleteOnClose);
    QtHelper::setFormInCenter(form);
    form->show();
}

void frmMapDraw::on_btnAddOverlay_clicked()
{
    int count = 1;
    QString overlay = ui->cboxOverlay->itemData(ui->cboxOverlay->currentIndex()).toString();
    if (overlay == "polyline") {
        count = 2;
    } else if (overlay == "polygon") {
        count = 3;
    } else if (overlay == "rectangle") {
        count = 2;
    }

    //隨機生成模擬資料
    QStringList points = QtHelper::getRandPoint(count, 121.204610, 31.018220, 0.415, 0.3);
    QString flag = ui->txtFlag->text().trimmed();
    QString color = QtHelper::getRandColor().name();
    int radius = QtHelper::getRandValue(1000, 8000);

    QString js;
    QString data = points.join("|");
    if (overlay == "marker") {
        js = QString("addMarker('%1', '%2', '', '../mapimage/marker5.png', 100, 100)").arg(flag).arg(data);
    } else if (overlay == "polyline") {
        js = QString("addPolyline('%1', '%2', '%3')").arg(flag).arg(data).arg(color);
    } else if (overlay == "polygon") {
        js = QString("addPolygon('%1', '%2', '%3')").arg(flag).arg(data).arg(color);
    } else if (overlay == "rectangle") {
        js = QString("addRectangle('%1', '%2', '%3')").arg(flag).arg(data).arg(color);
    } else if (overlay == "circle") {
        js = QString("addCircle('%1', '%2', %4, '%3')").arg(flag).arg(data).arg(color).arg(radius);
    }

    this->runJs(js);

    //自動將序號遞增
    if (flag.startsWith("overlay")) {
        int index = flag.mid(7, flag.length()).toInt();
        flag = QString("overlay%1").arg(index + 1);
        ui->txtFlag->setText(flag);
    }
}

void frmMapDraw::on_btnDeleteOverlay_clicked()
{
    QString flag = ui->txtFlag->text().trimmed();
    QString js = QString("deleteOverlay('', '%1')").arg(flag);
    this->runJs(js);
}

void frmMapDraw::on_btnUpdateOverlay_clicked()
{
    QString overlay = ui->cboxOverlay->itemData(ui->cboxOverlay->currentIndex()).toString();
    QString flag = ui->txtFlag->text().trimmed();
    QString data = "121.428961,31.249075|121.557167,31.213504|121.469780,31.135397|121.403090,31.198678";
    if (overlay == "marker") {
        //可以分開執行也可以合併執行
        //this->runJs(QString("setMarker('%1', '121.424362,31.175942', 45)").arg(flag));
        //this->runJs(QString("setMarker('%1', null, null, '../mapimage/marker.png', 45, 65)").arg(flag));
        this->runJs(QString("setMarker('%1', '121.424362,31.175942', 45, '../mapimage/marker.png', 45, 65)").arg(flag));
    } else if (overlay == "circle") {
        this->runJs(QString("updateOverlay('%1', '%2', '121.424362,31.175942', 10000)").arg(overlay).arg(flag));
    } else {
        this->runJs(QString("updateOverlay('%1', '%2', '%3')").arg(overlay).arg(flag).arg(data));
    }
}

void frmMapDraw::on_btnClearOverlay_clicked()
{
    this->runJs("clearOverlay()");
    ui->txtFlag->setText("overlay1");
}

void frmMapDraw::on_btnEditOverlay_clicked()
{
    QString flag = ui->txtFlag->text().trimmed();
    this->runJs(QString("editOverlay('%1', true)").arg(flag));
}

void frmMapDraw::on_btnCloseEdit_clicked()
{
    QString flag = ui->txtFlag->text().trimmed();
    this->runJs(QString("editOverlay('%1', false)").arg(flag));
}

void frmMapDraw::on_btnListenOverlay_clicked()
{
    this->runJs("addListener()");
}

void frmMapDraw::on_btnCloseListen_clicked()
{
    this->runJs("removeListener()");
    this->runJs("editOverlays(false)");
}

void frmMapDraw::on_btnShowOverlay_clicked()
{
    this->runJs("setOverlayVisible('', true)");
}

void frmMapDraw::on_btnHideOverlay_clicked()
{
    this->runJs("setOverlayVisible('', false)");
}

void frmMapDraw::on_btnPolylineArrow_clicked()
{
    QString points = "121.225890,31.261530|121.371460,31.262120|121.316530,31.353050";
    this->runJs(QString("addCover('PolylineArrow', '', '%1', '#753775', 5, 1, 'dashed', null, 0)").arg(points));
}

void frmMapDraw::on_btnStraightArrow_clicked()
{
    QString points = "121.192930,31.041760|121.318590,31.140540";
    this->runJs(QString("addCover('StraightArrow', '', '%1')").arg(points));
}

void frmMapDraw::on_btnDiagonalArrow_clicked()
{
    QString points = "121.623460,31.123500|121.617970,31.021750";
    this->runJs(QString("addCover('DiagonalArrow', '', '%1')").arg(points));
}

void frmMapDraw::on_btnSector_clicked()
{
    QString points = "121.428961,31.249075|121.557167,31.213504|121.403090,31.198678";
    this->runJs(QString("addCover('Sector', '', '%1')").arg(points));
}

void frmMapDraw::on_btnAddSector_clicked()
{
    //第一個參數列示唯一標識/是為了方便指定標識來刪除
    QString points = "121.344680,31.001150|121.483380,31.041760|121.449050,31.121730";
    this->runJs(QString("addCover('Sector', 's1', '%1', '#00ff00')").arg(points));
}

void frmMapDraw::on_btnDeleteSector_clicked()
{
    //指定唯一標識刪除
    QString js = "deleteOverlay('', 's1')";
    this->runJs(js);
}

void frmMapDraw::on_cboxMapType_currentIndexChanged(int index)
{
    if (this->isVisible()) {
        this->runJs(QString("setMapType(%1)").arg(index));
    }
}

三、相關連結

  1. 體驗地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取碼:o05q 檔名:bin_map.zip
  2. 國內站點:https://gitee.com/feiyangqingyun
  3. 國際站點:https://github.com/feiyangqingyun

四、效果圖

五、功能特點

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版本、任意系統、任意編譯器。

相關文章