1<?php 2/** 3 * Base class for all GIS data type classes 4 */ 5 6declare(strict_types=1); 7 8namespace PhpMyAdmin\Gis; 9 10use TCPDF; 11use function explode; 12use function floatval; 13use function intval; 14use function mb_strlen; 15use function mb_strripos; 16use function mb_substr; 17use function mt_rand; 18use function preg_match; 19use function sprintf; 20use function str_replace; 21use function trim; 22 23/** 24 * Base class for all GIS data type classes. 25 */ 26abstract class GisGeometry 27{ 28 /** 29 * Prepares and returns the code related to a row in the GIS dataset as SVG. 30 * 31 * @param string $spatial GIS data object 32 * @param string $label label for the GIS data object 33 * @param string $color color for the GIS data object 34 * @param array $scale_data data related to scaling 35 * 36 * @return string the code related to a row in the GIS dataset 37 * 38 * @access public 39 */ 40 abstract public function prepareRowAsSvg($spatial, $label, $color, array $scale_data); 41 42 /** 43 * Adds to the PNG image object, the data related to a row in the GIS dataset. 44 * 45 * @param string $spatial GIS POLYGON object 46 * @param string|null $label Label for the GIS POLYGON object 47 * @param string $color Color for the GIS POLYGON object 48 * @param array $scale_data Array containing data related to scaling 49 * @param resource $image Image object 50 * 51 * @return resource the modified image object 52 * 53 * @access public 54 */ 55 abstract public function prepareRowAsPng( 56 $spatial, 57 ?string $label, 58 $color, 59 array $scale_data, 60 $image 61 ); 62 63 /** 64 * Adds to the TCPDF instance, the data related to a row in the GIS dataset. 65 * 66 * @param string $spatial GIS data object 67 * @param string|null $label label for the GIS data object 68 * @param string $color color for the GIS data object 69 * @param array $scale_data array containing data related to scaling 70 * @param TCPDF $pdf TCPDF instance 71 * 72 * @return TCPDF the modified TCPDF instance 73 * 74 * @access public 75 */ 76 abstract public function prepareRowAsPdf( 77 $spatial, 78 ?string $label, 79 $color, 80 array $scale_data, 81 $pdf 82 ); 83 84 /** 85 * Prepares the JavaScript related to a row in the GIS dataset 86 * to visualize it with OpenLayers. 87 * 88 * @param string $spatial GIS data object 89 * @param int $srid spatial reference ID 90 * @param string $label label for the GIS data object 91 * @param array $color color for the GIS data object 92 * @param array $scale_data array containing data related to scaling 93 * 94 * @return string the JavaScript related to a row in the GIS dataset 95 * 96 * @access public 97 */ 98 abstract public function prepareRowAsOl( 99 $spatial, 100 $srid, 101 $label, 102 $color, 103 array $scale_data 104 ); 105 106 /** 107 * Scales each row. 108 * 109 * @param string $spatial spatial data of a row 110 * 111 * @return array array containing the min, max values for x and y coordinates 112 * 113 * @access public 114 */ 115 abstract public function scaleRow($spatial); 116 117 /** 118 * Generates the WKT with the set of parameters passed by the GIS editor. 119 * 120 * @param array $gis_data GIS data 121 * @param int $index index into the parameter object 122 * @param string $empty value for empty points 123 * 124 * @return string WKT with the set of parameters passed by the GIS editor 125 * 126 * @access public 127 */ 128 abstract public function generateWkt(array $gis_data, $index, $empty = ''); 129 130 /** 131 * Returns OpenLayers.Bounds object that correspond to the bounds of GIS data. 132 * 133 * @param string $srid spatial reference ID 134 * @param array $scale_data data related to scaling 135 * 136 * @return string OpenLayers.Bounds object that 137 * correspond to the bounds of GIS data 138 * 139 * @access protected 140 */ 141 protected function getBoundsForOl($srid, array $scale_data) 142 { 143 return sprintf( 144 'var minLoc = [%s, %s];' 145 . 'var maxLoc = [%s, %s];' 146 . 'var ext = ol.extent.boundingExtent([minLoc, maxLoc]);' 147 . 'ext = ol.proj.transformExtent(ext, ol.proj.get("EPSG:%s"), ol.proj.get(\'EPSG:3857\'));' 148 . 'map.getView().fit(ext, map.getSize());', 149 $scale_data['minX'], 150 $scale_data['minY'], 151 $scale_data['maxX'], 152 $scale_data['maxY'], 153 intval($srid) 154 ); 155 } 156 157 /** 158 * Updates the min, max values with the given point set. 159 * 160 * @param string $point_set point set 161 * @param array $min_max existing min, max values 162 * 163 * @return array the updated min, max values 164 * 165 * @access protected 166 */ 167 protected function setMinMax($point_set, array $min_max) 168 { 169 // Separate each point 170 $points = explode(',', $point_set); 171 172 foreach ($points as $point) { 173 // Extract coordinates of the point 174 $cordinates = explode(' ', $point); 175 176 $x = (float) $cordinates[0]; 177 if (! isset($min_max['maxX']) || $x > $min_max['maxX']) { 178 $min_max['maxX'] = $x; 179 } 180 if (! isset($min_max['minX']) || $x < $min_max['minX']) { 181 $min_max['minX'] = $x; 182 } 183 $y = (float) $cordinates[1]; 184 if (! isset($min_max['maxY']) || $y > $min_max['maxY']) { 185 $min_max['maxY'] = $y; 186 } 187 if (isset($min_max['minY']) && $y >= $min_max['minY']) { 188 continue; 189 } 190 191 $min_max['minY'] = $y; 192 } 193 194 return $min_max; 195 } 196 197 /** 198 * Generates parameters for the GIS data editor from the value of the GIS column. 199 * This method performs common work. 200 * More specific work is performed by each of the geom classes. 201 * 202 * @param string $value value of the GIS column 203 * 204 * @return array parameters for the GIS editor from the value of the GIS column 205 * 206 * @access protected 207 */ 208 public function generateParams($value) 209 { 210 $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING' 211 . '|POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)'; 212 $srid = 0; 213 $wkt = ''; 214 215 if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $value)) { 216 $last_comma = mb_strripos($value, ','); 217 $srid = trim(mb_substr($value, $last_comma + 1)); 218 $wkt = trim(mb_substr($value, 1, $last_comma - 2)); 219 } elseif (preg_match('/^' . $geom_types . '\(.*\)$/i', $value)) { 220 $wkt = $value; 221 } 222 223 return [ 224 'srid' => $srid, 225 'wkt' => $wkt, 226 ]; 227 } 228 229 /** 230 * Extracts points, scales and returns them as an array. 231 * 232 * @param string $point_set string of comma separated points 233 * @param array|null $scale_data data related to scaling 234 * @param bool $linear if true, as a 1D array, else as a 2D array 235 * 236 * @return array scaled points 237 * 238 * @access protected 239 */ 240 protected function extractPoints($point_set, $scale_data, $linear = false): array 241 { 242 $points_arr = []; 243 244 // Separate each point 245 $points = explode(',', $point_set); 246 247 foreach ($points as $point) { 248 $point = str_replace(['(', ')'], '', $point); 249 // Extract coordinates of the point 250 $coordinates = explode(' ', $point); 251 252 if (isset($coordinates[0], $coordinates[1]) && trim($coordinates[0]) != '' && trim($coordinates[1]) != '') { 253 if ($scale_data != null) { 254 $x = ($coordinates[0] - $scale_data['x']) * $scale_data['scale']; 255 $y = $scale_data['height'] 256 - ($coordinates[1] - $scale_data['y']) * $scale_data['scale']; 257 } else { 258 $x = floatval(trim($coordinates[0])); 259 $y = floatval(trim($coordinates[1])); 260 } 261 } else { 262 $x = 0; 263 $y = 0; 264 } 265 266 if (! $linear) { 267 $points_arr[] = [ 268 $x, 269 $y, 270 ]; 271 } else { 272 $points_arr[] = $x; 273 $points_arr[] = $y; 274 } 275 } 276 277 return $points_arr; 278 } 279 280 /** 281 * Generates JavaScript for adding an array of polygons to OpenLayers. 282 * 283 * @param array $polygons x and y coordinates for each polygon 284 * @param string $srid spatial reference id 285 * 286 * @return string JavaScript for adding an array of polygons to OpenLayers 287 * 288 * @access protected 289 */ 290 protected function getPolygonArrayForOpenLayers(array $polygons, $srid) 291 { 292 $ol_array = 'var polygonArray = [];'; 293 foreach ($polygons as $polygon) { 294 $rings = explode('),(', $polygon); 295 $ol_array .= $this->getPolygonForOpenLayers($rings, $srid); 296 $ol_array .= 'polygonArray.push(polygon);'; 297 } 298 299 return $ol_array; 300 } 301 302 /** 303 * Generates JavaScript for adding points for OpenLayers polygon. 304 * 305 * @param array $polygon x and y coordinates for each line 306 * @param string $srid spatial reference id 307 * 308 * @return string JavaScript for adding points for OpenLayers polygon 309 * 310 * @access protected 311 */ 312 protected function getPolygonForOpenLayers(array $polygon, $srid) 313 { 314 return $this->getLineArrayForOpenLayers($polygon, $srid, false) 315 . 'var polygon = new ol.geom.Polygon(arr);'; 316 } 317 318 /** 319 * Generates JavaScript for adding an array of LineString 320 * or LineRing to OpenLayers. 321 * 322 * @param array $lines x and y coordinates for each line 323 * @param string $srid spatial reference id 324 * @param bool $is_line_string whether it's an array of LineString 325 * 326 * @return string JavaScript for adding an array of LineString 327 * or LineRing to OpenLayers 328 * 329 * @access protected 330 */ 331 protected function getLineArrayForOpenLayers( 332 array $lines, 333 $srid, 334 $is_line_string = true 335 ) { 336 $ol_array = 'var arr = [];'; 337 foreach ($lines as $line) { 338 $ol_array .= 'var lineArr = [];'; 339 $points_arr = $this->extractPoints($line, null); 340 $ol_array .= 'var line = ' . $this->getLineForOpenLayers( 341 $points_arr, 342 $srid, 343 $is_line_string 344 ) . ';'; 345 $ol_array .= 'var coord = line.getCoordinates();'; 346 $ol_array .= 'for (var i = 0; i < coord.length; i++) lineArr.push(coord[i]);'; 347 $ol_array .= 'arr.push(lineArr);'; 348 } 349 350 return $ol_array; 351 } 352 353 /** 354 * Generates JavaScript for adding a LineString or LineRing to OpenLayers. 355 * 356 * @param array $points_arr x and y coordinates for each point 357 * @param string $srid spatial reference id 358 * @param bool $is_line_string whether it's a LineString 359 * 360 * @return string JavaScript for adding a LineString or LineRing to OpenLayers 361 * 362 * @access protected 363 */ 364 protected function getLineForOpenLayers( 365 array $points_arr, 366 $srid, 367 $is_line_string = true 368 ) { 369 return 'new ol.geom.' 370 . ($is_line_string ? 'LineString' : 'LinearRing') . '(' 371 . $this->getPointsArrayForOpenLayers($points_arr, $srid) 372 . ')'; 373 } 374 375 /** 376 * Generates JavaScript for adding an array of points to OpenLayers. 377 * 378 * @param array $points_arr x and y coordinates for each point 379 * @param string $srid spatial reference id 380 * 381 * @return string JavaScript for adding an array of points to OpenLayers 382 * 383 * @access protected 384 */ 385 protected function getPointsArrayForOpenLayers(array $points_arr, $srid) 386 { 387 $ol_array = 'new Array('; 388 foreach ($points_arr as $point) { 389 $ol_array .= $this->getPointForOpenLayers($point, $srid) . '.getCoordinates(), '; 390 } 391 392 $ol_array 393 = mb_substr( 394 $ol_array, 395 0, 396 mb_strlen($ol_array) - 2 397 ); 398 399 return $ol_array . ')'; 400 } 401 402 /** 403 * Generates JavaScript for adding a point to OpenLayers. 404 * 405 * @param array $point array containing the x and y coordinates of the point 406 * @param string $srid spatial reference id 407 * 408 * @return string JavaScript for adding points to OpenLayers 409 * 410 * @access protected 411 */ 412 protected function getPointForOpenLayers(array $point, $srid) 413 { 414 return '(new ol.geom.Point([' . $point[0] . ',' . $point[1] . '])' 415 . '.transform(ol.proj.get("EPSG:' . ((int) $srid) . '")' 416 . ', ol.proj.get(\'EPSG:3857\')))'; 417 } 418 419 protected function getRandomId(): int 420 { 421 return mt_rand(); 422 } 423} 424