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