From c5343eeed9944781a3b98d844c086ccdfb3f1b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Wed, 1 Mar 2023 02:16:17 +0100 Subject: [PATCH] Fix gis extent calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Krög --- libraries/classes/Gis/GisGeometry.php | 11 ++++ .../classes/Gis/GisGeometryCollection.php | 2 +- libraries/classes/Gis/GisLineString.php | 3 +- libraries/classes/Gis/GisMultiLineString.php | 3 +- libraries/classes/Gis/GisMultiPoint.php | 3 +- libraries/classes/Gis/GisMultiPolygon.php | 3 +- libraries/classes/Gis/GisPoint.php | 3 +- libraries/classes/Gis/GisPolygon.php | 3 +- libraries/classes/Gis/GisVisualization.php | 23 ++++--- phpstan-baseline.neon | 64 +++++++++---------- psalm-baseline.xml | 3 +- test/classes/Gis/GisGeometryTest.php | 2 +- test/classes/Gis/GisVisualizationTest.php | 26 ++++++++ 13 files changed, 98 insertions(+), 51 deletions(-) diff --git a/libraries/classes/Gis/GisGeometry.php b/libraries/classes/Gis/GisGeometry.php index deef0cfb2254..cf1189f798ef 100644 --- a/libraries/classes/Gis/GisGeometry.php +++ b/libraries/classes/Gis/GisGeometry.php @@ -21,11 +21,20 @@ use function str_replace; use function trim; +use const INF; + /** * Base class for all GIS data type classes. */ abstract class GisGeometry { + public const EMPTY_EXTENT = [ + 'minX' => +INF, + 'minY' => +INF, + 'maxX' => -INF, + 'maxY' => -INF, + ]; + /** * Prepares and returns the code related to a row in the GIS dataset as SVG. * @@ -143,8 +152,10 @@ protected function getBoundsForOl(int $srid, array $scale_data) * * @param string $point_set point set * @param array $min_max existing min, max values + * @psalm-param array{minX:float,minY:float,maxX:float,maxY:float} $min_max * * * @return array the updated min, max values + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ protected function setMinMax($point_set, array $min_max) { diff --git a/libraries/classes/Gis/GisGeometryCollection.php b/libraries/classes/Gis/GisGeometryCollection.php index e8b2ad59cd12..1d8707fa96c8 100644 --- a/libraries/classes/Gis/GisGeometryCollection.php +++ b/libraries/classes/Gis/GisGeometryCollection.php @@ -54,7 +54,7 @@ public static function singleton() */ public function scaleRow($spatial) { - $min_max = []; + $min_max = GisGeometry::EMPTY_EXTENT; // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' $goem_col = mb_substr($spatial, 19, -1); diff --git a/libraries/classes/Gis/GisLineString.php b/libraries/classes/Gis/GisLineString.php index 1993b060a295..c3477d8ce97d 100644 --- a/libraries/classes/Gis/GisLineString.php +++ b/libraries/classes/Gis/GisLineString.php @@ -52,13 +52,14 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { // Trim to remove leading 'LINESTRING(' and trailing ')' $linestring = mb_substr($spatial, 11, -1); - return $this->setMinMax($linestring, []); + return $this->setMinMax($linestring, GisGeometry::EMPTY_EXTENT); } /** diff --git a/libraries/classes/Gis/GisMultiLineString.php b/libraries/classes/Gis/GisMultiLineString.php index 645d664d8a42..1b48f62d3291 100644 --- a/libraries/classes/Gis/GisMultiLineString.php +++ b/libraries/classes/Gis/GisMultiLineString.php @@ -53,10 +53,11 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { - $min_max = []; + $min_max = GisGeometry::EMPTY_EXTENT; // Trim to remove leading 'MULTILINESTRING((' and trailing '))' $multilinestirng = mb_substr($spatial, 17, -2); diff --git a/libraries/classes/Gis/GisMultiPoint.php b/libraries/classes/Gis/GisMultiPoint.php index bc983cff9211..8e3d09be7dc1 100644 --- a/libraries/classes/Gis/GisMultiPoint.php +++ b/libraries/classes/Gis/GisMultiPoint.php @@ -52,13 +52,14 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { // Trim to remove leading 'MULTIPOINT(' and trailing ')' $multipoint = mb_substr($spatial, 11, -1); - return $this->setMinMax($multipoint, []); + return $this->setMinMax($multipoint, GisGeometry::EMPTY_EXTENT); } /** diff --git a/libraries/classes/Gis/GisMultiPolygon.php b/libraries/classes/Gis/GisMultiPolygon.php index 6e45a0335fa0..f276e04fff59 100644 --- a/libraries/classes/Gis/GisMultiPolygon.php +++ b/libraries/classes/Gis/GisMultiPolygon.php @@ -56,10 +56,11 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { - $min_max = []; + $min_max = GisGeometry::EMPTY_EXTENT; // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' $multipolygon = mb_substr($spatial, 15, -3); diff --git a/libraries/classes/Gis/GisPoint.php b/libraries/classes/Gis/GisPoint.php index 88fade5c15e5..fb67817953f8 100644 --- a/libraries/classes/Gis/GisPoint.php +++ b/libraries/classes/Gis/GisPoint.php @@ -51,13 +51,14 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { // Trim to remove leading 'POINT(' and trailing ')' $point = mb_substr($spatial, 6, -1); - return $this->setMinMax($point, []); + return $this->setMinMax($point, GisGeometry::EMPTY_EXTENT); } /** diff --git a/libraries/classes/Gis/GisPolygon.php b/libraries/classes/Gis/GisPolygon.php index 63582002438e..f4455f0433e4 100644 --- a/libraries/classes/Gis/GisPolygon.php +++ b/libraries/classes/Gis/GisPolygon.php @@ -59,6 +59,7 @@ public static function singleton() * @param string $spatial spatial data of a row * * @return array an array containing the min, max values for x and y coordinates + * @psalm-return array{minX:float,minY:float,maxX:float,maxY:float} */ public function scaleRow($spatial) { @@ -74,7 +75,7 @@ public function scaleRow($spatial) $ring = $parts[0]; } - return $this->setMinMax($ring, []); + return $this->setMinMax($ring, GisGeometry::EMPTY_EXTENT); } /** diff --git a/libraries/classes/Gis/GisVisualization.php b/libraries/classes/Gis/GisVisualization.php index af53773baae8..d6bad291b3e5 100644 --- a/libraries/classes/Gis/GisVisualization.php +++ b/libraries/classes/Gis/GisVisualization.php @@ -17,6 +17,7 @@ use function base64_encode; use function count; use function intval; +use function is_finite; use function is_numeric; use function is_string; use function mb_strlen; @@ -543,12 +544,7 @@ public function toFile($filename, $format): void */ private function scaleDataSet(array $data) { - $min_max = [ - 'maxX' => 0.0, - 'maxY' => 0.0, - 'minX' => 0.0, - 'minY' => 0.0, - ]; + $min_max = GisGeometry::EMPTY_EXTENT; $border = 15; // effective width and height of the plot $plot_width = $this->settings['width'] - 2 * $border; @@ -577,28 +573,35 @@ private function scaleDataSet(array $data) // Update minimum/maximum values for x and y coordinates. $c_maxX = (float) $scale_data['maxX']; - if ($min_max['maxX'] === 0.0 || $c_maxX > $min_max['maxX']) { + if ($c_maxX > $min_max['maxX']) { $min_max['maxX'] = $c_maxX; } $c_minX = (float) $scale_data['minX']; - if ($min_max['minX'] === 0.0 || $c_minX < $min_max['minX']) { + if ($c_minX < $min_max['minX']) { $min_max['minX'] = $c_minX; } $c_maxY = (float) $scale_data['maxY']; - if ($min_max['maxY'] === 0.0 || $c_maxY > $min_max['maxY']) { + if ($c_maxY > $min_max['maxY']) { $min_max['maxY'] = $c_maxY; } $c_minY = (float) $scale_data['minY']; - if ($min_max['minY'] !== 0.0 && $c_minY >= $min_max['minY']) { + if ($c_minY >= $min_max['minY']) { continue; } $min_max['minY'] = $c_minY; } + if (! is_finite($min_max['minX']) || ! is_finite($min_max['minY'])) { + $min_max['maxX'] = 0.0; + $min_max['maxY'] = 0.0; + $min_max['minX'] = 0.0; + $min_max['minY'] = 0.0; + } + // scale the visualization $x_ratio = ($min_max['maxX'] - $min_max['minX']) / $plot_width; $y_ratio = ($min_max['maxY'] - $min_max['minY']) / $plot_height; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 419f7d09e31d..c92ace452658 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3746,12 +3746,22 @@ parameters: path: libraries/classes/Gis/GisGeometry.php - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisGeometry\\:\\:setMinMax\\(\\) has parameter \\$min_max with no value type specified in iterable type array\\.$#" + message: "#^Offset 'maxX' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" count: 1 path: libraries/classes/Gis/GisGeometry.php - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisGeometry\\:\\:setMinMax\\(\\) return type has no value type specified in iterable type array\\.$#" + message: "#^Offset 'maxY' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometry.php + + - + message: "#^Offset 'minX' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometry.php + + - + message: "#^Offset 'minY' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" count: 1 path: libraries/classes/Gis/GisGeometry.php @@ -3805,6 +3815,26 @@ parameters: count: 1 path: libraries/classes/Gis/GisGeometryCollection.php + - + message: "#^Offset 'maxX' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometryCollection.php + + - + message: "#^Offset 'maxY' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometryCollection.php + + - + message: "#^Offset 'minX' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometryCollection.php + + - + message: "#^Offset 'minY' on array\\{minX\\: float, minY\\: float, maxX\\: float, maxY\\: float\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: libraries/classes/Gis/GisGeometryCollection.php + - message: "#^Static property PhpMyAdmin\\\\Gis\\\\GisGeometryCollection\\:\\:\\$instance \\(PhpMyAdmin\\\\Gis\\\\GisGeometryCollection\\) in isset\\(\\) is not nullable\\.$#" count: 1 @@ -3845,11 +3875,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisLineString.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisLineString\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisLineString.php - - message: "#^Parameter \\#5 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:line\\(\\) expects int, int\\|false given\\.$#" count: 1 @@ -3905,11 +3930,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisMultiLineString.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisMultiLineString\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisMultiLineString.php - - message: "#^Parameter \\#5 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:line\\(\\) expects int, int\\|false given\\.$#" count: 1 @@ -3970,11 +3990,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisMultiPoint.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisMultiPoint\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisMultiPoint.php - - message: "#^Parameter \\#5 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:string\\(\\) expects int, int\\|false given\\.$#" count: 1 @@ -4035,11 +4050,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisMultiPolygon.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisMultiPolygon\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisMultiPolygon.php - - message: "#^Parameter \\#2 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:filledPolygon\\(\\) expects int, int\\|false given\\.$#" count: 1 @@ -4095,11 +4105,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisPoint.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisPoint\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisPoint.php - - message: "#^Parameter \\#5 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:string\\(\\) expects int, int\\|false given\\.$#" count: 1 @@ -4185,11 +4190,6 @@ parameters: count: 1 path: libraries/classes/Gis/GisPolygon.php - - - message: "#^Method PhpMyAdmin\\\\Gis\\\\GisPolygon\\:\\:scaleRow\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: libraries/classes/Gis/GisPolygon.php - - message: "#^Parameter \\#2 \\$color of method PhpMyAdmin\\\\Image\\\\ImageWrapper\\:\\:filledPolygon\\(\\) expects int, int\\|false given\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7f3adf06e619..7402e30cd184 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -15640,7 +15640,8 @@ - + + $dataSet $dataSet $queryString $queryString diff --git a/test/classes/Gis/GisGeometryTest.php b/test/classes/Gis/GisGeometryTest.php index 58d47c6560a0..3648f0a4f8c2 100644 --- a/test/classes/Gis/GisGeometryTest.php +++ b/test/classes/Gis/GisGeometryTest.php @@ -71,7 +71,7 @@ public function providerForTestSetMinMax(): array return [ [ '12 35,48 75,69 23,25 45,14 53,35 78', - [], + GisGeometry::EMPTY_EXTENT, [ 'minX' => 12, 'maxX' => 69, diff --git a/test/classes/Gis/GisVisualizationTest.php b/test/classes/Gis/GisVisualizationTest.php index 617d8397b2d2..8568f7581a9b 100644 --- a/test/classes/Gis/GisVisualizationTest.php +++ b/test/classes/Gis/GisVisualizationTest.php @@ -80,6 +80,32 @@ public function testScaleDataSet(): void ], $dataSet ); + + // Regression test for bug with 0.0 sentinel values + $dataSet = $this->callFunction( + $gis, + GisVisualization::class, + 'scaleDataSet', + [ + [ + ['abc' => 'MULTIPOLYGON(((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1)))'], + ['abc' => 'MULTIPOLYGON(((10 10,10 13,13 13,13 10,10 10),(11 11,11 12,12 12,12 11,11 11)))'], + ], + ] + ); + $this->assertSame( + [ + 'scale' => 32.30769230769231, + 'x' => -2.7857142857142865, + 'y' => -0.4642857142857143, + 'minX' => 0.0, + 'maxX' => 13.0, + 'minY' => 0.0, + 'maxY' => 13.0, + 'height' => 450, + ], + $dataSet + ); } /**