您的位置:首页 > 编程语言 > PHP开发

PHP 判断点是否在多边形内

2015-06-29 17:18 288 查看
如何判断一个点是否在一个多边形内,何时会用到这个场景。

我们就模拟一个真是场景。我们公司是快递公司,在本地区域有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{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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: