From 2400dde8c11c5a545266215f5675ec8171ee7d73 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Wed, 3 May 2023 14:15:37 +0100 Subject: [PATCH 1/3] Add array shapes in GisPolygon Signed-off-by: Kamil Tekiela --- libraries/classes/Gis/GisPolygon.php | 14 ++-- phpstan-baseline.neon | 12 +-- psalm-baseline.xml | 114 --------------------------- test/classes/Gis/GisPolygonTest.php | 20 ++--- 4 files changed, 23 insertions(+), 137 deletions(-) diff --git a/libraries/classes/Gis/GisPolygon.php b/libraries/classes/Gis/GisPolygon.php index c00bc21d0d..66b7269eff 100644 --- a/libraries/classes/Gis/GisPolygon.php +++ b/libraries/classes/Gis/GisPolygon.php @@ -296,7 +296,7 @@ class GisPolygon extends GisGeometry /** * Calculates the area of a closed simple polygon. * - * @param mixed[] $ring array of points forming the ring + * @param non-empty-list $ring array of points forming the ring * * @return float the area of a closed simple polygon */ @@ -330,7 +330,7 @@ class GisPolygon extends GisGeometry * 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 + * @param non-empty-list $ring array of points forming the ring */ public static function isOuterRing(array $ring): bool { @@ -342,8 +342,8 @@ class GisPolygon extends GisGeometry /** * 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 + * @param array{x: float, y: float} $point x, y coordinates of the point + * @param non-empty-list $polygon array of points forming the ring */ public static function isPointInsidePolygon(array $point, array $polygon): bool { @@ -394,9 +394,9 @@ class GisPolygon extends GisGeometry * 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 + * @param non-empty-list $ring array of points forming the ring * - * @return mixed[]|false a point on the surface of the ring + * @return array{x: float, y: float}|false a point on the surface of the ring */ public static function getPointOnSurface(array $ring): array|false { @@ -415,7 +415,7 @@ class GisPolygon extends GisGeometry } } - if (! isset($x0)) { + if (! isset($x0, $x1, $y0, $y1)) { return false; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 222b80dc1f..c60c666727 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4006,7 +4006,7 @@ parameters: path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#1 \\$point of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects array, mixed given\\.$#" + message: "#^Parameter \\#1 \\$point of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects array\\{x\\: float, y\\: float\\}, mixed given\\.$#" count: 1 path: libraries/classes/Gis/GisMultiPolygon.php @@ -4016,12 +4016,12 @@ parameters: path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:getPointOnSurface\\(\\) expects array, mixed given\\.$#" + message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:getPointOnSurface\\(\\) expects non\\-empty\\-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 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isOuterRing\\(\\) expects non\\-empty\\-array\\, mixed given\\.$#" count: 1 path: libraries/classes/Gis/GisMultiPolygon.php @@ -4031,7 +4031,7 @@ parameters: path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#2 \\$polygon of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects array, mixed given\\.$#" + message: "#^Parameter \\#2 \\$polygon of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isPointInsidePolygon\\(\\) expects non\\-empty\\-array\\, mixed given\\.$#" count: 1 path: libraries/classes/Gis/GisMultiPolygon.php @@ -4092,12 +4092,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 - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7c7f49cdfa..fdf2bb55c4 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -6627,110 +6627,17 @@ $pointsArr - - ($y1 - $y0) ** 2 + ($x0 - $x1) ** 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $area - $area - $area $noOfLines $noOfPoints - $p1 - $p1 - $p1 - $p1 - $p1 - $p2 - - - - - $x0 - $x1 - $x2 - $xinters - $y0 - $y1 - $y2 - - float - - $area - $area - $epsilon * ($y1 - $y0) - $epsilon * ($y1 - $y0) - - - - - - - - - $x0 - $x0 - $x0 - $x0 - $x2 - $x2 - $y0 - $y1 - $y1 - $y1 - $y1 - $y1 - $y1 - $y0 - $y1 - $y0 - $y2 - $y2 - - - - ($y1 - $y0) ** 2 @@ -6738,31 +6645,10 @@ && trim((string) $gisData[$index]['POLYGON'][$i][$j]['y']) != '' ? $gisData[$index]['POLYGON'][$i][$j]['y'] : $empty]]> - - $area - $black $fillColor - - $x1 - $x1 - $x1 - $x1 - $y0 - $y0 - $y0 - $y0 - $y0 - $y0 - $y1 - $y1 - $y1 - $y1 - $y1 - $y1 - isset(self::$instance) diff --git a/test/classes/Gis/GisPolygonTest.php b/test/classes/Gis/GisPolygonTest.php index d5823ab822..9924aa2d19 100644 --- a/test/classes/Gis/GisPolygonTest.php +++ b/test/classes/Gis/GisPolygonTest.php @@ -126,8 +126,8 @@ class GisPolygonTest extends GisGeomTestCase /** * test for Area * - * @param mixed[] $ring array of points forming the ring - * @param float $area area of the ring + * @param non-empty-list $ring array of points forming the ring + * @param float $area area of the ring * * @dataProvider providerForTestArea */ @@ -140,7 +140,7 @@ class GisPolygonTest extends GisGeomTestCase /** * data provider for testArea * - * @return array + * @return list, float}> */ public static function providerForTestArea(): array { @@ -164,9 +164,9 @@ class GisPolygonTest extends GisGeomTestCase /** * 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 + * @param array{x: float, y: float} $point x, y coordinates of the point + * @param non-empty-list $polygon array of points forming the ring + * @param bool $isInside output * * @dataProvider providerForTestIsPointInsidePolygon */ @@ -179,7 +179,7 @@ class GisPolygonTest extends GisGeomTestCase /** * data provider for testIsPointInsidePolygon * - * @return array + * @return array, bool}> */ public static function providerForTestIsPointInsidePolygon(): array { @@ -205,7 +205,7 @@ class GisPolygonTest extends GisGeomTestCase /** * test for getPointOnSurface * - * @param mixed[] $ring array of points forming the ring + * @param non-empty-list $ring array of points forming the ring * * @dataProvider providerForTestGetPointOnSurface */ @@ -417,7 +417,7 @@ class GisPolygonTest extends GisGeomTestCase /** * test case for isOuterRing() method * - * @param array> $ring coordinates of the points in a ring + * @param non-empty-list $ring coordinates of the points in a ring * * @dataProvider providerForIsOuterRing */ @@ -430,7 +430,7 @@ class GisPolygonTest extends GisGeomTestCase /** * data provider for testIsOuterRing() test case * - * @return array>}> + * @return array}> */ public static function providerForIsOuterRing(): array { From ea616f6d8aea123e06deaf0cbbc645dc1e758ce1 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Thu, 4 May 2023 10:15:33 +0100 Subject: [PATCH 2/3] Add Gis Value Objects Signed-off-by: Kamil Tekiela --- libraries/classes/Gis/Ds/Point.php | 63 ++++++++ libraries/classes/Gis/Ds/Polygon.php | 132 +++++++++++++++ libraries/classes/Gis/GisMultiPolygon.php | 23 +-- libraries/classes/Gis/GisPolygon.php | 167 ------------------- phpstan-baseline.neon | 38 ++--- psalm-baseline.xml | 36 +++-- test/classes/Gis/Ds/PolygonTest.php | 185 ++++++++++++++++++++++ test/classes/Gis/GisPolygonTest.php | 131 --------------- 8 files changed, 435 insertions(+), 340 deletions(-) create mode 100644 libraries/classes/Gis/Ds/Point.php create mode 100644 libraries/classes/Gis/Ds/Polygon.php create mode 100644 test/classes/Gis/Ds/PolygonTest.php diff --git a/libraries/classes/Gis/Ds/Point.php b/libraries/classes/Gis/Ds/Point.php new file mode 100644 index 0000000000..695821e7e3 --- /dev/null +++ b/libraries/classes/Gis/Ds/Point.php @@ -0,0 +1,63 @@ +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; + } +} diff --git a/libraries/classes/Gis/Ds/Polygon.php b/libraries/classes/Gis/Ds/Polygon.php new file mode 100644 index 0000000000..71d576001e --- /dev/null +++ b/libraries/classes/Gis/Ds/Polygon.php @@ -0,0 +1,132 @@ + */ +final class Polygon extends SplDoublyLinkedList +{ + /** @param non-empty-list $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; + } +} diff --git a/libraries/classes/Gis/GisMultiPolygon.php b/libraries/classes/Gis/GisMultiPolygon.php index a38685ddd7..cf77ea6443 100644 --- a/libraries/classes/Gis/GisMultiPolygon.php +++ b/libraries/classes/Gis/GisMultiPolygon.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\Polygon; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; @@ -348,20 +349,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 +380,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; } diff --git a/libraries/classes/Gis/GisPolygon.php b/libraries/classes/Gis/GisPolygon.php index 66b7269eff..dc74aaf508 100644 --- a/libraries/classes/Gis/GisPolygon.php +++ b/libraries/classes/Gis/GisPolygon.php @@ -15,12 +15,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 +290,6 @@ class GisPolygon extends GisGeometry return $wkt . ')'; } - /** - * Calculates the area of a closed simple polygon. - * - * @param non-empty-list $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 non-empty-list $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 array{x: float, y: float} $point x, y coordinates of the point - * @param non-empty-list $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 non-empty-list $ring array of points forming the ring - * - * @return array{x: float, y: float}|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, $x1, $y0, $y1)) { - 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. * diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c60c666727..7efbbed582 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -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\\{x\\: float, y\\: float\\}, mixed given\\.$#" - count: 1 - path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#1 \\$points of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:filledPolygon\\(\\) expects array\\, array\\ given\\.$#" count: 1 path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:getPointOnSurface\\(\\) expects non\\-empty\\-array\\, mixed given\\.$#" - count: 1 - path: libraries/classes/Gis/GisMultiPolygon.php - - - - message: "#^Parameter \\#1 \\$ring of static method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:isOuterRing\\(\\) expects non\\-empty\\-array\\, mixed given\\.$#" + message: "#^Parameter \\#1 \\$points of static method PhpMyAdmin\\\\Gis\\\\Ds\\\\Polygon\\:\\:fromXYArray\\(\\) expects non\\-empty\\-array\\, 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 non\\-empty\\-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 @@ -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\\, 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 - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index fdf2bb55c4..68ce76df5b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -6367,6 +6367,11 @@ mixed + + + $polygon + + @@ -6508,9 +6513,6 @@ $labelPoint[0] $labelPoint[1] $labelPoint[1] - - - @@ -6532,11 +6534,8 @@ - - - @@ -6553,6 +6552,8 @@ + $polygons[$i] + $polygons[$k] @@ -6562,7 +6563,6 @@ $dataRow $i - $i $innerPoint $j $j @@ -6573,11 +6573,13 @@ $point $ring $ring - $ring $ring1 $ring2 + + isInsidePolygon + @@ -13800,6 +13802,19 @@ $privates + + + + + + + + + + + testGetPointOnSurface + + getMockForAbstractClass @@ -13812,12 +13827,7 @@ - - - - testGetPointOnSurface - diff --git a/test/classes/Gis/Ds/PolygonTest.php b/test/classes/Gis/Ds/PolygonTest.php new file mode 100644 index 0000000000..3d8e458e77 --- /dev/null +++ b/test/classes/Gis/Ds/PolygonTest.php @@ -0,0 +1,185 @@ + [ + '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 + */ + 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 + */ + 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 + */ + public static function providerForIsOuterRing(): array + { + return [ + [ + Polygon::fromXYArray([ + ['x' => 0, 'y' => 0], + ['x' => 0, 'y' => 1], + ['x' => 1, 'y' => 1], + ['x' => 1, 'y' => 0], + ]), + ], + ]; + } +} diff --git a/test/classes/Gis/GisPolygonTest.php b/test/classes/Gis/GisPolygonTest.php index 9924aa2d19..2c2882d9c9 100644 --- a/test/classes/Gis/GisPolygonTest.php +++ b/test/classes/Gis/GisPolygonTest.php @@ -123,114 +123,6 @@ class GisPolygonTest extends GisGeomTestCase ]; } - /** - * test for Area - * - * @param non-empty-list $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 list, 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 array{x: float, y: float} $point x, y coordinates of the point - * @param non-empty-list $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, 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 non-empty-list $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 non-empty-list $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}> - */ - public static function providerForIsOuterRing(): array - { - return [[[['x' => 0, 'y' => 0], ['x' => 0, 'y' => 1], ['x' => 1, 'y' => 1], ['x' => 1, 'y' => 0]]]]; - } } From 15bfd74c75a76b2c858c698fb3c855626e620305 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Thu, 4 May 2023 19:37:17 +0100 Subject: [PATCH 3/3] Move ScaleData to Ds namespace Signed-off-by: Kamil Tekiela --- libraries/classes/Gis/{ => Ds}/ScaleData.php | 2 +- libraries/classes/Gis/GisGeometry.php | 1 + libraries/classes/Gis/GisGeometryCollection.php | 1 + libraries/classes/Gis/GisLineString.php | 1 + libraries/classes/Gis/GisMultiLineString.php | 1 + libraries/classes/Gis/GisMultiPoint.php | 1 + libraries/classes/Gis/GisMultiPolygon.php | 1 + libraries/classes/Gis/GisPoint.php | 1 + libraries/classes/Gis/GisPolygon.php | 1 + libraries/classes/Gis/GisVisualization.php | 1 + test/classes/Gis/GisGeometryCollectionTest.php | 2 +- test/classes/Gis/GisGeometryTest.php | 2 +- test/classes/Gis/GisLineStringTest.php | 2 +- test/classes/Gis/GisMultiLineStringTest.php | 2 +- test/classes/Gis/GisMultiPointTest.php | 2 +- test/classes/Gis/GisMultiPolygonTest.php | 2 +- test/classes/Gis/GisPointTest.php | 2 +- test/classes/Gis/GisPolygonTest.php | 2 +- 18 files changed, 18 insertions(+), 9 deletions(-) rename libraries/classes/Gis/{ => Ds}/ScaleData.php (96%) diff --git a/libraries/classes/Gis/ScaleData.php b/libraries/classes/Gis/Ds/ScaleData.php similarity index 96% rename from libraries/classes/Gis/ScaleData.php rename to libraries/classes/Gis/Ds/ScaleData.php index 5fe9e33e44..9280475acf 100644 --- a/libraries/classes/Gis/ScaleData.php +++ b/libraries/classes/Gis/Ds/ScaleData.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace PhpMyAdmin\Gis; +namespace PhpMyAdmin\Gis\Ds; use function max; use function min; diff --git a/libraries/classes/Gis/GisGeometry.php b/libraries/classes/Gis/GisGeometry.php index ccb4267802..ddc71a98b7 100644 --- a/libraries/classes/Gis/GisGeometry.php +++ b/libraries/classes/Gis/GisGeometry.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisGeometryCollection.php b/libraries/classes/Gis/GisGeometryCollection.php index 84f1870d7c..cfdde03a61 100644 --- a/libraries/classes/Gis/GisGeometryCollection.php +++ b/libraries/classes/Gis/GisGeometryCollection.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; use ErrorException; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisLineString.php b/libraries/classes/Gis/GisLineString.php index 94ad073612..1269478cc1 100644 --- a/libraries/classes/Gis/GisLineString.php +++ b/libraries/classes/Gis/GisLineString.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisMultiLineString.php b/libraries/classes/Gis/GisMultiLineString.php index b18a1cb1dc..3b06a5158f 100644 --- a/libraries/classes/Gis/GisMultiLineString.php +++ b/libraries/classes/Gis/GisMultiLineString.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisMultiPoint.php b/libraries/classes/Gis/GisMultiPoint.php index f2c19f4ae6..3db9157d5e 100644 --- a/libraries/classes/Gis/GisMultiPoint.php +++ b/libraries/classes/Gis/GisMultiPoint.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisMultiPolygon.php b/libraries/classes/Gis/GisMultiPolygon.php index cf77ea6443..0e06e6245c 100644 --- a/libraries/classes/Gis/GisMultiPolygon.php +++ b/libraries/classes/Gis/GisMultiPolygon.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; use PhpMyAdmin\Gis\Ds\Polygon; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisPoint.php b/libraries/classes/Gis/GisPoint.php index 02ed49be4a..00145092cb 100644 --- a/libraries/classes/Gis/GisPoint.php +++ b/libraries/classes/Gis/GisPoint.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisPolygon.php b/libraries/classes/Gis/GisPolygon.php index dc74aaf508..51dc9ad9d9 100644 --- a/libraries/classes/Gis/GisPolygon.php +++ b/libraries/classes/Gis/GisPolygon.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Gis; +use PhpMyAdmin\Gis\Ds\ScaleData; use PhpMyAdmin\Image\ImageWrapper; use TCPDF; diff --git a/libraries/classes/Gis/GisVisualization.php b/libraries/classes/Gis/GisVisualization.php index 30bdcef7ea..ace1bf3430 100644 --- a/libraries/classes/Gis/GisVisualization.php +++ b/libraries/classes/Gis/GisVisualization.php @@ -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; diff --git a/test/classes/Gis/GisGeometryCollectionTest.php b/test/classes/Gis/GisGeometryCollectionTest.php index 1e5a46b63e..02271a8af4 100644 --- a/test/classes/Gis/GisGeometryCollectionTest.php +++ b/test/classes/Gis/GisGeometryCollectionTest.php @@ -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; diff --git a/test/classes/Gis/GisGeometryTest.php b/test/classes/Gis/GisGeometryTest.php index 2cdecec544..ea2073a127 100644 --- a/test/classes/Gis/GisGeometryTest.php +++ b/test/classes/Gis/GisGeometryTest.php @@ -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; diff --git a/test/classes/Gis/GisLineStringTest.php b/test/classes/Gis/GisLineStringTest.php index f2b2b8bcdf..8cb1409823 100644 --- a/test/classes/Gis/GisLineStringTest.php +++ b/test/classes/Gis/GisLineStringTest.php @@ -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; diff --git a/test/classes/Gis/GisMultiLineStringTest.php b/test/classes/Gis/GisMultiLineStringTest.php index 715756bc8f..2a763dfee9 100644 --- a/test/classes/Gis/GisMultiLineStringTest.php +++ b/test/classes/Gis/GisMultiLineStringTest.php @@ -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; diff --git a/test/classes/Gis/GisMultiPointTest.php b/test/classes/Gis/GisMultiPointTest.php index 6bf0a628e8..1033dce89c 100644 --- a/test/classes/Gis/GisMultiPointTest.php +++ b/test/classes/Gis/GisMultiPointTest.php @@ -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; diff --git a/test/classes/Gis/GisMultiPolygonTest.php b/test/classes/Gis/GisMultiPolygonTest.php index e56d420a13..832bea3e27 100644 --- a/test/classes/Gis/GisMultiPolygonTest.php +++ b/test/classes/Gis/GisMultiPolygonTest.php @@ -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; diff --git a/test/classes/Gis/GisPointTest.php b/test/classes/Gis/GisPointTest.php index 91a165457a..589d82d137 100644 --- a/test/classes/Gis/GisPointTest.php +++ b/test/classes/Gis/GisPointTest.php @@ -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; diff --git a/test/classes/Gis/GisPolygonTest.php b/test/classes/Gis/GisPolygonTest.php index 2c2882d9c9..d4a43d76b5 100644 --- a/test/classes/Gis/GisPolygonTest.php +++ b/test/classes/Gis/GisPolygonTest.php @@ -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;