QML QtLocation轻量级地图应用学习:实现面积测量

1.实现思路

参照网上的测面积功能,界面效果和测距差不多,在点和线的基础上多了一个填充区域。

点和线参照上一篇博客:https://blog.csdn.net/gongjianbo1992/article/details/103674047

填充区域使用 MapPolygon ,但是这个类接口很少,大部分操作还是借住折线 MapPolyline 来完成。

这个功能最主要的是根据坐标点的集合求面积,在网上找了很多参考代码,大部分思路是球面多边形面积计算,但是计算结果都不一样,有误差。

最后我用的是别人从高德的API里提取出来的函数。下面给出部分参考链接:

JS实现(首尾太近会算错):https://blog.csdn.net/neil89/article/details/49331641

Py实现:https://www.cnblogs.com/c-w20140301/p/10308431.html

Java参照的高德API:https://blog.csdn.net/zdb1314/article/details/80661602

根据球面面积计算公式:https://wenku.baidu.com/view/4e213e27162ded630b1c59eef8c75fbfc67d94e2.html

2.实现代码及git链接

下面是实现效果:

Area组件实现代码:

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

// 计算地图连线围成面积
MapItemGroup{
    id: control

    property bool _pathClose: false
    property double areaValue: 0
    //MapPolygon很多方法没有,所以拿MapPolyline来记录坐标点
    //优化的话自定义cpp类型
    MapPolygon{
        id: item_polygon
        color: Qt.rgba(0,1,0,0.4);
        border.width: 0
        path: item_line.path
    }
    MapPolyline{
        id: item_line
        line.width: 1
        line.color: "red"
    }

    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"
                //Component.onDestruction: console.log("destory item");

                Loader{
                    anchors.horizontalCenter: parent.horizontalCenter
                    anchors.top: parent.bottom
                    anchors.margins: 5
                    sourceComponent: (_pathClose&&index==(item_model.count-1))?area_comp:null_comp
                }
            }
            //通过listmodel来设置数据
            coordinate{
                latitude: latitudeval
                longitude: longitudeval
            }
            anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
        }
    }

    Component{
        id: null_comp
        Item{}
    }
    Component{
        id: area_comp
        Rectangle{
            width: area_text.width+5+5+14+5
            height: area_text.height+10
            border.color: "gray"
            Text {
                id: area_text
                x: 5
                anchors.verticalCenter: parent.verticalCenter
                text: control.areaValue+" m^2"
            }
            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: {
                        clearPath();
                    }
                }
            }
        }
    }

    function appendPoint(coord){
        item_model.append({"latitudeval":coord.latitude,"longitudeval":coord.longitude});
        item_line.addCoordinate(coord);
    }

    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(){
        control._pathClose=true;
        while(item_line.pathLength()>item_model.count){
            item_line.removeCoordinate(item_line.pathLength()-1);
        }
        if(item_line.pathLength()<3){
            clearPath();
            return;
        }
        control.areaValue=getPolygonArea(item_line.path);
        item_line.addCoordinate(item_line.path[0]);
    }

    function clearPath(){
        item_line.path=[];
        item_model.clear();
    }

    //计算方式1:https://www.cnblogs.com/c-w20140301/p/10308431.html
    //根据py代码换砖而来
    //转换为弧度
    function convertToRadian(num){
        return num*Math.PI/180;
    }
    //计算地图区域面积
    function calculatePolygonArea(path){
        let area_count=0;
        let path_len=path.length;
        if(path_len<3)
            return area_count;
        let data_list=[];
        for(let i=0;i<path_len;i++){
            area_count+=convertToRadian(path[(i+1)%path_len].longitude-path[(i)%path_len].longitude)*
                    (2+Math.sin(convertToRadian(path[(i)%path_len].latitude))+
                     Math.sin(convertToRadian(path[(i+1)%path_len].latitude)));
        }
        area_count*=6378137.0 * 6378137.0 / 2.0;
        return Math.abs(area_count);
    }

    //计算方式2:https://blog.csdn.net/zdb1314/article/details/80661602
    //应该是提取的高德api里的函数,命名应该是混淆加密之后的
    function getPolygonArea(path){
        let area_count=0;
        let path_len=path.length;
        if(path_len<3)
            return area_count;
        let data_list=[];
        //WGS84地球半径
        let sJ = 6378137;
        //Math.PI/180
        let Hq = 0.017453292519943295;
        let c = sJ *Hq;
        for(let i=0;i<path_len-1;i++){
            let h=path[i];
            let k=path[i+1];
            let u=h.longitude*c*Math.cos(h.latitude*Hq);
            let hhh=h.latitude*c;
            let v=k.longitude*c*Math.cos(k.latitude*Hq);
            area_count+=(u*k.latitude*c-v*hhh);
        }
        let eee=path[path_len-1].longitude*c*Math.cos(path[path_len-1].latitude*Hq);
        let g2=path[path_len-1].latitude*c;
        let k=path[0].longitude*c*Math.cos(path[0].latitude*Hq);
        area_count+=eee*path[0].latitude*c-k*g2;

        return Math.round(Math.abs(area_count)/2);
    }
}

在 Window 中调用下面组件来展示 Demo:

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

//地图自定义
Item{
    id: control
    //地图的模式
    // 0:普通浏览
    // 1:测距
    // 2:截图
    // 3:面积
    property int mapMode: 0
    property MapArea currentArea: null

    property alias map: the_map
    clip: true

    onMapModeChanged: {
        console.log("map mode",mapMode);
        if(control.mapMode!=3&&currentArea){
            currentArea.closePath();
            currentArea=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: "Area"
            onCheckedChanged: if(checked)control.mapMode=3;
        }
    }

    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: "mymap" //"esri" "mapbox" "osm" "here"

            PluginParameter {
                name: "baseUrl"
                // 自行指定瓦片路径
                value: "file:///"+applicationDirPath+"/dianzi_gaode_ArcgisServerTiles/_alllayers"
            }
            PluginParameter {
                name: "format"
                value: "png"
            }
        }

        //显示缩放等级与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: {
                // 3 面积
                if(control.mapMode===3){
                    if(!currentArea){
                        currentArea=area_comp.createObject(the_map);
                        if(currentArea)
                            the_map.addMapItemGroup(currentArea);
                    }
                    if(currentArea){
                        var coord=the_map.toCoordinate(Qt.point(mouseX,mouseY),false);
                        currentArea.appendPoint(coord);
                    }
                }
            }
            onDoubleClicked: {
                // 3 面积
                if(control.mapMode===3){
                    if(currentArea){
                        currentArea.closePath();
                        currentArea=null;
                    }
                }
            }
            onPositionChanged: {
                // 3 面积
                if(control.mapMode===3){
                    if(currentArea){
                        var coord=the_map.toCoordinate(Qt.point(mouseX,mouseY),false);
                        currentArea.followMouse(coord);
                    }
                }
            }
        }
    }

    Component{
        id: area_comp
        MapArea{

        }
    }
}

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

发布了95 篇原创文章 · 获赞 26 · 访问量 12万+

猜你喜欢

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