QML QtLocation轻量级地图应用学习:行政区划

1.实现思路

行政区划是一个常见的地图功能,我们需要给不同的区域绘制轮廓。如果使用百度或高德的网页在线地图进行开发,那么直接就可以查询到一个区域的边界点(高德比百度更密集点),之后就可以绘制这个区域了。

参见百度Demo:http://lbsyun.baidu.com/jsdemo.htm#c1_10

百度的接口是这样获取区域边界的:

我们可以把需要的区域输入进去,然后把点打印出来,保存到文件里(这里我保存了成都的几个区的边界)。

有了边界点,那么绘制区划图就简单了,可以参见Qt示例(E:\Qt\Qt5.12.6\Examples\Qt-5.12.6\location\itemview_transitions)。使用一个 MapItemView来加载数据Model,Model的每一项都包含一个区划路径,而delegate使用MapPolygon来绘制多边形区域。Model可以是QML定义的,也可以是C++定义的,如果是C++定义的,注意QList<QGeoCoordinate>是不会转为QML的path的(只有单个坐标点可以互相转换,Qt的源码中path也是通过JSValue来中转的),所以C++转的时候要自己处理下(我是分为了两个QList<double>)。

后来我发现一个新问题,MapPolygon的边框可能拖动或者缩放后会无缘无故的消失,于是采用了MapPolygon绘制区域加MapPolyline绘制边界的方式。

2.实现代码

效果:

(代码git链接:https://github.com/gongjianbo/MyQtLocation

主要代码:

//QML

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtLocation 5.12
import QtPositioning 5.12

import MyMap 1.0  //C++注册的Model对应模块

Item {
    id: control

    property int hoverIndex: -1
    //地图
    Map {
        id: the_map
        anchors.fill: parent

        minimumZoomLevel: 4
        maximumZoomLevel: 16
        zoomLevel: 10
        center: QtPositioning.coordinate(30.67, 104.06)

        plugin: Plugin {
            name: "mymap" //"esri" "mapbox" "osm" "here"

            PluginParameter {
                name: "baseUrl"
                // 自行指定瓦片路径
                value: "file:///"+applicationDirPath+"/dianzi_gaode_ArcgisServerTiles/_alllayers"
            }
            PluginParameter {
                name: "format"
                value: "png"
            }
        }
        MapItemView{
            id: map_itemview
            //Cpp扩展的model,只有坐标点,还没写其他信息
            model: BoundaryModel{
                id: map_itemmodel
            }
            //MapPolygon的边框有Bug,所以还是用折线来画,但是务须让首尾相连
            delegate: MapItemGroup{
                MapPolygon{
                    id: item_polygon
                    function fromLatAndLong(latList, longList) {
                        let the_path=[];
                        if(latList.length!==longList.length)
                            return the_path;
                        for (let i=0; i<latList.length; i++) {
                            the_path.push( QtPositioning.coordinate(latList[i],longList[i]) );
                        }
                        return the_path;
                    }
                    //QList<coordxxx>不能直接和qml交互,so
                    path: fromLatAndLong(map_itemmodel.getLatitudes(index),
                                         map_itemmodel.getLongitudes(index))
                    //随机颜色,貌似飞地颜色也不一样了,待解决
                    color: Qt.hsla(Math.random(),0.9,0.3,1)
                    opacity: (control.hoverIndex===map_itemmodel.getId(index))?0.8:0.4
                    MouseArea{
                        anchors.fill: parent
                        onClicked: control.hoverIndex=map_itemmodel.getId(index);
                    }
                }
                MapPolyline{
                    id: item_polyline
                    path: item_polygon.path
                    line.width: 2
                    line.color: "black"
                }
            }//end delegate
        }

    }
}

//C++Model类

#ifndef BOUNDARYMODEL_H
#define BOUNDARYMODEL_H

#include <QAbstractListModel>
#include <QQmlParserStatus>
#include <QList>
#include <QVariant>

class BoundaryModel : public QAbstractListModel, public QQmlParserStatus
{
    Q_OBJECT
    Q_INTERFACES(QQmlParserStatus)
public:
    explicit BoundaryModel(QObject *parent = nullptr);

    // QQmlParserStatus:构造前
    void classBegin() override;
    // QQmlParserStatus:构造后
    void componentComplete() override;

    QHash<int,QByteArray> roleNames() const override;

    // Basic functionality:
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    Q_INVOKABLE int getId(int index);
    Q_INVOKABLE QList<double> getLatitudes(int index);
    Q_INVOKABLE QList<double> getLongitudes(int index);

    //加载数据
    void loadData();
    void updateData(const QList<int> &idList,
                    const QList<QList<double>> &latitudesList,
                    const QList<QList<double>> &longitudesList);

private:
    void doLoad(const QString &path);

signals:
    void loadFinish(const QList<int> &idList,
                    const QList<QList<double>> &latitudesList,
                    const QList<QList<double>> &longitudesList);

private:
    QList<int> _id;
    QList<QList<double>> _latitudes;
    QList<QList<double>> _longitudes;

    QString _loadPath;
};

#endif // BOUNDARYMODEL_H
#include "BoundaryModel.h"

#include <QGuiApplication>
#include <QFile>
#include <QDir>
#include <QRegularExpression>
//QT += concurrent
#include <QtConcurrentRun>

#include <QDebug>

BoundaryModel::BoundaryModel(QObject *parent)
    : QAbstractListModel(parent)
{
    qRegisterMetaType<QList<QList<double>>>("QList<QList<double>>");
    connect(this,&BoundaryModel::loadFinish,this,&BoundaryModel::updateData);
}

void BoundaryModel::classBegin()
{

}

void BoundaryModel::componentComplete()
{
    if(_loadPath.isEmpty()){ //区划边界数据的路径
        _loadPath=qApp->applicationDirPath()+"/pathfiles/";
    }
    loadData();
}

QHash<int, QByteArray> BoundaryModel::roleNames() const
{
    return QHash<int,QByteArray>{{Qt::UserRole,"boundary"}};
}

int BoundaryModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    //qDebug()<<"BoundaryModel::rowCount"<<_data.count()<<parent.isValid();
    return _id.count();
}

QVariant BoundaryModel::data(const QModelIndex &index, int role) const
{
    Q_UNUSED(index)
    Q_UNUSED(role)
    //qDebug()<<"BoundaryModel::data";
    return QVariant();
}

int BoundaryModel::getId(int index)
{
    if(index<0||index>_id.count())
        return -1;
    return _id.at(index);
}

QList<double> BoundaryModel::getLatitudes(int index)
{
    if(index<0||index>_id.count())
        return QList<double>();
    return _latitudes.at(index);
}

QList<double> BoundaryModel::getLongitudes(int index)
{
    if(index<0||index>_id.count())
        return QList<double>();
    return _longitudes.at(index);
}

void BoundaryModel::loadData()
{
    if(_loadPath.isEmpty())
        return;
    QtConcurrent::run([this](){
        doLoad(_loadPath);
    });
}

void BoundaryModel::updateData(const QList<int> &idList,
                               const QList<QList<double> > &latitudesList,
                               const QList<QList<double> > &longitudesList)
{
    if(idList.count()!=latitudesList.count()
            ||latitudesList.count()!=longitudesList.count())
        return;
    beginResetModel();
    _id=idList;
    _latitudes=latitudesList;
    _longitudes=longitudesList;
    endResetModel();
}

void BoundaryModel::doLoad(const QString &path)
{
    QList<int> id_list;
    QList<QList<double>> latitude_list;
    QList<QList<double>> longitude_list;

    //读取指定路径下的txt文件,并解析为坐标点List
    //const QString path=qApp->applicationDirPath()+"/data/PageBoundary/";
    QDir dir(path);
    const QStringList file_list = dir.entryList(QStringList{"*.txt"},
                                                QDir::Files|QDir::Readable, QDir::Name);
    QRegularExpression re(R"(:\s*([0-9\.]+))");
    const int capturingGroupsCount = re.captureCount() + 1;
    if(capturingGroupsCount!=2)
        return;
    for(int i=0;i<file_list.count();i++){
        QFile file(path+file_list.at(i));
        if(file.open(QIODevice::ReadOnly|QIODevice::Text)){
            QList<double> one_lat,one_long;
            double first_lat,first_long;
            bool is_first=true;
            while (!file.atEnd()) {
                QString line = QString::fromLatin1(file.readLine());
                if(line.isEmpty()) continue;
                //QRegularExpressionMatch match=re.match(line);
                //if(match.hasMatch()&&match.lastCapturedIndex()==1){
                //    qDebug()<<match.captured(0)<<match.captured(1);
                //}

                QRegularExpressionMatchIterator iterator = re.globalMatch(line);
                double latitude=0,longitude=0;
                if(iterator.hasNext()) {
                    longitude=iterator.next().captured(1).toDouble();
                }
                if(iterator.hasNext()) {
                    latitude=iterator.next().captured(1).toDouble();
                }
                //把飞地单独作为一个区域,但是使用同一个id
                if(is_first){
                    is_first=false;
                    first_lat=latitude;
                    first_long=longitude;
                    one_lat.push_back(latitude);
                    one_long.push_back(longitude);
                }else{
                    //十位小数精度就够了
                    if(qAbs(first_lat-latitude)<1E-10
                            &&qAbs(first_long-longitude)<1E-10){
                        one_lat.push_back(latitude);
                        latitude_list.push_back(one_lat);
                        one_long.push_back(longitude);
                        longitude_list.push_back(one_long);
                        id_list.push_back(i);
                        is_first=true;
                        one_lat.clear();
                        one_long.clear();
                    }else{
                        one_lat.push_back(latitude);
                        one_long.push_back(longitude);
                    }
                }
            }
            file.close();
            if(one_lat.count()>3){
                latitude_list.push_back(one_lat);
                longitude_list.push_back(one_long);
                id_list.push_back(i);
            }
        }else{
            qDebug()<<"open error:"<<path+file_list.at(i)<<file.errorString();
        }
    }
    //qDebug()<<id_list.count()<<data_list.count();
    emit loadFinish(id_list,latitude_list,longitude_list);
}
发布了106 篇原创文章 · 获赞 31 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/104229842