1<?php 2/** 3 * Handles actions related to GIS MULTILINESTRING objects 4 */ 5 6declare(strict_types=1); 7 8namespace PhpMyAdmin\Gis; 9 10use TCPDF; 11use function count; 12use function explode; 13use function hexdec; 14use function imagecolorallocate; 15use function imageline; 16use function imagestring; 17use function json_encode; 18use function mb_strlen; 19use function mb_substr; 20use function round; 21use function trim; 22 23/** 24 * Handles actions related to GIS MULTILINESTRING objects 25 */ 26class GisMultiLineString extends GisGeometry 27{ 28 /** @var self */ 29 private static $instance; 30 31 /** 32 * A private constructor; prevents direct creation of object. 33 * 34 * @access private 35 */ 36 private function __construct() 37 { 38 } 39 40 /** 41 * Returns the singleton. 42 * 43 * @return GisMultiLineString the singleton 44 * 45 * @access public 46 */ 47 public static function singleton() 48 { 49 if (! isset(self::$instance)) { 50 self::$instance = new GisMultiLineString(); 51 } 52 53 return self::$instance; 54 } 55 56 /** 57 * Scales each row. 58 * 59 * @param string $spatial spatial data of a row 60 * 61 * @return array an array containing the min, max values for x and y coordinates 62 * 63 * @access public 64 */ 65 public function scaleRow($spatial) 66 { 67 $min_max = []; 68 69 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 70 $multilinestirng 71 = mb_substr( 72 $spatial, 73 17, 74 mb_strlen($spatial) - 19 75 ); 76 // Separate each linestring 77 $linestirngs = explode('),(', $multilinestirng); 78 79 foreach ($linestirngs as $linestring) { 80 $min_max = $this->setMinMax($linestring, $min_max); 81 } 82 83 return $min_max; 84 } 85 86 /** 87 * Adds to the PNG image object, the data related to a row in the GIS dataset. 88 * 89 * @param string $spatial GIS POLYGON object 90 * @param string|null $label Label for the GIS POLYGON object 91 * @param string $line_color Color for the GIS POLYGON object 92 * @param array $scale_data Array containing data related to scaling 93 * @param resource $image Image object 94 * 95 * @return resource the modified image object 96 * 97 * @access public 98 */ 99 public function prepareRowAsPng( 100 $spatial, 101 ?string $label, 102 $line_color, 103 array $scale_data, 104 $image 105 ) { 106 // allocate colors 107 $black = imagecolorallocate($image, 0, 0, 0); 108 $red = hexdec(mb_substr($line_color, 1, 2)); 109 $green = hexdec(mb_substr($line_color, 3, 2)); 110 $blue = hexdec(mb_substr($line_color, 4, 2)); 111 $color = imagecolorallocate($image, $red, $green, $blue); 112 113 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 114 $multilinestirng 115 = mb_substr( 116 $spatial, 117 17, 118 mb_strlen($spatial) - 19 119 ); 120 // Separate each linestring 121 $linestirngs = explode('),(', $multilinestirng); 122 123 $first_line = true; 124 foreach ($linestirngs as $linestring) { 125 $points_arr = $this->extractPoints($linestring, $scale_data); 126 foreach ($points_arr as $point) { 127 if (isset($temp_point)) { 128 // draw line section 129 imageline( 130 $image, 131 (int) round($temp_point[0]), 132 (int) round($temp_point[1]), 133 (int) round($point[0]), 134 (int) round($point[1]), 135 $color 136 ); 137 } 138 $temp_point = $point; 139 } 140 unset($temp_point); 141 // print label if applicable 142 if (isset($label) && trim($label) != '' && $first_line) { 143 imagestring( 144 $image, 145 1, 146 (int) round($points_arr[1][0]), 147 (int) round($points_arr[1][1]), 148 trim($label), 149 $black 150 ); 151 } 152 $first_line = false; 153 } 154 155 return $image; 156 } 157 158 /** 159 * Adds to the TCPDF instance, the data related to a row in the GIS dataset. 160 * 161 * @param string $spatial GIS MULTILINESTRING object 162 * @param string|null $label Label for the GIS MULTILINESTRING object 163 * @param string $line_color Color for the GIS MULTILINESTRING object 164 * @param array $scale_data Array containing data related to scaling 165 * @param TCPDF $pdf TCPDF instance 166 * 167 * @return TCPDF the modified TCPDF instance 168 * 169 * @access public 170 */ 171 public function prepareRowAsPdf($spatial, ?string $label, $line_color, array $scale_data, $pdf) 172 { 173 // allocate colors 174 $red = hexdec(mb_substr($line_color, 1, 2)); 175 $green = hexdec(mb_substr($line_color, 3, 2)); 176 $blue = hexdec(mb_substr($line_color, 4, 2)); 177 $line = [ 178 'width' => 1.5, 179 'color' => [ 180 $red, 181 $green, 182 $blue, 183 ], 184 ]; 185 186 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 187 $multilinestirng 188 = mb_substr( 189 $spatial, 190 17, 191 mb_strlen($spatial) - 19 192 ); 193 // Separate each linestring 194 $linestirngs = explode('),(', $multilinestirng); 195 196 $first_line = true; 197 foreach ($linestirngs as $linestring) { 198 $points_arr = $this->extractPoints($linestring, $scale_data); 199 foreach ($points_arr as $point) { 200 if (isset($temp_point)) { 201 // draw line section 202 $pdf->Line( 203 $temp_point[0], 204 $temp_point[1], 205 $point[0], 206 $point[1], 207 $line 208 ); 209 } 210 $temp_point = $point; 211 } 212 unset($temp_point); 213 // print label 214 if (isset($label) && trim($label) != '' && $first_line) { 215 $pdf->SetXY($points_arr[1][0], $points_arr[1][1]); 216 $pdf->SetFontSize(5); 217 $pdf->Cell(0, 0, trim($label)); 218 } 219 $first_line = false; 220 } 221 222 return $pdf; 223 } 224 225 /** 226 * Prepares and returns the code related to a row in the GIS dataset as SVG. 227 * 228 * @param string $spatial GIS MULTILINESTRING object 229 * @param string $label Label for the GIS MULTILINESTRING object 230 * @param string $line_color Color for the GIS MULTILINESTRING object 231 * @param array $scale_data Array containing data related to scaling 232 * 233 * @return string the code related to a row in the GIS dataset 234 * 235 * @access public 236 */ 237 public function prepareRowAsSvg($spatial, $label, $line_color, array $scale_data) 238 { 239 $line_options = [ 240 'name' => $label, 241 'class' => 'linestring vector', 242 'fill' => 'none', 243 'stroke' => $line_color, 244 'stroke-width' => 2, 245 ]; 246 247 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 248 $multilinestirng 249 = mb_substr( 250 $spatial, 251 17, 252 mb_strlen($spatial) - 19 253 ); 254 // Separate each linestring 255 $linestirngs = explode('),(', $multilinestirng); 256 257 $row = ''; 258 foreach ($linestirngs as $linestring) { 259 $points_arr = $this->extractPoints($linestring, $scale_data); 260 261 $row .= '<polyline points="'; 262 foreach ($points_arr as $point) { 263 $row .= $point[0] . ',' . $point[1] . ' '; 264 } 265 $row .= '"'; 266 $line_options['id'] = $label . $this->getRandomId(); 267 foreach ($line_options as $option => $val) { 268 $row .= ' ' . $option . '="' . trim((string) $val) . '"'; 269 } 270 $row .= '/>'; 271 } 272 273 return $row; 274 } 275 276 /** 277 * Prepares JavaScript related to a row in the GIS dataset 278 * to visualize it with OpenLayers. 279 * 280 * @param string $spatial GIS MULTILINESTRING object 281 * @param int $srid Spatial reference ID 282 * @param string $label Label for the GIS MULTILINESTRING object 283 * @param array $line_color Color for the GIS MULTILINESTRING object 284 * @param array $scale_data Array containing data related to scaling 285 * 286 * @return string JavaScript related to a row in the GIS dataset 287 * 288 * @access public 289 */ 290 public function prepareRowAsOl($spatial, $srid, $label, $line_color, array $scale_data) 291 { 292 $stroke_style = [ 293 'color' => $line_color, 294 'width' => 2, 295 ]; 296 297 $row = 'var style = new ol.style.Style({' 298 . 'stroke: new ol.style.Stroke(' . json_encode($stroke_style) . ')'; 299 if (trim($label) !== '') { 300 $text_style = ['text' => trim($label)]; 301 $row .= ', text: new ol.style.Text(' . json_encode($text_style) . ')'; 302 } 303 304 $row .= '});'; 305 306 if ($srid == 0) { 307 $srid = 4326; 308 } 309 $row .= $this->getBoundsForOl($srid, $scale_data); 310 311 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 312 $multilinestirng 313 = mb_substr( 314 $spatial, 315 17, 316 mb_strlen($spatial) - 19 317 ); 318 // Separate each linestring 319 $linestirngs = explode('),(', $multilinestirng); 320 321 return $row . $this->getLineArrayForOpenLayers($linestirngs, $srid) 322 . 'var multiLineString = new ol.geom.MultiLineString(arr);' 323 . 'var feature = new ol.Feature({geometry: multiLineString});' 324 . 'feature.setStyle(style);' 325 . 'vectorLayer.addFeature(feature);'; 326 } 327 328 /** 329 * Generate the WKT with the set of parameters passed by the GIS editor. 330 * 331 * @param array $gis_data GIS data 332 * @param int $index Index into the parameter object 333 * @param string $empty Value for empty points 334 * 335 * @return string WKT with the set of parameters passed by the GIS editor 336 * 337 * @access public 338 */ 339 public function generateWkt(array $gis_data, $index, $empty = '') 340 { 341 $data_row = $gis_data[$index]['MULTILINESTRING']; 342 343 $no_of_lines = $data_row['no_of_lines'] ?? 1; 344 if ($no_of_lines < 1) { 345 $no_of_lines = 1; 346 } 347 348 $wkt = 'MULTILINESTRING('; 349 for ($i = 0; $i < $no_of_lines; $i++) { 350 $no_of_points = $data_row[$i]['no_of_points'] ?? 2; 351 if ($no_of_points < 2) { 352 $no_of_points = 2; 353 } 354 $wkt .= '('; 355 for ($j = 0; $j < $no_of_points; $j++) { 356 $wkt .= (isset($data_row[$i][$j]['x']) 357 && trim((string) $data_row[$i][$j]['x']) != '' 358 ? $data_row[$i][$j]['x'] : $empty) 359 . ' ' . (isset($data_row[$i][$j]['y']) 360 && trim((string) $data_row[$i][$j]['y']) != '' 361 ? $data_row[$i][$j]['y'] : $empty) . ','; 362 } 363 $wkt 364 = mb_substr( 365 $wkt, 366 0, 367 mb_strlen($wkt) - 1 368 ); 369 $wkt .= '),'; 370 } 371 $wkt 372 = mb_substr( 373 $wkt, 374 0, 375 mb_strlen($wkt) - 1 376 ); 377 378 return $wkt . ')'; 379 } 380 381 /** 382 * Generate the WKT for the data from ESRI shape files. 383 * 384 * @param array $row_data GIS data 385 * 386 * @return string the WKT for the data from ESRI shape files 387 * 388 * @access public 389 */ 390 public function getShape(array $row_data) 391 { 392 $wkt = 'MULTILINESTRING('; 393 for ($i = 0; $i < $row_data['numparts']; $i++) { 394 $wkt .= '('; 395 foreach ($row_data['parts'][$i]['points'] as $point) { 396 $wkt .= $point['x'] . ' ' . $point['y'] . ','; 397 } 398 $wkt 399 = mb_substr( 400 $wkt, 401 0, 402 mb_strlen($wkt) - 1 403 ); 404 $wkt .= '),'; 405 } 406 $wkt 407 = mb_substr( 408 $wkt, 409 0, 410 mb_strlen($wkt) - 1 411 ); 412 413 return $wkt . ')'; 414 } 415 416 /** 417 * Generate parameters for the GIS data editor from the value of the GIS column. 418 * 419 * @param string $value Value of the GIS column 420 * @param int $index Index of the geometry 421 * 422 * @return array params for the GIS data editor from the value of the GIS column 423 * 424 * @access public 425 */ 426 public function generateParams($value, $index = -1) 427 { 428 $params = []; 429 if ($index == -1) { 430 $index = 0; 431 $data = GisGeometry::generateParams($value); 432 $params['srid'] = $data['srid']; 433 $wkt = $data['wkt']; 434 } else { 435 $params[$index]['gis_type'] = 'MULTILINESTRING'; 436 $wkt = $value; 437 } 438 439 // Trim to remove leading 'MULTILINESTRING((' and trailing '))' 440 $multilinestirng 441 = mb_substr( 442 $wkt, 443 17, 444 mb_strlen($wkt) - 19 445 ); 446 // Separate each linestring 447 $linestirngs = explode('),(', $multilinestirng); 448 $params[$index]['MULTILINESTRING']['no_of_lines'] = count($linestirngs); 449 450 $j = 0; 451 foreach ($linestirngs as $linestring) { 452 $points_arr = $this->extractPoints($linestring, null); 453 $no_of_points = count($points_arr); 454 $params[$index]['MULTILINESTRING'][$j]['no_of_points'] = $no_of_points; 455 for ($i = 0; $i < $no_of_points; $i++) { 456 $params[$index]['MULTILINESTRING'][$j][$i]['x'] = $points_arr[$i][0]; 457 $params[$index]['MULTILINESTRING'][$j][$i]['y'] = $points_arr[$i][1]; 458 } 459 $j++; 460 } 461 462 return $params; 463 } 464} 465