在多边形 PHP 中查找点

Posted

技术标签:

【中文标题】在多边形 PHP 中查找点【英文标题】:Find Point in polygon PHP 【发布时间】:2011-07-01 04:55:07 【问题描述】:

我有一个关于 mysql 的几何数据类型多边形的典型问题。

我有多边形数据,以纬度和经度数组的形式,例如:

[["x":37.628134,  "y":-77.458334],
["x":37.629867,   "y":-77.449021],
["x":37.62324,    "y":-77.445416],
["x":37.622424,   "y":-77.457819]]

我有一个点(顶点),其坐标为经纬度,例如:

$location = new vertex($_GET["longitude"], $_GET["latitude"]);

现在我想找出这个顶点(点)是否在多边形内。 我如何在 php 中做到这一点?

【问题讨论】:

你的多边形保证是凸的吗? 哦,酷,你在做什么? 不知道是凸的还是凹的,基本上是用一组顶点组成一个多边形,代表一个特定地理位置的经纬度。我想找出一个几何点(顶点)是否在多边形内。 在***.com/questions/217578/… 的回答中有一个很好的解释如何做到这一点,代码可以很容易地移植到 PHP 【参考方案1】:

这是我从另一种语言转换成 PHP 的函数:

$vertices_x = array(37.628134, 37.629867, 37.62324, 37.622424);    // x-coordinates of the vertices of the polygon
$vertices_y = array(-77.458334,-77.449021,-77.445416,-77.457819); // y-coordinates of the vertices of the polygon
$points_polygon = count($vertices_x) - 1;  // number vertices - zero-based array
$longitude_x = $_GET["longitude"];  // x-coordinate of the point to test
$latitude_y = $_GET["latitude"];    // y-coordinate of the point to test

if (is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y))
  echo "Is in polygon!";

else echo "Is not in polygon";


function is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y)

  $i = $j = $c = 0;
  for ($i = 0, $j = $points_polygon ; $i < $points_polygon; $j = $i++) 
    if ( (($vertices_y[$i]  >  $latitude_y != ($vertices_y[$j] > $latitude_y)) &&
     ($longitude_x < ($vertices_x[$j] - $vertices_x[$i]) * ($latitude_y - $vertices_y[$i]) / ($vertices_y[$j] - $vertices_y[$i]) + $vertices_x[$i]) ) )
       $c = !$c;
  
  return $c;

补充: 对于更多功能,我建议您使用 polygon.php 类available here。 使用您的顶点创建类并使用您的测试点作为输入调用函数isInside,以让另一个函数解决您的问题。

【讨论】:

+1 - 并访问 ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html 了解其工作原理 感谢这些链接帮助了我。 还在这里找到了另一个工作示例:assemblysys.com/dataServices/php_pointinpolygon.php 这个算法非常适用于多边形的 Xs 和 Ys 都是正数的情况,但是因为问题是关于纬度和经度:是只有我一个人,或者如果多边形被格林威治子午线穿过,这个算法会严重失败,即一个点的经度为正,如 1.000000,而下一个点的经度为负,如 -1.000000?可能的解决方案:用 +180 偏移所有经度(这不是向东移动到数学运算更便宜的中国,而是使所有经度为正 :-)) @Ogre_BGR 是对的,在这里发布了可靠的证明版本 -> ***.com/a/18190354/1407478【参考方案2】:

上面的流行答案包含拼写错误。在其他地方,此代码已被清理。更正后的代码如下:

<?php
/**
  From: http://www.daniweb.com/web-development/php/threads/366489
  Also see http://en.wikipedia.org/wiki/Point_in_polygon
*/
$vertices_x = array(37.628134, 37.629867, 37.62324, 37.622424); // x-coordinates of the vertices of the polygon
$vertices_y = array(-77.458334,-77.449021,-77.445416,-77.457819); // y-coordinates of the vertices of the polygon
$points_polygon = count($vertices_x); // number vertices
$longitude_x = $_GET["longitude"]; // x-coordinate of the point to test
$latitude_y = $_GET["latitude"]; // y-coordinate of the point to test
//// For testing.  This point lies inside the test polygon.
// $longitude_x = 37.62850;
// $latitude_y = -77.4499;

if (is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y))
  echo "Is in polygon!";

else echo "Is not in polygon";


function is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y)

  $i = $j = $c = 0;
  for ($i = 0, $j = $points_polygon-1 ; $i < $points_polygon; $j = $i++) 
    if ( (($vertices_y[$i] > $latitude_y != ($vertices_y[$j] > $latitude_y)) &&
    ($longitude_x < ($vertices_x[$j] - $vertices_x[$i]) * ($latitude_y - $vertices_y[$i]) / ($vertices_y[$j] - $vertices_y[$i]) + $vertices_x[$i]) ) ) 
        $c = !$c;
  
  return $c;

?>

【讨论】:

这个函数效果很好,但是如果测试点等于其中一个顶点就不行了。这是一个要添加的简单测试用例。此外,您必须注意您的多边形不要越过国际日期变更线。如果需要这样做,则必须将多边形分解为两侧的两个多边形。 您纠正了哪些具体的错别字?据我所知,您所做的只是将-1is_in_polygon() 函数之外移到内联。 最初提供的代码没有正确解析。从那以后它似乎已经修复(在我的回答后编辑)。见这里:***.com/posts/5065219/revisions【参考方案3】:

上面的解决方案没有像我预期的那样工作,而不是使用上面的解决方案,你可以更喜欢下面的解决方案

    使用 PHP

    function pointInPolygon($point, $polygon, $pointOnVertex = true) 
        $this->pointOnVertex = $pointOnVertex;
    
        // Transform string coordinates into arrays with x and y values
        $point = $this->pointStringToCoordinates($point);
        $vertices = array(); 
        foreach ($polygon as $vertex) 
            $vertices[] = $this->pointStringToCoordinates($vertex); 
        
    
        // Check if the lat lng sits exactly on a vertex
        if ($this->pointOnVertex == true and $this->pointOnVertex($point, $vertices) == true) 
            return "vertex";
        
    
        // Check if the lat lng is inside the polygon or on the boundary
        $intersections = 0; 
        $vertices_count = count($vertices);
    
        for ($i=1; $i < $vertices_count; $i++) 
            $vertex1 = $vertices[$i-1]; 
            $vertex2 = $vertices[$i];
            if ($vertex1['y'] == $vertex2['y'] and $vertex1['y'] == $point['y'] and $point['x'] > min($vertex1['x'], $vertex2['x']) and $point['x'] < max($vertex1['x'], $vertex2['x']))  // Check if point is on an horizontal polygon boundary
                return "boundary";
            
            if ($point['y'] > min($vertex1['y'], $vertex2['y']) and $point['y'] <= max($vertex1['y'], $vertex2['y']) and $point['x'] <= max($vertex1['x'], $vertex2['x']) and $vertex1['y'] != $vertex2['y'])  
                $xinters = ($point['y'] - $vertex1['y']) * ($vertex2['x'] - $vertex1['x']) / ($vertex2['y'] - $vertex1['y']) + $vertex1['x']; 
                if ($xinters == $point['x'])  // Check if lat lng is on the polygon boundary (other than horizontal)
                    return "boundary";
                
                if ($vertex1['x'] == $vertex2['x'] || $point['x'] <= $xinters) 
                    $intersections++; 
                
             
         
        // If the number of edges we passed through is odd, then it's in the polygon. 
        if ($intersections % 2 != 0) 
            return "inside";
         else 
            return "outside";
        
    
    
    function pointOnVertex($point, $vertices) 
      foreach($vertices as $vertex) 
          if ($point == $vertex) 
              return true;
          
      
    
    
    
    function pointStringToCoordinates($pointString) 
        $coordinates = explode(" ", $pointString);
        return array("x" => $coordinates[0], "y" => $coordinates[1]);
    
    // Function to check lat lng
    function check()
        $points = array("22.367582 70.711816", "21.43567582 72.5811816","22.367582117085913 70.71181669186944","22.275334996986643 70.88614147123701","22.36934302329968 70.77627818998701"); // Array of latlng which you want to find
        $polygon = array(
            "22.367582117085913 70.71181669186944",
            "22.225161442616514 70.65582486840117",
            "22.20736264867434 70.83229276390898",
            "22.18701840565626 70.9867880031668",
            "22.22452581029355 71.0918447658621",
            "22.382709129816103 70.98884793969023",
            "22.40112042636022 70.94078275414336",
            "22.411912121843205 70.7849142238699",
            "22.367582117085913 70.71181669186944"
        );
        // The last lat lng must be the same as the first one's, to "close the loop"
        foreach($points as $key => $point) 
            echo "(Lat Lng) " . ($key+1) . " ($point): " . $this->pointInPolygon($point, $polygon) . "<br>";
        
    
    

    使用 MySql

CREATE TABLE `TestPoly` (
   `id` int(11) NOT NULL,
   `name` varchar(255) NOT NULL,
   `pol` polygon NOT NULL
 )

SET @g = 'POLYGON((22.367582117085913 70.71181669186944, 22.225161442616514 70.65582486840117, 22.20736264867434 70.83229276390898, 22.18701840565626 70.9867880031668, 22.22452581029355 71.0918447658621, 22.382709129816103 70.98884793969023, 22.40112042636022 70.94078275414336, 22.411912121843205 70.7849142238699, 22.367582117085913 70.71181669186944))';
INSERT INTO TestPoly (pol) VALUES (ST_GeomFromText(@g))

set @p = GeomFromText('POINT(22.4053386588057 70.86240663480157)');
select * FROM TestPoly where ST_Contains(pol, @p);

【讨论】:

我可以知道不工作的解决方案编号吗?? 这里是详细信息:***.com/questions/61302366/… 您可以使用您的输入检查以下 URL。我同意谷歌地图显示正确的多边形。对于解决方案,您必须使用我提供的 MySQL 解决方案。它会完全正常工作。 keene.edu/campus/maps/tool 我也尝试过 MySql 解决方案,但它不起作用。您也可以从头开始检查。【参考方案4】:

如果你的多边形是自闭合的,也就是说它的最终顶点是它的最后一个点和它的第一个点之间的线,那么你需要在你的循环中添加一个变量和一个条件来处理最终的顶点。您还需要将顶点数作为点数传递。

这是为处理自闭合多边形而修改的公认答案:

$vertices_x = array(37.628134, 37.629867, 37.62324, 37.622424);    // x-coordinates of the vertices of the polygon
$vertices_y = array(-77.458334,-77.449021,-77.445416,-77.457819); // y-coordinates of the vertices of the polygon
$points_polygon = count($vertices_x);  // number vertices = number of points in a self-closing polygon
$longitude_x = $_GET["longitude"];  // x-coordinate of the point to test
$latitude_y = $_GET["latitude"];    // y-coordinate of the point to test

if (is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y))
  echo "Is in polygon!";

else echo "Is not in polygon";


function is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y)

  $i = $j = $c = $point = 0;
  for ($i = 0, $j = $points_polygon ; $i < $points_polygon; $j = $i++) 
    $point = $i;
    if( $point == $points_polygon )
      $point = 0;
    if ( (($vertices_y[$point]  >  $latitude_y != ($vertices_y[$j] > $latitude_y)) &&
     ($longitude_x < ($vertices_x[$j] - $vertices_x[$point]) * ($latitude_y - $vertices_y[$point]) / ($vertices_y[$j] - $vertices_y[$point]) + $vertices_x[$point]) ) )
       $c = !$c;
  
  return $c;

谢谢!我发现这个页面和它被接受的答案非常有帮助,我很自豪能提供这种变化。

【讨论】:

您好,请检查一下 - ***.com/questions/61302366/…【参考方案5】:

我将泰国多边形放入 MySQL。并将接受的答案功能与 MySQL 8 中的内置功能进行了比较。

CREATE TABLE `polygons` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `polygon` POLYGON NOT NULL,
    `country` VARCHAR(50) NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    SPATIAL INDEX `polygon` (`polygon`)
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
AUTO_INCREMENT=652
;

INSERT INTO `polygons` (`country`, `polygon`) VALUES ('Thailand', ST_GEOMFROMTEXT('POLYGON((102.1728516 6.1842462,101.6894531 5.7253114,101.1401367 5.6815837,101.1181641 6.2497765,100.1074219 6.4899833,96.3281250 6.4244835,96.1083984 9.8822755,98.7670898 10.1419317,99.5800781 11.8243415,98.2177734 15.1569737,98.9868164 16.3201395,97.4267578 18.4587681,98.1079102 19.7253422,99.0087891 19.7460242,100.2612305 20.2828087,100.4809570 19.4769502,101.2060547 19.4147924,100.8544922 17.4135461,102.0849609 17.9996316,102.8320313 17.7696122,103.3593750 18.3545255,104.7875977 17.4554726,104.6337891 16.4676947,105.5126953 15.6018749,105.2270508 14.3069695,102.9858398 14.2643831,102.3486328 13.5819209,103.0297852 11.0059045,103.6669922 8.5592939,102.1728516 6.1842462))'));

这是上面带点的多边形 - RED是第一个,BLUE - 最后一个:

我使用https://www.gpsvisualizer.com/draw/在地图上的Thailand Polygon内外绘制了一些点,并制作了屏幕以可视化所有点。

我将点作为 PHP 函数的坐标 + 使用查询与 MySQL 函数比较结果:

SELECT TRUE FROM `polygons` WHERE `polygons`.`country` = 'Thailand' AND ST_CONTAINS(`polygons`.`polygon`, POINT($long, $lat));

结果:

MySQL 总是给我关于所有点的正确答案。 PHP 函数有错误答案 RED - 如果我删除多边形的结束点 ORANGE - 不删除与开头相同的最后一个点,与 MYSQL 多边形中的相同。 WHITE 点与 PHP / MySQL 的结果相同并且是正确答案

我试图改变多边形,但是 php 函数总是对那些点出错,这意味着某处有我找不到的错误。

更新 1

找到解决方案 assemblysys.com/php-point-in-polygon-algorithm - 这个算法和 MySQL 算法一样工作!

更新 2

比较 PHP 与 MySQL 的速度(我认为 PHP 应该更快),但没有。比较了 47k 点。

18-06-2020 21:34:45 - PHP Speed Check Start
18-06-2020 21:34:51 - FIN! PHP Check. NOT = 41085 / IN = 5512
18-06-2020 21:34:51 - MYSQL Speed Check Start
18-06-2020 21:34:58 - FIN! MYSQL Check. NOT = 41085 / IN = 5512

【讨论】:

创立的解决方案 assemblysys.com/php-point-in-polygon-algorithm - 这个算法和 Mysql 算法一样工作!【参考方案6】:

这是一个可能的算法。

    以您的兴趣点为中心定义一个新坐标系。 在您的新坐标系中,将所有多边形顶点转换为极坐标。 遍历多边形,跟踪角度的净变化 Δθ。每次角度变化时始终使用尽可能小的值。 如果在您遍历多边形后,总的 Δθ 为 0,那么您就在多边形之外。另一方面,如果它是 ±2π,那么你就在里面。 如果偶然 Δθ>2π 或 Δθ

编写代码留作练习。 :)

【讨论】:

抱歉,我无法理解这个场景……它看起来很复杂。任何示例代码或链接? 某处可能有一个复杂的数学函数库。也许其他人知道它在哪里(我不知道)。我的回答只有在您要自己编写代码时才有用。 :)【参考方案7】:

我在 php codeigniter 中创建了代码,在我的控制器中我创建了两个函数,如下所示

public function checkLatLng()
    $vertices_y = array(22.774,22.174,22.466,22.666,22.966,22.321);    // x-coordinates of the vertices of the polygon (LATITUDES)
    $vertices_x = array(70.190,70.090,77.118,77.618,77.418,77.757); // y-coordinates of the vertices of the polygon (LONGITUDES)
    $points_polygon = count($vertices_x)-1; 
    $longitude_x = $this->input->get("longitude");  // Your Longitude
    $latitude_y = $this->input->get("latitude");    // Your Latitude
    if ($this->is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y))
        echo "Is in polygon!";
    
    else
        echo "Is not in polygon";

下面是另一个检查 lat-lng 的函数

public function is_in_polygon($points_polygon, $vertices_x, $vertices_y, $longitude_x, $latitude_y)
    $i = $j = $c = $point = 0;
    for ($i = 0, $j = $points_polygon ; $i < $points_polygon; $j = $i++) 
        $point = $i;
        if( $point == $points_polygon )
            $point = 0;
        if ( (($vertices_y[$point]  >  $latitude_y != ($vertices_y[$j] > $latitude_y)) && ($longitude_x < ($vertices_x[$j] - $vertices_x[$point]) * ($latitude_y - $vertices_y[$point]) / ($vertices_y[$j] - $vertices_y[$point]) + $vertices_x[$point]) ) )
            $c = !$c;
    
    return $c;

为了您的测试目的,我通过了以下内容

纬度=22.808059

经度=77.522014

我的多边形

【讨论】:

【参考方案8】:

更新的代码让我更容易使用谷歌地图: 它接受像这样的数组:

Array
(
    [0] => stdClass Object
        (
            [lat] => 43.685927
            [lng] => -79.745829
        )

    [1] => stdClass Object
        (
            [lat] => 43.686004
            [lng] => -79.745954
        )

    [2] => stdClass Object
        (
            [lat] => 43.686429
            [lng] => -79.746642
        )

所以它会更容易与谷歌地图一起使用:

function is_in_polygon2($longitude_x, $latitude_y,$polygon)

  $i = $j = $c = 0;
  $points_polygon = count($polygon)-1;
  for ($i = 0, $j = $points_polygon ; $i < $points_polygon; $j = $i++) 
    if ( (($polygon[$i]->lat  >  $latitude_y != ($polygon[$j]->lat > $latitude_y)) &&
     ($longitude_x < ($polygon[$j]->lng - $polygon[$i]->lng) * ($latitude_y - $polygon[$i]->lat) / ($polygon[$j]->lat - $polygon[$i]->lat) + $polygon[$i]->lng) ) )
       $c = !$c;
  
  return $c;

【讨论】:

以上是关于在多边形 PHP 中查找点的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Elasticsearch 中查找包含给定点的多边形

使用shapefile在Python中查找点的封闭多边形[重复]

在给定多边形坐标的情况下查找点属于哪个多边形的算法

在多边形的正常空间内的 pcl 点云中查找点

查找给定多边形 w.r.t 的两个“边界”顶点。已知(光源)点

地理围栏:使用 oracle 空间查找多边形内的要素数量(点/线/多边形)