PHP 判斷點是否在多邊形內

如何判斷一個點是否在一個多邊形內,什麼時候會用到這個場景。javascript

咱們就模擬一個真是場景。咱們公司是快遞公司,在本地區域有6個分點。每一個分點有3-5個工人負責附近的快遞派遣發送,因此根據每一個點的服務區域咱們就能大概知道咱們的服務範圍。若是客戶要收發快遞咱們會告知是否在服務範圍內,且那個點離的最近,應派誰去收發快遞。……css

網上其實找了好多判斷點是否在經緯度的多邊形內,但都是Javascript版:html

http://www.voidcn.com/blog/jq_develop/article/p-3221513.htmljava

http://www.html-js.com/article/1528jquery

http://api.map.baidu.com/library/GeoUtils/1.2/examples/simple.htmlweb

google算法:ajax

https://en.wikipedia.org/wiki/Geohash算法

其中第三個是百度官網的一個示例json

 

查看源碼,百度裏面有一個http://api.map.baidu.com/library/GeoUtils/1.2/src/GeoUtils_min.js文件api

其中isPointInPolygon方法就是判斷點是否在多邊形內部

//點在多邊形內
function ptInPolygon(){
    var pts = [];
    var pt1 = new BMap.Point(116.395, 39.910);
    var pt2 = new BMap.Point(116.394, 39.914);
    var pt3 = new BMap.Point(116.403, 39.920);
    var pt4 = new BMap.Point(116.402, 39.914);
    var pt5 = new BMap.Point(116.410, 39.913);    
    
    pts.push(pt1);
    pts.push(pt2);
    pts.push(pt3);
    pts.push(pt4);
    pts.push(pt5);  
    var ply = new BMap.Polygon(pts);
    
    var pt =new BMap.Point(116.400, 39.914);
    
    var result = BMapLib.GeoUtils.isPointInPolygon(pt, ply);
    if(result == true){
        alert("點在多邊形內");
    } else {
        alert("點在多邊形外")
    } 
    
    //演示:將面添加到地圖上    
    map.clearOverlays();
    var mkr = new BMap.Marker(pt);
    map.addOverlay(mkr);
    map.addOverlay(ply);      
}

 

PHP版的也有好幾個,都是翻譯Javascript但試了下,幾乎沒一個能夠判斷驗證的。後來在一個論壇中找到了一個很精準的計算多邊形內代碼,貼出來和你們分享

  
  $point=[
      'lng'=>121.427417,
      'lat'=>31.20357
  ];
  $arr=[
      [
          'lng'=>121.23036,
          'lat'=>31.218609
      ],
      [
          'lng'=>121.233666,
          'lat'=>31.210579
      ],
      [
          'lng'=>121.247177,
          'lat'=>31.206749
      ],
      [
          'lng'=>121.276353,
          'lat'=>31.190811
      ],
      [
          'lng'=>121.267442,
          'lat'=>31.237383
      ],
  ];
  
  $a= is_point_in_polygon($point, $arr);
  var_dump($a);


/**
 * 判斷一個座標是否在一個多邊形內(由多個座標圍成的)
 * 基本思想是利用射線法,計算射線與多邊形各邊的交點,若是是偶數,則點在多邊形外,不然
 * 在多邊形內。還會考慮一些特殊狀況,如點在多邊形頂點上,點在多邊形邊上等特殊狀況。
 * @param $point 指定點座標
 * @param $pts 多邊形座標 順時針方向
 */
function is_point_in_polygon($point, $pts) {
    $N = count($pts);
    $boundOrVertex = true; //若是點位於多邊形的頂點或邊上,也算作點在多邊形內,直接返回true
    $intersectCount = 0;//cross points count of x 
    $precision = 2e-10; //浮點類型計算時候與0比較時候的容差
    $p1 = 0;//neighbour bound vertices
    $p2 = 0;
    $p = $point; //測試點
 
    $p1 = $pts[0];//left vertex        
    for ($i = 1; $i <= $N; ++$i) {//check all rays
        // dump($p1);
        if ($p['lng'] == $p1['lng'] && $p['lat'] == $p1['lat']) {
            return $boundOrVertex;//p is an vertex
        }
         
        $p2 = $pts[$i % $N];//right vertex            
        if ($p['lat'] < min($p1['lat'], $p2['lat']) || $p['lat'] > max($p1['lat'], $p2['lat'])) {//ray is outside of our interests
            $p1 = $p2; 
            continue;//next ray left point
        }
         
        if ($p['lat'] > min($p1['lat'], $p2['lat']) && $p['lat'] < max($p1['lat'], $p2['lat'])) {//ray is crossing over by the algorithm (common part of)
            if($p['lng'] <= max($p1['lng'], $p2['lng'])){//x is before of ray
                if ($p1['lat'] == $p2['lat'] && $p['lng'] >= min($p1['lng'], $p2['lng'])) {//overlies on a horizontal ray
                    return $boundOrVertex;
                }
                 
                if ($p1['lng'] == $p2['lng']) {//ray is vertical                        
                    if ($p1['lng'] == $p['lng']) {//overlies on a vertical ray
                        return $boundOrVertex;
                    } else {//before ray
                        ++$intersectCount;
                    }
                } else {//cross point on the left side
                    $xinters = ($p['lat'] - $p1['lat']) * ($p2['lng'] - $p1['lng']) / ($p2['lat'] - $p1['lat']) + $p1['lng'];//cross point of lng
                    if (abs($p['lng'] - $xinters) < $precision) {//overlies on a ray
                        return $boundOrVertex;
                    }
                     
                    if ($p['lng'] < $xinters) {//before ray
                        ++$intersectCount;
                    } 
                }
            }
        } else {//special case when ray is crossing through the vertex
            if ($p['lat'] == $p2['lat'] && $p['lng'] <= $p2['lng']) {//p crossing over p2
                $p3 = $pts[($i+1) % $N]; //next vertex
                if ($p['lat'] >= min($p1['lat'], $p3['lat']) && $p['lat'] <= max($p1['lat'], $p3['lat'])) { //p.lat lies between p1.lat & p3.lat
                    ++$intersectCount;
                } else {
                    $intersectCount += 2;
                }
            }
        }
        $p1 = $p2;//next ray left point
    }
 
    if ($intersectCount % 2 == 0) {//偶數在多邊形外
        return false;
    } else { //奇數在多邊形內
        return true;
    }
}

打印:bool(false)

  $point=[
      'lng'=>121.427417,
      'lat'=>31.20357
  ];

替換爲

  $point=[
      'lng'=>121.257428,
      'lat'=>31.222481 
  ];

打印:bool(true)

 

 

-------------  擴展  -------------------------

在百度地圖上繪製多邊形並保存繪製的點的經緯度

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
        <style type="text/css">
            body, html{width: 100%;height: 100%;margin:0;font-family:"微軟雅黑";}
            #allmap {width: 100%; height:500px; overflow: hidden;}
            #result {width:100%;font-size:12px;}
            dl,dt,dd,ul,li{
                margin:0;
                padding:0;
                list-style:none;
            }
            p{font-size:12px;}
            dt{
                font-size:14px;
                font-family:"微軟雅黑";
                font-weight:bold;
                border-bottom:1px dotted #000;
                padding:5px 0 5px 5px;
                margin:5px 0;
            }
            dd{
                padding:5px 0 0 5px;
            }
            li{
                line-height:28px;
            }
        </style>
        <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=25eb303c9c5df0ec2424fa86816437da"></script>
        <!--加載鼠標繪製工具-->
        <script type="text/javascript" src="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
        <link rel="stylesheet" href="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.css" />
        <!--加載檢索信息窗口-->
        <script type="text/javascript" src="http://api.map.baidu.com/library/SearchInfoWindow/1.4/src/SearchInfoWindow_min.js"></script>
        <link rel="stylesheet" href="http://api.map.baidu.com/library/SearchInfoWindow/1.4/src/SearchInfoWindow_min.css" />
        <script src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.min.js"></script>
        <title>鼠標繪製工具</title>
    </head>
    <body>
        <div id="allmap" style="overflow:hidden;zoom:1;position:relative;">    
            <div id="map" style="height:100%;-webkit-transition: all 0.5s ease-in-out;transition: all 0.5s ease-in-out;"></div>
        </div> 
        <div>
            <input type="button" class="btn_gray"  value="添加多邊形" onclick="addPolyline()">
            <input type="button" class="btn_gray" value="保存數據" onclick="saveData()">
        </div>

        <script type="text/javascript">
            //記錄marker、label、polyline的個數
            var NUM_MARKER = 0,
                    NUM_LABEL = 0,
                    NUM_POLYLINE = 0;

            var polyDefaultStyle = {
                strokeColor: "#f00",
                strokeOpacity: 0.6,
                strokeWeight: 4
            }
            /*
             *用於存儲地圖各個配置項的數據結構
             *包括:地圖中心點、地圖的監聽事件、地圖的控件、地圖上的覆蓋物等信息
             *用於獲取代碼的時候繪製地圖
             */
            var config = {
                city: "北京",
                center_point: new BMap.Point(116.403874, 39.914889),
                zoom: 12,
                container_width: 700,
                container_height: 550,
                enableScrollWheelZoom: true,
                enableKeyboard: true,
                enableDragging: true,
                enableDoubleClickZoom: true,
                scale_control: {
                    added: true,
                    anchor: "BMAP_ANCHOR_BOTTOM_LEFT",
                    type: "BMAP_UNIT_IMPERIAL"
                },
                nav_control: {
                    added: true,
                    anchor: "BMAP_ANCHOR_TOP_LEFT",
                    type: "BMAP_NAVIGATION_CONTROL_LARGE"
                },
                overview_control: {
                    added: true,
                    anchor: "BMAP_ANCHOR_BOTTOM_RIGHT",
                    isopen: true
                },
                label_array: [],
                label_config: [],
                marker_array: [],
                marker_config: [],
                polyline_config: [],
                polyline_array: [],
                polyline_name_array: []
            }



            // 百度地圖API功能
            var map = new BMap.Map('map');
            var poi = new BMap.Point(116.307852, 40.057031);
            map.centerAndZoom(poi, 16);
            map.enableScrollWheelZoom();




            function drawMap(areaStr) {
                var areaObj = JSON.parse(areaStr);
                if (areaObj) {
                    //清空服務範圍緩存
                    config.polyline_array = [];
                    //對服務範圍數據進行遍歷
                    for (var len = 0; len < areaObj.service_area.length; len++) {
                        var area_item = areaObj.service_area[len];
                        if (area_item) {
                            var polyPoint = [];
                            var name = area_item.name;
                            var points = area_item.points;
                            for (var p_num = 0; p_num < points.length; p_num++) {
                                var point = new BMap.Point(points[p_num].lng, points[p_num].lat);
                                polyPoint.push(point);
                                if (p_num == 0) {
                                    map.centerAndZoom(point, 12);
                                }
                            }
                            //在地圖上繪製服務範圍區域
                            var polygon = new BMap.Polygon(polyPoint, {
                                strokeColor: polyDefaultStyle.strokeColor,
                                strokeWeight: polyDefaultStyle.strokeWeight,
                                strokeOpacity: polyDefaultStyle.strokeOpacity});
                            map.addOverlay(polygon);

                            //將服務範圍加入到緩存中
                            config.polyline_array.push(polygon);
                            config.polyline_name_array.push(name);
                        }
                    }
                }
            }




           var ranges = '{"service_area":[{"name":"多邊形1","points":[{"lng":121.23036,"lat":31.218609},{"lng":121.233666,"lat":31.210579},{"lng":121.247177,"lat":31.206749},{"lng":121.276353,"lat":31.190811},{"lng":121.267442,"lat":31.237383}]}]}';
            setTimeout(function () {
                drawMap(ranges);
            }, 1000);
        </script>

        <script>
            // 此變量在添加標註功能時,用於記錄當前的click事件的處理函數
            var clickHandler;
            //鼠標樣式
            var cursorStyle = {
                "ol_marker": "hand",
                "ol_polygen": "crosshair",
                "ol_label": "text",
                "default": "auto"
            };
            //添加標註時,鼠標的label信息
            var labelInfo = {
                "ol_marker": "左鍵標記,右鍵退出",
                "ol_polygen": "左鍵單擊開始畫線,雙擊結束畫線,右鍵退出",
                "ol_label": "左鍵標記,右鍵退出",
                "drawing_line": "雙擊結束畫線"
            };
            //顯示鼠標提示信息
            function cursorLableShow() {
                cursorLabel.show();
            }

            //隱藏鼠標提示信息
            function cursorLabelHide() {
                cursorLabel.hide();
            }
            //聲明和初始化跟隨鼠標移動的label
            var cursorLabel = new BMap.Label();
            cursorLabel.setOffset(new BMap.Size(10, 10));
            cursorLabel.hide();
            //退出標註的繪製。須要清除掉map上click的處理函數。並把鼠標設置爲默認的樣式
            function exitDrawing(handlerToRemove) {
                cursorLabel.hide();
                map.setDefaultCursor(cursorStyle["default"]);
                map.removeEventListener("click", handlerToRemove);
                map.removeEventListener("mouseout", cursorLabelHide);
                map.removeEventListener("mouseover", cursorLableShow);
            }
            //添加polyline的具體操做
            function addPolyline() {
//                initPanel(); 

                //若是click已經有事件處理函數,先清除掉
                if (clickHandler)
                    exitDrawing(clickHandler);

                map.setDefaultCursor(cursorStyle["ol_polygen"]);
                var polyPoint = [];
                var polyline = null;

                /*
                 * click事件的監聽事件
                 * 若是沒有初始化polyline變量,則初始化polyline並添加到地圖
                 * 若是已經初始化了polyline變量,則將click的經緯度加到polyline 的路徑中,並從新設定設定polyline的path
                 */
                function addPolyClickHandler(e) {
                    cursorLabel.setContent(labelInfo["drawing_line"]);

                    var point = e.point;
                    polyPoint.push(point);

                    //畫多邊形
                    if (!polyline) {
                        polyline = new BMap.Polygon(polyPoint, {
                            strokeColor: polyDefaultStyle.strokeColor,
                            strokeWeight: polyDefaultStyle.strokeWeight,
                            strokeOpacity: polyDefaultStyle.strokeOpacity});
                        polyline.setPath(polyPoint);
                        map.addOverlay(polyline);
                        polyPoint.length++;
                    } else {
                        polyline.setPath(polyPoint);
                    }
                }

                /*
                 * 雙擊(dbclick)事件的監聽事件
                 * 清除爲繪製polyline添加的幾個監聽函數
                 * 將polyline變量保存到config變量中
                 */
                function addPolyDdclickHandler(e) {
                    exitDrawing(addPolyClickHandler);
                    if (polyline) {
                        polyline.addEventListener("mouseover", function (e) {
                            polyline.enableEditing();
                        });
                        polyline.addEventListener("mouseout", function (e) {
                            polyline.disableEditing();
                        });
                        config.polyline_array.push(polyline);
                        config.polyline_config.push({path: polyline.getPath(),
                            strokeColor: polyDefaultStyle.strokeColor,
                            strokeWeight: polyDefaultStyle.strokeWeight,
                            strokeOpacity: polyDefaultStyle.strokeOpacity});

                        var name = "多邊形" + config.polyline_array.length;
                        config.polyline_name_array.push(name);
                    }

                    map.removeEventListener("click", addPolyClickHandler);
                    map.removeEventListener("mousemove", polyMoveHandler);
                    map.removeEventListener("dblclick", addPolyDdclickHandler);
                    map.removeEventListener("rightclick", polyRemove);

                    //若是有容許雙擊放大地圖,則從新加上
                    setTimeout(function () {
                        if (config.enableDoubleClickZoom)
                            map.enableDoubleClickZoom();
                    }, 1000);
                }

                //鼠標移動事件的監聽函數
                function polyMoveHandler(e) {
                    if (!polyline)
                        return;
                    if (polyPoint.length > 0) {
                        polyPoint[polyPoint.length - 1] = e.point; 
                        cursorLabel.show();
                        cursorLabel.setPosition(e.point);
                        polyline.setPath(polyPoint);
                    }
                }

                //取消繪製折線
                function polyRemove(e) {
                    map.removeOverlay(polyline);
                    exitDrawing(addPolyClickHandler);

                    //若是有容許雙擊放大地圖,則從新加上
                    setTimeout(function () {
                        if (config.enableDoubleClickZoom)
                            map.enableDoubleClickZoom();
                    }, 1000);
                }

                clickHandler = addPolyClickHandler;

                map.disableDoubleClickZoom();
                map.addEventListener("click", addPolyClickHandler);
                map.addEventListener("mousemove", polyMoveHandler);
                map.addEventListener("dblclick", addPolyDdclickHandler);
                map.addEventListener("rightclick", polyRemove);
            }


            /*
             * 保存服務範圍數據
             */
            function saveData() {
                if (config.polyline_array) {
                    var area = {
                        "service_area": []
                    };
                    for (var len = 0; len < config.polyline_array.length; len++) {
                        var pl = config.polyline_array[len];
                        if (pl != null) {
                            var points_json = [];
                            var points = pl.getPath();
                            for (var p_num = 0; p_num < points.length; p_num++) {
                                var point = {
                                    "lng": points[p_num].lng,
                                    "lat": points[p_num].lat
                                }
                                points_json[p_num] = point;
                            }
                            var item_json = {
                                "name": config.polyline_name_array[len],
                                "points": points_json
                            }
                            area.service_area[len] = item_json;
                        }
                    }
                    localStorage.setItem("area", JSON.stringify(area));
              
                    var gid = "2318";  //getQueryString("gid")
                    var city = "上海";  // getQueryString("city");
//                    var ranges = JSON.stringify(area);
                     var ranges = [{"city":city,"ranges":JSON.stringify(area)}];
                    var url = "/?ranges=" + JSON.stringify(ranges);

                    $.ajax({
                        type: 'get',
                        url: url,
                        data: {},
                        dataType: 'json',
                        async: false, //同步
                        crossDomain: true,
                        withCredentials: true,
                        success: function (data) {
                            if (data.code == 0) {
                                alert("保存成功");
                            }
                        },
                        error: function (error, type) {
                            alert("type:" + type + "error:" + error);
                            if (type == "abort") {
                                showAbortToast();
                            } else if (type == "timeout") {
                                showTimeoutToast();
                            }
                        }
                    });
                }
            }
        </script>
    </body>
</html>

訪問效果:

保存數據:

[{"city":"上海","ranges":"{\"service_area\":[{\"name\":\"多邊形1\",\"points\":[{\"lng\":121.23036,\"lat\":31.218609},{\"lng\":121.233666,\"lat\":31.210579},{\"lng\":121.247177,\"lat\":31.206749},{\"lng\":121.276353,\"lat\":31.190811},{\"lng\":121.267442,\"lat\":31.237383}]},{\"name\":\"多邊形2\",\"points\":[{\"lng\":121.05846,\"lat\":31.257636},{\"lng\":121.044662,\"lat\":31.151385},{\"lng\":121.165969,\"lat\":31.157318},{\"lng\":121.165969,\"lat\":31.157318},{\"lng\":121.165969,\"lat\":31.157318}]},{\"name\":\"多邊形3\",\"points\":[{\"lng\":121.16482,\"lat\":31.269489},{\"lng\":121.165395,\"lat\":31.235901},{\"lng\":121.19414,\"lat\":31.240347},{\"lng\":121.201614,\"lat\":31.262081},{\"lng\":121.243583,\"lat\":31.279859},{\"lng\":121.244733,\"lat\":31.249239},{\"lng\":121.207363,\"lat\":31.293191},{\"lng\":121.207363,\"lat\":31.293191},{\"lng\":121.207363,\"lat\":31.293191},{\"lng\":121.207363,\"lat\":31.293191}]},{\"name\":\"多邊形4\",\"points\":[{\"lng\":121.371789,\"lat\":31.274921},{\"lng\":121.326946,\"lat\":31.208232},{\"lng\":121.36719,\"lat\":31.136055},{\"lng\":121.552888,\"lat\":31.167701},{\"lng\":121.590257,\"lat\":31.154352},{\"lng\":121.623602,\"lat\":31.223056},{\"lng\":121.560936,\"lat\":31.280353},{\"lng\":121.519542,\"lat\":31.300102},{\"lng\":121.519542,\"lat\":31.300102},{\"lng\":121.519542,\"lat\":31.300102},{\"lng\":121.450553,\"lat\":31.252203}]}]}"}]

 

 

還有一個編輯和保存多邊形文件是複製百度地圖生成器樣式修改的。

百度地圖生成器:http://api.map.baidu.com/lbsapi/createmap/

文件下載:http://files.cnblogs.com/files/dcb3688/baiduPolyLine.7z

效果:

2017-03-2更新:升級版百度多邊形編輯

http://files.cnblogs.com/files/dcb3688/baiduPolyLine2.7z

相關文章
相關標籤/搜索