"Fossies" - the Fresh Open Source Software Archive

Member "phpMyAdmin-5.1.0-english/libraries/classes/Gis/GisMultiPolygon.php" (24 Feb 2021, 20875 Bytes) of package /linux/www/phpMyAdmin-5.1.0-english.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "GisMultiPolygon.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.0.4-english_vs_5.1.0-english.

    1 <?php
    2 /**
    3  * Handles actions related to GIS MULTIPOLYGON objects
    4  */
    5 
    6 declare(strict_types=1);
    7 
    8 namespace PhpMyAdmin\Gis;
    9 
   10 use TCPDF;
   11 use function array_merge;
   12 use function array_push;
   13 use function array_slice;
   14 use function count;
   15 use function explode;
   16 use function hexdec;
   17 use function imagecolorallocate;
   18 use function imagefilledpolygon;
   19 use function imagestring;
   20 use function json_encode;
   21 use function mb_strlen;
   22 use function mb_strpos;
   23 use function mb_substr;
   24 use function trim;
   25 
   26 /**
   27  * Handles actions related to GIS MULTIPOLYGON objects
   28  */
   29 class GisMultiPolygon extends GisGeometry
   30 {
   31     /** @var self */
   32     private static $instance;
   33 
   34     /**
   35      * A private constructor; prevents direct creation of object.
   36      *
   37      * @access private
   38      */
   39     private function __construct()
   40     {
   41     }
   42 
   43     /**
   44      * Returns the singleton.
   45      *
   46      * @return GisMultiPolygon the singleton
   47      *
   48      * @access public
   49      */
   50     public static function singleton()
   51     {
   52         if (! isset(self::$instance)) {
   53             self::$instance = new GisMultiPolygon();
   54         }
   55 
   56         return self::$instance;
   57     }
   58 
   59     /**
   60      * Scales each row.
   61      *
   62      * @param string $spatial spatial data of a row
   63      *
   64      * @return array an array containing the min, max values for x and y coordinates
   65      *
   66      * @access public
   67      */
   68     public function scaleRow($spatial)
   69     {
   70         $min_max = [];
   71 
   72         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
   73         $multipolygon
   74             = mb_substr(
   75                 $spatial,
   76                 15,
   77                 mb_strlen($spatial) - 18
   78             );
   79         // Separate each polygon
   80         $polygons = explode(')),((', $multipolygon);
   81 
   82         foreach ($polygons as $polygon) {
   83             // If the polygon doesn't have an inner ring, use polygon itself
   84             if (mb_strpos($polygon, '),(') === false) {
   85                 $ring = $polygon;
   86             } else {
   87                 // Separate outer ring and use it to determine min-max
   88                 $parts = explode('),(', $polygon);
   89                 $ring = $parts[0];
   90             }
   91             $min_max = $this->setMinMax($ring, $min_max);
   92         }
   93 
   94         return $min_max;
   95     }
   96 
   97     /**
   98      * Adds to the PNG image object, the data related to a row in the GIS dataset.
   99      *
  100      * @param string      $spatial    GIS POLYGON object
  101      * @param string|null $label      Label for the GIS POLYGON object
  102      * @param string      $fill_color Color for the GIS POLYGON object
  103      * @param array       $scale_data Array containing data related to scaling
  104      * @param resource    $image      Image object
  105      *
  106      * @return resource the modified image object
  107      *
  108      * @access public
  109      */
  110     public function prepareRowAsPng(
  111         $spatial,
  112         ?string $label,
  113         $fill_color,
  114         array $scale_data,
  115         $image
  116     ) {
  117         // allocate colors
  118         $black = imagecolorallocate($image, 0, 0, 0);
  119         $red = hexdec(mb_substr($fill_color, 1, 2));
  120         $green = hexdec(mb_substr($fill_color, 3, 2));
  121         $blue = hexdec(mb_substr($fill_color, 4, 2));
  122         $color = imagecolorallocate($image, $red, $green, $blue);
  123 
  124         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
  125         $multipolygon
  126             = mb_substr(
  127                 $spatial,
  128                 15,
  129                 mb_strlen($spatial) - 18
  130             );
  131         // Separate each polygon
  132         $polygons = explode(')),((', $multipolygon);
  133 
  134         $first_poly = true;
  135         $points_arr = [];
  136         foreach ($polygons as $polygon) {
  137             // If the polygon doesn't have an inner polygon
  138             if (mb_strpos($polygon, '),(') === false) {
  139                 $points_arr = $this->extractPoints($polygon, $scale_data, true);
  140             } else {
  141                 // Separate outer and inner polygons
  142                 $parts = explode('),(', $polygon);
  143                 $outer = $parts[0];
  144                 $inner = array_slice($parts, 1);
  145 
  146                 $points_arr = $this->extractPoints($outer, $scale_data, true);
  147 
  148                 foreach ($inner as $inner_poly) {
  149                     $points_arr = array_merge(
  150                         $points_arr,
  151                         $this->extractPoints($inner_poly, $scale_data, true)
  152                     );
  153                 }
  154             }
  155             // draw polygon
  156             imagefilledpolygon($image, $points_arr, count($points_arr) / 2, $color);
  157             // mark label point if applicable
  158             if (isset($label) && trim($label) != '' && $first_poly) {
  159                 $label_point = [
  160                     $points_arr[2],
  161                     $points_arr[3],
  162                 ];
  163             }
  164             $first_poly = false;
  165         }
  166         // print label if applicable
  167         if (isset($label_point)) {
  168             imagestring(
  169                 $image,
  170                 1,
  171                 $points_arr[2],
  172                 $points_arr[3],
  173                 trim((string) $label),
  174                 $black
  175             );
  176         }
  177 
  178         return $image;
  179     }
  180 
  181     /**
  182      * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
  183      *
  184      * @param string      $spatial    GIS MULTIPOLYGON object
  185      * @param string|null $label      Label for the GIS MULTIPOLYGON object
  186      * @param string      $fill_color Color for the GIS MULTIPOLYGON object
  187      * @param array       $scale_data Array containing data related to scaling
  188      * @param TCPDF       $pdf        TCPDF instance
  189      *
  190      * @return TCPDF the modified TCPDF instance
  191      *
  192      * @access public
  193      */
  194     public function prepareRowAsPdf($spatial, ?string $label, $fill_color, array $scale_data, $pdf)
  195     {
  196         // allocate colors
  197         $red = hexdec(mb_substr($fill_color, 1, 2));
  198         $green = hexdec(mb_substr($fill_color, 3, 2));
  199         $blue = hexdec(mb_substr($fill_color, 4, 2));
  200         $color = [
  201             $red,
  202             $green,
  203             $blue,
  204         ];
  205 
  206         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
  207         $multipolygon
  208             = mb_substr(
  209                 $spatial,
  210                 15,
  211                 mb_strlen($spatial) - 18
  212             );
  213         // Separate each polygon
  214         $polygons = explode(')),((', $multipolygon);
  215 
  216         $first_poly = true;
  217         foreach ($polygons as $polygon) {
  218             // If the polygon doesn't have an inner polygon
  219             if (mb_strpos($polygon, '),(') === false) {
  220                 $points_arr = $this->extractPoints($polygon, $scale_data, true);
  221             } else {
  222                 // Separate outer and inner polygons
  223                 $parts = explode('),(', $polygon);
  224                 $outer = $parts[0];
  225                 $inner = array_slice($parts, 1);
  226 
  227                 $points_arr = $this->extractPoints($outer, $scale_data, true);
  228 
  229                 foreach ($inner as $inner_poly) {
  230                     $points_arr = array_merge(
  231                         $points_arr,
  232                         $this->extractPoints($inner_poly, $scale_data, true)
  233                     );
  234                 }
  235             }
  236             // draw polygon
  237             $pdf->Polygon($points_arr, 'F*', [], $color, true);
  238             // mark label point if applicable
  239             if (isset($label) && trim($label) != '' && $first_poly) {
  240                 $label_point = [
  241                     $points_arr[2],
  242                     $points_arr[3],
  243                 ];
  244             }
  245             $first_poly = false;
  246         }
  247 
  248         // print label if applicable
  249         if (isset($label_point)) {
  250             $pdf->SetXY($label_point[0], $label_point[1]);
  251             $pdf->SetFontSize(5);
  252             $pdf->Cell(0, 0, trim((string) $label));
  253         }
  254 
  255         return $pdf;
  256     }
  257 
  258     /**
  259      * Prepares and returns the code related to a row in the GIS dataset as SVG.
  260      *
  261      * @param string $spatial    GIS MULTIPOLYGON object
  262      * @param string $label      Label for the GIS MULTIPOLYGON object
  263      * @param string $fill_color Color for the GIS MULTIPOLYGON object
  264      * @param array  $scale_data Array containing data related to scaling
  265      *
  266      * @return string the code related to a row in the GIS dataset
  267      *
  268      * @access public
  269      */
  270     public function prepareRowAsSvg($spatial, $label, $fill_color, array $scale_data)
  271     {
  272         $polygon_options = [
  273             'name'         => $label,
  274             'class'        => 'multipolygon vector',
  275             'stroke'       => 'black',
  276             'stroke-width' => 0.5,
  277             'fill'         => $fill_color,
  278             'fill-rule'    => 'evenodd',
  279             'fill-opacity' => 0.8,
  280         ];
  281 
  282         $row = '';
  283 
  284         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
  285         $multipolygon
  286             = mb_substr(
  287                 $spatial,
  288                 15,
  289                 mb_strlen($spatial) - 18
  290             );
  291         // Separate each polygon
  292         $polygons = explode(')),((', $multipolygon);
  293 
  294         foreach ($polygons as $polygon) {
  295             $row .= '<path d="';
  296 
  297             // If the polygon doesn't have an inner polygon
  298             if (mb_strpos($polygon, '),(') === false) {
  299                 $row .= $this->drawPath($polygon, $scale_data);
  300             } else {
  301                 // Separate outer and inner polygons
  302                 $parts = explode('),(', $polygon);
  303                 $outer = $parts[0];
  304                 $inner = array_slice($parts, 1);
  305 
  306                 $row .= $this->drawPath($outer, $scale_data);
  307 
  308                 foreach ($inner as $inner_poly) {
  309                     $row .= $this->drawPath($inner_poly, $scale_data);
  310                 }
  311             }
  312             $polygon_options['id'] = $label . $this->getRandomId();
  313             $row .= '"';
  314             foreach ($polygon_options as $option => $val) {
  315                 $row .= ' ' . $option . '="' . trim((string) $val) . '"';
  316             }
  317             $row .= '/>';
  318         }
  319 
  320         return $row;
  321     }
  322 
  323     /**
  324      * Prepares JavaScript related to a row in the GIS dataset
  325      * to visualize it with OpenLayers.
  326      *
  327      * @param string $spatial    GIS MULTIPOLYGON object
  328      * @param int    $srid       Spatial reference ID
  329      * @param string $label      Label for the GIS MULTIPOLYGON object
  330      * @param array  $fill_color Color for the GIS MULTIPOLYGON object
  331      * @param array  $scale_data Array containing data related to scaling
  332      *
  333      * @return string JavaScript related to a row in the GIS dataset
  334      *
  335      * @access public
  336      */
  337     public function prepareRowAsOl($spatial, $srid, $label, $fill_color, array $scale_data)
  338     {
  339         $fill_opacity = 0.8;
  340         array_push($fill_color, $fill_opacity);
  341         $fill_style = ['color' => $fill_color];
  342         $stroke_style = [
  343             'color' => [0,0,0],
  344             'width' => 0.5,
  345         ];
  346         $row =  'var style = new ol.style.Style({'
  347             . 'fill: new ol.style.Fill(' . json_encode($fill_style) . '),'
  348             . 'stroke: new ol.style.Stroke(' . json_encode($stroke_style) . ')';
  349 
  350         if ($label) {
  351             $text_style = ['text' => $label];
  352             $row .= ',text: new ol.style.Text(' . json_encode($text_style) . ')';
  353         }
  354 
  355         $row .= '});';
  356 
  357         if ($srid == 0) {
  358             $srid = 4326;
  359         }
  360         $row .= $this->getBoundsForOl($srid, $scale_data);
  361 
  362         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
  363         $multipolygon
  364             = mb_substr(
  365                 $spatial,
  366                 15,
  367                 mb_strlen($spatial) - 18
  368             );
  369         // Separate each polygon
  370         $polygons = explode(')),((', $multipolygon);
  371 
  372         return $row . $this->getPolygonArrayForOpenLayers($polygons, $srid)
  373             . 'var multiPolygon = new ol.geom.MultiPolygon(polygonArray);'
  374             . 'var feature = new ol.Feature(multiPolygon);'
  375             . 'feature.setStyle(style);'
  376             . 'vectorLayer.addFeature(feature);';
  377     }
  378 
  379     /**
  380      * Draws a ring of the polygon using SVG path element.
  381      *
  382      * @param string $polygon    The ring
  383      * @param array  $scale_data Array containing data related to scaling
  384      *
  385      * @return string the code to draw the ring
  386      *
  387      * @access private
  388      */
  389     private function drawPath($polygon, array $scale_data)
  390     {
  391         $points_arr = $this->extractPoints($polygon, $scale_data);
  392 
  393         $row = ' M ' . $points_arr[0][0] . ', ' . $points_arr[0][1];
  394         $other_points = array_slice($points_arr, 1, count($points_arr) - 2);
  395         foreach ($other_points as $point) {
  396             $row .= ' L ' . $point[0] . ', ' . $point[1];
  397         }
  398         $row .= ' Z ';
  399 
  400         return $row;
  401     }
  402 
  403     /**
  404      * Generate the WKT with the set of parameters passed by the GIS editor.
  405      *
  406      * @param array  $gis_data GIS data
  407      * @param int    $index    Index into the parameter object
  408      * @param string $empty    Value for empty points
  409      *
  410      * @return string WKT with the set of parameters passed by the GIS editor
  411      *
  412      * @access public
  413      */
  414     public function generateWkt(array $gis_data, $index, $empty = '')
  415     {
  416         $data_row = $gis_data[$index]['MULTIPOLYGON'];
  417 
  418         $no_of_polygons = $data_row['no_of_polygons'] ?? 1;
  419         if ($no_of_polygons < 1) {
  420             $no_of_polygons = 1;
  421         }
  422 
  423         $wkt = 'MULTIPOLYGON(';
  424         for ($k = 0; $k < $no_of_polygons; $k++) {
  425             $no_of_lines = $data_row[$k]['no_of_lines'] ?? 1;
  426             if ($no_of_lines < 1) {
  427                 $no_of_lines = 1;
  428             }
  429             $wkt .= '(';
  430             for ($i = 0; $i < $no_of_lines; $i++) {
  431                 $no_of_points = $data_row[$k][$i]['no_of_points'] ?? 4;
  432                 if ($no_of_points < 4) {
  433                     $no_of_points = 4;
  434                 }
  435                 $wkt .= '(';
  436                 for ($j = 0; $j < $no_of_points; $j++) {
  437                     $wkt .= (isset($data_row[$k][$i][$j]['x'])
  438                             && trim((string) $data_row[$k][$i][$j]['x']) != ''
  439                             ? $data_row[$k][$i][$j]['x'] : $empty)
  440                         . ' ' . (isset($data_row[$k][$i][$j]['y'])
  441                             && trim((string) $data_row[$k][$i][$j]['y']) != ''
  442                             ? $data_row[$k][$i][$j]['y'] : $empty) . ',';
  443                 }
  444                 $wkt
  445                     = mb_substr(
  446                         $wkt,
  447                         0,
  448                         mb_strlen($wkt) - 1
  449                     );
  450                 $wkt .= '),';
  451             }
  452             $wkt
  453                 = mb_substr(
  454                     $wkt,
  455                     0,
  456                     mb_strlen($wkt) - 1
  457                 );
  458             $wkt .= '),';
  459         }
  460         $wkt
  461             = mb_substr(
  462                 $wkt,
  463                 0,
  464                 mb_strlen($wkt) - 1
  465             );
  466 
  467         return $wkt . ')';
  468     }
  469 
  470     /**
  471      * Generate the WKT for the data from ESRI shape files.
  472      *
  473      * @param array $row_data GIS data
  474      *
  475      * @return string the WKT for the data from ESRI shape files
  476      *
  477      * @access public
  478      */
  479     public function getShape(array $row_data)
  480     {
  481         // Determines whether each line ring is an inner ring or an outer ring.
  482         // If it's an inner ring get a point on the surface which can be used to
  483         // correctly classify inner rings to their respective outer rings.
  484         foreach ($row_data['parts'] as $i => $ring) {
  485             $row_data['parts'][$i]['isOuter']
  486                 = GisPolygon::isOuterRing($ring['points']);
  487         }
  488 
  489         // Find points on surface for inner rings
  490         foreach ($row_data['parts'] as $i => $ring) {
  491             if ($ring['isOuter']) {
  492                 continue;
  493             }
  494 
  495             $row_data['parts'][$i]['pointOnSurface']
  496                 = GisPolygon::getPointOnSurface($ring['points']);
  497         }
  498 
  499         // Classify inner rings to their respective outer rings.
  500         foreach ($row_data['parts'] as $j => $ring1) {
  501             if ($ring1['isOuter']) {
  502                 continue;
  503             }
  504             foreach ($row_data['parts'] as $k => $ring2) {
  505                 if (! $ring2['isOuter']) {
  506                     continue;
  507                 }
  508 
  509                 // If the pointOnSurface of the inner ring
  510                 // is also inside the outer ring
  511                 if (! GisPolygon::isPointInsidePolygon(
  512                     $ring1['pointOnSurface'],
  513                     $ring2['points']
  514                 )
  515                 ) {
  516                     continue;
  517                 }
  518 
  519                 if (! isset($ring2['inner'])) {
  520                     $row_data['parts'][$k]['inner'] = [];
  521                 }
  522                 $row_data['parts'][$k]['inner'][] = $j;
  523             }
  524         }
  525 
  526         $wkt = 'MULTIPOLYGON(';
  527         // for each polygon
  528         foreach ($row_data['parts'] as $ring) {
  529             if (! $ring['isOuter']) {
  530                 continue;
  531             }
  532 
  533             $wkt .= '('; // start of polygon
  534 
  535             $wkt .= '('; // start of outer ring
  536             foreach ($ring['points'] as $point) {
  537                 $wkt .= $point['x'] . ' ' . $point['y'] . ',';
  538             }
  539             $wkt
  540                 = mb_substr(
  541                     $wkt,
  542                     0,
  543                     mb_strlen($wkt) - 1
  544                 );
  545             $wkt .= ')'; // end of outer ring
  546 
  547             // inner rings if any
  548             if (isset($ring['inner'])) {
  549                 foreach ($ring['inner'] as $j) {
  550                     $wkt .= ',('; // start of inner ring
  551                     foreach ($row_data['parts'][$j]['points'] as $innerPoint) {
  552                         $wkt .= $innerPoint['x'] . ' ' . $innerPoint['y'] . ',';
  553                     }
  554                     $wkt
  555                         = mb_substr(
  556                             $wkt,
  557                             0,
  558                             mb_strlen($wkt) - 1
  559                         );
  560                     $wkt .= ')';  // end of inner ring
  561                 }
  562             }
  563 
  564             $wkt .= '),'; // end of polygon
  565         }
  566         $wkt
  567             = mb_substr(
  568                 $wkt,
  569                 0,
  570                 mb_strlen($wkt) - 1
  571             );
  572 
  573         return $wkt . ')';
  574     }
  575 
  576     /**
  577      * Generate parameters for the GIS data editor from the value of the GIS column.
  578      *
  579      * @param string $value Value of the GIS column
  580      * @param int    $index Index of the geometry
  581      *
  582      * @return array params for the GIS data editor from the value of the GIS column
  583      *
  584      * @access public
  585      */
  586     public function generateParams($value, $index = -1)
  587     {
  588         $params = [];
  589         if ($index == -1) {
  590             $index = 0;
  591             $data = GisGeometry::generateParams($value);
  592             $params['srid'] = $data['srid'];
  593             $wkt = $data['wkt'];
  594         } else {
  595             $params[$index]['gis_type'] = 'MULTIPOLYGON';
  596             $wkt = $value;
  597         }
  598 
  599         // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))'
  600         $multipolygon
  601             = mb_substr(
  602                 $wkt,
  603                 15,
  604                 mb_strlen($wkt) - 18
  605             );
  606         // Separate each polygon
  607         $polygons = explode(')),((', $multipolygon);
  608 
  609         $param_row =& $params[$index]['MULTIPOLYGON'];
  610         $param_row['no_of_polygons'] = count($polygons);
  611 
  612         $k = 0;
  613         foreach ($polygons as $polygon) {
  614             // If the polygon doesn't have an inner polygon
  615             if (mb_strpos($polygon, '),(') === false) {
  616                 $param_row[$k]['no_of_lines'] = 1;
  617                 $points_arr = $this->extractPoints($polygon, null);
  618                 $no_of_points = count($points_arr);
  619                 $param_row[$k][0]['no_of_points'] = $no_of_points;
  620                 for ($i = 0; $i < $no_of_points; $i++) {
  621                     $param_row[$k][0][$i]['x'] = $points_arr[$i][0];
  622                     $param_row[$k][0][$i]['y'] = $points_arr[$i][1];
  623                 }
  624             } else {
  625                 // Separate outer and inner polygons
  626                 $parts = explode('),(', $polygon);
  627                 $param_row[$k]['no_of_lines'] = count($parts);
  628                 $j = 0;
  629                 foreach ($parts as $ring) {
  630                     $points_arr = $this->extractPoints($ring, null);
  631                     $no_of_points = count($points_arr);
  632                     $param_row[$k][$j]['no_of_points'] = $no_of_points;
  633                     for ($i = 0; $i < $no_of_points; $i++) {
  634                         $param_row[$k][$j][$i]['x'] = $points_arr[$i][0];
  635                         $param_row[$k][$j][$i]['y'] = $points_arr[$i][1];
  636                     }
  637                     $j++;
  638                 }
  639             }
  640             $k++;
  641         }
  642 
  643         return $params;
  644     }
  645 }