diff options
| author | Charles <sircharlesaze@gmail.com> | 2020-01-09 10:55:03 +0100 |
|---|---|---|
| committer | Charles <sircharlesaze@gmail.com> | 2020-01-09 13:09:38 +0100 |
| commit | 04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa (patch) | |
| tree | 5c691241355c943a3c68ddb06b8cf8c60aa11319 /srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php | |
| parent | 7e0d85db834d6351ed85d01e5126ac31dc510b86 (diff) | |
| download | ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.tar.gz ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.tar.bz2 ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.zip | |
phpmyadmin working
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php')
| -rw-r--r-- | srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php b/srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php new file mode 100644 index 0000000..19f5ca8 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Gis/GisVisualization.php @@ -0,0 +1,726 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Handles visualization of GIS data + * + * @package PhpMyAdmin-GIS + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Gis; + +use PhpMyAdmin\Core; +use PhpMyAdmin\Sanitize; +use PhpMyAdmin\Util; +use TCPDF; + +/** + * Handles visualization of GIS data + * + * @package PhpMyAdmin-GIS + */ +class GisVisualization +{ + /** + * @var array Raw data for the visualization + */ + private $_data; + private $_modified_sql; + /** + * @var array Set of default settings values are here. + */ + private $_settings = [ + // Array of colors to be used for GIS visualizations. + 'colors' => [ + '#B02EE0', + '#E0642E', + '#E0D62E', + '#2E97E0', + '#BCE02E', + '#E02E75', + '#5CE02E', + '#E0B02E', + '#0022E0', + '#726CB1', + '#481A36', + '#BAC658', + '#127224', + '#825119', + '#238C74', + '#4C489B', + '#87C9BF', + ], + // The width of the GIS visualization. + 'width' => 600, + // The height of the GIS visualization. + 'height' => 450, + ]; + /** + * @var array Options that the user has specified. + */ + private $_userSpecifiedSettings = null; + + /** + * Returns the settings array + * + * @return array the settings array + * @access public + */ + public function getSettings() + { + return $this->_settings; + } + + /** + * Factory + * + * @param string $sql_query SQL to fetch raw data for visualization + * @param array $options Users specified options + * @param integer $row number of rows + * @param integer $pos start position + * + * @return GisVisualization + * + * @access public + */ + public static function get($sql_query, array $options, $row, $pos) + { + return new GisVisualization($sql_query, $options, $row, $pos); + } + + /** + * Get visualization + * + * @param array $data Raw data, if set, parameters other than $options will be + * ignored + * @param array $options Users specified options + * + * @return GisVisualization + */ + public static function getByData(array $data, array $options) + { + return new GisVisualization(null, $options, null, null, $data); + } + + /** + * Check if data has SRID + * + * @return bool + */ + public function hasSrid() + { + foreach ($this->_data as $row) { + if ($row['srid'] != 0) { + return true; + } + } + + return false; + } + + /** + * Constructor. Stores user specified options. + * + * @param string $sql_query SQL to fetch raw data for visualization + * @param array $options Users specified options + * @param integer $row number of rows + * @param integer $pos start position + * @param array|null $data raw data. If set, parameters other than $options + * will be ignored + * + * @access public + */ + private function __construct($sql_query, array $options, $row, $pos, $data = null) + { + $this->_userSpecifiedSettings = $options; + if (isset($data)) { + $this->_data = $data; + } else { + $this->_modified_sql = $this->_modifySqlQuery($sql_query, $row, $pos); + $this->_data = $this->_fetchRawData(); + } + } + + /** + * All the variable initialization, options handling has to be done here. + * + * @return void + * @access protected + */ + protected function init() + { + $this->_handleOptions(); + } + + /** + * Returns sql for fetching raw data + * + * @param string $sql_query The SQL to modify. + * @param integer $rows Number of rows. + * @param integer $pos Start position. + * + * @return string the modified sql query. + */ + private function _modifySqlQuery($sql_query, $rows, $pos) + { + $modified_query = 'SELECT '; + $spatialAsText = 'ASTEXT'; + $spatialSrid = 'SRID'; + + if ($this->_userSpecifiedSettings['mysqlVersion'] >= 50600) { + $spatialAsText = 'ST_ASTEXT'; + $spatialSrid = 'ST_SRID'; + } + + // If label column is chosen add it to the query + if (! empty($this->_userSpecifiedSettings['labelColumn'])) { + $modified_query .= Util::backquote( + $this->_userSpecifiedSettings['labelColumn'] + ) + . ', '; + } + // Wrap the spatial column with 'ST_ASTEXT()' function and add it + $modified_query .= $spatialAsText . '(' + . Util::backquote($this->_userSpecifiedSettings['spatialColumn']) + . ') AS ' . Util::backquote( + $this->_userSpecifiedSettings['spatialColumn'] + ) + . ', '; + + // Get the SRID + $modified_query .= $spatialSrid . '(' + . Util::backquote($this->_userSpecifiedSettings['spatialColumn']) + . ') AS ' . Util::backquote('srid') . ' '; + + // Append the original query as the inner query + $modified_query .= 'FROM (' . $sql_query . ') AS ' + . Util::backquote('temp_gis'); + + // LIMIT clause + if (is_numeric($rows) && $rows > 0) { + $modified_query .= ' LIMIT '; + if (is_numeric($pos) && $pos >= 0) { + $modified_query .= $pos . ', ' . $rows; + } else { + $modified_query .= $rows; + } + } + + return $modified_query; + } + + /** + * Returns raw data for GIS visualization. + * + * @return array the raw data. + */ + private function _fetchRawData() + { + $modified_result = $GLOBALS['dbi']->tryQuery($this->_modified_sql); + + if ($modified_result === false) { + return []; + } + + $data = []; + while ($row = $GLOBALS['dbi']->fetchAssoc($modified_result)) { + $data[] = $row; + } + + return $data; + } + + /** + * A function which handles passed parameters. Useful if desired + * chart needs to be a little bit different from the default one. + * + * @return void + * @access private + */ + private function _handleOptions() + { + if ($this->_userSpecifiedSettings !== null) { + $this->_settings = array_merge( + $this->_settings, + $this->_userSpecifiedSettings + ); + } + } + + /** + * Sanitizes the file name. + * + * @param string $file_name file name + * @param string $ext extension of the file + * + * @return string the sanitized file name + * @access private + */ + private function _sanitizeName($file_name, $ext) + { + $file_name = Sanitize::sanitizeFilename($file_name); + + // Check if the user already added extension; + // get the substring where the extension would be if it was included + $extension_start_pos = mb_strlen($file_name) - mb_strlen($ext) - 1; + $user_extension + = mb_substr( + $file_name, + $extension_start_pos, + mb_strlen($file_name) + ); + $required_extension = "." . $ext; + if (mb_strtolower($user_extension) != $required_extension) { + $file_name .= $required_extension; + } + + return $file_name; + } + + /** + * Handles common tasks of writing the visualization to file for various formats. + * + * @param string $file_name file name + * @param string $type mime type + * @param string $ext extension of the file + * + * @return void + * @access private + */ + private function _toFile($file_name, $type, $ext) + { + $file_name = $this->_sanitizeName($file_name, $ext); + Core::downloadHeader($file_name, $type); + } + + /** + * Generate the visualization in SVG format. + * + * @return string the generated image resource + * @access private + */ + private function _svg() + { + $this->init(); + + $output = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + . "\n" + . '<svg version="1.1" xmlns:svg="http://www.w3.org/2000/svg"' + . ' xmlns="http://www.w3.org/2000/svg"' + . ' width="' . intval($this->_settings['width']) . '"' + . ' height="' . intval($this->_settings['height']) . '">' + . '<g id="groupPanel">'; + + $scale_data = $this->_scaleDataSet($this->_data); + $output .= $this->_prepareDataSet($this->_data, $scale_data, 'svg', ''); + + $output .= '</g></svg>'; + + return $output; + } + + /** + * Get the visualization as a SVG. + * + * @return string the visualization as a SVG + * @access public + */ + public function asSVG() + { + return $this->_svg(); + } + + /** + * Saves as a SVG image to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsSvg($file_name) + { + $img = $this->_svg(); + $this->_toFile($file_name, 'image/svg+xml', 'svg'); + echo($img); + } + + /** + * Generate the visualization in PNG format. + * + * @return resource the generated image resource + * @access private + */ + private function _png() + { + $this->init(); + + // create image + $image = imagecreatetruecolor( + $this->_settings['width'], + $this->_settings['height'] + ); + + // fill the background + $bg = imagecolorallocate($image, 229, 229, 229); + imagefilledrectangle( + $image, + 0, + 0, + $this->_settings['width'] - 1, + $this->_settings['height'] - 1, + $bg + ); + + $scale_data = $this->_scaleDataSet($this->_data); + $image = $this->_prepareDataSet($this->_data, $scale_data, 'png', $image); + + return $image; + } + + /** + * Get the visualization as a PNG. + * + * @return string the visualization as a PNG + * @access public + */ + public function asPng() + { + $img = $this->_png(); + + // render and save it to variable + ob_start(); + imagepng($img, null, 9, PNG_ALL_FILTERS); + imagedestroy($img); + $output = ob_get_contents(); + ob_end_clean(); + + // base64 encode + $encoded = base64_encode($output); + + return '<img src="data:image/png;base64,' . $encoded . '">'; + } + + /** + * Saves as a PNG image to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsPng($file_name) + { + $img = $this->_png(); + $this->_toFile($file_name, 'image/png', 'png'); + imagepng($img, null, 9, PNG_ALL_FILTERS); + imagedestroy($img); + } + + /** + * Get the code for visualization with OpenLayers. + * + * @todo Should return JSON to avoid eval() in gis_data_editor.js + * + * @return string the code for visualization with OpenLayers + * @access public + */ + public function asOl() + { + $this->init(); + $scale_data = $this->_scaleDataSet($this->_data); + $output + = 'if (typeof OpenLayers !== "undefined") {' + . 'var options = {' + . 'projection: new OpenLayers.Projection("EPSG:900913"),' + . 'displayProjection: new OpenLayers.Projection("EPSG:4326"),' + . 'units: "m",' + . 'numZoomLevels: 18,' + . 'maxResolution: 156543.0339,' + . 'maxExtent: new OpenLayers.Bounds(' + . '-20037508, -20037508, 20037508, 20037508),' + . 'restrictedExtent: new OpenLayers.Bounds(' + . '-20037508, -20037508, 20037508, 20037508)' + . '};' + . 'var map = new OpenLayers.Map("openlayersmap", options);' + . 'var layerNone = new OpenLayers.Layer.Boxes(' + . '"None", {isBaseLayer: true});' + . 'var layerOSM = new OpenLayers.Layer.OSM("OSM",' + . '[' + . '"https://a.tile.openstreetmap.org/${z}/${x}/${y}.png",' + . '"https://b.tile.openstreetmap.org/${z}/${x}/${y}.png",' + . '"https://c.tile.openstreetmap.org/${z}/${x}/${y}.png"' + . ']);' + . 'map.addLayers([layerOSM,layerNone]);' + . 'var vectorLayer = new OpenLayers.Layer.Vector("Data");' + . 'var bound;'; + $output .= $this->_prepareDataSet($this->_data, $scale_data, 'ol', ''); + $output .= 'map.addLayer(vectorLayer);' + . 'map.zoomToExtent(bound);' + . 'if (map.getZoom() < 2) {' + . 'map.zoomTo(2);' + . '}' + . 'map.addControl(new OpenLayers.Control.LayerSwitcher());' + . 'map.addControl(new OpenLayers.Control.MousePosition());' + . '}'; + + return $output; + } + + /** + * Saves as a PDF to a file. + * + * @param string $file_name File name + * + * @return void + * @access public + */ + public function toFileAsPdf($file_name) + { + $this->init(); + + // create pdf + $pdf = new TCPDF( + '', + 'pt', + $GLOBALS['cfg']['PDFDefaultPageSize'], + true, + 'UTF-8', + false + ); + + // disable header and footer + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + + //set auto page breaks + $pdf->SetAutoPageBreak(false); + + // add a page + $pdf->AddPage(); + + $scale_data = $this->_scaleDataSet($this->_data); + $pdf = $this->_prepareDataSet($this->_data, $scale_data, 'pdf', $pdf); + + // sanitize file name + $file_name = $this->_sanitizeName($file_name, 'pdf'); + $pdf->Output($file_name, 'D'); + } + + /** + * Convert file to image + * + * @param string $format Output format + * + * @return string File + */ + public function toImage($format) + { + if ($format == 'svg') { + return $this->asSVG(); + } elseif ($format == 'png') { + return $this->asPng(); + } elseif ($format == 'ol') { + return $this->asOl(); + } + } + + /** + * Convert file to given format + * + * @param string $filename Filename + * @param string $format Output format + * + * @return void + */ + public function toFile($filename, $format) + { + if ($format == 'svg') { + $this->toFileAsSvg($filename); + } elseif ($format == 'png') { + $this->toFileAsPng($filename); + } elseif ($format == 'pdf') { + $this->toFileAsPdf($filename); + } + } + + /** + * Calculates the scale, horizontal and vertical offset that should be used. + * + * @param array $data Row data + * + * @return array an array containing the scale, x and y offsets + * @access private + */ + private function _scaleDataSet(array $data) + { + $min_max = [ + 'maxX' => 0.0, + 'maxY' => 0.0, + 'minX' => 0.0, + 'minY' => 0.0, + ]; + $border = 15; + // effective width and height of the plot + $plot_width = $this->_settings['width'] - 2 * $border; + $plot_height = $this->_settings['height'] - 2 * $border; + + foreach ($data as $row) { + // Figure out the data type + $ref_data = $row[$this->_settings['spatialColumn']]; + $type_pos = mb_strpos($ref_data, '('); + if ($type_pos === false) { + continue; + } + $type = mb_substr($ref_data, 0, $type_pos); + + $gis_obj = GisFactory::factory($type); + if (! $gis_obj) { + continue; + } + $scale_data = $gis_obj->scaleRow( + $row[$this->_settings['spatialColumn']] + ); + + // 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']) { + $min_max['maxX'] = $c_maxX; + } + + $c_minX = (float) $scale_data['minX']; + if ($min_max['minX'] === 0.0 || $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']) { + $min_max['maxY'] = $c_maxY; + } + + $c_minY = (float) $scale_data['minY']; + if ($min_max['minY'] === 0.0 || $c_minY < $min_max['minY']) { + $min_max['minY'] = $c_minY; + } + } + + // scale the visualization + $x_ratio = ($min_max['maxX'] - $min_max['minX']) / $plot_width; + $y_ratio = ($min_max['maxY'] - $min_max['minY']) / $plot_height; + $ratio = ($x_ratio > $y_ratio) ? $x_ratio : $y_ratio; + + $scale = ($ratio != 0) ? (1 / $ratio) : 1; + + if ($x_ratio < $y_ratio) { + // center horizontally + $x = ($min_max['maxX'] + $min_max['minX'] - $plot_width / $scale) / 2; + // fit vertically + $y = $min_max['minY'] - ($border / $scale); + } else { + // fit horizontally + $x = $min_max['minX'] - ($border / $scale); + // center vertically + $y = ($min_max['maxY'] + $min_max['minY'] - $plot_height / $scale) / 2; + } + + return [ + 'scale' => $scale, + 'x' => $x, + 'y' => $y, + 'minX' => $min_max['minX'], + 'maxX' => $min_max['maxX'], + 'minY' => $min_max['minY'], + 'maxY' => $min_max['maxY'], + 'height' => $this->_settings['height'], + ]; + } + + /** + * Prepares and return the dataset as needed by the visualization. + * + * @param array $data Raw data + * @param array $scale_data Data related to scaling + * @param string $format Format of the visualization + * @param object $results Image object in the case of png + * TCPDF object in the case of pdf + * + * @return mixed the formatted array of data + * @access private + */ + private function _prepareDataSet(array $data, array $scale_data, $format, $results) + { + $color_number = 0; + + // loop through the rows + foreach ($data as $row) { + $index = $color_number % count($this->_settings['colors']); + + // Figure out the data type + $ref_data = $row[$this->_settings['spatialColumn']]; + $type_pos = mb_strpos($ref_data, '('); + if ($type_pos === false) { + continue; + } + $type = mb_substr($ref_data, 0, $type_pos); + + $gis_obj = GisFactory::factory($type); + if (! $gis_obj) { + continue; + } + $label = ''; + if (isset($this->_settings['labelColumn']) + && isset($row[$this->_settings['labelColumn']]) + ) { + $label = $row[$this->_settings['labelColumn']]; + } + + if ($format == 'svg') { + $results .= $gis_obj->prepareRowAsSvg( + $row[$this->_settings['spatialColumn']], + $label, + $this->_settings['colors'][$index], + $scale_data + ); + } elseif ($format == 'png') { + $results = $gis_obj->prepareRowAsPng( + $row[$this->_settings['spatialColumn']], + $label, + $this->_settings['colors'][$index], + $scale_data, + $results + ); + } elseif ($format == 'pdf') { + $results = $gis_obj->prepareRowAsPdf( + $row[$this->_settings['spatialColumn']], + $label, + $this->_settings['colors'][$index], + $scale_data, + $results + ); + } elseif ($format == 'ol') { + $results .= $gis_obj->prepareRowAsOl( + $row[$this->_settings['spatialColumn']], + $row['srid'], + $label, + $this->_settings['colors'][$index], + $scale_data + ); + } + $color_number++; + } + + return $results; + } + + /** + * Set user specified settings + * + * @param array $userSpecifiedSettings User specified settings + * + * @return void + */ + public function setUserSpecifiedSettings(array $userSpecifiedSettings) + { + $this->_userSpecifiedSettings = $userSpecifiedSettings; + } +} |
