From ea616f6d8aea123e06deaf0cbbc645dc1e758ce1 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Thu, 4 May 2023 10:15:33 +0100 Subject: [PATCH] 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]]]]; - } }