php 判断一个点的经纬度是否在多边形或圆里

phpwsl  发布于 2018-12-06 22:38:38    73

<?php
/**
* Created by PhpStorm.
* User: xiaobao
* Date: 2018/12/5
* Time: 下午6:53
*/
namespace App\Library;

class MapRangeService
{
   private $PI = 3.14159265358979324;
   private $x_pi = 0;

   public function __construct()
   {
       $this->x_pi = 3.14159265358979324 * 3000.0 / 180.0;
   }

   /**
    * 判断一个坐标是否在圆内
    * 思路:判断此点的经纬度到圆心的距离  然后和半径做比较
    * 如果此点刚好在圆上 则返回true
    * @param $point ['lng'=>'','lat'=>''] array指定点的坐标
    * @param $circle array ['center'=>['lng'=>'','lat'=>''],'radius'=>'']  中心点和半径
    */
   function is_point_in_circle($point, $circle){

       $distance = $this -> distance($point['lat'],$point['lng'],$circle['center']['lat'],$circle['center']['lng']);
       if($distance <= $circle['radius']){
           return true;
       }else{
           return false;
       }
   }


   /**
    *  计算两个点之间的距离
    * @param $latA  第一个点的纬度
    * @param $lonA  第一个点的经度
    * @param $latB  第二个点的纬度
    * @param $lonB  第二个点的经度
    * @return float
    */
   function distance($latA, $lonA, $latB, $lonB)
   {
       $earthR = 6371000.;
       $x = cos($latA * $this->PI / 180.) * cos($latB * $this->PI / 180.) * cos(($lonA - $lonB) * $this->PI / 180);
       $y = sin($latA * $this->PI / 180.) * sin($latB * $this->PI / 180.);
       $s = $x + $y;
       if ($s > 1) $s = 1;
       if ($s < -1) $s = -1;
       $alpha = acos($s);
       $distance = $alpha * $earthR;
       return $distance;
   }


   /**
    * 判断一个坐标是否在一个多边形内(由多个坐标围成的)
    * 基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
    * 在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
    * @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;
       }
   }

}

测试代码

<?php
/**
* Created by PhpStorm.
* User: xiaobao
* Date: 2018/12/5
* Time: 下午7:05
*/
namespace App\Http\Controllers\Api;

use App\Library\MapRangeService;
use App\Models\Category;
use App\Models\Goods;
use Illuminate\Http\Request;

class MapRangeController extends BaseController
{
   /**
    * @param MapRangeService $mapRangeService
    * @param Request $request
    * @param Category $category
    * @param Goods $goods
    * @param MapRangeService $mapRangeService
    * $circle  radius 半径:误差40米左右
    */
   function circle(MapRangeService $mapRangeService, Request $request, Category $category, Goods $goods)
   {
       $data['district'] = $request->input('district') or $this->responseApi(1004, '定位失败');
       $data['address'] = $request->input('address') or $this->responseApi(1004, '定位失败');
       $data['longitude'] = $request->input('longitude') or $this->responseApi(1004, '定位失败');
       $data['latitude'] = $request->input('latitude') or $this->responseApi(1004, '定位失败');

       // 当前定位信息
       $point = [
           'lng' => $data['longitude'],
           'lat' => $data['latitude']
       ];

       $categoryId = $category->getFirst($category->getTable(), ['name' => $data['district']], ['id']);

       // 判断当前区级是否有收录
       if (!$categoryId['id'])
           $this->responseApi(1004, '此区域暂无厂房');

       // 查出区级内的厂房
       $goodsList = $goods->getAll($goods->baseTb(), ['category_id' => $categoryId['id']], ['id', 'name', 'address', 'lng', 'lat']);

       // 查询是否在圆内
       foreach ($goodsList as $k => $val) {
           if (!empty($val['address'])) {
               // 组合条件
               $circle = [
                   'center' => [
                       'lng'=>$val['lng'],
                       'lat'=>$val['lat']
                   ],
                   'radius' => 10000
               ];

               // 判断是否在圆内
               if ($mapRangeService->is_point_in_circle($point,$circle)) {
                   $restlt[] = [
                       'title' => $val['name'],
                       'address' => $val['address'],
                       'district' => $data['district'],
                       'location' => ['lng' => $val['lng'], 'lat' => $val['lat']]
                   ];
               } else {
                   $restlt = false;
               }
           } else {
               unset($val);
           }

       }

       return $restlt ? $this->responseApi(0, '', $restlt) :  $this->responseApi(1004, '此区域暂无厂房');
   }
}


转载至:yilukuangpao