QML地图Map中提供了供绘制图形的组件,例如MapPolyline,MapCircle等,但是这些组件在绘制复杂轨迹时就显得功能不够全面,因此我将QPainterPath在Map中进行使用并进行绘制,并使用C++和Qml中的函数进行相互调用计算获取点屏幕坐标和经纬度坐标。例子中使用了QPainterPath的QPainterPath::pointAtPercent获取绘制的轨迹全过程中的各个位置的经纬度。效果如图:
QML主要功能为地图显示,其中Plugin中定义的路径为我地图瓦片存放路径,你们需要修改为自己的或者直接使用在线地图。我们自定义了一个类 MapPainter,该类继承至QQuickPaintedItem,并通过元对象系统注册至QML中,覆盖到Map上方作为画布使用。
Demo项目地址:https://github.com/zjgo007/QmlDemo/tree/master/MapPainterhttps://github.com/zjgo007/QmlDemo/tree/master/MapPainter
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtLocation 5.9
import QtPositioning 5.9
import QtQuick.Controls 2.9
import MapPainter 1.0
Window {
id:window
width: 640
height: 480
visible: true
title: qsTr("Map Painter Path")
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl", "esri", ...
PluginParameter{//自定义地图瓦片路径
name:"osm.mapping.offline.directory"
value: "G:/Map/"
}
PluginParameter{
name:"osm.mapping.offline.maptiledir"
value:true
}
}
Map {
id: myMap
center: QtPositioning.coordinate(24,104)
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 8
color: "#00000000"
copyrightsVisible: false
activeMapType: supportedMapTypes[2]
// onVisibleRegionChanged: {//Qt 5.15以上使用
// mapPainter.mapRegionChanged()
// }
onCenterChanged: {//Qt 5.15以下使用
mapPainter.mapRegionChanged()
}
onZoomLevelChanged: {//Qt 5.15以下使用
mapPainter.mapRegionChanged()
}
MapPainter{
id:mapPainter
anchors.fill: parent
}
MapQuickItem{
id: anchorMarker
width: 50
height: 36
anchorPoint.x: image.width/2
anchorPoint.y: image.height
coordinate: myMap.center
sourceItem: Item{
Image {
id:image
source: "qrc:/anchor.png"
sourceSize.height: 36
sourceSize.width: 50
}
Text {
id: label
y:-15
color: "#00ffff"
text: qsTr("")
font.bold: true
font.pointSize: 11
font.family: "微软雅黑"
}
}
}
MouseArea {
id: mouseArea_measure
anchors.fill: parent
onClicked: {
var coordinate = myMap.toCoordinate(Qt.point(mouse.x, mouse.y))
mapPainter.addPathPoint(mouse.x, mouse.y,coordinate)
anchorMarker.coordinate = coordinate
}
}
}
Slider {
id: slider
x: 430
y: 10
stepSize: 0.01
value: 1
onValueChanged: {
var coordinate = mapPainter.mapPathData(value)
anchorMarker.coordinate = coordinate
label.text = "("+coordinate.latitude.toFixed(4)+","+coordinate.longitude.toFixed(4)+")"
}
}
Component.onCompleted: {
mapPainter.setQmlObject(window)
}
function transGeoToPoint(coordinate){
return myMap.fromCoordinate(coordinate,false)
}
function transPointToGeo(pointf){
return myMap.toCoordinate(pointf,false)
}
}
其中需要注意的是,在QML的Component.onCompleted信号发出后,需要该QML的QObject传递至MapPainter中,便于在C++中调用qml里定义的函数,这两个函数用于经纬度坐标和屏幕坐标转换。
Component.onCompleted: {
mapPainter.setQmlObject(window)
}
function transGeoToPoint(coordinate){
return myMap.fromCoordinate(coordinate,false)
}
function transPointToGeo(pointf){
return myMap.toCoordinate(pointf,false)
}
在C++中,addPathPoint(qreal x,qreal y,QGeoCoordinate coordinate)函数用于传入鼠标点击位置的屏幕坐标和经纬度坐标。mapRegionChanged()用于标记当前地图已被平移或者缩放,需要重新绘制轨迹。
同时自定义了类GeoPainterPath,该类记录了鼠标绘制轨迹的点位置和绘制方式,我只简单的放了MoveTo和LineTo,其他绘制方式可自行添加。
mappainter.h
#ifndef MAPPAINTER_H
#define MAPPAINTER_H
#include <QQuickPaintedItem>
#include <QObject>
#include <QPainter>
#include <QPainterPath>
#include <QGeoCoordinate>
class GeoPainterPath
{
public:
GeoPainterPath() {}
~GeoPainterPath(){}
enum PainterType{
None,
MoveTo,
LineTo
};
void addGeoPath(PainterType type,QGeoCoordinate coordinate);
PainterType painterType(int index);
QGeoCoordinate coordinate(int index);
int size();
void clear();
private:
QList<PainterType> typeList;
QList<QGeoCoordinate> geoList;
};
class MapPainter : public QQuickPaintedItem
{
Q_OBJECT
public:
MapPainter(QQuickItem *parent = nullptr);
~MapPainter();
virtual void paint(QPainter *painter) Q_DECL_OVERRIDE;
Q_INVOKABLE void setQmlObject(QObject* object);
public slots:
void addPathPoint(qreal x,qreal y,QGeoCoordinate coordinate);
void updatePainterPath();
void mapRegionChanged();
QGeoCoordinate mapPathData(qreal percent);
private:
QPainterPath* testPath;
bool pathDirty;
GeoPainterPath mGeoPainterPath;
QObject* qmlObject;
};
#endif // MAPPAINTER_H
mappainter.cpp
#include "mappainter.h"
MapPainter::MapPainter(QQuickItem *parent):QQuickPaintedItem(parent),pathDirty(false)
{
testPath = new QPainterPath();
connect(this,&QQuickPaintedItem::widthChanged,this,&MapPainter::mapRegionChanged);
connect(this,&QQuickPaintedItem::heightChanged,this,&MapPainter::mapRegionChanged);
}
MapPainter::~MapPainter()
{
delete testPath;
}
void MapPainter::paint(QPainter *painter)
{
if(pathDirty)
updatePainterPath();
painter->setPen(QPen(QColor("red"), 2, Qt::SolidLine,
Qt::RoundCap, Qt::RoundJoin));
painter->drawPath(*testPath);
}
void MapPainter::setQmlObject(QObject *object)
{
qmlObject = object;
}
void MapPainter::addPathPoint(qreal x, qreal y, QGeoCoordinate coordinate)
{
if(testPath->elementCount()==0){
testPath->moveTo(x,y);
mGeoPainterPath.addGeoPath(GeoPainterPath::MoveTo,coordinate);
}else{
testPath->lineTo(x,y);
mGeoPainterPath.addGeoPath(GeoPainterPath::LineTo,coordinate);
}
update();
}
void MapPainter::updatePainterPath()
{
if(qmlObject){
testPath->clear();
int size = mGeoPainterPath.size();
for(int i=0;i<size;i++){
QGeoCoordinate coordinate = mGeoPainterPath.coordinate(i);
QVariant mapPointVar;
QMetaObject::invokeMethod(qmlObject,"transGeoToPoint",Qt::DirectConnection,
Q_RETURN_ARG(QVariant,mapPointVar),
Q_ARG(QVariant,QVariant::fromValue(coordinate))
);
GeoPainterPath::PainterType painterType = mGeoPainterPath.painterType(i);
QPointF mapPoint = mapPointVar.toPointF();
switch (painterType) {
case GeoPainterPath::MoveTo:
testPath->moveTo(mapPoint);
break;
case GeoPainterPath::LineTo:
testPath->lineTo(mapPoint);
break;
default:
break;
}
}
pathDirty = false;
}
}
void MapPainter::mapRegionChanged()
{
pathDirty = true;
update();
}
QGeoCoordinate MapPainter::mapPathData(qreal percent)
{
QPointF pointf = testPath->pointAtPercent(percent);
QVariant coordinateVar;
QMetaObject::invokeMethod(qmlObject,"transPointToGeo",Qt::DirectConnection,
Q_RETURN_ARG(QVariant,coordinateVar),
Q_ARG(QVariant,QVariant::fromValue(pointf)));
return coordinateVar.value<QGeoCoordinate>();
}
void GeoPainterPath::addGeoPath(PainterType type, QGeoCoordinate coordinate)
{
typeList.append(type);
geoList.append(coordinate);
}
GeoPainterPath::PainterType GeoPainterPath::painterType(int index)
{
if(index>=typeList.size()){
return PainterType::None;
}
return typeList.at(index);
}
QGeoCoordinate GeoPainterPath::coordinate(int index)
{
if(index>=geoList.size()){
return QGeoCoordinate();
}
return geoList.at(index);
}
int GeoPainterPath::size()
{
return geoList.size();
}
void GeoPainterPath::clear()
{
typeList.clear();
geoList.clear();
}
Demo项目地址:https://github.com/zjgo007/QmlDemo/tree/master/MapPainterhttps://github.com/zjgo007/QmlDemo/tree/master/MapPainter