如何判斷一個點是否在一個多邊形內,什麼時候會用到這個場景。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更新:升級版百度多邊形編輯