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);
}