Merge pull request #18384 from kamil-tekiela/GisPolygon-shapes

Gis polygon shapes
This commit is contained in:
Maurício Meneghini Fauth 2023-06-06 11:44:09 -03:00 committed by GitHub
commit 25dd2fb236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 455 additions and 465 deletions

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Gis\Ds;
use function max;
use function min;
final class Point
{
public function __construct(public readonly float $x, public readonly float $y)
{
}
/**
* Determines whether a given point is inside a given polygon.
*/
public function isInsidePolygon(Polygon $polygon): bool
{
$noOfPoints = $polygon->count();
// If first point is repeated at the end remove it
if ($polygon->top() == $polygon->bottom()) {
--$noOfPoints;
}
$counter = 0;
// Use ray casting algorithm
$p1 = $polygon->bottom();
for ($i = 1; $i <= $noOfPoints; $i++) {
$p2 = $polygon[$i % $noOfPoints];
if ($this->y <= min($p1->y, $p2->y)) {
$p1 = $p2;
continue;
}
if ($this->y > max($p1->y, $p2->y)) {
$p1 = $p2;
continue;
}
if ($this->x > max($p1->x, $p2->x)) {
$p1 = $p2;
continue;
}
if ($p1->y != $p2->y) {
$xinters = ($this->y - $p1->y)
* ($p2->x - $p1->x)
/ ($p2->y - $p1->y) + $p1->x;
if ($p1->x == $p2->x || $this->x <= $xinters) {
$counter++;
}
}
$p1 = $p2;
}
return $counter % 2 !== 0;
}
}

View File

@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Gis\Ds;
use SplDoublyLinkedList;
use function count;
use function sqrt;
/** @extends SplDoublyLinkedList<Point> */
final class Polygon extends SplDoublyLinkedList
{
/** @param non-empty-list<array{x: float, y: float}> $points */
public static function fromXYArray(array $points): self
{
$polygon = new self();
foreach ($points as $pointXY) {
$polygon[] = new Point($pointXY['x'], $pointXY['y']);
}
return $polygon;
}
/**
* Calculates the area of a closed simple polygon.
*/
public function area(): float
{
$noOfPoints = $this->count();
// If the last point is same as the first point ignore it
if ($this->top() == $this->bottom()) {
--$noOfPoints;
}
// _n-1
// A = _1_ \ (X(i) * Y(i+1)) - (Y(i) * X(i+1))
// 2 /__
// i=0
$area = 0;
for ($i = 0; $i < $noOfPoints; $i++) {
$j = ($i + 1) % $noOfPoints;
$area += $this[$i]->x * $this[$j]->y;
$area -= $this[$i]->y * $this[$j]->x;
}
$area /= 2.0;
return $area;
}
/**
* Determines whether a set of points represents an outer ring.
* If points are in clockwise orientation then, they form an outer ring.
*/
public function isOuterRing(): bool
{
// If area is negative then it's in clockwise orientation,
// i.e. it's an outer ring
return $this->area() < 0;
}
/**
* Returns a point that is guaranteed to be on the surface of the ring.
* (for simple closed rings)
*
* @return Point|false a point on the surface of the ring
*/
public function getPointOnSurface(): Point|false
{
$points = $this->findTwoConsecutiveDistinctPoints();
if ($points === false) {
return false;
}
$pointPrev = $points[0];
$pointNext = $points[1];
// Find the mid point
$midPoint = new Point(($pointPrev->x + $pointNext->x) / 2, ($pointPrev->y + $pointNext->y) / 2);
// Always keep $epsilon < 1 to go with the reduction logic down here
$epsilon = 0.1;
$denominator = sqrt(($pointNext->y - $pointPrev->y) ** 2 + ($pointPrev->x - $pointNext->x) ** 2);
while (true) {
// Get the points on either sides of the line
// with a distance of epsilon to the mid point
$x = $midPoint->x + ($epsilon * ($pointNext->y - $pointPrev->y)) / $denominator;
$y = $midPoint->y + ($x - $midPoint->x) * ($pointPrev->x - $pointNext->x) / ($pointNext->y - $pointPrev->y);
$pointA = new Point($x, $y);
$x = $midPoint->x + ($epsilon * ($pointNext->y - $pointPrev->y)) / (0 - $denominator);
$y = $midPoint->y + ($x - $midPoint->x) * ($pointPrev->x - $pointNext->x) / ($pointNext->y - $pointPrev->y);
$pointB = new Point($x, $y);
// One of the points should be inside the polygon,
// unless epsilon chosen is too large
if ($pointA->isInsidePolygon($this)) {
return $pointA;
}
if ($pointB->isInsidePolygon($this)) {
return $pointB;
}
//If both are outside the polygon reduce the epsilon and
//recalculate the points(reduce exponentially for faster convergence)
$epsilon **= 2;
if ($epsilon == 0) {
return false;
}
}
}
/** @return array{Point, Point}|false */
private function findTwoConsecutiveDistinctPoints(): array|false
{
for ($i = 0, $nb = count($this) - 1; $i < $nb; $i++) {
$pointPrev = $this->offsetGet($i);
$pointNext = $this->offsetGet($i + 1);
if ($pointPrev->y !== $pointNext->y) {
return [$pointNext, $pointPrev];
}
}
return false;
}
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace PhpMyAdmin\Gis;
namespace PhpMyAdmin\Gis\Ds;
use function max;
use function min;

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -8,6 +8,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use ErrorException;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -7,6 +7,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\Polygon;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;
@ -348,20 +350,22 @@ class GisMultiPolygon extends GisGeometry
*/
public function getShape(array $rowData): string
{
// Determines whether each line ring is an inner ring or an outer ring.
// If it's an inner ring get a point on the surface which can be used to
// correctly classify inner rings to their respective outer rings.
// Buffer polygons for further use
/** @var Polygon[] $polygons */
$polygons = [];
foreach ($rowData['parts'] as $i => $ring) {
$rowData['parts'][$i]['isOuter'] = GisPolygon::isOuterRing($ring['points']);
}
$polygons[$i] = Polygon::fromXYArray($ring['points']);
// Find points on surface for inner rings
foreach ($rowData['parts'] as $i => $ring) {
if ($ring['isOuter']) {
// Determines whether each line ring is an inner ring or an outer ring.
// If it's an inner ring get a point on the surface which can be used to
// correctly classify inner rings to their respective outer rings.
$rowData['parts'][$i]['isOuter'] = $polygons[$i]->isOuterRing();
if ($rowData['parts'][$i]['isOuter']) {
continue;
}
$rowData['parts'][$i]['pointOnSurface'] = GisPolygon::getPointOnSurface($ring['points']);
// Find points on surface for inner rings
$rowData['parts'][$i]['pointOnSurface'] = $polygons[$i]->getPointOnSurface();
}
// Classify inner rings to their respective outer rings.
@ -377,7 +381,7 @@ class GisMultiPolygon extends GisGeometry
// If the pointOnSurface of the inner ring
// is also inside the outer ring
if (! GisPolygon::isPointInsidePolygon($ring1['pointOnSurface'], $ring2['points'])) {
if (! $ring1['pointOnSurface']->isInsidePolygon($polygons[$k])) {
continue;
}

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;
@ -15,12 +16,9 @@ use function array_slice;
use function count;
use function explode;
use function json_encode;
use function max;
use function mb_substr;
use function min;
use function round;
use function sprintf;
use function sqrt;
use function trim;
/**
@ -293,170 +291,6 @@ class GisPolygon extends GisGeometry
return $wkt . ')';
}
/**
* Calculates the area of a closed simple polygon.
*
* @param mixed[] $ring array of points forming the ring
*
* @return float the area of a closed simple polygon
*/
public static function area(array $ring): float
{
$noOfPoints = count($ring);
// If the last point is same as the first point ignore it
$last = count($ring) - 1;
if (($ring[0]['x'] == $ring[$last]['x']) && ($ring[0]['y'] == $ring[$last]['y'])) {
$noOfPoints--;
}
// _n-1
// A = _1_ \ (X(i) * Y(i+1)) - (Y(i) * X(i+1))
// 2 /__
// i=0
$area = 0;
for ($i = 0; $i < $noOfPoints; $i++) {
$j = ($i + 1) % $noOfPoints;
$area += $ring[$i]['x'] * $ring[$j]['y'];
$area -= $ring[$i]['y'] * $ring[$j]['x'];
}
$area /= 2.0;
return $area;
}
/**
* Determines whether a set of points represents an outer ring.
* If points are in clockwise orientation then, they form an outer ring.
*
* @param mixed[] $ring array of points forming the ring
*/
public static function isOuterRing(array $ring): bool
{
// If area is negative then it's in clockwise orientation,
// i.e. it's an outer ring
return self::area($ring) < 0;
}
/**
* Determines whether a given point is inside a given polygon.
*
* @param mixed[] $point x, y coordinates of the point
* @param mixed[] $polygon array of points forming the ring
*/
public static function isPointInsidePolygon(array $point, array $polygon): bool
{
// If first point is repeated at the end remove it
$last = count($polygon) - 1;
if (($polygon[0]['x'] == $polygon[$last]['x']) && ($polygon[0]['y'] == $polygon[$last]['y'])) {
$polygon = array_slice($polygon, 0, $last);
}
$noOfPoints = count($polygon);
$counter = 0;
// Use ray casting algorithm
$p1 = $polygon[0];
for ($i = 1; $i <= $noOfPoints; $i++) {
$p2 = $polygon[$i % $noOfPoints];
if ($point['y'] <= min([$p1['y'], $p2['y']])) {
$p1 = $p2;
continue;
}
if ($point['y'] > max([$p1['y'], $p2['y']])) {
$p1 = $p2;
continue;
}
if ($point['x'] > max([$p1['x'], $p2['x']])) {
$p1 = $p2;
continue;
}
if ($p1['y'] != $p2['y']) {
$xinters = ($point['y'] - $p1['y'])
* ($p2['x'] - $p1['x'])
/ ($p2['y'] - $p1['y']) + $p1['x'];
if ($p1['x'] == $p2['x'] || $point['x'] <= $xinters) {
$counter++;
}
}
$p1 = $p2;
}
return $counter % 2 != 0;
}
/**
* Returns a point that is guaranteed to be on the surface of the ring.
* (for simple closed rings)
*
* @param mixed[] $ring array of points forming the ring
*
* @return mixed[]|false a point on the surface of the ring
*/
public static function getPointOnSurface(array $ring): array|false
{
$x0 = null;
$x1 = null;
$y0 = null;
$y1 = null;
// Find two consecutive distinct points.
for ($i = 0, $nb = count($ring) - 1; $i < $nb; $i++) {
if ($ring[$i]['y'] != $ring[$i + 1]['y']) {
$x0 = $ring[$i]['x'];
$x1 = $ring[$i + 1]['x'];
$y0 = $ring[$i]['y'];
$y1 = $ring[$i + 1]['y'];
break;
}
}
if (! isset($x0)) {
return false;
}
// Find the mid point
$x2 = ($x0 + $x1) / 2;
$y2 = ($y0 + $y1) / 2;
// Always keep $epsilon < 1 to go with the reduction logic down here
$epsilon = 0.1;
$denominator = sqrt(($y1 - $y0) ** 2 + ($x0 - $x1) ** 2);
$pointA = [];
$pointB = [];
while (true) {
// Get the points on either sides of the line
// with a distance of epsilon to the mid point
$pointA['x'] = $x2 + ($epsilon * ($y1 - $y0)) / $denominator;
$pointA['y'] = $y2 + ($pointA['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0);
$pointB['x'] = $x2 + ($epsilon * ($y1 - $y0)) / (0 - $denominator);
$pointB['y'] = $y2 + ($pointB['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0);
// One of the points should be inside the polygon,
// unless epsilon chosen is too large
if (self::isPointInsidePolygon($pointA, $ring)) {
return $pointA;
}
if (self::isPointInsidePolygon($pointB, $ring)) {
return $pointB;
}
//If both are outside the polygon reduce the epsilon and
//recalculate the points(reduce exponentially for faster convergence)
$epsilon **= 2;
if ($epsilon == 0) {
return false;
}
}
}
/**
* Generate coordinate parameters for the GIS data editor from the value of the GIS column.
*

View File

@ -8,6 +8,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Gis;
use PhpMyAdmin\Core;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Util;

View File

@ -3922,7 +3922,7 @@ parameters:
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 8
count: 7
path: libraries/classes/Gis/GisMultiPolygon.php
-
@ -3967,7 +3967,7 @@ parameters:
-
message: "#^Cannot access offset 'points' on mixed\\.$#"
count: 5
count: 3
path: libraries/classes/Gis/GisMultiPolygon.php
-
@ -3997,6 +3997,11 @@ parameters:
-
message: "#^Cannot access offset mixed on mixed\\.$#"
count: 2
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Cannot call method isInsidePolygon\\(\\) on mixed\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
@ -4005,23 +4010,13 @@ parameters:
count: 2
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#1 \\$point of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects array, mixed given\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#1 \\$points of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:filledPolygon\\(\\) expects array\\<int, int\\>, array\\<float\\> given\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:getPointOnSurface\\(\\) expects array, mixed given\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isOuterRing\\(\\) expects array, mixed given\\.$#"
message: "#^Parameter \\#1 \\$points of static method PhpMyAdmin\\\\Gis\\\\Ds\\\\Polygon\\:\\:fromXYArray\\(\\) expects non\\-empty\\-array\\<int, array\\{x\\: float, y\\: float\\}\\>, mixed given\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
@ -4030,11 +4025,6 @@ parameters:
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#2 \\$polygon of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects array, mixed given\\.$#"
count: 1
path: libraries/classes/Gis/GisMultiPolygon.php
-
message: "#^Parameter \\#5 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:string\\(\\) expects int, int\\|false given\\.$#"
count: 1
@ -4092,12 +4082,12 @@ parameters:
-
message: "#^Cannot access offset 'x' on mixed\\.$#"
count: 18
count: 3
path: libraries/classes/Gis/GisPolygon.php
-
message: "#^Cannot access offset 'y' on mixed\\.$#"
count: 22
count: 3
path: libraries/classes/Gis/GisPolygon.php
-
@ -8965,6 +8955,16 @@ parameters:
count: 1
path: test/classes/ErrorHandlerTest.php
-
message: "#^Cannot access offset 'no_of_points' on mixed\\.$#"
count: 2
path: test/classes/Gis/Ds/PolygonTest.php
-
message: "#^Parameter \\#1 \\$points of static method PhpMyAdmin\\\\Gis\\\\Ds\\\\Polygon\\:\\:fromXYArray\\(\\) expects non\\-empty\\-array\\<int, array\\{x\\: float, y\\: float\\}\\>, mixed given\\.$#"
count: 2
path: test/classes/Gis/Ds/PolygonTest.php
-
message: "#^Cannot access offset 'no_of_lines' on mixed\\.$#"
count: 1
@ -8977,7 +8977,7 @@ parameters:
-
message: "#^Cannot access offset 'no_of_points' on mixed\\.$#"
count: 3
count: 1
path: test/classes/Gis/GisPolygonTest.php
-

View File

@ -6363,6 +6363,11 @@
<code>mixed</code>
</UnusedReturnValue>
</file>
<file src="libraries/classes/Gis/Ds/Polygon.php">
<NullArgument>
<code>$polygon</code>
</NullArgument>
</file>
<file src="libraries/classes/Gis/GisGeometry.php">
<MixedOperand>
<code><![CDATA[$scaleData['height']]]></code>
@ -6504,9 +6509,6 @@
<code>$labelPoint[0]</code>
<code>$labelPoint[1]</code>
<code>$labelPoint[1]</code>
<code><![CDATA[$ring1['pointOnSurface']]]></code>
<code><![CDATA[$ring2['points']]]></code>
<code><![CDATA[$ring['points']]]></code>
<code><![CDATA[$ring['points']]]></code>
</MixedArgument>
<MixedArrayAccess>
@ -6528,11 +6530,8 @@
<code><![CDATA[$ring1['isOuter']]]></code>
<code><![CDATA[$ring1['pointOnSurface']]]></code>
<code><![CDATA[$ring2['isOuter']]]></code>
<code><![CDATA[$ring2['points']]]></code>
<code><![CDATA[$ring['inner']]]></code>
<code><![CDATA[$ring['isOuter']]]></code>
<code><![CDATA[$ring['isOuter']]]></code>
<code><![CDATA[$ring['points']]]></code>
<code><![CDATA[$ring['points']]]></code>
<code><![CDATA[$ring['points']]]></code>
<code><![CDATA[$rowData['parts'][$j]]]></code>
@ -6549,6 +6548,8 @@
<code><![CDATA[$rowData['parts'][$k]['inner']]]></code>
</MixedArrayAssignment>
<MixedArrayOffset>
<code>$polygons[$i]</code>
<code>$polygons[$k]</code>
<code><![CDATA[$rowData['parts'][$i]]]></code>
<code><![CDATA[$rowData['parts'][$i]]]></code>
<code><![CDATA[$rowData['parts'][$j]]]></code>
@ -6558,7 +6559,6 @@
<MixedAssignment>
<code>$dataRow</code>
<code>$i</code>
<code>$i</code>
<code>$innerPoint</code>
<code>$j</code>
<code>$j</code>
@ -6569,11 +6569,13 @@
<code>$point</code>
<code>$ring</code>
<code>$ring</code>
<code>$ring</code>
<code>$ring1</code>
<code>$ring2</code>
<code><![CDATA[$rowData['parts'][$k]['inner'][]]]></code>
</MixedAssignment>
<MixedMethodCall>
<code>isInsidePolygon</code>
</MixedMethodCall>
<MixedOperand>
<code><![CDATA[$innerPoint['x']]]></code>
<code><![CDATA[$innerPoint['y']]]></code>
@ -6623,110 +6625,17 @@
<ArgumentTypeCoercion>
<code>$pointsArr</code>
</ArgumentTypeCoercion>
<MixedArgument>
<code>($y1 - $y0) ** 2 + ($x0 - $x1) ** 2</code>
</MixedArgument>
<MixedArrayAccess>
<code><![CDATA[$gisData[$index]['POLYGON']]]></code>
<code><![CDATA[$gisData[$index]['POLYGON'][$i]]]></code>
<code><![CDATA[$gisData[$index]['POLYGON'][$i]['no_of_points']]]></code>
<code><![CDATA[$gisData[$index]['POLYGON']['no_of_lines']]]></code>
<code><![CDATA[$p1['x']]]></code>
<code><![CDATA[$p1['x']]]></code>
<code><![CDATA[$p1['x']]]></code>
<code><![CDATA[$p1['x']]]></code>
<code><![CDATA[$p1['y']]]></code>
<code><![CDATA[$p1['y']]]></code>
<code><![CDATA[$p1['y']]]></code>
<code><![CDATA[$p1['y']]]></code>
<code><![CDATA[$p1['y']]]></code>
<code><![CDATA[$p2['x']]]></code>
<code><![CDATA[$p2['x']]]></code>
<code><![CDATA[$p2['x']]]></code>
<code><![CDATA[$p2['y']]]></code>
<code><![CDATA[$p2['y']]]></code>
<code><![CDATA[$p2['y']]]></code>
<code><![CDATA[$p2['y']]]></code>
<code><![CDATA[$polygon[$last]['x']]]></code>
<code><![CDATA[$polygon[$last]['y']]]></code>
<code><![CDATA[$polygon[0]['x']]]></code>
<code><![CDATA[$polygon[0]['y']]]></code>
<code><![CDATA[$ring[$i + 1]['x']]]></code>
<code><![CDATA[$ring[$i + 1]['y']]]></code>
<code><![CDATA[$ring[$i + 1]['y']]]></code>
<code><![CDATA[$ring[$i]['x']]]></code>
<code><![CDATA[$ring[$i]['x']]]></code>
<code><![CDATA[$ring[$i]['y']]]></code>
<code><![CDATA[$ring[$i]['y']]]></code>
<code><![CDATA[$ring[$i]['y']]]></code>
<code><![CDATA[$ring[$j]['x']]]></code>
<code><![CDATA[$ring[$j]['y']]]></code>
<code><![CDATA[$ring[$last]['x']]]></code>
<code><![CDATA[$ring[$last]['y']]]></code>
<code><![CDATA[$ring[0]['x']]]></code>
<code><![CDATA[$ring[0]['y']]]></code>
</MixedArrayAccess>
<MixedAssignment>
<code>$area</code>
<code>$area</code>
<code>$area</code>
<code>$noOfLines</code>
<code>$noOfPoints</code>
<code>$p1</code>
<code>$p1</code>
<code>$p1</code>
<code>$p1</code>
<code>$p1</code>
<code>$p2</code>
<code><![CDATA[$pointA['x']]]></code>
<code><![CDATA[$pointA['y']]]></code>
<code><![CDATA[$pointB['x']]]></code>
<code><![CDATA[$pointB['y']]]></code>
<code>$x0</code>
<code>$x1</code>
<code>$x2</code>
<code>$xinters</code>
<code>$y0</code>
<code>$y1</code>
<code>$y2</code>
</MixedAssignment>
<MixedInferredReturnType>
<code>float</code>
</MixedInferredReturnType>
<MixedOperand>
<code>$area</code>
<code>$area</code>
<code>$epsilon * ($y1 - $y0)</code>
<code>$epsilon * ($y1 - $y0)</code>
<code><![CDATA[$p2['x']]]></code>
<code><![CDATA[$p2['y']]]></code>
<code><![CDATA[$pointA['x']]]></code>
<code><![CDATA[$pointB['x']]]></code>
<code><![CDATA[$point['y']]]></code>
<code><![CDATA[$ring[$i]['x']]]></code>
<code><![CDATA[$ring[$i]['x'] * $ring[$j]['y']]]></code>
<code><![CDATA[$ring[$i]['y']]]></code>
<code>$x0</code>
<code>$x0</code>
<code>$x0</code>
<code>$x0</code>
<code>$x2</code>
<code>$x2</code>
<code>$y0</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1 - $y0</code>
<code>$y1 - $y0</code>
<code>$y2</code>
<code>$y2</code>
<code><![CDATA[($pointA['x'] - $x2) * ($x0 - $x1)]]></code>
<code><![CDATA[($pointB['x'] - $x2) * ($x0 - $x1)]]></code>
<code><![CDATA[($point['y'] - $p1['y'])
* ($p2['x'] - $p1['x'])]]></code>
<code>($y1 - $y0) ** 2</code>
<code><![CDATA[isset($gisData[$index]['POLYGON'][$i][$j]['x'])
&& trim((string) $gisData[$index]['POLYGON'][$i][$j]['x']) != ''
? $gisData[$index]['POLYGON'][$i][$j]['x'] : $empty]]></code>
@ -6734,31 +6643,10 @@
&& trim((string) $gisData[$index]['POLYGON'][$i][$j]['y']) != ''
? $gisData[$index]['POLYGON'][$i][$j]['y'] : $empty]]></code>
</MixedOperand>
<MixedReturnStatement>
<code>$area</code>
</MixedReturnStatement>
<PossiblyFalseArgument>
<code>$black</code>
<code>$fillColor</code>
</PossiblyFalseArgument>
<PossiblyNullOperand>
<code>$x1</code>
<code>$x1</code>
<code>$x1</code>
<code>$x1</code>
<code>$y0</code>
<code>$y0</code>
<code>$y0</code>
<code>$y0</code>
<code>$y0</code>
<code>$y0</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
<code>$y1</code>
</PossiblyNullOperand>
<RedundantPropertyInitializationCheck>
<code>isset(self::$instance)</code>
</RedundantPropertyInitializationCheck>
@ -13910,6 +13798,19 @@
<code>$privates</code>
</PossiblyUnusedProperty>
</file>
<file src="test/classes/Gis/Ds/PolygonTest.php">
<MixedArgument>
<code><![CDATA[$temp['POLYGON'][0]]]></code>
<code><![CDATA[$temp['POLYGON'][1]]]></code>
</MixedArgument>
<MixedArrayAccess>
<code><![CDATA[$temp['POLYGON'][0]['no_of_points']]]></code>
<code><![CDATA[$temp['POLYGON'][1]['no_of_points']]]></code>
</MixedArrayAccess>
<PossiblyInvalidArgument>
<code>testGetPointOnSurface</code>
</PossiblyInvalidArgument>
</file>
<file src="test/classes/Gis/GisGeometryTest.php">
<DeprecatedMethod>
<code>getMockForAbstractClass</code>
@ -13922,12 +13823,7 @@
<MixedArrayAccess>
<code><![CDATA[$temp1[0]['POLYGON'][1][3]]]></code>
<code><![CDATA[$temp1[0]['POLYGON'][1][3]['y']]]></code>
<code><![CDATA[$temp['POLYGON'][0]['no_of_points']]]></code>
<code><![CDATA[$temp['POLYGON'][1]['no_of_points']]]></code>
</MixedArrayAccess>
<PossiblyInvalidArgument>
<code>testGetPointOnSurface</code>
</PossiblyInvalidArgument>
</file>
<file src="test/classes/Gis/GisVisualizationTest.php">
<MixedAssignment>

View File

@ -0,0 +1,185 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis\Ds;
use PhpMyAdmin\Gis\Ds\Point;
use PhpMyAdmin\Gis\Ds\Polygon;
use PhpMyAdmin\Tests\AbstractTestCase;
/** @covers \PhpMyAdmin\Ds\Polygon */
class PolygonTest extends AbstractTestCase
{
/**
* Provide some common data to data providers
*
* @return mixed[][]
*/
private static function getData(): array
{
return [
'POLYGON' => [
'no_of_lines' => 2,
0 => [
'no_of_points' => 5,
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 20],
2 => ['x' => 15, 'y' => 40],
3 => ['x' => 45, 'y' => 45],
4 => ['x' => 35, 'y' => 10],
],
1 => [
'no_of_points' => 4,
0 => ['x' => 20, 'y' => 30],
1 => ['x' => 35, 'y' => 32],
2 => ['x' => 30, 'y' => 20],
3 => ['x' => 20, 'y' => 30],
],
],
];
}
/**
* test for Area
*
* @dataProvider providerForTestArea
*/
public function testArea(Polygon $ring, float $area): void
{
$this->assertEquals($area, $ring->area());
}
/**
* data provider for testArea
*
* @return list<array{Polygon, float}>
*/
public static function providerForTestArea(): array
{
return [
[
Polygon::fromXYArray([
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 15, 'y' => 40],
]),
-375.00,
],
// first point of the ring repeated as the last point
[
Polygon::fromXYArray([
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 15, 'y' => 40],
3 => ['x' => 35, 'y' => 10],
]),
-375.00,
],
// anticlockwise gives positive area
[
Polygon::fromXYArray([
0 => ['x' => 15, 'y' => 40],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 35, 'y' => 10],
]),
375.00,
],
];
}
/**
* test for isPointInsidePolygon
*
* @dataProvider providerForTestIsPointInsidePolygon
*/
public function testIsPointInsidePolygon(Point $point, Polygon $polygon, bool $isInside): void
{
$this->assertEquals($isInside, $point->isInsidePolygon($polygon));
}
/**
* data provider for testIsPointInsidePolygon
*
* @return array<array{Point, Polygon, bool}>
*/
public static function providerForTestIsPointInsidePolygon(): array
{
$ring = Polygon::fromXYArray([
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 15, 'y' => 40],
3 => ['x' => 35, 'y' => 10],
]);
return [
// point inside the ring
[new Point(20, 15), $ring, true],
// point on an edge of the ring
[new Point(20, 10), $ring, false],
// point on a vertex of the ring
[new Point(10, 10), $ring, false],
// point outside the ring
[new Point(5, 10), $ring, false],
];
}
/**
* test for getPointOnSurface
*
* @param Polygon $ring array of points forming the ring
*
* @dataProvider providerForTestGetPointOnSurface
*/
public function testGetPointOnSurface(Polygon $ring): void
{
$point = $ring->getPointOnSurface();
$this->assertInstanceOf(Point::class, $point);
$this->assertTrue($point->isInsidePolygon($ring));
}
/**
* data provider for testGetPointOnSurface
*
* @return list{list{mixed}, list{mixed}}
*/
public static function providerForTestGetPointOnSurface(): array
{
$temp = self::getData();
unset($temp['POLYGON'][0]['no_of_points']);
unset($temp['POLYGON'][1]['no_of_points']);
return [[Polygon::fromXYArray($temp['POLYGON'][0])], [Polygon::fromXYArray($temp['POLYGON'][1])]];
}
/**
* test case for isOuterRing() method
*
* @param Polygon $ring coordinates of the points in a ring
*
* @dataProvider providerForIsOuterRing
*/
public function testIsOuterRing(Polygon $ring): void
{
$this->assertTrue($ring->isOuterRing());
}
/**
* data provider for testIsOuterRing() test case
*
* @return array<array{Polygon}>
*/
public static function providerForIsOuterRing(): array
{
return [
[
Polygon::fromXYArray([
['x' => 0, 'y' => 0],
['x' => 0, 'y' => 1],
['x' => 1, 'y' => 1],
['x' => 1, 'y' => 0],
]),
],
];
}
}

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisGeometryCollection;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisGeometry;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Tests\AbstractTestCase;
use PHPUnit\Framework\MockObject\MockObject;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisLineString;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisMultiLineString;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisMultiPoint;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisMultiPolygon;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisPoint;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Gis;
use PhpMyAdmin\Gis\Ds\ScaleData;
use PhpMyAdmin\Gis\GisPolygon;
use PhpMyAdmin\Gis\ScaleData;
use PhpMyAdmin\Image\ImageWrapper;
use TCPDF;
@ -123,114 +123,6 @@ class GisPolygonTest extends GisGeomTestCase
];
}
/**
* test for Area
*
* @param mixed[] $ring array of points forming the ring
* @param float $area area of the ring
*
* @dataProvider providerForTestArea
*/
public function testArea(array $ring, float $area): void
{
$object = GisPolygon::singleton();
$this->assertEquals($area, $object->area($ring));
}
/**
* data provider for testArea
*
* @return array<array{mixed[], float}>
*/
public static function providerForTestArea(): array
{
return [
[[0 => ['x' => 35, 'y' => 10], 1 => ['x' => 10, 'y' => 10], 2 => ['x' => 15, 'y' => 40]], -375.00],
// first point of the ring repeated as the last point
[
[
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 15, 'y' => 40],
3 => ['x' => 35, 'y' => 10],
],
-375.00,
],
// anticlockwise gives positive area
[[0 => ['x' => 15, 'y' => 40], 1 => ['x' => 10, 'y' => 10], 2 => ['x' => 35, 'y' => 10]], 375.00],
];
}
/**
* test for isPointInsidePolygon
*
* @param mixed[] $point x, y coordinates of the point
* @param mixed[] $polygon array of points forming the ring
* @param bool $isInside output
*
* @dataProvider providerForTestIsPointInsidePolygon
*/
public function testIsPointInsidePolygon(array $point, array $polygon, bool $isInside): void
{
$object = GisPolygon::singleton();
$this->assertEquals($isInside, $object->isPointInsidePolygon($point, $polygon));
}
/**
* data provider for testIsPointInsidePolygon
*
* @return array<array{mixed[], mixed[], bool}>
*/
public static function providerForTestIsPointInsidePolygon(): array
{
$ring = [
0 => ['x' => 35, 'y' => 10],
1 => ['x' => 10, 'y' => 10],
2 => ['x' => 15, 'y' => 40],
3 => ['x' => 35, 'y' => 10],
];
return [
// point inside the ring
[['x' => 20, 'y' => 15], $ring, true],
// point on an edge of the ring
[['x' => 20, 'y' => 10], $ring, false],
// point on a vertex of the ring
[['x' => 10, 'y' => 10], $ring, false],
// point outside the ring
[['x' => 5, 'y' => 10], $ring, false],
];
}
/**
* test for getPointOnSurface
*
* @param mixed[] $ring array of points forming the ring
*
* @dataProvider providerForTestGetPointOnSurface
*/
public function testGetPointOnSurface(array $ring): void
{
$object = GisPolygon::singleton();
$point = $object->getPointOnSurface($ring);
$this->assertIsArray($point);
$this->assertTrue($object->isPointInsidePolygon($point, $ring));
}
/**
* data provider for testGetPointOnSurface
*
* @return list{list{mixed}, list{mixed}}
*/
public static function providerForTestGetPointOnSurface(): array
{
$temp = self::getData();
unset($temp['POLYGON'][0]['no_of_points']);
unset($temp['POLYGON'][1]['no_of_points']);
return [[$temp['POLYGON'][0]], [$temp['POLYGON'][1]]];
}
/**
* test scaleRow method
*
@ -413,27 +305,4 @@ class GisPolygonTest extends GisGeomTestCase
],
];
}
/**
* test case for isOuterRing() method
*
* @param array<array<string, int>> $ring coordinates of the points in a ring
*
* @dataProvider providerForIsOuterRing
*/
public function testIsOuterRing(array $ring): void
{
$object = GisPolygon::singleton();
$this->assertTrue($object->isOuterRing($ring));
}
/**
* data provider for testIsOuterRing() test case
*
* @return array<array{array<array<string, int>>}>
*/
public static function providerForIsOuterRing(): array
{
return [[[['x' => 0, 'y' => 0], ['x' => 0, 'y' => 1], ['x' => 1, 'y' => 1], ['x' => 1, 'y' => 0]]]];
}
}