2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * Handles visualization of GIS data
6 * @package PhpMyAdmin-GIS
9 if (! defined('PHPMYADMIN')) {
13 require_once 'libraries/sql.lib.php';
16 * Handles visualization of GIS data
18 * @package PhpMyAdmin-GIS
20 class PMA_GIS_Visualization
23 * @var array Raw data for the visualization
27 private $_modified_sql;
30 * @var array Set of default settings values are here.
32 private $_settings = array(
34 // Array of colors to be used for GIS visualizations.
55 // The width of the GIS visualization.
58 // The height of the GIS visualization.
63 * @var array Options that the user has specified.
65 private $_userSpecifiedSettings = null;
68 * Returns the settings array
70 * @return array the settings array
73 public function getSettings()
75 return $this->_settings
;
81 * @param string $sql_query SQL to fetch raw data for visualization
82 * @param array $options Users specified options
83 * @param integer $row number of rows
84 * @param integer $pos start position
86 * @return PMA_GIS_Visualization
90 public static function get($sql_query, $options, $row, $pos)
92 return new PMA_GIS_Visualization($sql_query, $options, $row, $pos);
98 * @param array $data Raw data, if set, parameters other than $options will be
100 * @param array $options Users specified options
102 * @return PMA_GIS_Visualization
104 public static function getByData($data, $options)
106 return new PMA_GIS_Visualization(null, $options, null, null, $data);
110 * Check if data has SRID
114 public function hasSrid()
116 foreach ($this->_data
as $row) {
117 if ($row['srid'] != 0) {
125 * Constructor. Stores user specified options.
127 * @param string $sql_query SQL to fetch raw data for visualization
128 * @param array $options Users specified options
129 * @param integer $row number of rows
130 * @param integer $pos start position
131 * @param array $data raw data. If set, parameters other than $options
136 private function __construct($sql_query, $options, $row, $pos, $data = null)
138 $this->_userSpecifiedSettings
= $options;
140 $this->_data
= $data;
142 $this->_modified_sql
= $this->_modifySqlQuery($sql_query, $row, $pos);
143 $this->_data
= $this->_fetchRawData();
149 * All the variable initialization, options handling has to be done here.
154 protected function init()
156 $this->_handleOptions();
160 * Returns sql for fetching raw data
162 * @param string $sql_query The SQL to modify.
163 * @param integer $rows Number of rows.
164 * @param integer $pos Start position.
166 * @return string the modified sql query.
168 private function _modifySqlQuery($sql_query, $rows, $pos)
170 $modified_query = 'SELECT ';
171 // If label column is chosen add it to the query
172 if (! empty($this->_userSpecifiedSettings
['labelColumn'])) {
173 $modified_query .= PMA_Util
::backquote(
174 $this->_userSpecifiedSettings
['labelColumn']
178 // Wrap the spatial column with 'ASTEXT()' function and add it
179 $modified_query .= 'ASTEXT('
180 . PMA_Util
::backquote($this->_userSpecifiedSettings
['spatialColumn'])
181 . ') AS ' . PMA_Util
::backquote(
182 $this->_userSpecifiedSettings
['spatialColumn']
187 $modified_query .= 'SRID('
188 . PMA_Util
::backquote($this->_userSpecifiedSettings
['spatialColumn'])
189 . ') AS ' . PMA_Util
::backquote('srid') . ' ';
191 // Append the original query as the inner query
192 $modified_query .= 'FROM (' . $sql_query . ') AS '
193 . PMA_Util
::backquote('temp_gis');
196 if (is_numeric($rows) && $rows > 0) {
197 $modified_query .= ' LIMIT ';
198 if (is_numeric($pos) && $pos >= 0) {
199 $modified_query .= $pos . ', ' . $rows;
201 $modified_query .= $rows;
205 return $modified_query;
209 * Returns raw data for GIS visualization.
211 * @return string the raw data.
213 private function _fetchRawData()
215 $modified_result = $GLOBALS['dbi']->tryQuery($this->_modified_sql
);
218 while ($row = $GLOBALS['dbi']->fetchAssoc($modified_result)) {
226 * A function which handles passed parameters. Useful if desired
227 * chart needs to be a little bit different from the default one.
232 private function _handleOptions()
234 if (! is_null($this->_userSpecifiedSettings
)) {
235 $this->_settings
= array_merge(
237 $this->_userSpecifiedSettings
243 * Sanitizes the file name.
245 * @param string $file_name file name
246 * @param string $ext extension of the file
248 * @return string the sanitized file name
251 private function _sanitizeName($file_name, $ext)
253 $file_name = PMA_sanitizeFilename($file_name);
255 // Check if the user already added extension;
256 // get the substring where the extension would be if it was included
257 $extension_start_pos = /*overload*/mb_strlen($file_name)
258 - /*overload*/mb_strlen($ext) - 1;
259 $user_extension = /*overload*/mb_substr(
260 $file_name, $extension_start_pos, /*overload*/mb_strlen($file_name)
262 $required_extension = "." . $ext;
263 if (/*overload*/mb_strtolower($user_extension) != $required_extension) {
264 $file_name .= $required_extension;
270 * Handles common tasks of writing the visualization to file for various formats.
272 * @param string $file_name file name
273 * @param string $type mime type
274 * @param string $ext extension of the file
279 private function _toFile($file_name, $type, $ext)
281 $file_name = $this->_sanitizeName($file_name, $ext);
282 PMA_downloadHeader($file_name, $type);
286 * Generate the visualization in SVG format.
288 * @return string the generated image resource
291 private function _svg()
295 $output = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' . "\n";
296 $output .= '<svg version="1.1" xmlns:svg="http://www.w3.org/2000/svg"'
297 . ' xmlns="http://www.w3.org/2000/svg"'
298 . ' width="' . $this->_settings
['width'] . '"'
299 . ' height="' . $this->_settings
['height'] . '">';
300 $output .= '<g id="groupPanel">';
302 $scale_data = $this->_scaleDataSet($this->_data
);
303 $output .= $this->_prepareDataSet($this->_data
, $scale_data, 'svg', '');
312 * Get the visualization as a SVG.
314 * @return string the visualization as a SVG
317 public function asSVG()
319 $output = $this->_svg();
324 * Saves as a SVG image to a file.
326 * @param string $file_name File name
331 public function toFileAsSvg($file_name)
333 $img = $this->_svg();
334 $this->_toFile($file_name, 'image/svg+xml', 'svg');
339 * Generate the visualization in PNG format.
341 * @return resource the generated image resource
344 private function _png()
349 $image = imagecreatetruecolor(
350 $this->_settings
['width'],
351 $this->_settings
['height']
354 // fill the background
355 $bg = imagecolorallocate($image, 229, 229, 229);
356 imagefilledrectangle(
357 $image, 0, 0, $this->_settings
['width'] - 1,
358 $this->_settings
['height'] - 1, $bg
361 $scale_data = $this->_scaleDataSet($this->_data
);
362 $image = $this->_prepareDataSet($this->_data
, $scale_data, 'png', $image);
368 * Get the visualization as a PNG.
370 * @return string the visualization as a PNG
373 public function asPng()
375 $img = $this->_png();
377 // render and save it to variable
379 imagepng($img, null, 9, PNG_ALL_FILTERS
);
381 $output = ob_get_contents();
385 $encoded = base64_encode($output);
386 return '<img src="data:image/png;base64,' . $encoded . '" />';
390 * Saves as a PNG image to a file.
392 * @param string $file_name File name
397 public function toFileAsPng($file_name)
399 $img = $this->_png();
400 $this->_toFile($file_name, 'image/png', 'png');
401 imagepng($img, null, 9, PNG_ALL_FILTERS
);
406 * Get the code for visualization with OpenLayers.
408 * @return string the code for visualization with OpenLayers
411 public function asOl()
414 $scale_data = $this->_scaleDataSet($this->_data
);
417 . 'projection: new OpenLayers.Projection("EPSG:900913"),'
418 . 'displayProjection: new OpenLayers.Projection("EPSG:4326"),'
420 . 'numZoomLevels: 18,'
421 . 'maxResolution: 156543.0339,'
422 . 'maxExtent: new OpenLayers.Bounds('
423 . '-20037508, -20037508, 20037508, 20037508),'
424 . 'restrictedExtent: new OpenLayers.Bounds('
425 . '-20037508, -20037508, 20037508, 20037508)'
427 . 'var map = new OpenLayers.Map("openlayersmap", options);'
428 . 'var layerNone = new OpenLayers.Layer.Boxes('
429 . '"None", {isBaseLayer: true});'
430 . 'var layerMapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");'
431 . 'var layerCycleMap = new OpenLayers.Layer.OSM.CycleMap("CycleMap");'
432 . 'map.addLayers([layerMapnik,layerCycleMap,layerNone]);'
433 . 'var vectorLayer = new OpenLayers.Layer.Vector("Data");'
435 $output .= $this->_prepareDataSet($this->_data
, $scale_data, 'ol', '');
437 'map.addLayer(vectorLayer);'
438 . 'map.zoomToExtent(bound);'
439 . 'if (map.getZoom() < 2) {'
442 . 'map.addControl(new OpenLayers.Control.LayerSwitcher());'
443 . 'map.addControl(new OpenLayers.Control.MousePosition());';
448 * Saves as a PDF to a file.
450 * @param string $file_name File name
455 public function toFileAsPdf($file_name)
459 include_once './libraries/tcpdf/tcpdf.php';
463 '', 'pt', $GLOBALS['cfg']['PDFDefaultPageSize'], true, 'UTF-8', false
466 // disable header and footer
467 $pdf->setPrintHeader(false);
468 $pdf->setPrintFooter(false);
470 //set auto page breaks
471 $pdf->SetAutoPageBreak(false);
476 $scale_data = $this->_scaleDataSet($this->_data
);
477 $pdf = $this->_prepareDataSet($this->_data
, $scale_data, 'pdf', $pdf);
479 // sanitize file name
480 $file_name = $this->_sanitizeName($file_name, 'pdf');
481 $pdf->Output($file_name, 'D');
485 * Convert file to image
487 * @param string $format Output format
489 * @return string File
491 public function toImage($format)
493 if ($format == 'svg') {
494 return $this->asSvg();
495 } elseif ($format == 'png') {
496 return $this->asPng();
497 } elseif ($format == 'ol') {
498 return $this->asOl();
503 * Convert file to given format
505 * @param string $filename Filename
506 * @param string $format Output format
510 public function toFile($filename, $format)
512 if ($format == 'svg') {
513 $this->toFileAsSvg($filename);
514 } elseif ($format == 'png') {
515 $this->toFileAsPng($filename);
516 } elseif ($format == 'pdf') {
517 $this->toFileAsPdf($filename);
522 * Calculates the scale, horizontal and vertical offset that should be used.
524 * @param array $data Row data
526 * @return array an array containing the scale, x and y offsets
529 private function _scaleDataSet($data)
533 // effective width and height of the plot
534 $plot_width = $this->_settings
['width'] - 2 * $border;
535 $plot_height = $this->_settings
['height'] - 2 * $border;
537 foreach ($data as $row) {
539 // Figure out the data type
540 $ref_data = $row[$this->_settings
['spatialColumn']];
541 $type_pos = /*overload*/mb_stripos($ref_data, '(');
542 $type = /*overload*/mb_substr($ref_data, 0, $type_pos);
544 $gis_obj = PMA_GIS_Factory
::factory($type);
548 $scale_data = $gis_obj->scaleRow(
549 $row[$this->_settings
['spatialColumn']]
552 // Update minimum/maximum values for x and y coordinates.
553 $c_maxX = (float) $scale_data['maxX'];
554 if (! isset($min_max['maxX']) ||
$c_maxX > $min_max['maxX']) {
555 $min_max['maxX'] = $c_maxX;
558 $c_minX = (float) $scale_data['minX'];
559 if (! isset($min_max['minX']) ||
$c_minX < $min_max['minX']) {
560 $min_max['minX'] = $c_minX;
563 $c_maxY = (float) $scale_data['maxY'];
564 if (! isset($min_max['maxY']) ||
$c_maxY > $min_max['maxY']) {
565 $min_max['maxY'] = $c_maxY;
568 $c_minY = (float) $scale_data['minY'];
569 if (! isset($min_max['minY']) ||
$c_minY < $min_max['minY']) {
570 $min_max['minY'] = $c_minY;
574 // scale the visualization
575 $x_ratio = ($min_max['maxX'] - $min_max['minX']) / $plot_width;
576 $y_ratio = ($min_max['maxY'] - $min_max['minY']) / $plot_height;
577 $ratio = ($x_ratio > $y_ratio) ?
$x_ratio : $y_ratio;
579 $scale = ($ratio != 0) ?
(1 / $ratio) : 1;
581 if ($x_ratio < $y_ratio) {
582 // center horizontally
583 $x = ($min_max['maxX'] +
$min_max['minX'] - $plot_width / $scale) / 2;
585 $y = $min_max['minY'] - ($border / $scale);
588 $x = $min_max['minX'] - ($border / $scale);
590 $y =($min_max['maxY'] +
$min_max['minY'] - $plot_height / $scale) / 2;
597 'minX' => $min_max['minX'],
598 'maxX' => $min_max['maxX'],
599 'minY' => $min_max['minY'],
600 'maxY' => $min_max['maxY'],
601 'height' => $this->_settings
['height'],
606 * Prepares and return the dataset as needed by the visualization.
608 * @param array $data Raw data
609 * @param array $scale_data Data related to scaling
610 * @param string $format Format of the visualization
611 * @param object $results Image object in the case of png
612 * TCPDF object in the case of pdf
614 * @return mixed the formatted array of data
617 private function _prepareDataSet($data, $scale_data, $format, $results)
621 // loop through the rows
622 foreach ($data as $row) {
623 $index = $color_number %
sizeof($this->_settings
['colors']);
625 // Figure out the data type
626 $ref_data = $row[$this->_settings
['spatialColumn']];
627 $type_pos = /*overload*/mb_stripos($ref_data, '(');
628 $type = /*overload*/mb_substr($ref_data, 0, $type_pos);
630 $gis_obj = PMA_GIS_Factory
::factory($type);
635 if (isset($this->_settings
['labelColumn'])
636 && isset($row[$this->_settings
['labelColumn']])
638 $label = $row[$this->_settings
['labelColumn']];
641 if ($format == 'svg') {
642 $results .= $gis_obj->prepareRowAsSvg(
643 $row[$this->_settings
['spatialColumn']], $label,
644 $this->_settings
['colors'][$index], $scale_data
646 } elseif ($format == 'png') {
647 $results = $gis_obj->prepareRowAsPng(
648 $row[$this->_settings
['spatialColumn']], $label,
649 $this->_settings
['colors'][$index], $scale_data, $results
651 } elseif ($format == 'pdf') {
652 $results = $gis_obj->prepareRowAsPdf(
653 $row[$this->_settings
['spatialColumn']], $label,
654 $this->_settings
['colors'][$index], $scale_data, $results
656 } elseif ($format == 'ol') {
657 $results .= $gis_obj->prepareRowAsOl(
658 $row[$this->_settings
['spatialColumn']], $row['srid'],
659 $label, $this->_settings
['colors'][$index], $scale_data
668 * Set user specified settings
670 * @param array $userSpecifiedSettings User specified settings
674 public function setUserSpecifiedSettings($userSpecifiedSettings)
676 $this->_userSpecifiedSettings
= $userSpecifiedSettings;