Qt/C++地圖動態繪製折線多邊形矩形圓形標註點/可編輯拖動調整大小和位置

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

一、前言說明

無論哪一家的地圖,都提供了呼叫函式繪製各種覆蓋物,但是有時候的場景是希望進入新增覆蓋物模式,然後每次在地圖上按下都自動生成對應的覆蓋物比如圓形,這樣就不需要使用者提前知道經緯度座標等引數,而是讓使用者自己在地圖上拾取即可,這樣靈活性就極大的提高了。百度地圖和騰訊地圖是提供了繪圖工具欄來實現,高德和天地圖提供了對應的函式介面,函式介面名基本上是在對應覆蓋物末尾加上tool字樣,比如折線覆蓋物是polyline,折線工具是polylinetool,一旦進入某一種覆蓋物新增模式,則每次在地圖上新增的都是該種型別的覆蓋物,如果需要新增其他覆蓋物,需要重新切換覆蓋物的型別。

新增好的覆蓋物,有時候又需要重新編輯調整,比如標註點調整位置,圓形調整半徑,多邊形調整座標等,所以需要讓各種覆蓋物進入編輯狀態,於是新增通用函式介面,用於將介面上的所有覆蓋物進入和退出編輯狀態。不同地圖廠家對應提供的介面都不同,有些是呼叫enableEdit,有些是enableEditing,甚至還有是enableDragging,而且有些是拖動和編輯都需要單獨呼叫函式,比如setDraggable和setEditable,因為多邊形既可以拖動位置,也可以調整邊邊角角拉伸座標。所有就封裝了這麼個通用的函式來處理。

二、功能特點

2.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,支援編譯到安卓系統執行。

2.2 其他功能

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

三、相關連結

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

四、效果圖

五、相關程式碼

void MapObjBaiDu::addDrawingTool()
{
    if (!(mapControl & MapControl_Drawing)) {
        return;
    }

    //初始化繪圖工具欄
    html << QString("  var styleOptions = getOverlayProperty();");
    html << QString("  var drawingManager = new BMapLib.DrawingManager(map, {");
    html << QString("    isOpen:false,");
    html << QString("    enableDrawingTool:true,");
    html << QString("    drawingToolOptions:{anchor:BMAP_ANCHOR_TOP_RIGHT, offset:new BMap.Size(100, 5)},");
    html << QString("    circleOptions:styleOptions, polylineOptions:styleOptions, polygonOptions:styleOptions, rectangleOptions:styleOptions");
    html << QString("  });");

    //新增監聽事件獲取繪製結果
    html << QString("  drawingManager.addEventListener('overlaycomplete', function(e) {");
    html << QString("    overlays.push(e.overlay);");
    html << QString("    receiveData('overlaycomplete');");
    html << QString("  });");

    //執行指定的繪製動作
    html << QString("  function doDraw(type) {");
    html << QString("    drawingManager.close();");
    html << QString("    if (type == 'marker') {");
    html << QString("      drawingManager.setDrawingMode(BMAP_DRAWING_MARKER);");
    html << QString("    } else if (type == 'polyline') {");
    html << QString("      drawingManager.setDrawingMode(BMAP_DRAWING_POLYLINE);");
    html << QString("    } else if (type == 'polygon') {");
    html << QString("      drawingManager.setDrawingMode(BMAP_DRAWING_POLYGON);");
    html << QString("    } else if (type == 'rectangle') {");
    html << QString("      drawingManager.setDrawingMode(BMAP_DRAWING_RECTANGLE);");
    html << QString("    } else if (type == 'circle') {");
    html << QString("      drawingManager.setDrawingMode(BMAP_DRAWING_CIRCLE);");
    html << QString("    }");
    html << QString("    if (type != 'cancel') {");
    html << QString("      drawingManager.open();");
    html << QString("    }");
    html << QString("  }");
}

#include "frmmapdemodraw.h"
#include "ui_frmmapdemodraw.h"
#include "qthelper.h"
#include "maphelper.h"
#include "webview.h"

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

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

void frmMapDemoDraw::initForm()
{
    //設定右側固定寬度
    ui->frameRight->setFixedWidth(AppData::RightWidth / 2);    

    //單選框訊號槽
    QList<QRadioButton *> rbtns = ui->frameRight->findChildren<QRadioButton *>();
    foreach (QRadioButton *rbtn, rbtns) {
        connect(rbtn, SIGNAL(toggled(bool)), this, SLOT(toggled(bool)));
    }

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

    //例項化地圖類
    mapObj = MapHelper::getMapObj(this, AppConfig::MapDemoCore);
    mapObj->setWebView(webView);
    mapObj->setSaveFile(SaveFile);
    mapObj->setMapControl(MapControl_Drawing);
    mapObj->setMapLocal(AppConfig::MapDemoLocal);
}

void frmMapDemoDraw::toggled(bool checked)
{
    if (!checked) {
        return;
    }

    //判斷按下了哪個單選框
    QRadioButton *rbtn = (QRadioButton *)sender();
    QString type = rbtn->objectName().replace("rbtn", "").toLower();
    this->runJs(QString("doDraw('%1')").arg(type));
}

void frmMapDemoDraw::loadSuccess()
{
    //禁用雙擊放大
    mapObj->setEnable(EnableType_DoubleClickZoom, false);
}

void frmMapDemoDraw::runJs(const QString &js)
{
    //非繪製指令則先要取消繪製
    if (!js.startsWith("doDraw")) {
        ui->rbtnCancel->setChecked(true);
        this->runJs("doDraw('cancel')");
    }

    mapObj->runJs(js);
}

void frmMapDemoDraw::receiveDataFromJs(const QString &type, const QVariant &data)
{
    QString result = data.toString();
    if (type == "rightclick") {
        //識別滑鼠右鍵自動取消繪製
        ui->rbtnCancel->setChecked(true);
    } else if (type == "overlayinfo") {
        QtHelper::showMessageBoxInfo("覆蓋物資訊\n" + result);
        //如果是矩形可以取出四個點繪製標註點看下位置
        if (result.startsWith("rectangle")) {
            QString rect = result.split("|").at(1);
            QStringList points = rect.split(";");
            for (int i = 0; i < points.count(); ++i) {
                //this->runJs(QString("addMarker('%1', '%2', '%1')").arg(i).arg(points.at(i)));
            }
        }
    } else if (type == "overlaycomplete") {
        //ui->rbtnCancel->setChecked(true);
    }
}

void frmMapDemoDraw::on_btnLoadMap_clicked()
{
    ui->rbtnCancel->setChecked(true);
    mapObj->load();
}

void frmMapDemoDraw::on_btnStartEdit_clicked()
{
    //高德地圖用的方式比較特別
    if (mapObj->getMapCore() == MapCore_GaoDe) {
        this->runJs("enableEdit(true)");
    } else {
        this->runJs("editOverlays(true)");
    }
}

void frmMapDemoDraw::on_btnStopEdit_clicked()
{
    //高德地圖用的方式比較特別
    if (mapObj->getMapCore() == MapCore_GaoDe) {
        this->runJs("enableEdit(false)");
    } else {
        this->runJs("editOverlays(false)");
    }
}

void frmMapDemoDraw::on_btnGetOverlay_clicked()
{
    //獲取前先要停止編輯
    on_btnStopEdit_clicked();
    this->runJs("getOverlayInfo()");
}

void frmMapDemoDraw::on_btnClearOverlay_clicked()
{
    this->runJs("clearOverlay()");
}

相關文章