• PHP 判断点是否在多边形内


    PHP 判断点是否在多边形内

     

    如何判断一个点是否在一个多边形内,何时会用到这个场景。

    我们就模拟一个真是场景。我们公司是快递公司,在本地区域有6个分点。每个分点有3-5个工人负责附近的快递派遣发送,所以根据每个点的服务区域我们就能大概知道我们的服务范围。如果客户要收发快递我们会告知是否在服务范围内,且那个点离的最近,应派谁去收发快递。……

    网上其实找了好多判断点是否在经纬度的多边形内,但都是Javascript版:

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

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

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

    google算法:

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

    其中第三个是百度官网的一个示例

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

    其中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{ 100%;height: 100%;margin:0;font-family:"微软雅黑";}
                #allmap { 100%; height:500px; overflow: hidden;}
                #result {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_ 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

    https://www.cnblogs.com/dcb3688/p/4608026.html

  • 相关阅读:
    P2602 [ZJOI2010]数字计数
    P2657 [SCOI2009] windy 数
    Gym
    B
    Problem E The League of Sequence Designers
    C. Vladik and fractions
    hdu6069
    hdu 6096
    30道经典面试题,靠它我在一线拿到了20k的前端开发工程师岗位
    【面经分享】互联网寒冬,7面阿里,终获Offer!
  • 原文地址:https://www.cnblogs.com/lxwphp/p/10785033.html
Copyright © 2020-2023  润新知