在多边形PHP中查找点

sha*_*nth 22 php mysql polygon point-in-polygon

我有一个几何数据类型为mysql,polygon的典型问题.

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

[["x":37.628134,  "y":-77.458334],
["x":37.629867,   "y":-77.449021],
["x":37.62324,    "y":-77.445416],
["x":37.622424,   "y":-77.457819]]
Run Code Online (Sandbox Code Playgroud)

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

$location = new vertex($_GET["longitude"], $_GET["latitude"]);
Run Code Online (Sandbox Code Playgroud)

现在我想找到这个顶点(点)是否在多边形内.我怎么能在PHP中这样做?

Tha*_*ama 43

这是我从另一种语言转换为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;
}
Run Code Online (Sandbox Code Playgroud)

附加: 对于更多功能,我建议您使用此处提供的polygon.php类.使用顶点创建类,并使用isInside测试点作为输入调用函数,以使另一个函数解决您的问题.

  • 这个算法非常适用于多边形的X和Y都是正的情况,但是因为问题是关于纬度和经度:如果多边形被格林威治子午线穿过,那么它只是我或者这个算法将会失败,即一个点具有正的经度,如1.000000而下一个负像-1.000000?可能的解决方案:用+180抵消所有经度(这不是向东移动到中国,在那里数学运算更便宜但是让所有经度都是积极的:-)) (11认同)
  • 另外还找到了另一个工作示例:http://www.assemblysys.com/dataServices/php_pointinpolygon.php (5认同)
  • +1 - 并访问http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html以获取有关其工作原理的说明 (4认同)

amh*_*h15 11

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

<?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;
}
?>
Run Code Online (Sandbox Code Playgroud)


Ren*_*cha 6

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

  1. 使用 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>";
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用 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);
Run Code Online (Sandbox Code Playgroud)


Mar*_*ner 5

If your polygons are self-closing, that is to say that it's final vertex is the line between it's last point and it's first point then you need to add a variable and a condition to your loop to deal with the final vertex. You also need to pass the number of vertices as being equal to the number of points.

Here is the accepted answer modified to deal with self-closing polygons:

$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;
}
Run Code Online (Sandbox Code Playgroud)

Thank you! I found this page and it's accepted answer very helpful and I am proud to offer this variation.


小智 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))'));
Run Code Online (Sandbox Code Playgroud)

这是上面有点的多边形 -红色是第一个,蓝色- 最后一个:

泰国多边形点

我使用https://www.gpsvisualizer.com/draw/在地图上的泰国多边形外部和内部绘制了一些点,并制作了屏幕来可视化所有点。

MySQL / PHP 的点差

我给出了 PHP 函数的点作为坐标 + 使用查询与 MySQL 函数进行了比较结果:

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

结果:

  • MySQL 总是给我关于所有点的正确答案。
  • PHP 函数有错误答案
    • 红色- 如果我删除多边形的闭合点
    • 橙色- 不删除与打开相同的最后一个点,并且与 MYSQL 多边形中的相同。
    • 点与 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
Run Code Online (Sandbox Code Playgroud)