1.实现思路
参照百度or高德地图的测距功能,主要由两种元素组成,标记点和连线。
其中连线很好解决,Qt 提供了 MapPolyline 类型,可以用来绘制折线,并且提供了增删的便捷函数:
对于标记点,我们可以用 MapQuickItem 来实现,里面包含一个标记圆圈、长度Text、删除按钮。对于一组 MapQuickItem,我使用 MapItemView 来管理。而求两个坐标点的距离,直接调用的 coordinate 类型的 distanceTo 函数,总长直接遍历求距离就行了。
接下来就是封装一个 Ruler 的组件,实现多次测距的绘制。每次开始测距时,通过 createObject 动态创建一个 Ruler 组件,然后往里面添加坐标点。坐标点我是在 Map 上放了一个 MouseArea ,然后用 Map 的 toCoordinate 方法把 point 转换成坐标点。
此外,如果要自己计算两点距离,可以参考网上球面两点距离的公式,如:
上面参考的表示法应该是十进制的,如果是度分秒的形式,可以先转化下:
2.实现代码及git链接
下面是实现效果:
有一点还没解决,那就是 MapPolyline 这种图元设置 layer 实现平滑效果的话,放大之后很卡。另外就是,一个 Ruler 所有元素被删之后我也没有析构它。
Ruler组件的实现代码:
//MapRuler.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtLocation 5.12
import QtPositioning 5.12
// 一次测距里包含多个标记点以及连线
MapItemGroup{
id: control
MapPolyline {
id: item_line
line.color: "red"
line.width: 2
//平滑后放大有点卡
//layer.enabled: true
//layer.smooth: true
//layer.samples: 8
function getDistanceCount(){
var distance_count=0;
for(var i=1;i<pathLength();i++){
distance_count+=item_line.coordinateAt(i).distanceTo(item_line.coordinateAt(i-1));
}
return Math.round(distance_count);
}
}
MapItemView{
id: item_view
add: Transition {}
remove: Transition {}
model: ListModel{
id: item_model
}
delegate: MapQuickItem{
id: ietm_delegate
sourceItem: Rectangle {
width: 14
height: 14
radius: 7
color: "white"
border.width: 2
border.color: "red"
Rectangle{
anchors.left: parent.right
anchors.top: parent.bottom
width: item_text.width+5+5+14+5
height: item_text.height+10
border.color: "gray"
Text {
id: item_text
x: 5
anchors.verticalCenter: parent.verticalCenter
text: index<=0
? "起点"
: (index==item_model.count-1)
? ("总长 "+item_line.getDistanceCount()/1000+" km")
:(Math.round(ietm_delegate.coordinate.distanceTo(item_line.coordinateAt(index-1)))/1000+" km")
}
Rectangle{
width: 14
height: 14
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
border.color: "red"
Text {
color: "red"
anchors.centerIn: parent
text: "+"
rotation: 45
}
MouseArea{
anchors.fill: parent
onClicked: {
//最后一个全部删除,否则一个一个的删除
//为0的时候发送信号给group请求删除
if(index==item_model.count-1){
item_line.path=[];
item_model.clear();
//control.destroy();
}else{
item_line.removeCoordinate(index);
item_model.remove(index);
}
}
}
}
}
//Component.onDestruction: console.log("destory item");
}
//通过listmodel来设置数据
coordinate{
latitude: latitudeval
longitude: longitudeval
}
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
}
}
function appendPoint(coord){
//var coord=the_map.toCoordinate(Qt.point(mouseX,mouseY),false);
//console.log("area",coord.latitude,coord.longitude);
item_model.append({"latitudeval":coord.latitude,"longitudeval":coord.longitude});
item_line.addCoordinate(coord);
//mouse_area._closePath=false;
//console.log("ruler append",item_model.count,item_line.pathLength())
}
function followMouse(coord){
if(item_line.pathLength()<=0)
return;
if(item_line.pathLength()===item_model.count){
item_line.addCoordinate(coord);
}else{
item_line.replaceCoordinate(item_line.pathLength()-1,coord);
}
}
function closePath(){
while(item_line.pathLength()>item_model.count){
item_line.removeCoordinate(item_line.pathLength()-1);
}
}
}
在 Window 中调用下面组件来展示 Demo:
//Demo.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtLocation 5.12
import QtPositioning 5.12
//地图自定义
Item{
id: control
//地图的模式
// 0:普通浏览
// 1:测距
property int mapMode: 0
property MapRuler currentRuler: null
property alias map: the_map
clip: true
onMapModeChanged: {
console.log("map mode",mapMode);
if(control.mapMode!=1&¤tRuler){
currentRuler.closePath();
currentRuler=null;
}
}
//缩放等级,维度,精度
function viewPoint(zoomLevel,latitude,longitude){
the_map.zoomLevel=zoomLevel;
the_map.center=QtPositioning.coordinate(latitude, longitude);
}
Row{
RadioButton{
text: "Normal"
checked: true
onCheckedChanged: if(checked)control.mapMode=0;
}
RadioButton{
text: "Ruler"
onCheckedChanged: if(checked)control.mapMode=1;
}
}
Map {
id: the_map
anchors.fill: parent
anchors.topMargin: 40
minimumZoomLevel: 4
maximumZoomLevel: 16
zoomLevel: 10
center: QtPositioning.coordinate(30.6562, 104.0657)
plugin: Plugin {
name: "esri" //"esri" "mapbox" "osm" "here"
}
//显示缩放等级与center
Rectangle{
anchors{
left: the_map.left
bottom: the_map.bottom
margins: 5
}
width: content.width+20
height: content.height+10
Text {
id: content
x: 10
y: 5
font.pixelSize: 14
text: "Zoom Level "+Math.floor(the_map.zoomLevel)+" Center:"+the_map.center.latitude+" "+the_map.center.longitude
}
}
MouseArea{
id: map_mouse
anchors.fill: parent
enabled: control.mapMode!=0
//画了一个点后跟随鼠标,除非双击
hoverEnabled: true
onClicked: {
// 1 测距
if(control.mapMode===1){
if(!currentRuler){
currentRuler=ruler_comp.createObject(the_map);
if(currentRuler)
the_map.addMapItemGroup(currentRuler);
}
if(currentRuler){
var coord=the_map.toCoordinate(Qt.point(mouseX,mouseY),false);
currentRuler.appendPoint(coord);
}
}
}
onDoubleClicked: {
// 1 测距
if(control.mapMode===1){
if(currentRuler){
currentRuler.closePath();
currentRuler=null;
}
}
}
onPositionChanged: {
// 1 测距
if(control.mapMode===1){
if(currentRuler){
var coord=the_map.toCoordinate(Qt.point(mouseX,mouseY),false);
currentRuler.followMouse(coord);
}
}
}
}
}
Component{
id: ruler_comp
MapRuler{
}
}
}
代码 github 链接:https://github.com/gongjianbo/MyQtLocation