Qt实现离线地图下载/瓦片地图下载/划区域下载离线地图/多线程批量下载

0 阅读15分钟

一、前言说明

搞地图软件开发,在联网的环境,一般会采用直接联网拉取瓦片地图文件,有很多场景是没有网络的环境,这就需要先把一张张的瓦片地图文件下载下来,在通过固定的路径格式,加载到软件中,关于离线地图下载这块,网上的文章不要太多,而且都有一定的参考价值,核心就是搞到一个瓦片的地址即可,而且不同地图厂商提供的地址格式不一样,甚至有些还要做一定的运算,比如腾讯地图,最麻烦的就是百度地图,用的不是国家标准也不是国际标准的坐标系,所以现在通用的地图软件都不大喜欢接入百度地图瓦片。现在AI这么牛逼,基本上问问也能找到合适的地址。

之前就已经实现了各大地图厂家的离线地图下载,比如百度地图、高德地图、天地图、腾讯地图、谷歌地图等,基本上就是按照当前软件展示的区域来下载,近期有个用户需求是希望能自己选择一块区域,然后下载这块指定的区域的地图即可,这样可以大量节约下载的时间,毕竟瓦片文件的数量可以少很多,如何实现呢?其实就是让用户在地图上画一个矩形区域,然后立即获取这个矩形区域的经纬度坐标区域,然后根据这个区域大小计算出对应的瓦片即可。

二、效果图

在这里插入图片描述 在这里插入图片描述

三、相关代码

#include "frmmapdownload.h"
#include "ui_frmmapdownload.h"
#include "qthelper.h"
#include "webview.h"
#include "maphelper.h"
#include "maputil.h"
#include "mapdownload.h"
#include "mapdownhelper.h"

#define zoomMin 1   //表示开始的级别/一般来说1-4级都只有1张图而且很可能是黑的/建议从5级别开始
#define zoomMax 20  //表示最大的级别/百度地图到18/大城市到19/谷歌地图到20/特大城市可以到22

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

frmMapDownload::~frmMapDownload()
{
    if (ui->btnStart->text() == "停止") {
        on_btnStart_clicked();
    }

    delete ui;
}

void frmMapDownload::showEvent(QShowEvent *)
{
    //只需要加载一次/避免重复初始化
    static bool isLoad = false;
    if (!isLoad) {
        isLoad = true;
        QMetaObject::invokeMethod(this, "loadMap", Qt::QueuedConnection);
    }
}

void frmMapDownload::initForm()
{
    //设置右侧固定宽度
    ui->widgetRight->setFixedWidth(AppData::RightWidth);
    ui->tabWidget->setCurrentIndex(0);

    //实例化地图下载类并关联信号
    mapDown = new MapDownload(this);
    connect(mapDown, SIGNAL(finsh(int)), this, SLOT(finsh(int)));
    connect(mapDown, SIGNAL(finsh(int, int, int, int, bool)), this, SLOT(finsh(int, int, int, int, bool)));

    //实例化浏览器控件并加入到布局
    mapObj = NULL;
    webView = new WebView(this);
    webView->setLayout(ui->gridLayout);

    //关联浏览器控件信号
    connect(webView, SIGNAL(loadSuccess()), this, SLOT(loadSuccess()), Qt::QueuedConnection);
    connect(webView, SIGNAL(receiveDataFromJs(QString, QVariant)), this, SLOT(receiveDataFromJs(QString, QVariant)));
}

void frmMapDownload::initConfig()
{
    MapCore mapCore = (MapCore)AppConfig::DownLoadCore;
    MapHelper::loadMapCore(ui->cboxMapCore, AppConfig::DownLoadCore);
    //重新纠正当前下拉框对应的内核
    mapCore = (MapCore)AppConfig::DownLoadCore;
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

    MapHelper::loadDownType(ui->cboxDownType, mapCore);
    ui->cboxDownType->setCurrentIndex(AppConfig::DownLoadType);
    connect(ui->cboxDownType, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    ui->cboxMapArea->setCurrentIndex(AppConfig::DownLoadArea);
    on_cboxMapArea_currentIndexChanged(AppConfig::DownLoadArea);
    connect(ui->cboxMapArea, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    ui->cboxCityName->lineEdit()->setText(AppConfig::DownLoadCity);
    connect(ui->cboxCityName->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));

    ui->sboxThread->setValue(AppConfig::DownLoadThread);
    connect(ui->sboxThread, SIGNAL(valueChanged(int)), this, SLOT(saveConfig()));

    ui->txtPath->setText(MapHelper::getTilePath(mapCore, 0));
}

void frmMapDownload::saveConfig()
{
    int downLoadCore = ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    if (AppConfig::DownLoadCore != downLoadCore) {
        AppConfig::DownLoadCore = downLoadCore;
        MapHelper::loadDownType(ui->cboxDownType, (MapCore)AppConfig::DownLoadCore);
    }

    AppConfig::DownLoadType = ui->cboxDownType->currentIndex();
    AppConfig::DownLoadArea = ui->cboxMapArea->currentIndex();
    AppConfig::DownLoadCity = ui->cboxCityName->lineEdit()->text().trimmed();
    AppConfig::DownLoadThread = ui->sboxThread->value();
    AppConfig::writeConfig();
}

void frmMapDownload::initTable()
{
    QList<QString> columnNames;
    QList<int> columnWidths;
    columnNames << "级别" << "瓦片数" << "进度";
    columnWidths << 70 << 70 << 30;

    int columnCount = columnNames.count();
    ui->tableWidget->setColumnCount(columnCount);
    ui->tableWidget->setHorizontalHeaderLabels(columnNames);
    for (int i = 0; i < columnCount; ++i) {
        ui->tableWidget->setColumnWidth(i, columnWidths.at(i));
    }

    QtHelper::initTableView(ui->tableWidget, AppData::RowHeight);
    ui->tableWidget->setStyleSheet("QCheckBox{padding:0px 0px 0px 6px;}");

    //添加数据
    ui->tableWidget->setRowCount(zoomMax - zoomMin + 1);
    for (int zoom = zoomMin; zoom <= zoomMax; zoom++) {
        int index = zoom - zoomMin;
        QCheckBox *ck = new QCheckBox;
        ck->setText(QString("%1级").arg(zoom, 2, 10, QChar('0')));
        cks << ck;
        ui->tableWidget->setCellWidget(index, 0, ck);

        QLabel *lab = new QLabel;
        lab->setAlignment(Qt::AlignCenter);
        lab->setText("0");
        labs << lab;
        ui->tableWidget->setCellWidget(index, 1, lab);

        QProgressBar *bar = new QProgressBar;
        bar->setAlignment(Qt::AlignCenter);
        //bar->setFormat("%v");
        bars << bar;
        ui->tableWidget->setCellWidget(index, 2, bar);

        pointLeftBottoms << QPoint(0, 0);
        pointRightTops << QPoint(0, 0);
    }
}

void frmMapDownload::loadMap()
{
    //根据不同地图内核实例化地图类
    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    int mapType = ui->cboxDownType->currentIndex();
    int zoom = MapHelper::getMapZoom(mapCore, this->objectName());
    MapHelper::initMapObj(this, &mapObj, mapCore);
    mapObj->setWebView(webView);
    mapObj->setSaveFile(SaveFile);
    mapObj->setZoom(zoom, zoomMin, zoomMax);
    mapObj->setCenterPoint(AppConfig::MapCenter);

    if (mapCore == MapCore_Tian) {
        mapObj->setMapControl(MapControl_Navigation);
    } else {
        mapObj->setMapControl(MapControl_Navigation | MapControl_Drawing);
    }

    //天地图比较特殊有多种地图类型/设置弱属性在那边调用判断
    mapObj->setProperty("setMapTypeFull", (mapCore == MapCore_Tian));
    mapObj->setMapType(mapType);
    mapObj->load();

    //设置对应地图内核的离线地图存放目录
    ui->txtPath->setText(MapHelper::getTilePath(mapCore, 0));

    //当前地图内核不存在该级别的地图则隐藏该级别
    int indexLast = zoomMax - zoomMin;
    ui->tableWidget->setRowHidden(indexLast, false);
    ui->tableWidget->setRowHidden(indexLast - 1, false);
    if (MapHelper::isMapBaiDu(mapCore)) {
        ui->tableWidget->setRowHidden(indexLast, true);
    } else if (mapCore != MapCore_Google) {
        ui->tableWidget->setRowHidden(indexLast, true);
        ui->tableWidget->setRowHidden(indexLast - 1, true);
    }
}

void frmMapDownload::getCount()
{
    //计算瓦片数
    QString pointLeftBottom = ui->txtPointLeftBottom->text();
    QString pointRightTop = ui->txtPointRightTop->text();
    QStringList listLeftBottom = pointLeftBottom.split(",");
    QStringList listRightTop = pointRightTop.split(",");

    double lngLeftBottom = listLeftBottom.at(0).toDouble();
    double latLeftBottom = listLeftBottom.at(1).toDouble();
    double lngRightTop = listRightTop.at(0).toDouble();
    double latRightTop = listRightTop.at(1).toDouble();

    //根据不同的地图内核计算不同的范围
    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    for (int zoom = zoomMin; zoom <= zoomMax; zoom++) {
        QPoint pt1, pt2;
        if (MapHelper::isMapBaiDu(mapCore)) {
            pt1 = MapUtil::lngLatToTileBaiDu(lngLeftBottom, latLeftBottom, zoom);
            pt2 = MapUtil::lngLatToTileBaiDu(lngRightTop, latRightTop, zoom);
        } else {
            pt1 = MapUtil::lngLatToTile(lngLeftBottom, latLeftBottom, zoom);
            pt2 = MapUtil::lngLatToTile(lngRightTop, latRightTop, zoom);
        }

        //计算XY坐标最大值最小值
        int xmin = qMin(pt1.x(), pt2.x());
        int xmax = qMax(pt1.x(), pt2.x());
        int ymin = qMin(pt1.y(), pt2.y());
        int ymax = qMax(pt1.y(), pt2.y());
        pt1 = QPoint(xmin, ymin);
        pt2 = QPoint(xmax, ymax);

        //方便打印查看计算的结果
        if (zoom == 25) {
            qDebug() << qSetRealNumberPrecision(10) << lngLeftBottom << latLeftBottom << lngRightTop << latRightTop << pt1 << pt2;
        }

        //当前级别的瓦片数
        quint64 count = 0;
        for (int j = xmin; j <= xmax; j++) {
            for (int k = ymin; k <= ymax; k++) {
                count++;
            }
        }

        //显示对应的瓦片总数/设置进度条参数/并更新对应的值
        int index = zoom - zoomMin;
        if (count > 0 && count < 2147483647) {
            bars.at(index)->setRange(0, count);
            bars.at(index)->setValue(0);
        }

        labs.at(index)->setText(QString::number(count));
        pointLeftBottoms[index] = pt1;
        pointRightTops[index] = pt2;
    }
}

void frmMapDownload::clear()
{
    //清空计数
    MapDownHelper::downloadCount = 0;
    redownCount = 0;
    redownUrls.clear();
    ui->txtTip->setText("等待下载...");

    //进度条全部复位
    foreach (QProgressBar *bar, bars) {
        bar->setValue(0);
    }
}

void frmMapDownload::loadSuccess()
{
    //禁用双击放大
    mapObj->setEnable(EnableType_DoubleClickZoom, false);
    on_btnLoad_clicked();
}

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

void frmMapDownload::receiveDataFromJs(const QString &type, const QVariant &data)
{
    //qDebug() << TIMEMS << "frmMapDownload" << type << data;
    QString result = data.toString();
    if (type == "click") {
        QString point = MapHelper::getLngLat2(result);
        ui->txtPointCenter->setText(point);
    } else if (type == "boundary") {
        //收到的行政区划返回结果则立即执行
        on_btnGet_clicked();
    } else if (type == "bounds") {
        MapHelper::loadMapBounds(result, ui->txtPointLeftBottom, ui->txtPointRightTop, ui->txtPointCenter, ui->txtZoom);
        //自动统计瓦片数
        this->getCount();
        //滚动条滚到最下面/一般都是需要下载级别大的
        ui->tableWidget->scrollToBottom();
    } else if (type == "overlaycomplete") {
        //选择区域完成后结束绘制模式
        this->runJs("doDraw('cancel')");

        //获取刚才选择的矩形区域的范围
        MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
        if (MapHelper::isMapBaiDu(mapCore)) {
            this->runJs("getOverlayInfo('polygon')");
        } else {
            this->runJs("getOverlayInfo('rectangle')");
        }
    } else if (type == "overlayinfo" && (result.startsWith("rectangle|") || result.startsWith("polygon|"))) {
        QStringList list = result.split("|");
        QString data = list.at(2);
        list = data.split(";");
        if (list.count() == 4) {
            //矩形区域4个坐标对象/顺序依次是左上/右上/右下/左下
            ui->txtPointLeftBottom->setText(list.at(3));
            ui->txtPointRightTop->setText(list.at(1));
            this->receiveDataFromJs("bounds", "");
        }
    }
}

void frmMapDownload::finsh(int useTime)
{
    int hour, min, sec;
    MapDownHelper::getTime(useTime, hour, min, sec);
    QString text = QString("用时: %1 时 %2 分 %3 秒").arg(hour).arg(min).arg(sec);
    ui->txtTip->setText(text);
    ui->btnStart->setText("开始");
    ui->btnPause->setText("暂停");
    ui->widgetPara->setEnabled(true);
    ui->btnLoad->setEnabled(true);
    ui->btnGet->setEnabled(true);
    mapDown->stop();

    //统计下载失败张数
    int count = redownUrls.count();
#ifdef betaversion
    QString msg = QString("试用版最大下载张数 %1 张, 不好意思!").arg(100);
    QtHelper::showMessageBoxError(msg);
#else
    QString msg = QString("恭喜你全部下载完成, 共下载 %1 张, 失败 %2 张!\n%3").arg(MapDownHelper::downloadCount).arg(count).arg(text);
    QtHelper::showMessageBoxInfo(msg);
#endif

    if (count > 0) {
        qDebug() << TIMEMS << "下载失败图片" << redownUrls;
    }
}

void frmMapDownload::finsh(int x, int y, int z, int useTime, bool error)
{
    if (z <= 0) {
        return;
    }

    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    int downType = ui->cboxDownType->currentIndex();
    QString url = MapDownHelper::getUrl(mapCore, downType, x, y, z);

    //打印下载的地址/级别/下载时间(毫秒)
    if (error) {
        qDebug() << TIMEMS << "图片下载失败" << url << useTime;
        //继续重新下载限定最多次数/防止死循环一直下载本来不存在的图片
        if (redownCount < 5) {
            redownCount++;
            redownUrls << url;
            mapDown->download(x, y, z);
            return;
        }
    }

    //下载成功则将重新下载地址集合移除对应地址
    redownCount = 0;
    int index = redownUrls.indexOf(url);
    if (index >= 0) {
        redownUrls.removeAll(url);
        qDebug() << TIMEMS << "重新下载成功" << url << useTime;
    }

    //找到对应级别的进度条更新值
    index = z - zoomMin;
    QProgressBar *bar = bars.at(index);
    int value = bar->value();
    int maxValue = bar->maximum();
    value = bar->value() + 1;
    bar->setValue(value);
    QString text = QString("%1/%2 - %3").arg(value).arg(maxValue).arg(z);
    ui->txtTip->setText(text);
}

void frmMapDownload::on_btnSelectAll_clicked()
{
    foreach (QCheckBox *ck, cks) {
        ck->setChecked(true);
    }
}

void frmMapDownload::on_btnSelectNone_clicked()
{
    foreach (QCheckBox *ck, cks) {
        ck->setChecked(false);
    }
}

void frmMapDownload::on_btnMapArea_clicked()
{
    ui->cboxMapArea->setCurrentIndex(0);
    this->runJs("doDraw('rectangle')");
}

void frmMapDownload::on_btnPath_clicked()
{
    QString dirName = QFileDialog::getExistingDirectory(this, "选择文件夹");
    if (!dirName.isEmpty()) {
        ui->txtPath->setText(dirName + "/");
        on_cboxDownType_currentIndexChanged(ui->cboxDownType->currentIndex());
    }
}

void frmMapDownload::on_btnGet_clicked()
{
    this->runJs("getBounds()");
    on_cboxDownType_currentIndexChanged(ui->cboxDownType->currentIndex());
}

void frmMapDownload::on_btnClear_clicked()
{
    this->runJs("clearOverlay()");
}

bool frmMapDownload::startDownload()
{
    //先清空
    this->clear();

    //判断不同类型做出提示/只提示一次就好
    static bool isShow = false;
    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    if (!isShow) {
        isShow = true;
        if (mapCore == MapCore_Tian) {
            QtHelper::showMessageBoxInfo("天地图每个秘钥限制了下载张数!", 0, true);
        } else if (mapCore == MapCore_Google) {
            QtHelper::showMessageBoxInfo("谷歌地图可能需要翻墙才能使用!", 0, true);
        }
    }

    //定义标志位标记是否选中了级别
    bool select = false;
    int downType = ui->cboxDownType->currentIndex();
    QString savePath = ui->txtPath->text().trimmed();
    for (int zoom = zoomMin; zoom <= zoomMax; zoom++) {
        int index = zoom - zoomMin;
        //复位进度条
        bars.at(index)->setValue(0);
        //判断哪些勾选了
        if (cks.at(index)->isChecked() && cks.at(index)->isVisible()) {
            select = true;
            int xmin = pointLeftBottoms.at(index).x();
            int ymin = pointLeftBottoms.at(index).y();
            int xmax = pointRightTops.at(index).x();
            int ymax = pointRightTops.at(index).y();
            //qDebug() << TIMEMS << zoom << index << xmin << ymin << xmax << ymax;

            //过滤掉太大的瓦片数/建议分区域下载/不要一次性下载很多瓦片
            //一个原因是数量太多没必要/一个原因是后面可能用int会溢出
            quint64 count = labs.at(index)->text().toULong();
            if (count > 10000000) {
                QtHelper::showMessageBoxError("当前级别的瓦片地图数量过多, 请重新选择!");
                return false;
            }

            //如果需要从中断的地方开始下载则修改xmin值即可
            //比如之前下载到了101008文件夹/则将xmin改成101008开始就行
            //不然一旦中断又需要彻底重新下载该级别的所有文件
            //xmin = 101008;
            mapDown->download(mapCore, downType, savePath, xmin, xmax, ymin, ymax, zoom);
        }
    }

    if (!select) {
        QtHelper::showMessageBoxError("请先勾选要下载的地图级别!");
    }

    return select;
}

void frmMapDownload::on_btnLoad_clicked()
{
    //先清空
    on_btnClear_clicked();
    //可视区域直接获取边界/行政区域先切换到行政区域然后再获取边界
    if (ui->cboxMapArea->currentText() == "可视区域") {
        on_btnGet_clicked();
    } else {
        QString name = ui->cboxCityName->currentText();
        QString js = QString("searchBoundary('%1', true)").arg(name);
        this->runJs(js);
    }
}

void frmMapDownload::on_btnStart_clicked()
{
    ui->btnPause->setText("暂停");
    if (ui->btnStart->text() == "开始") {
        //根据线程数量初始化下载线程队列
        mapDown->init(ui->sboxThread->value());
        if (this->startDownload()) {
            mapDown->start();
            ui->btnStart->setText("停止");
            ui->widgetPara->setEnabled(false);
            ui->btnLoad->setEnabled(false);
            ui->btnGet->setEnabled(false);
        } else {
            mapDown->stop();
        }
    } else {
        mapDown->stop();
        //需要延时去清空复位(因为可能停止以后还有一个正在下载的文件在路上返回)
        QTimer::singleShot(500, this, SLOT(clear()));
        ui->btnStart->setText("开始");
        ui->widgetPara->setEnabled(true);
        ui->btnLoad->setEnabled(true);
        ui->btnGet->setEnabled(true);
    }
}

void frmMapDownload::on_btnPause_clicked()
{
    //启动阶段才能暂停
    if (ui->btnStart->text() == "开始") {
        return;
    }

    mapDown->pause();
    if (ui->btnPause->text() == "暂停") {
        ui->btnPause->setText("继续");
    } else {
        ui->btnPause->setText("暂停");
    }
}

void frmMapDownload::on_cboxDownType_currentIndexChanged(int index)
{
    if (this->isVisible()) {
        MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
        ui->txtPath->setText(MapHelper::getTilePath(mapCore, index));
        this->runJs(QString("setMapType(%1)").arg(index));
    }
}

void frmMapDownload::on_cboxMapArea_currentIndexChanged(int index)
{
    ui->cboxCityName->setEnabled(index == 1);
}

void frmMapDownload::on_tableWidget_cellPressed(int row, int column)
{
    //选中行切换复选框
    if (row >= 0) {
        QCheckBox *cbox = cks.at(row);
        cbox->setCheckState(cbox->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
    }
}

四、相关地址

  1. 国内站点:gitee.com/feiyangqing…
  2. 国际站点:github.com/feiyangqing…
  3. 个人作品:blog.csdn.net/feiyangqing…
  4. 文件地址:pan.baidu.com/s/1ZxG-oyUK… 提取码:o05q 文件名:bin_map.zip

五、功能特点

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版本、任意系统、任意编译器。