diff --git a/ChangeLog b/ChangeLog index e6c1bc4605..f70eae5870 100644 --- a/ChangeLog +++ b/ChangeLog @@ -44,6 +44,9 @@ phpMyAdmin - ChangeLog - bug #3380946 [export] no uid Query result export (Suhosin limit) + Grid editing in browse mode (replaces row inline edit) + Zoom-search in table Search ++ [interface] Editor for GIS data ++ [import] Import GIS data from ESRI Shapefiles ++ [interface] 'Function based search' for GIS data 3.4.5.0 (not yet released) - bug #3375325 [interface] Page list in navigation frame looks odd diff --git a/Documentation.html b/Documentation.html index 8f7e66f871..758a2bc90f 100644 --- a/Documentation.html +++ b/Documentation.html @@ -2088,9 +2088,10 @@ $cfg['TrustedProxies'] = The name of the directory where temporary files can be stored.

- This is needed to work around limitations of open_basedir - for uploaded files, see - FAQ + This is needed for importing ESRI Shapefiles, see + FAQ + 6.30 and to work around limitations of open_basedir for uploaded + files, see FAQ 1.11.

@@ -4373,6 +4374,41 @@ INSERT INTO REL_towns VALUES ('M', 'Montréal');

Not every table can be put to the chart. Only tables with one, two or three columns can be visualised as a chart. Moreover the table must be in a special format for chart script to understand it. Currently supported formats can be found in the wiki.

+

+ 6.30 Import: How can I import ESRI Shapefiles

+ +

+ An ESRI Shapefile is actually a set of several files, where .shp file contains + geometry data and .dbf file contains data related to those geometry data. + To read data from .dbf file you need to have PHP compiled with the dBase extension + (--enable-dbase). Otherwise only geometry data will be imported. +

+ +

To upload these set of files you can use either of the following methods:

+ +

phpMyAdmin project

diff --git a/gis_data_editor.php b/gis_data_editor.php new file mode 100644 index 0000000000..7614aba536 --- /dev/null +++ b/gis_data_editor.php @@ -0,0 +1,335 @@ +generateParams($_REQUEST['value'])); +} + +// Generate Well Known Text +$srid = (isset($gis_data['srid']) && $gis_data['srid'] != '') ? htmlspecialchars($gis_data['srid']) : 0; +$wkt = $gis_obj->generateWkt($gis_data, 0); +$wkt_with_zero = $gis_obj->generateWkt($gis_data, 0, '0'); +$result = "'" . $wkt . "'," . $srid; + +// Gererate PNG or SVG based visualization +$format = (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER <= 8) ? 'png' : 'svg'; +$visualizationSettings = array('width' => 450, 'height' => 300, 'spatialColumn' => 'wkt'); +$data = array(array('wkt' => $wkt_with_zero, 'srid' => $srid)); +$visualization = PMA_GIS_visualizationResults($data, $visualizationSettings, $format); +$open_layers = PMA_GIS_visualizationResults($data, $visualizationSettings, 'ol'); + +// If the call is to update the WKT and visualization make an AJAX response +if(isset($_REQUEST['generate']) && $_REQUEST['generate'] == true) { + $extra_data = array( + 'result' => $result, + 'visualization' => $visualization, + 'openLayers' => $open_layers, + ); + PMA_ajaxResponse(null, true, $extra_data); +} + +// If the call is to get the whole content, start buffering, skipping and tags +if(isset($_REQUEST['get_gis_editor']) && $_REQUEST['get_gis_editor'] == true) { + ob_start(); +} else { +?> + + + +
+ +
+

+ +'); + // The input field to which the final result should be added and corresponding null checkbox + if (isset($_REQUEST['input_name'])) { + echo(''); + } + echo PMA_generate_common_hidden_inputs(); +?> + +
+
+
+
+
+ /> + +
+ + + + +
+ + " /> + + +
+ + + +
+'); + } + for ($a = 0; $a < $geom_count; $a++) { + if ($geom_type == 'GEOMETRYCOLLECTION') { + echo('

'); echo __("Geometry"); echo($a + 1 . ':
'); + if (isset($gis_data[$a]['gis_type'])) { + $type = $gis_data[$a]['gis_type']; + } else { + $type = $gis_types[0]; + } + echo(''); + echo(''); + } else { + $type = $geom_type; + } + + if ($type == 'POINT') { + echo('
'); echo __("Point"); echo(' :'); +?> + + + +'); + + for ($i = 0; $i < $no_of_points; $i++) { + echo('
'); echo __("Point"); echo($i + 1 . ':'); +?> + + + + + "> +'); + + for ($i = 0; $i < $no_of_lines; $i++) { + echo('
'); + if ($type == 'MULTILINESTRING') { + echo __("Linestring"); echo($i + 1 . ':'); + } else { + if ($i == 0) { + echo __("Outer Ring:"); + } else { + echo __("Inner Ring"); echo($i . ':'); + } + } + + $no_of_points = isset($gis_data[$a][$type][$i]['no_of_points']) ? $gis_data[$a][$type][$i]['no_of_points'] : 2; + if ($type == 'MULTILINESTRING' && $no_of_points < 2) { + $no_of_points = 2; + } + if ($type == 'POLYGON' && $no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$i]['add_point'])) { + $no_of_points++; + } + echo(''); + + for ($j = 0; $j < $no_of_points; $j++) { + echo('
'); echo __("Point"); echo($j + 1 . ':'); +?> + + + + "> +
+'); + + for ($k = 0; $k < $no_of_polygons; $k++) { + echo('
'); echo __("Polygon"); echo($k + 1 . ':'); + $no_of_lines = isset($gis_data[$a][$type][$k]['no_of_lines']) ? $gis_data[$a][$type][$k]['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + if (isset($gis_data[$a][$type][$k]['add_line'])) { + $no_of_lines++; + } + echo(''); + + for ($i = 0; $i < $no_of_lines; $i++) { + echo('

'); + if ($i == 0) { + echo __("Outer Ring:"); + } else { + echo __("Inner Ring"); echo($i . ':'); + } + + $no_of_points = isset($gis_data[$a][$type][$k][$i]['no_of_points']) ? $gis_data[$a][$type][$k][$i]['no_of_points'] : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + if (isset($gis_data[$a][$type][$k][$i]['add_point'])) { + $no_of_points++; + } + echo(''); + + for ($j = 0; $j < $no_of_points; $j++) { + echo('
'); echo __("Point"); echo($j + 1 . ':'); +?> + " value="" /> + + " value="" /> + " class="add addPoint" value=""> +
" class="add addLine" value="">
+
" class="add addPolygon" value=""> +

" /> +
+ + +
+
+

+

+ +
+
+
+ + + + \ No newline at end of file diff --git a/js/OpenStreetMap.js b/js/OpenStreetMap.js new file mode 100644 index 0000000000..aebfa65db0 --- /dev/null +++ b/js/OpenStreetMap.js @@ -0,0 +1,125 @@ +/** + * Namespace: Util.OSM + */ +OpenLayers.Util.OSM = {}; + +/** + * Constant: MISSING_TILE_URL + * {String} URL of image to display for missing tiles + */ +OpenLayers.Util.OSM.MISSING_TILE_URL = "http://www.openstreetmap.org/openlayers/img/404.png"; + +/** + * Property: originalOnImageLoadError + * {Function} Original onImageLoadError function. + */ +OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError; + +/** + * Function: onImageLoadError + */ +OpenLayers.Util.onImageLoadError = function() { + if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) { + this.src = OpenLayers.Util.OSM.MISSING_TILE_URL; + } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) { + // do nothing - this layer is transparent + } else { + OpenLayers.Util.OSM.originalOnImageLoadError; + } +}; + +/** + * Class: OpenLayers.Layer.OSM.Mapnik + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Mapnik + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 19, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik" +}); + +/** + * Class: OpenLayers.Layer.OSM.Osmarender + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Osmarender + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 18, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender" +}); + +/** + * Class: OpenLayers.Layer.OSM.CycleMap + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.CycleMap + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + numZoomLevels: 19, + buffer: 0, + transitionEffect: "resize" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap" +}); diff --git a/js/gis_data_editor.js b/js/gis_data_editor.js new file mode 100644 index 0000000000..b2bab89ba7 --- /dev/null +++ b/js/gis_data_editor.js @@ -0,0 +1,309 @@ +/** + * @fileoverview functions used in GIS data editor + * + * @requires jQuery + * + */ + +/** + * Closes the GIS data editor and perform necessary clean up work. + */ +function closeGISEditor(){ + $("#popup_background").fadeOut("fast"); + $("#gis_editor").fadeOut("fast"); + $("#gis_editor").html(''); +} + +/** + * Prepares the HTML recieved via AJAX. + */ +function prepareJSVersion() { + // Hide 'Go' buttons associated with the dropdowns + $('.go').hide(); + + // Change the text on the submit button + $("input[name='gis_data[save]']") + .attr('value', PMA_messages['strCopy']) + .insertAfter($('#gis_data_textarea')) + .before('

'); + + // Add close and cancel links + $('#gis_data_editor').prepend('' + PMA_messages['strClose'] + ''); + $(' ' + PMA_messages['strCancel'] + '') + .insertAfter($("input[name='gis_data[save]']")); + + // Remove the unnecessary text + $('div#gis_data_output p').remove(); + + // Remove 'add' buttons and add links + $('.add').each(function(e) { + var $button = $(this); + $button.addClass('addJs').removeClass('add'); + var classes = $button.attr('class'); + $button + .after('+ ' + $button.attr('value') + '') + .remove(); + }); +} + +/** + * Returns the HTML for a data point. + * + * @param pointNumber point number + * @param prefix prefix of the name + * @returns the HTML for a data point + */ +function addDataPoint(pointNumber, prefix) { + return '
' + PMA_messages['strPoint'] + (pointNumber + 1) + ':' + + '' + + '' + + '' + + ''; +} + +/** + * Initialize the visualization in the GIS data editor. + */ +function initGISEditorVisualization() { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +/** + * Opens up the GIS data editor. + * + * @param value current value of the geometry field + * @param field field name + * @param type geometry type + * @param input_name name of the input field + * @param token token + */ +function openGISEditor(value, field, type, input_name, token) { + // Center the popup + var windowWidth = document.documentElement.clientWidth; + var windowHeight = document.documentElement.clientHeight; + var popupWidth = windowWidth * 0.9; + var popupHeight = windowHeight * 0.9; + var popupOffsetTop = windowHeight / 2 - popupHeight / 2; + var popupOffsetLeft = windowWidth / 2 - popupWidth / 2; + var $gis_editor = $("#gis_editor"); + $gis_editor.css({"top": popupOffsetTop, "left": popupOffsetLeft, "width": popupWidth, "height": popupHeight}); + + $.post('gis_data_editor.php', { + 'field' : field, + 'value' : value, + 'type' : type, + 'input_name' : input_name, + 'get_gis_editor' : true, + 'token' : token + }, function(data) { + if(data.success == true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error); + } + }, 'json'); + + // Make it appear + $("#popup_background").css({"opacity":"0.7"}); + $("#popup_background").fadeIn("fast"); + $gis_editor.fadeIn("fast"); +} + +/** + * Prepare and insert the GIS data in Well Known Text format + * to the input field. + */ +function insertDataAndClose() { + var $form = $('form#gis_data_editor_form'); + var input_name = $form.find("input[name='input_name']").val(); + + $.post('gis_data_editor.php', $form.serialize() + "&generate=true", function(data) { + if(data.success == true) { + $("input[name='" + input_name + "']").val(data.result); + } else { + PMA_ajaxShowMessage(data.error); + } + }, 'json'); + closeGISEditor(); +} + +$(document).ready(function() { + + // Remove the class that is added due to the URL being too long. + $('.open_gis_editor a').removeClass('formLinkSubmit'); + + /** + * Prepares and insert the GIS data to the input field on clicking 'copy'. + */ + $("input[name='gis_data[save]']").live('click', function(event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Prepares and insert the GIS data to the input field on pressing 'enter'. + */ + $('#gis_editor').live('submit', function(event) { + event.preventDefault(); + insertDataAndClose(); + }); + + /** + * Trigger asynchronous calls on data change and update the output. + */ + $('#gis_editor').find("input[type='text']").live('change', function() { + var $form = $('form#gis_data_editor_form'); + $.post('gis_data_editor.php', $form.serialize() + "&generate=true", function(data) { + if(data.success == true) { + $('#gis_data_textarea').val(data.result); + $('#placeholder').empty().removeClass('hasSVG').html(data.visualization); + $('#openlayersmap').empty(); + eval(data.openLayers); + initGISEditorVisualization(); + } else { + PMA_ajaxShowMessage(data.error); + } + }, 'json'); + }); + + /** + * Update the form on change of the GIS type. + */ + $(".gis_type").live('change', function(event) { + var $gis_editor = $("#gis_editor"); + var $form = $('form#gis_data_editor_form'); + + $.post('gis_data_editor.php', $form.serialize() + "&get_gis_editor=true", function(data) { + if(data.success == true) { + $gis_editor.html(data.gis_editor); + initGISEditorVisualization(); + prepareJSVersion(); + } else { + PMA_ajaxShowMessage(data.error); + } + }, 'json'); + }); + + /** + * Handles closing of the GIS data editor. + */ + $('.close_gis_editor, .cancel_gis_editor').live('click', function() { + closeGISEditor(); + }); + + /** + * Handles adding data points + */ + $('.addJs.addPoint').live('click', function() { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOINT][add_point] => prefix = gis_data[0][MULTIPOINT] + var prefix = name.substr(0, name.length - 11); + // Find the number of points + var $noOfPointsInput = $("input[name='" + prefix + "[no_of_points]" + "']"); + var noOfPoints = parseInt($noOfPointsInput.attr('value')); + // Add the new data point + var html = addDataPoint(noOfPoints, prefix); + $a.before(html); + $noOfPointsInput.attr('value', noOfPoints + 1); + }); + + /** + * Handles adding linestrings and inner rings + */ + $('.addLine.addJs').live('click', function() { + var $a = $(this); + var name = $a.attr('name'); + + // Eg. name = gis_data[0][MULTILINESTRING][add_line] => prefix = gis_data[0][MULTILINESTRING] + var prefix = name.substr(0, name.length - 10); + var type = prefix.slice(prefix.lastIndexOf('[') + 1, prefix.lastIndexOf(']')); + + // Find the number of lines + var $noOfLinesInput = $("input[name='" + prefix + "[no_of_lines]" + "']"); + var noOfLines = parseInt($noOfLinesInput.attr('value')); + + // Add the new linesting of inner ring based on the type + var html = '
'; + if (type == 'MULTILINESTRING') { + html += PMA_messages['strLineString'] + (noOfLines + 1) + ':'; + var noOfPoints = 2; + } else { + html += PMA_messages['strInnerRing'] + noOfLines + ':'; + var noOfPoints = 4; + } + html += ''; + for (i = 0; i < noOfPoints; i++) { + html += addDataPoint(i, (prefix + '[' + noOfLines + ']')); + } + html += '+ ' + + PMA_messages['strAddPoint'] + '
'; + + $a.before(html); + $noOfLinesInput.attr('value', noOfLines + 1); + }); + + /** + * Handles adding polygons + */ + $('.addJs.addPolygon').live('click', function() { + var $a = $(this); + var name = $a.attr('name'); + // Eg. name = gis_data[0][MULTIPOLYGON][add_polygon] => prefix = gis_data[0][MULTIPOLYGON] + var prefix = name.substr(0, name.length - 13); + // Find the number of polygons + var $noOfPolygonsInput = $("input[name='" + prefix + "[no_of_polygons]" + "']"); + var noOfPolygons = parseInt($noOfPolygonsInput.attr('value')); + + // Add the new polygon + var html = PMA_messages['strPolygon'] + (noOfPolygons + 1) + ':
'; + html += ''; + + '
' + PMA_messages['strOuterRing'] + ':'; + + ''; + for (i = 0; i < 4; i++) { + html += addDataPoint(i, (prefix + '[' + noOfPolygons + '][0]')); + } + html += '+ ' + + PMA_messages['strAddPoint'] + '
' + + '+ ' + + PMA_messages['strAddInnerRing'] + '

'; + + $a.before(html); + $noOfPolygonsInput.attr('value', noOfPolygons + 1); + }); + + /** + * Handles adding geoms + */ + $('.addJs.addGeom').live('click', function() { + var $a = $(this); + var prefix = 'gis_data[GEOMETRYCOLLECTION]'; + // Find the number of geoms + var $noOfGeomsInput = $("input[name='" + prefix + "[geom_count]" + "']"); + var noOfGeoms = parseInt($noOfGeomsInput.attr('value')); + + var html1 = PMA_messages['strGeometry'] + (noOfGeoms + 1) + ':
'; + var $geomType = $("select[name='gis_data[" + (noOfGeoms - 1) + "][gis_type]']").clone(); + $geomType.attr('name', 'gis_data[' + noOfGeoms + '][gis_type]').val('POINT'); + var html2 = '
' + PMA_messages['strPoint'] + ' :' + + '' + + '' + + '' + + '' + + '

'; + + $a.before(html1); $geomType.insertBefore($a); $a.before(html2); + $noOfGeomsInput.attr('value', noOfGeoms + 1); + }); +}); diff --git a/js/messages.php b/js/messages.php index a8c7d108bc..10beb0ad66 100644 --- a/js/messages.php +++ b/js/messages.php @@ -280,6 +280,18 @@ $js_messages['strSameInputs'] = '' . __('Select two different columns') /* For tbl_change.js */ $js_messages['strIgnore'] = __('Ignore'); +$js_messages['strCopy'] = __('Copy'); +$js_messages['strX'] = __('X'); +$js_messages['strY'] = __('Y'); +$js_messages['strPoint'] = __('Point'); +$js_messages['strLineString'] = __('Linestring'); +$js_messages['strPolygon'] = __('Polygon'); +$js_messages['strGeometry'] = __('Geometry'); +$js_messages['strInnerRing'] = __('Inner Ring'); +$js_messages['strOuterRing'] = __('Outer Ring'); +$js_messages['strAddPoint'] = __('Add a point'); +$js_messages['strAddInnerRing'] = __('Add an inner ring'); +$js_messages['strAddPolygon'] = __('Add a polygon'); /* For tbl_structure.js */ $js_messages['strAddColumns'] = __('Add columns'); diff --git a/js/sql.js b/js/sql.js index f4a7475152..99fd93c703 100644 --- a/js/sql.js +++ b/js/sql.js @@ -334,10 +334,10 @@ $(document).ready(function() { */ var button_options = {}; // in the following function we need to use $(this) - button_options[PMA_messages['strCancel']] = function() {$(this).parent().dialog('close').remove();} + button_options[PMA_messages['strCancel']] = function() {$(this).dialog('close').remove();} var button_options_error = {}; - button_options_error[PMA_messages['strOK']] = function() {$(this).parent().dialog('close').remove();} + button_options_error[PMA_messages['strOK']] = function() {$(this).dialog('close').remove();} var $form = $("#resultsForm"); var $msgbox = PMA_ajaxShowMessage(); @@ -351,6 +351,9 @@ $(document).ready(function() { height: 230, width: 900, open: PMA_verifyTypeOfAllColumns, + close: function(event, ui) { + $('#change_row_dialog').remove(); + }, buttons : button_options_error })// end dialog options } else { @@ -361,6 +364,9 @@ $(document).ready(function() { height: 600, width: 900, open: PMA_verifyTypeOfAllColumns, + close: function(event, ui) { + $('#change_row_dialog').remove(); + }, buttons : button_options }) //Remove the top menu container from the dialog diff --git a/js/tbl_change.js b/js/tbl_change.js index 17677b97ee..25bc120349 100644 --- a/js/tbl_change.js +++ b/js/tbl_change.js @@ -218,11 +218,38 @@ function verificationsAfterFieldChange(urlField, multi_edit, theType) * Ajax handlers for Change Table page * * Actions Ajaxified here: - * Submit Data to be inserted into the table + * Submit Data to be inserted into the table. * Restart insertion with 'N' rows. */ $(document).ready(function() { + $('.open_gis_editor').live('click', function(event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children("input[type='text']").val(); + // Field name + var field = $span.parents('tr').children('td:first').find("input[type='hidden']").val(); + // Column type + var type = $span.parents('tr').find('span.column_type').text(); + // Names of input field and null checkbox + var input_name = $span.parent('td').children("input[type='text']").attr('name'); + //Token + var token = $("input[name='token']").val(); + + openGISEditor(value, field, type, input_name, token); + }); + + /** + * Uncheck the null checkbox as geometry data is placed on the input field + */ + $("input[name='gis_data[save]']").live('click', function(event) { + var input_name = $('form#gis_data_editor_form').find("input[name='input_name']").val(); + var $null_checkbox = $("input[name='" + input_name + "']").parents('tr').find('.checkbox_null'); + $null_checkbox.attr('checked', false); + }); + // these were hidden via the "hide" class $('.foreign_values_anchor').show(); diff --git a/js/tbl_gis_visualization.js b/js/tbl_gis_visualization.js index 978cd69308..0a84189c49 100644 --- a/js/tbl_gis_visualization.js +++ b/js/tbl_gis_visualization.js @@ -8,7 +8,9 @@ */ var x = 0; +var default_x = 0; var y = 0; +var default_y = 0; var scale = 1; var svg; @@ -51,56 +53,52 @@ function zoomAndPan() } /** - * Ajax handlers for GIS visualization page - * - * Actions Ajaxified here: - * - * Zooming in and zooming out on mousewheel movement. - * Panning the visualization on dragging. - * Zooming in on double clicking. - * Zooming out on clicking the zoom out button. - * Panning on clicking the arrow buttons. - * Displaying tooltips for GIS objects. + * Initially loads either SVG or OSM visualization based on the choice. */ -$(document).ready(function() { - var $placeholder = $('#placeholder'); - var $openlayersmap = $('#openlayersmap'); - - if ($('#choice').prop('checked') != true) { - $openlayersmap.hide(); +function selectVisualization() { + if ($('#choice').prop('checked') != true) { + $('#openlayersmap').hide(); } else { - $placeholder.hide(); + $('#placeholder').hide(); } + $('.choice').show(); +} +/** + * Adds necessary styles to the div that coontains the openStreetMap. + */ +function styleOSM() { + var $placeholder = $('#placeholder'); var cssObj = { 'border' : '1px solid #aaa', 'width' : $placeholder.width(), 'height' : $placeholder.height(), 'float' : 'right' }; - $openlayersmap.css(cssObj); - drawOpenLayers(); + $('#openlayersmap').css(cssObj); +} - $('.choice').show(); - $('#choice').bind('click', function() { - if ($(this).prop('checked') == false) { - $placeholder.show(); - $openlayersmap.hide(); - } else { - $placeholder.hide(); - $openlayersmap.show(); - } - }); +/** + * Loads the SVG element and make a reference to it. + */ +function loadSVG() { + var $placeholder = $('#placeholder'); - $('#placeholder').svg({ + $placeholder.svg({ onLoad: function(svg_ref) { svg = svg_ref; } }); - // Removes the second SVG element unnecessarily added due to the above command. - $('#placeholder').find('svg:nth-child(2)').remove(); + // Removes the second SVG element unnecessarily added due to the above command + $placeholder.find('svg:nth-child(2)').remove(); +} +/** + * Adds controllers for zooming and panning. + */ +function addZoomPanControllers() { + var $placeholder = $('#placeholder'); if ($("#placeholder svg").length > 0) { var pmaThemeImage = $('#pmaThemeImage').attr('value'); // add panning arrows @@ -113,7 +111,81 @@ $(document).ready(function() { $('').appendTo($placeholder); $('').appendTo($placeholder); } +} +/** + * Resizes the GIS visualization to fit into the space available. + */ +function resizeGISVisualization() { + var $placeholder = $('#placeholder'); + + // Hide inputs for width and height + $("input[name='visualizationSettings[width]']").parents('tr').remove(); + $("input[name='visualizationSettings[height]']").parents('tr').remove(); + + var old_width = $placeholder.width(); + var extraPadding = 100; + var leftWidth = $('.gis_table').width(); + var windowWidth = document.documentElement.clientWidth; + var visWidth = windowWidth - extraPadding - leftWidth; + + // Assign new value for width + $placeholder.width(visWidth); + $('svg').attr('width', visWidth); + + // Assign the offset created due to resizing to default_x and center the svg. + default_x = (visWidth - old_width) / 2; + x = default_x; +} + +/** + * Initialize the GIS visualization. + */ +function initGISVisualization() { + // Loads either SVG or OSM visualization based on the choice + selectVisualization(); + // Resizes the GIS visualization to fit into the space available + resizeGISVisualization(); + // Adds necessary styles to the div that coontains the openStreetMap + styleOSM(); + // Draws openStreetMap with openLayers + drawOpenLayers(); + // Loads the SVG element and make a reference to it + loadSVG(); + // Adds controllers for zooming and panning + addZoomPanControllers(); + zoomAndPan(); +} + +/** + * Ajax handlers for GIS visualization page + * + * Actions Ajaxified here: + * + * Zooming in and zooming out on mousewheel movement. + * Panning the visualization on dragging. + * Zooming in on double clicking. + * Zooming out on clicking the zoom out button. + * Panning on clicking the arrow buttons. + * Displaying tooltips for GIS objects. + */ +$(document).ready(function() { + + // If we are in GIS visualization, initialize it + if ($('.gis_table').length > 0) { + initGISVisualization(); + } + + $('#choice').live('click', function() { + if ($(this).prop('checked') == false) { + $('#placeholder').show(); + $('#openlayersmap').hide(); + } else { + $('#placeholder').hide(); + $('#openlayersmap').show(); + } + }); + $('#placeholder').live('mousewheel', function(event, delta) { if (delta > 0) { //zoom in @@ -135,13 +207,13 @@ $(document).ready(function() { var dragX = 0; var dragY = 0; $('svg').live('dragstart', function(event, dd) { - $placeholder.addClass('placeholderDrag'); + $('#placeholder').addClass('placeholderDrag'); dragX = Math.round(dd.offsetX); dragY = Math.round(dd.offsetY); }); $('svg').live('mouseup', function(event) { - $placeholder.removeClass('placeholderDrag'); + $('#placeholder').removeClass('placeholderDrag'); }); $('svg').live('drag', function(event, dd) { @@ -178,8 +250,8 @@ $(document).ready(function() { $('#zoom_world').live('click', function(e) { e.preventDefault(); scale = 1; - x = 0; - y = 0; + x = default_x; + y = default_y; zoomAndPan(); }); @@ -225,7 +297,7 @@ $(document).ready(function() { */ $('.polygon, .multipolygon, .point, .multipoint, .linestring, .multilinestring, ' + '.geometrycollection').live('mousemove', function(event) { - contents = $(this).attr('name'); + contents = $.trim($(this).attr('name')); $("#tooltip").remove(); if (contents != '') { $('
' + contents + '
').css({ diff --git a/js/tbl_select.js b/js/tbl_select.js index af2242ad5e..4f6ddf0883 100644 --- a/js/tbl_select.js +++ b/js/tbl_select.js @@ -68,7 +68,7 @@ $(document).ready(function() { $("#sqlqueryresults").html(response); $("#sqlqueryresults").trigger('appendAnchor'); $('#tbl_search_form') - // work around for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome. + // workaround for bug #3168569 - Issue on toggling the "Hide search criteria" in chrome. .slideToggle() .hide(); $('#togglesearchformlink') @@ -87,4 +87,91 @@ $(document).ready(function() { }); }) // end $.post() }) + + // Following section is related to the 'function based search' for geometry data types. + // Initialy hide all the open_gis_editor spans + $('.open_search_gis_editor').hide(); + + $('.geom_func').bind('change', function() { + var $geomFuncSelector = $(this); + + var binaryFunctions = [ + 'Contains', + 'Crosses', + 'Disjoint', + 'Equals', + 'Intersects', + 'Overlaps', + 'Touches', + 'Within', + 'MBRContains', + 'MBRDisjoint', + 'MBREquals', + 'MBRIntersects', + 'MBROverlaps', + 'MBRTouches', + 'MBRWithin', + 'ST_Contains', + 'ST_Crosses', + 'ST_Disjoint', + 'ST_Equals', + 'ST_Intersects', + 'ST_Overlaps', + 'ST_Touches', + 'ST_Within', + ]; + + var tempArray = [ + 'Envelope', + 'EndPoint', + 'StartPoint', + 'ExteriorRing', + 'Centroid', + 'PointOnSurface' + ]; + var outputGeomFunctions = binaryFunctions.concat(tempArray); + + // If the chosen function takes two geomerty objects as parameters + var $operator = $geomFuncSelector.parents('tr').find('td:nth-child(5)').find('select'); + if ($.inArray($geomFuncSelector.val(), binaryFunctions) >= 0){ + $operator.attr('readonly', true); + } else { + $operator.attr('readonly', false); + } + + // if the chosen function's output is a geometry, enable GIS editor + var $editorSpan = $geomFuncSelector.parents('tr').find('.open_search_gis_editor'); + if ($.inArray($geomFuncSelector.val(), outputGeomFunctions) >= 0){ + $editorSpan.show(); + } else { + $editorSpan.hide(); + } + + }); + + $('.open_search_gis_editor').live('click', function(event) { + event.preventDefault(); + + var $span = $(this); + // Current value + var value = $span.parent('td').children("input[type='text']").val(); + // Field name + var field = 'Parameter'; + // Column type + var geom_func = $span.parents('tr').find('.geom_func').val(); + if (geom_func == 'Envelope') { + var type = 'polygon'; + } else if (geom_func == 'ExteriorRing') { + var type = 'linestring'; + } else { + var type = 'point'; + } + // Names of input field and null checkbox + var input_name = $span.parent('td').children("input[type='text']").attr('name'); + //Token + var token = $("input[name='token']").val(); + + openGISEditor(value, field, type, input_name, token); + }); + }, 'top.frame_content'); // end $(document).ready() diff --git a/libraries/bfShapeFiles/ShapeFile.lib.php b/libraries/bfShapeFiles/ShapeFile.lib.php new file mode 100644 index 0000000000..0680708c05 --- /dev/null +++ b/libraries/bfShapeFiles/ShapeFile.lib.php @@ -0,0 +1,649 @@ += 0 ; $i--) { + $result .= $binValue{$i}; + } + + return $result; + } + + function packDouble($value, $mode = 'LE') { + $value = (double)$value; + $bin = pack("d", $value); + + //We test if the conversion of an integer (1) is done as LE or BE by default + switch (pack ('L', 1)) { + case pack ('V', 1): //Little Endian + $result = ($mode == 'LE') ? $bin : swap($bin); + break; + case pack ('N', 1): //Big Endian + $result = ($mode == 'BE') ? $bin : swap($bin); + break; + default: //Some other thing, we just return false + $result = FALSE; + } + + return $result; + } + + class ShapeFile { + var $FileName; + + var $SHPFile; + var $SHXFile; + var $DBFFile; + + var $DBFHeader; + + var $lastError = ""; + + var $boundingBox = array("xmin" => 0.0, "ymin" => 0.0, "xmax" => 0.0, "ymax" => 0.0); + var $fileLength = 0; + var $shapeType = 0; + + var $records; + + function ShapeFile($shapeType, $boundingBox = array("xmin" => 0.0, "ymin" => 0.0, "xmax" => 0.0, "ymax" => 0.0), $FileName = NULL) { + $this->shapeType = $shapeType; + $this->boundingBox = $boundingBox; + $this->FileName = $FileName; + $this->fileLength = 50; + } + + function loadFromFile($FileName) { + $this->FileName = $FileName; + + if (($this->_openSHPFile()) && ($this->_openDBFFile())) { + $this->_loadHeaders(); + $this->_loadRecords(); + $this->_closeSHPFile(); + $this->_closeDBFFile(); + } else { + return false; + } + } + + function saveToFile($FileName = NULL) { + if ($FileName != NULL) $this->FileName = $FileName; + + if (($this->_openSHPFile(TRUE)) && ($this->_openSHXFile(TRUE)) && ($this->_openDBFFile(TRUE))) { + $this->_saveHeaders(); + $this->_saveRecords(); + $this->_closeSHPFile(); + $this->_closeSHXFile(); + $this->_closeDBFFile(); + } else { + return false; + } + } + + function addRecord($record) { + if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) { + $record->updateDBFInfo($this->DBFHeader); + } + + $this->fileLength += ($record->getContentLength() + 4); + $this->records[] = $record; + $this->records[count($this->records) - 1]->recordNumber = count($this->records); + + return (count($this->records) - 1); + } + + function deleteRecord($index) { + if (isset($this->records[$index])) { + $this->fileLength -= ($this->records[$index]->getContentLength() + 4); + for ($i = $index; $i < (count($this->records) - 1); $i++) { + $this->records[$i] = $this->records[$i + 1]; + } + unset($this->records[count($this->records) - 1]); + $this->_deleteRecordFromDBF($index); + } + } + + function getDBFHeader() { + return $this->DBFHeader; + } + + function setDBFHeader($header) { + $this->DBFHeader = $header; + + for ($i = 0; $i < count($this->records); $i++) { + $this->records[$i]->updateDBFInfo($header); + } + } + + function getIndexFromDBFData($field, $value) { + $result = -1; + for ($i = 0; $i < (count($this->records) - 1); $i++) { + if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) { + $result = $i; + } + } + + return $result; + } + + function _loadDBFHeader() { + $DBFFile = fopen(str_replace('.*', '.dbf', $this->FileName), 'r'); + + $result = array(); + $buff32 = array(); + $i = 1; + $inHeader = true; + + while ($inHeader) { + if (!feof($DBFFile)) { + $buff32 = fread($DBFFile, 32); + if ($i > 1) { + if (substr($buff32, 0, 1) == chr(13)) { + $inHeader = false; + } else { + $pos = strpos(substr($buff32, 0, 10), chr(0)); + $pos = ($pos == 0 ? 10 : $pos); + + $fieldName = substr($buff32, 0, $pos); + $fieldType = substr($buff32, 11, 1); + $fieldLen = ord(substr($buff32, 16, 1)); + $fieldDec = ord(substr($buff32, 17, 1)); + + array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec)); + } + } + $i++; + } else { + $inHeader = false; + } + } + + fclose($DBFFile); + return($result); + } + + function _deleteRecordFromDBF($index) { + if (@dbase_delete_record($this->DBFFile, $index)) { + @dbase_pack($this->DBFFile); + } + } + + function _loadHeaders() { + fseek($this->SHPFile, 24, SEEK_SET); + $this->fileLength = loadData("N", fread($this->SHPFile, 4)); + + fseek($this->SHPFile, 32, SEEK_SET); + $this->shapeType = loadData("V", fread($this->SHPFile, 4)); + + $this->boundingBox = array(); + $this->boundingBox["xmin"] = loadData("d", fread($this->SHPFile, 8)); + $this->boundingBox["ymin"] = loadData("d", fread($this->SHPFile, 8)); + $this->boundingBox["xmax"] = loadData("d", fread($this->SHPFile, 8)); + $this->boundingBox["ymax"] = loadData("d", fread($this->SHPFile, 8)); + + $this->DBFHeader = $this->_loadDBFHeader(); + } + + function _saveHeaders() { + fwrite($this->SHPFile, pack("NNNNNN", 9994, 0, 0, 0, 0, 0)); + fwrite($this->SHPFile, pack("N", $this->fileLength)); + fwrite($this->SHPFile, pack("V", 1000)); + fwrite($this->SHPFile, pack("V", $this->shapeType)); + fwrite($this->SHPFile, packDouble($this->boundingBox['xmin'])); + fwrite($this->SHPFile, packDouble($this->boundingBox['ymin'])); + fwrite($this->SHPFile, packDouble($this->boundingBox['xmax'])); + fwrite($this->SHPFile, packDouble($this->boundingBox['ymax'])); + fwrite($this->SHPFile, pack("dddd", 0, 0, 0, 0)); + + fwrite($this->SHXFile, pack("NNNNNN", 9994, 0, 0, 0, 0, 0)); + fwrite($this->SHXFile, pack("N", 50 + 4*count($this->records))); + fwrite($this->SHXFile, pack("V", 1000)); + fwrite($this->SHXFile, pack("V", $this->shapeType)); + fwrite($this->SHXFile, packDouble($this->boundingBox['xmin'])); + fwrite($this->SHXFile, packDouble($this->boundingBox['ymin'])); + fwrite($this->SHXFile, packDouble($this->boundingBox['xmax'])); + fwrite($this->SHXFile, packDouble($this->boundingBox['ymax'])); + fwrite($this->SHXFile, pack("dddd", 0, 0, 0, 0)); + } + + function _loadRecords() { + fseek($this->SHPFile, 100); + while (!feof($this->SHPFile)) { + $bByte = ftell($this->SHPFile); + $record = new ShapeRecord(-1); + $record->loadFromFile($this->SHPFile, $this->DBFFile); + $eByte = ftell($this->SHPFile); + if (($eByte <= $bByte) || ($record->lastError != "")) { + return false; + } + + $this->records[] = $record; + } + } + + function _saveRecords() { + if (file_exists(str_replace('.*', '.dbf', $this->FileName))) { + @unlink(str_replace('.*', '.dbf', $this->FileName)); + } + if (!($this->DBFFile = @dbase_create(str_replace('.*', '.dbf', $this->FileName), $this->DBFHeader))) { + return $this->setError(sprintf("It wasn't possible to create the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName))); + } + + $offset = 50; + if (is_array($this->records) && (count($this->records) > 0)) { + reset($this->records); + while (list($index, $record) = each($this->records)) { + //Save the record to the .shp file + $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1); + + //Save the record to the .shx file + fwrite($this->SHXFile, pack("N", $offset)); + fwrite($this->SHXFile, pack("N", $record->getContentLength())); + $offset += (4 + $record->getContentLength()); + } + } + @dbase_pack($this->DBFFile); + } + + function _openSHPFile($toWrite = false) { + $this->SHPFile = @fopen(str_replace('.*', '.shp', $this->FileName), ($toWrite ? "wb+" : "rb")); + if (!$this->SHPFile) { + return $this->setError(sprintf("It wasn't possible to open the Shape file '%s'", str_replace('.*', '.shp', $this->FileName))); + } + + return TRUE; + } + + function _closeSHPFile() { + if ($this->SHPFile) { + fclose($this->SHPFile); + $this->SHPFile = NULL; + } + } + + function _openSHXFile($toWrite = false) { + $this->SHXFile = @fopen(str_replace('.*', '.shx', $this->FileName), ($toWrite ? "wb+" : "rb")); + if (!$this->SHXFile) { + return $this->setError(sprintf("It wasn't possible to open the Index file '%s'", str_replace('.*', '.shx', $this->FileName))); + } + + return TRUE; + } + + function _closeSHXFile() { + if ($this->SHXFile) { + fclose($this->SHXFile); + $this->SHXFile = NULL; + } + } + + function _openDBFFile($toWrite = false) { + $checkFunction = $toWrite ? "is_writable" : "is_readable"; + if (($toWrite) && (!file_exists(str_replace('.*', '.dbf', $this->FileName)))) { + if (!@dbase_create(str_replace('.*', '.dbf', $this->FileName), $this->DBFHeader)) { + return $this->setError(sprintf("It wasn't possible to create the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName))); + } + } + if ($checkFunction(str_replace('.*', '.dbf', $this->FileName))) { + $this->DBFFile = dbase_open(str_replace('.*', '.dbf', $this->FileName), ($toWrite ? 2 : 0)); + if (!$this->DBFFile) { + return $this->setError(sprintf("It wasn't possible to open the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName))); + } + } else { + return $this->setError(sprintf("It wasn't possible to find the DBase file '%s'", str_replace('.*', '.dbf', $this->FileName))); + } + return TRUE; + } + + function _closeDBFFile() { + if ($this->DBFFile) { + dbase_close($this->DBFFile); + $this->DBFFile = NULL; + } + } + + function setError($error) { + $this->lastError = $error; + return false; + } + } + + class ShapeRecord { + var $SHPFile = NULL; + var $DBFFile = NULL; + + var $recordNumber = NULL; + var $shapeType = NULL; + + var $lastError = ""; + + var $SHPData = array(); + var $DBFData = array(); + + function ShapeRecord($shapeType) { + $this->shapeType = $shapeType; + } + + function loadFromFile(&$SHPFile, &$DBFFile) { + $this->SHPFile = $SHPFile; + $this->DBFFile = $DBFFile; + $this->_loadHeaders(); + + switch ($this->shapeType) { + case 0: + $this->_loadNullRecord(); + break; + case 1: + $this->_loadPointRecord(); + break; + case 3: + $this->_loadPolyLineRecord(); + break; + case 5: + $this->_loadPolygonRecord(); + break; + case 8: + $this->_loadMultiPointRecord(); + break; + default: + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + $this->_loadDBFData(); + } + + function saveToFile(&$SHPFile, &$DBFFile, $recordNumber) { + $this->SHPFile = $SHPFile; + $this->DBFFile = $DBFFile; + $this->recordNumber = $recordNumber; + $this->_saveHeaders(); + + switch ($this->shapeType) { + case 0: + $this->_saveNullRecord(); + break; + case 1: + $this->_savePointRecord(); + break; + case 3: + $this->_savePolyLineRecord(); + break; + case 5: + $this->_savePolygonRecord(); + break; + case 8: + $this->_saveMultiPointRecord(); + break; + default: + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + $this->_saveDBFData(); + } + + function updateDBFInfo($header) { + $tmp = $this->DBFData; + unset($this->DBFData); + $this->DBFData = array(); + reset($header); + while (list($key, $value) = each($header)) { + $this->DBFData[$value[0]] = (isset($tmp[$value[0]])) ? $tmp[$value[0]] : ""; + } + } + + function _loadHeaders() { + $this->recordNumber = loadData("N", fread($this->SHPFile, 4)); + $tmp = loadData("N", fread($this->SHPFile, 4)); //We read the length of the record + $this->shapeType = loadData("V", fread($this->SHPFile, 4)); + } + + function _saveHeaders() { + fwrite($this->SHPFile, pack("N", $this->recordNumber)); + fwrite($this->SHPFile, pack("N", $this->getContentLength())); + fwrite($this->SHPFile, pack("V", $this->shapeType)); + } + + function _loadPoint() { + $data = array(); + + $data["x"] = loadData("d", fread($this->SHPFile, 8)); + $data["y"] = loadData("d", fread($this->SHPFile, 8)); + + return $data; + } + + function _savePoint($data) { + fwrite($this->SHPFile, packDouble($data["x"])); + fwrite($this->SHPFile, packDouble($data["y"])); + } + + function _loadNullRecord() { + $this->SHPData = array(); + } + + function _saveNullRecord() { + //Don't save anything + } + + function _loadPointRecord() { + $this->SHPData = $this->_loadPoint(); + } + + function _savePointRecord() { + $this->_savePoint($this->SHPData); + } + + function _loadMultiPointRecord() { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["ymin"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["xmax"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["ymax"] = loadData("d", fread($this->SHPFile, 8)); + + $this->SHPData["numpoints"] = loadData("V", fread($this->SHPFile, 4)); + + for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) { + $this->SHPData["points"][] = $this->_loadPoint(); + } + } + + function _saveMultiPointRecord() { + fwrite($this->SHPFile, pack("dddd", $this->SHPData["xmin"], $this->SHPData["ymin"], $this->SHPData["xmax"], $this->SHPData["ymax"])); + + fwrite($this->SHPFile, pack("V", $this->SHPData["numpoints"])); + + for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) { + $this->_savePoint($this->SHPData["points"][$i]); + } + } + + function _loadPolyLineRecord() { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["ymin"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["xmax"] = loadData("d", fread($this->SHPFile, 8)); + $this->SHPData["ymax"] = loadData("d", fread($this->SHPFile, 8)); + + $this->SHPData["numparts"] = loadData("V", fread($this->SHPFile, 4)); + $this->SHPData["numpoints"] = loadData("V", fread($this->SHPFile, 4)); + + for ($i = 0; $i < $this->SHPData["numparts"]; $i++) { + $this->SHPData["parts"][$i] = loadData("V", fread($this->SHPFile, 4)); + } + + $firstIndex = ftell($this->SHPFile); + $readPoints = 0; + reset($this->SHPData["parts"]); + while (list($partIndex, $partData) = each($this->SHPData["parts"])) { + if (!isset($this->SHPData["parts"][$partIndex]["points"]) || !is_array($this->SHPData["parts"][$partIndex]["points"])) { + $this->SHPData["parts"][$partIndex] = array(); + $this->SHPData["parts"][$partIndex]["points"] = array(); + } + while (!in_array($readPoints, $this->SHPData["parts"]) && ($readPoints < ($this->SHPData["numpoints"])) && !feof($this->SHPFile)) { + $this->SHPData["parts"][$partIndex]["points"][] = $this->_loadPoint(); + $readPoints++; + } + } + + fseek($this->SHPFile, $firstIndex + ($readPoints*16)); + } + + function _savePolyLineRecord() { + fwrite($this->SHPFile, pack("dddd", $this->SHPData["xmin"], $this->SHPData["ymin"], $this->SHPData["xmax"], $this->SHPData["ymax"])); + + fwrite($this->SHPFile, pack("VV", $this->SHPData["numparts"], $this->SHPData["numpoints"])); + + for ($i = 0; $i < $this->SHPData["numparts"]; $i++) { + fwrite($this->SHPFile, pack("V", count($this->SHPData["parts"][$i]))); + } + + reset($this->SHPData["parts"]); + foreach ($this->SHPData["parts"] as $partData){ + reset($partData["points"]); + while (list($pointIndex, $pointData) = each($partData["points"])) { + $this->_savePoint($pointData); + } + } + } + + function _loadPolygonRecord() { + $this->_loadPolyLineRecord(); + } + + function _savePolygonRecord() { + $this->_savePolyLineRecord(); + } + + function addPoint($point, $partIndex = 0) { + switch ($this->shapeType) { + case 0: + //Don't add anything + break; + case 1: + //Substitutes the value of the current point + $this->SHPData = $point; + break; + case 3: + case 5: + //Adds a new point to the selected part + if (!isset($this->SHPData["xmin"]) || ($this->SHPData["xmin"] > $point["x"])) $this->SHPData["xmin"] = $point["x"]; + if (!isset($this->SHPData["ymin"]) || ($this->SHPData["ymin"] > $point["y"])) $this->SHPData["ymin"] = $point["y"]; + if (!isset($this->SHPData["xmax"]) || ($this->SHPData["xmax"] < $point["x"])) $this->SHPData["xmax"] = $point["x"]; + if (!isset($this->SHPData["ymax"]) || ($this->SHPData["ymax"] < $point["y"])) $this->SHPData["ymax"] = $point["y"]; + + $this->SHPData["parts"][$partIndex]["points"][] = $point; + + $this->SHPData["numparts"] = count($this->SHPData["parts"]); + $this->SHPData["numpoints"]++; + break; + case 8: + //Adds a new point + if (!isset($this->SHPData["xmin"]) || ($this->SHPData["xmin"] > $point["x"])) $this->SHPData["xmin"] = $point["x"]; + if (!isset($this->SHPData["ymin"]) || ($this->SHPData["ymin"] > $point["y"])) $this->SHPData["ymin"] = $point["y"]; + if (!isset($this->SHPData["xmax"]) || ($this->SHPData["xmax"] < $point["x"])) $this->SHPData["xmax"] = $point["x"]; + if (!isset($this->SHPData["ymax"]) || ($this->SHPData["ymax"] < $point["y"])) $this->SHPData["ymax"] = $point["y"]; + + $this->SHPData["points"][] = $point; + $this->SHPData["numpoints"]++; + break; + default: + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + } + + function deletePoint($pointIndex = 0, $partIndex = 0) { + switch ($this->shapeType) { + case 0: + //Don't delete anything + break; + case 1: + //Sets the value of the point to zero + $this->SHPData["x"] = 0.0; + $this->SHPData["y"] = 0.0; + break; + case 3: + case 5: + //Deletes the point from the selected part, if exists + if (isset($this->SHPData["parts"][$partIndex]) && isset($this->SHPData["parts"][$partIndex]["points"][$pointIndex])) { + for ($i = $pointIndex; $i < (count($this->SHPData["parts"][$partIndex]["points"]) - 1); $i++) { + $this->SHPData["parts"][$partIndex]["points"][$i] = $this->SHPData["parts"][$partIndex]["points"][$i + 1]; + } + unset($this->SHPData["parts"][$partIndex]["points"][count($this->SHPData["parts"][$partIndex]["points"]) - 1]); + + $this->SHPData["numparts"] = count($this->SHPData["parts"]); + $this->SHPData["numpoints"]--; + } + break; + case 8: + //Deletes the point, if exists + if (isset($this->SHPData["points"][$pointIndex])) { + for ($i = $pointIndex; $i < (count($this->SHPData["points"]) - 1); $i++) { + $this->SHPData["points"][$i] = $this->SHPData["points"][$i + 1]; + } + unset($this->SHPData["points"][count($this->SHPData["points"]) - 1]); + + $this->SHPData["numpoints"]--; + } + break; + default: + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + } + + function getContentLength() { + switch ($this->shapeType) { + case 0: + $result = 0; + break; + case 1: + $result = 10; + break; + case 3: + case 5: + $result = 22 + 2*count($this->SHPData["parts"]); + for ($i = 0; $i < count($this->SHPData["parts"]); $i++) { + $result += 8*count($this->SHPData["parts"][$i]["points"]); + } + break; + case 8: + $result = 20 + 8*count($this->SHPData["points"]); + break; + default: + $result = false; + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + return $result; + } + + function _loadDBFData() { + $this->DBFData = @dbase_get_record_with_names($this->DBFFile, $this->recordNumber); + unset($this->DBFData["deleted"]); + } + + function _saveDBFData() { + unset($this->DBFData["deleted"]); + if ($this->recordNumber <= dbase_numrecords($this->DBFFile)) { + if (!dbase_replace_record($this->DBFFile, array_values($this->DBFData), $this->recordNumber)) { + $this->setError("I wasn't possible to update the information in the DBF file."); + } + } else { + if (!dbase_add_record($this->DBFFile, array_values($this->DBFData))) { + $this->setError("I wasn't possible to add the information to the DBF file."); + } + } + } + + function setError($error) { + $this->lastError = $error; + return false; + } + } + +?> diff --git a/libraries/common.lib.php b/libraries/common.lib.php index 0b89345629..f232b985cb 100644 --- a/libraries/common.lib.php +++ b/libraries/common.lib.php @@ -2073,6 +2073,13 @@ function PMA_getUniqueCondition($handle, $fields_cnt, $fields_meta, $row, $force // this blob won't be part of the final condition $con_val = null; } + } elseif (in_array($meta->type, PMA_getGISDatatypes()) && ! empty($row[$i])) { + // do not build a too big condition + if (strlen($row[$i]) < 5000) { + $condition .= '=0x' . bin2hex($row[$i]) . ' AND'; + } else { + $condition = ''; + } } elseif ($meta->type == 'bit') { $con_val = "= b'" . PMA_printable_bit_value($row[$i], $meta->length) . "'"; } else { @@ -2854,6 +2861,31 @@ function PMA_replace_binary_contents($content) return $result; } +/** + * Converts GIS data to Well Known Text format + * + * @param $data GIS data + * @param $includeSRID Add SRID to the WKT + * @return GIS data in Well Know Text format + */ +function PMA_asWKT($data, $includeSRID = false) { + // Convert to WKT format + $hex = bin2hex($data); + $wktsql = "SELECT ASTEXT(x'" . $hex . "')"; + if ($includeSRID) { + $wktsql .= ", SRID(x'" . $hex . "')"; + } + $wktresult = PMA_DBI_try_query($wktsql, null, PMA_DBI_QUERY_STORE); + $wktarr = PMA_DBI_fetch_row($wktresult, 0); + $wktval = $wktarr[0]; + if ($includeSRID) { + $srid = $wktarr[1]; + $wktval = "'" . $wktval . "'," . $srid; + } + @PMA_DBI_free_result($wktresult); + return $wktval; +} + /** * If the string starts with a \r\n pair (0x0d0a) add an extra \n * @@ -3166,22 +3198,157 @@ function PMA_getSupportedDatatypes($html = false, $selected = '') * @return array list of datatypes */ -function PMA_unsupportedDatatypes() -{ - // These GIS data types are not yet supported. - $no_support_types = array('geometry', - 'point', - 'linestring', - 'polygon', - 'multipoint', - 'multilinestring', - 'multipolygon', - 'geometrycollection' - ); - +function PMA_unsupportedDatatypes() { + $no_support_types = array(); return $no_support_types; } +function PMA_getGISDatatypes($upper_case = false) { + $gis_data_types = array('geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + 'geometrycollection' + ); + if ($upper_case) { + for ($i = 0; $i < count($gis_data_types); $i++) { + $gis_data_types[$i] = strtoupper($gis_data_types[$i]); + } + } + + return $gis_data_types; +} + +/** + * Generates GIS data based on the string passed. + * + * @param string $gis_string GIS string + */ +function PMA_createGISData($gis_string) { + $gis_string = trim($gis_string); + $geom_types = '(POINT|MULTIPOINT|LINESTRING|MULTILINESTRING|POLYGON|MULTIPOLYGON|GEOMETRYCOLLECTION)'; + if (preg_match("/^'" . $geom_types . "\(.*\)',[0-9]*$/i", $gis_string)) { + return 'GeomFromText(' . $gis_string . ')'; + } elseif (preg_match("/^" . $geom_types . "\(.*\)$/i", $gis_string)) { + return "GeomFromText('" . $gis_string . "')"; + } else { + return $gis_string; + } +} + +/** + * Returns the names and details of the functions + * that can be applied on geometry data typess. + * + * @param string $geom_type if provided the output is limited to the functions + * that are applicable to the provided geometry type. + * @param bool $binary if set to false functions that take two geometries + * as arguments will not be included. + * @param bool $display if set to true seperators will be added to the + * output array. + * + * @return array names and details of the functions that can be applied on + * geometry data typess. + */ +function PMA_getGISFunctions($geom_type = null, $binary = true, $display = false) { + + $funcs = array(); + if ($display) { + $funcs[] = array('display' => ' '); + } + + // Unary functions common to all geomety types + $funcs['Dimension'] = array('params' => 1, 'type' => 'int'); + $funcs['Envelope'] = array('params' => 1, 'type' => 'Polygon'); + $funcs['GeometryType'] = array('params' => 1, 'type' => 'text'); + $funcs['SRID'] = array('params' => 1, 'type' => 'int'); + $funcs['IsEmpty'] = array('params' => 1, 'type' => 'int'); + $funcs['IsSimple'] = array('params' => 1, 'type' => 'int'); + + $geom_type = trim(strtolower($geom_type)); + if ($display && $geom_type != 'geometry' && $geom_type != 'multipoint') { + $funcs[] = array('display' => '--------'); + } + + // Unary functions that are specific to each geomety type + if ($geom_type == 'point') { + $funcs['X'] = array('params' => 1, 'type' => 'float'); + $funcs['Y'] = array('params' => 1, 'type' => 'float'); + + } elseif ($geom_type == 'multipoint') { + // no fucntions here + } elseif ($geom_type == 'linestring') { + $funcs['EndPoint'] = array('params' => 1, 'type' => 'point'); + $funcs['GLength'] = array('params' => 1, 'type' => 'float'); + $funcs['NumPoints'] = array('params' => 1, 'type' => 'int'); + $funcs['StartPoint'] = array('params' => 1, 'type' => 'point'); + $funcs['IsRing'] = array('params' => 1, 'type' => 'int'); + + } elseif ($geom_type == 'multilinestring') { + $funcs['GLength'] = array('params' => 1, 'type' => 'float'); + $funcs['IsClosed'] = array('params' => 1, 'type' => 'int'); + + } elseif ($geom_type == 'polygon') { + $funcs['Area'] = array('params' => 1, 'type' => 'float'); + $funcs['ExteriorRing'] = array('params' => 1, 'type' => 'linestring'); + $funcs['NumInteriorRings'] = array('params' => 1, 'type' => 'int'); + + } elseif ($geom_type == 'multipolygon') { + $funcs['Area'] = array('params' => 1, 'type' => 'float'); + $funcs['Centroid'] = array('params' => 1, 'type' => 'point'); + // Not yet implemented in MySQL + //$funcs['PointOnSurface'] = array('params' => 1, 'type' => 'point'); + + } elseif ($geom_type == 'geometrycollection') { + $funcs['NumGeometries'] = array('params' => 1, 'type' => 'int'); + } + + // If we are asked for binary functions as well + if ($binary) { + // section seperator + if ($display) { + $funcs[] = array('display' => '--------'); + } + if (PMA_MYSQL_INT_VERSION < 50601) { + $funcs['Crosses'] = array('params' => 2, 'type' => 'int'); + $funcs['Contains'] = array('params' => 2, 'type' => 'int'); + $funcs['Disjoint'] = array('params' => 2, 'type' => 'int'); + $funcs['Equals'] = array('params' => 2, 'type' => 'int'); + $funcs['Intersects'] = array('params' => 2, 'type' => 'int'); + $funcs['Overlaps'] = array('params' => 2, 'type' => 'int'); + $funcs['Touches'] = array('params' => 2, 'type' => 'int'); + $funcs['Within'] = array('params' => 2, 'type' => 'int'); + } else { + // If MySQl version is greaeter than or equal 5.6.1, use the ST_ prefix. + $funcs['ST_Crosses'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Contains'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Disjoint'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Equals'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Intersects'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Overlaps'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Touches'] = array('params' => 2, 'type' => 'int'); + $funcs['ST_Within'] = array('params' => 2, 'type' => 'int'); + + } + + if ($display) { + $funcs[] = array('display' => '--------'); + } + // Minimum bounding rectangle functions + $funcs['MBRContains'] = array('params' => 2, 'type' => 'int'); + $funcs['MBRDisjoint'] = array('params' => 2, 'type' => 'int'); + $funcs['MBREquals'] = array('params' => 2, 'type' => 'int'); + $funcs['MBRIntersects'] = array('params' => 2, 'type' => 'int'); + $funcs['MBROverlaps'] = array('params' => 2, 'type' => 'int'); + $funcs['MBRTouches'] = array('params' => 2, 'type' => 'int'); + $funcs['MBRWithin'] = array('params' => 2, 'type' => 'int'); + } + return $funcs; +} + /** * Creates a dropdown box with MySQL functions for a particular column. * diff --git a/libraries/config.default.php b/libraries/config.default.php index c40e784f83..9dea34af1e 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -2806,6 +2806,7 @@ if ($cfg['ShowFunctionFields']) { 'FUNC_CHAR' => '', 'FUNC_DATE' => '', 'FUNC_NUMBER' => '', + 'FUNC_SPATIAL' => 'GeomFromText', 'first_timestamp' => 'NOW', 'pk_char36' => 'UUID', ); diff --git a/libraries/display_tbl.lib.php b/libraries/display_tbl.lib.php index 4baa65fbd0..ddcfb7a175 100644 --- a/libraries/display_tbl.lib.php +++ b/libraries/display_tbl.lib.php @@ -1555,65 +1555,67 @@ function PMA_displayTableBody(&$dt_result, &$is_display, $map, $analyzed_sql) // Remove 'grid_edit' from $class as we do not allow to inline-edit geometry data. $class = str_replace('grid_edit', '', $class); - // Display as [GEOMETRY - (size)] - if ('GEOM' == $_SESSION['tmp_user_values']['geometry_display']) { - $geometry_text = PMA_handle_non_printable_contents( - 'GEOMETRY', (isset($row[$i]) ? $row[$i] : ''), $transform_function, - $transform_options, $default_function, $meta - ); - $vertical_display['data'][$row_no][$i] = PMA_buildValueDisplay( - $class, $condition_field, $geometry_text - ); + if (! isset($row[$i]) || is_null($row[$i])) { + $vertical_display['data'][$row_no][$i] = PMA_buildNullDisplay($class, $condition_field); + } elseif ($row[$i] != '') { + // Display as [GEOMETRY - (size)] + if ('GEOM' == $_SESSION['tmp_user_values']['geometry_display']) { + $geometry_text = PMA_handle_non_printable_contents( + 'GEOMETRY', (isset($row[$i]) ? $row[$i] : ''), $transform_function, + $transform_options, $default_function, $meta + ); + $vertical_display['data'][$row_no][$i] = PMA_buildValueDisplay( + $class, $condition_field, $geometry_text + ); - // Display in Well Known Text(WKT) format. - } elseif ('WKT' == $_SESSION['tmp_user_values']['geometry_display']) { - // Convert to WKT format - $wktsql = "SELECT ASTEXT (GeomFromWKB(x'" . PMA_substr(bin2hex($row[$i]), 8) . "'))"; - $wktresult = PMA_DBI_try_query($wktsql, null, PMA_DBI_QUERY_STORE); - $wktarr = PMA_DBI_fetch_row($wktresult, 0); - $wktval = $wktarr[0]; - @PMA_DBI_free_result($wktresult); + // Display in Well Known Text(WKT) format. + } elseif ('WKT' == $_SESSION['tmp_user_values']['geometry_display']) { + // Convert to WKT format + $wktval = PMA_asWKT($row[$i]); - if (PMA_strlen($wktval) > $GLOBALS['cfg']['LimitChars'] - && $_SESSION['tmp_user_values']['display_text'] == 'P' - ) { - $wktval = PMA_substr($wktval, 0, $GLOBALS['cfg']['LimitChars']) . '...'; - $is_field_truncated = true; - } - - $vertical_display['data'][$row_no][$i] = ' $GLOBALS['cfg']['LimitChars'] + if (PMA_strlen($wktval) > $GLOBALS['cfg']['LimitChars'] && $_SESSION['tmp_user_values']['display_text'] == 'P' ) { - $wkbval = PMA_substr($wkbval, 0, $GLOBALS['cfg']['LimitChars']) . '...'; + $wktval = PMA_substr($wktval, 0, $GLOBALS['cfg']['LimitChars']) . '...'; $is_field_truncated = true; } $vertical_display['data'][$row_no][$i] = ' $GLOBALS['cfg']['LimitChars'] + && $_SESSION['tmp_user_values']['display_text'] == 'P' + ) { + $wkbval = PMA_substr($wkbval, 0, $GLOBALS['cfg']['LimitChars']) . '...'; + $is_field_truncated = true; + } + + $vertical_display['data'][$row_no][$i] = ' $srid, 'wkt' => $wkt); + } + /** * Extracts points, scales and returns them as an array. * @@ -141,14 +176,22 @@ abstract class PMA_GIS_Geometry // Extract cordinates of the point $cordinates = explode(" ", $point); - if ($scale_data != null) { - $x = ($cordinates[0] - $scale_data['x']) * $scale_data['scale']; - $y = $scale_data['height'] - ($cordinates[1] - $scale_data['y']) * $scale_data['scale']; + if (isset($cordinates[0]) && trim($cordinates[0]) != '' + && isset($cordinates[1]) && trim($cordinates[1]) != '' + ) { + if ($scale_data != null) { + $x = ($cordinates[0] - $scale_data['x']) * $scale_data['scale']; + $y = $scale_data['height'] - ($cordinates[1] - $scale_data['y']) * $scale_data['scale']; + } else { + $x = trim($cordinates[0]); + $y = trim($cordinates[1]); + } } else { - $x = $cordinates[0]; - $y = $cordinates[1]; + $x = ''; + $y = ''; } + if (! $linear) { $points_arr[] = array($x, $y); } else { diff --git a/libraries/gis/pma_gis_geometrycollection.php b/libraries/gis/pma_gis_geometrycollection.php index a3da6bf3ad..96074d5767 100644 --- a/libraries/gis/pma_gis_geometrycollection.php +++ b/libraries/gis/pma_gis_geometrycollection.php @@ -53,6 +53,9 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry $type = substr($sub_part, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $scale_data = $gis_obj->scaleRow($sub_part); // Upadate minimum/maximum values for x and y cordinates. @@ -102,6 +105,9 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry $type = substr($sub_part, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $image = $gis_obj->prepareRowAsPng($sub_part, $label, $color, $scale_data, $image); } return $image; @@ -130,6 +136,9 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry $type = substr($sub_part, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $pdf = $gis_obj->prepareRowAsPdf($sub_part, $label, $color, $scale_data, $pdf); } return $pdf; @@ -159,6 +168,9 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry $type = substr($sub_part, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $row .= $gis_obj->prepareRowAsSvg($sub_part, $label, $color, $scale_data); } return $row; @@ -190,6 +202,9 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry $type = substr($sub_part, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $row .= $gis_obj->prepareRowAsOl($sub_part, $srid, $label, $color, $scale_data); } return $row; @@ -222,5 +237,71 @@ class PMA_GIS_Geometrycollection extends PMA_GIS_Geometry } return $sub_parts; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $geom_count = (isset($gis_data['GEOMETRYCOLLECTION']['geom_count'])) + ? $gis_data['GEOMETRYCOLLECTION']['geom_count'] : 1; + $wkt = 'GEOMETRYCOLLECTION('; + for ($i = 0; $i < $geom_count; $i++) { + if (isset($gis_data[$i]['gis_type'])) { + $type = $gis_data[$i]['gis_type']; + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $wkt .= $gis_obj->generateWkt($gis_data, $i, $empty) . ','; + } + } + if (isset($gis_data[0]['gis_type'])) { + $wkt = substr($wkt, 0, strlen($wkt) - 1); + } + $wkt .= ')'; + return $wkt; + } + + /** Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value) + { + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + + // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')' + $goem_col = substr($wkt, 19, (strlen($wkt) - 20)); + // Split the geometry collection object to get its constituents. + $sub_parts = $this->_explodeGeomCol($goem_col); + $params['GEOMETRYCOLLECTION']['geom_count'] = count($sub_parts); + + $i = 0; + foreach ($sub_parts as $sub_part) { + $type_pos = stripos($sub_part, '('); + $type = substr($sub_part, 0, $type_pos); + + $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } + $params = array_merge($params, $gis_obj->generateParams($sub_part, $i)); + $i++; + } + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_linestring.php b/libraries/gis/pma_gis_linestring.php index 97113b7d8d..fc36b220ab 100644 --- a/libraries/gis/pma_gis_linestring.php +++ b/libraries/gis/pma_gis_linestring.php @@ -59,6 +59,7 @@ class PMA_GIS_Linestring extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $line_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($line_color, 1, 2)); $green = hexdec(substr($line_color, 3, 2)); $blue = hexdec(substr($line_color, 4, 2)); @@ -77,6 +78,10 @@ class PMA_GIS_Linestring extends PMA_GIS_Geometry $temp_point = $point; } } + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring($image, 1, $points_arr[1][0], $points_arr[1][1], trim($label), $black); + } return $image; } @@ -112,6 +117,12 @@ class PMA_GIS_Linestring extends PMA_GIS_Geometry $temp_point = $point; } } + // print label + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[1][0], $points_arr[1][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } return $pdf; } @@ -196,5 +207,70 @@ class PMA_GIS_Linestring extends PMA_GIS_Geometry . json_encode($style_options) . '));'; return $result; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_points = isset($gis_data[$index]['LINESTRING']['no_of_points']) + ? $gis_data[$index]['LINESTRING']['no_of_points'] : 2; + if ($no_of_points < 2) { + $no_of_points = 2; + } + $wkt = 'LINESTRING('; + for ($i = 0; $i < $no_of_points; $i++) { + $wkt .= ((isset($gis_data[$index]['LINESTRING'][$i]['x']) + && trim($gis_data[$index]['LINESTRING'][$i]['x']) != '') + ? $gis_data[$index]['LINESTRING'][$i]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['LINESTRING'][$i]['y']) + && trim($gis_data[$index]['LINESTRING'][$i]['y']) != '') + ? $gis_data[$index]['LINESTRING'][$i]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'LINESTRING'; + $wkt = $value; + } + + // Trim to remove leading 'LINESTRING(' and trailing ')' + $linestring = substr($wkt, 11, (strlen($wkt) - 12)); + $points_arr = $this->extractPoints($linestring, null); + + $no_of_points = count($points_arr); + $params[$index]['LINESTRING']['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['LINESTRING'][$i]['x'] = $points_arr[$i][0]; + $params[$index]['LINESTRING'][$i]['y'] = $points_arr[$i][1]; + } + + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_multilinestring.php b/libraries/gis/pma_gis_multilinestring.php index 733cc84ff3..f5aa31f3ef 100644 --- a/libraries/gis/pma_gis_multilinestring.php +++ b/libraries/gis/pma_gis_multilinestring.php @@ -68,6 +68,7 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $line_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($line_color, 1, 2)); $green = hexdec(substr($line_color, 3, 2)); $blue = hexdec(substr($line_color, 4, 2)); @@ -78,6 +79,7 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry // Seperate each linestring $linestirngs = explode("),(", $multilinestirng); + $first_line = true; foreach ($linestirngs as $linestring) { $points_arr = $this->extractPoints($linestring, $scale_data); foreach ($points_arr as $point) { @@ -90,6 +92,11 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry } } unset($temp_point); + // print label if applicable + if (isset($label) && trim($label) != '' && $first_line) { + imagestring($image, 1, $points_arr[1][0], $points_arr[1][1], trim($label), $black); + } + $first_line = false; } return $image; } @@ -118,6 +125,7 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry // Seperate each linestring $linestirngs = explode("),(", $multilinestirng); + $first_line = true; foreach ($linestirngs as $linestring) { $points_arr = $this->extractPoints($linestring, $scale_data); foreach ($points_arr as $point) { @@ -130,6 +138,13 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry } } unset($temp_point); + // print label + if (isset($label) && trim($label) != '' && $first_line) { + $pdf->SetXY($points_arr[1][0], $points_arr[1][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + $first_line = false; } return $pdf; } @@ -225,5 +240,109 @@ class PMA_GIS_Multilinestring extends PMA_GIS_Geometry $row .= ')), null, ' . json_encode($style_options) . '));'; return $row; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_lines = isset($gis_data[$index]['MULTILINESTRING']['no_of_lines']) + ? $gis_data[$index]['MULTILINESTRING']['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt = 'MULTILINESTRING('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($gis_data[$index]['MULTILINESTRING'][$i]['no_of_points']) + ? $gis_data[$index]['MULTILINESTRING'][$i]['no_of_points'] : 2; + if ($no_of_points < 2) { + $no_of_points = 2; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($gis_data[$index]['MULTILINESTRING'][$i][$j]['x']) + && trim($gis_data[$index]['MULTILINESTRING'][$i][$j]['x']) != '') + ? $gis_data[$index]['MULTILINESTRING'][$i][$j]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['MULTILINESTRING'][$i][$j]['y']) + && trim($gis_data[$index]['MULTILINESTRING'][$i][$j]['y']) != '') + ? $gis_data[$index]['MULTILINESTRING'][$i][$j]['y'] : $empty) . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return the WKT for the data from ESRI shape files + */ + public function getShape($row_data) + { + $wkt = 'MULTILINESTRING('; + for ($i = 0; $i < $row_data['numparts']; $i++) { + $wkt .= '('; + foreach ($row_data['parts'][$i]['points'] as $point) { + $wkt .= $point['x'] . ' ' . $point['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTILINESTRING'; + $wkt = $value; + } + + // Trim to remove leading 'MULTILINESTRING((' and trailing '))' + $multilinestirng = substr($wkt, 17, (strlen($wkt) - 19)); + // Seperate each linestring + $linestirngs = explode("),(", $multilinestirng); + $params[$index]['MULTILINESTRING']['no_of_lines'] = count($linestirngs); + + $j = 0; + foreach ($linestirngs as $linestring) { + $points_arr = $this->extractPoints($linestring, null); + $no_of_points = count($points_arr); + $params[$index]['MULTILINESTRING'][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTILINESTRING'][$j][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTILINESTRING'][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_multipoint.php b/libraries/gis/pma_gis_multipoint.php index ce20f9248f..b557465b17 100644 --- a/libraries/gis/pma_gis_multipoint.php +++ b/libraries/gis/pma_gis_multipoint.php @@ -59,6 +59,7 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $point_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($point_color, 1, 2)); $green = hexdec(substr($point_color, 3, 2)); $blue = hexdec(substr($point_color, 4, 2)); @@ -70,7 +71,15 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry foreach ($points_arr as $point) { // draw a small circle to mark the point - imagearc($image, $point[0], $point[1], 7, 7, 0, 360, $color); + if ($point[0] != '' && $point[1] != '') { + imagearc($image, $point[0], $point[1], 7, 7, 0, 360, $color); + } + } + // print label for each point + if ((isset($label) && trim($label) != '') + && ($points_arr[0][0] != '' && $points_arr[0][1] != '') + ) { + imagestring($image, 1, $points_arr[0][0], $points_arr[0][1], trim($label), $black); } return $image; } @@ -100,7 +109,17 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry foreach ($points_arr as $point) { // draw a small circle to mark the point - $pdf->Circle($point[0], $point[1], 2, 0, 360, 'D', $line); + if ($point[0] != '' && $point[1] != '') { + $pdf->Circle($point[0], $point[1], 2, 0, 360, 'D', $line); + } + } + // print label for each point + if ((isset($label) && trim($label) != '') + && ($points_arr[0][0] != '' && $points_arr[0][1] != '') + ) { + $pdf->SetXY($points_arr[0][0], $points_arr[0][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); } return $pdf; } @@ -131,12 +150,14 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry $row = ''; foreach ($points_arr as $point) { - $row .= ' $val) { - $row .= ' ' . $option . '="' . trim($val) . '"'; + if ($point[0] != '' && $point[1] != '') { + $row .= ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; } - $row .= '/>'; } return $row; @@ -176,11 +197,15 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry $row = 'new Array('; foreach ($points_arr as $point) { - $row .= '(new OpenLayers.Geometry.Point(' . $point[0] . ', ' . $point[1] - . ')).transform(new OpenLayers.Projection("EPSG:' . $srid - . '"), map.getProjectionObject()), '; + if ($point[0] != '' && $point[1] != '') { + $row .= '(new OpenLayers.Geometry.Point(' . $point[0] . ', ' . $point[1] + . ')).transform(new OpenLayers.Projection("EPSG:' . $srid + . '"), map.getProjectionObject()), '; + } + } + if (substr($row, strlen($row) - 2) == ', ') { + $row = substr($row, 0, strlen($row) - 2); } - $row = substr($row, 0, strlen($row) - 2); $row .= ')'; $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector(' @@ -188,5 +213,88 @@ class PMA_GIS_Multipoint extends PMA_GIS_Geometry . json_encode($style_options) . '));'; return $result; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Multipoint does not adhere to this + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_points = isset($gis_data[$index]['MULTIPOINT']['no_of_points']) + ? $gis_data[$index]['MULTIPOINT']['no_of_points'] : 1; + if ($no_of_points < 1) { + $no_of_points = 1; + } + $wkt = 'MULTIPOINT('; + for ($i = 0; $i < $no_of_points; $i++) { + $wkt .= ((isset($gis_data[$index]['MULTIPOINT'][$i]['x']) + && trim($gis_data[$index]['MULTIPOINT'][$i]['x']) != '') + ? $gis_data[$index]['MULTIPOINT'][$i]['x'] : '') + . ' ' . ((isset($gis_data[$index]['MULTIPOINT'][$i]['y']) + && trim($gis_data[$index]['MULTIPOINT'][$i]['y']) != '') + ? $gis_data[$index]['MULTIPOINT'][$i]['y'] : '') . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return the WKT for the data from ESRI shape files + */ + public function getShape($row_data) + { + $wkt = 'MULTIPOINT('; + for ($i = 0; $i < $row_data['numpoints']; $i++) { + $wkt .= $row_data['points'][$i]['x'] . ' ' . $row_data['points'][$i]['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTIPOINT'; + $wkt = $value; + } + + // Trim to remove leading 'MULTIPOINT(' and trailing ')' + $points = substr($wkt, 11, (strlen($wkt) - 12)); + $points_arr = $this->extractPoints($points, null); + + $no_of_points = count($points_arr); + $params[$index]['MULTIPOINT']['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTIPOINT'][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTIPOINT'][$i]['y'] = $points_arr[$i][1]; + } + + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_multipolygon.php b/libraries/gis/pma_gis_multipolygon.php index 431fc88cb8..b5765efb2e 100644 --- a/libraries/gis/pma_gis_multipolygon.php +++ b/libraries/gis/pma_gis_multipolygon.php @@ -82,6 +82,7 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $fill_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($fill_color, 1, 2)); $green = hexdec(substr($fill_color, 3, 2)); $blue = hexdec(substr($fill_color, 4, 2)); @@ -92,6 +93,7 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry // Seperate each polygon $polygons = explode(")),((", $multipolygon); + $first_poly = true; foreach ($polygons as $polygon) { // If the polygon doesnt have an inner polygon if (strpos($polygon, "),(") === false) { @@ -112,6 +114,15 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry } // draw polygon imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color); + // mark label point if applicable + if (isset($label) && trim($label) != '' && $first_poly) { + $label_point = array($points_arr[2], $points_arr[3]); + } + $first_poly = false; + } + // print label if applicable + if (isset($label_point)) { + imagestring($image, 1, $points_arr[2], $points_arr[3], trim($label), $black); } return $image; } @@ -140,6 +151,7 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry // Seperate each polygon $polygons = explode(")),((", $multipolygon); + $first_poly = true; foreach ($polygons as $polygon) { // If the polygon doesnt have an inner polygon if (strpos($polygon, "),(") === false) { @@ -161,6 +173,18 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry } // draw polygon $pdf->Polygon($points_arr, 'F*', array(), $color, true); + // mark label point if applicable + if (isset($label) && trim($label) != '' && $first_poly) { + $label_point = array($points_arr[2], $points_arr[3]); + } + $first_poly = false; + } + + // print label if applicable + if (isset($label_point)) { + $pdf->SetXY($label_point[0], $label_point[1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); } return $pdf; } @@ -287,5 +311,190 @@ class PMA_GIS_Multipolygon extends PMA_GIS_Geometry return $row; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_polygons = isset($gis_data[$index]['MULTIPOLYGON']['no_of_polygons']) + ? $gis_data[$index]['MULTIPOLYGON']['no_of_polygons'] : 1; + if ($no_of_polygons < 1) { + $no_of_polygons = 1; + } + $wkt = 'MULTIPOLYGON('; + for ($k = 0; $k < $no_of_polygons; $k++) { + $no_of_lines = isset($gis_data[$index]['MULTIPOLYGON'][$k]['no_of_lines']) + ? $gis_data[$index]['MULTIPOLYGON'][$k]['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt .= '('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($gis_data[$index]['MULTIPOLYGON'][$k][$i]['no_of_points']) + ? $gis_data[$index]['MULTIPOLYGON'][$k][$i]['no_of_points'] : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['x']) + && trim($gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['x']) != '') + ? $gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['y']) + && trim($gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['y']) != '') + ? $gis_data[$index]['MULTIPOLYGON'][$k][$i][$j]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return the WKT for the data from ESRI shape files + */ + public function getShape($row_data) + { + // Determines whether each line ring is an inner ring or an outer ring. + // If it's an inner ring get a point on the surface which can be used to + // correctly classify inner rings to their respective outer rings. + require_once './libraries/gis/pma_gis_polygon.php'; + foreach ($row_data['parts'] as $i => $ring) { + $row_data['parts'][$i]['isOuter'] = PMA_GIS_Polygon::isOuterRing($ring['points']); + } + + // Find points on surface for inner rings + foreach ($row_data['parts'] as $i => $ring) { + if (! $ring['isOuter']) { + $row_data['parts'][$i]['pointOnSurface'] = PMA_GIS_Polygon::getPointOnSurface($ring['points']); + } + } + + // Classify inner rings to their respective outer rings. + foreach ($row_data['parts'] as $j => $ring1) { + if (! $ring1['isOuter']) { + foreach ($row_data['parts'] as $k => $ring2) { + if ($ring2['isOuter']) { + // If the pointOnSurface of the inner ring is also inside the outer ring + if (PMA_GIS_Polygon::isPointInsidePolygon($ring1['pointOnSurface'], $ring2['points'])) { + if (! isset($ring2['inner'])) { + $row_data['parts'][$k]['inner'] = array(); + } + $row_data['parts'][$k]['inner'][] = $j; + } + } + } + } + } + + $wkt = 'MULTIPOLYGON('; + // for each polygon + foreach ($row_data['parts'] as $ring) { + if ($ring['isOuter']) { + $wkt .= '('; // start of polygon + + $wkt .= '('; // start of outer ring + foreach($ring['points'] as $point) { + $wkt .= $point['x'] . ' ' . $point['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; // end of outer ring + + // inner rings if any + if (isset($ring['inner'])) { + foreach ($ring['inner'] as $j) { + $wkt .= ',('; // start of inner ring + foreach ($row_data['parts'][$j]['points'] as $innerPoint) { + $wkt .= $innerPoint['x'] . ' ' . $innerPoint['y'] . ','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; // end of inner ring + } + } + + $wkt .= '),'; // end of polygon + } + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + + $wkt .= ')'; // end of multipolygon + return $wkt; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'MULTIPOLYGON'; + $wkt = $value; + } + + // Trim to remove leading 'MULTIPOLYGON(((' and trailing ')))' + $multipolygon = substr($wkt, 15, (strlen($wkt) - 18)); + // Seperate each polygon + $polygons = explode(")),((", $multipolygon); + $params[$index]['MULTIPOLYGON']['no_of_polygons'] = count($polygons); + + $k = 0; + foreach ($polygons as $polygon) { + // If the polygon doesnt have an inner polygon + if (strpos($polygon, "),(") === false) { + $params[$index]['MULTIPOLYGON'][$k]['no_of_lines'] = 1; + $points_arr = $this->extractPoints($polygon, null); + $no_of_points = count($points_arr); + $params[$index]['MULTIPOLYGON'][$k][0]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTIPOLYGON'][$k][0][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTIPOLYGON'][$k][0][$i]['y'] = $points_arr[$i][1]; + } + } else { + // Seperate outer and inner polygons + $parts = explode("),(", $polygon); + $params[$index]['MULTIPOLYGON'][$k]['no_of_lines'] = count($parts); + $j = 0; + foreach ($parts as $ring) { + $points_arr = $this->extractPoints($ring, null); + $no_of_points = count($points_arr); + $params[$index]['MULTIPOLYGON'][$k][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['MULTIPOLYGON'][$k][$j][$i]['x'] = $points_arr[$i][0]; + $params[$index]['MULTIPOLYGON'][$k][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + } + $k++; + } + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_point.php b/libraries/gis/pma_gis_point.php index 31531f31b0..82fb00c4ce 100644 --- a/libraries/gis/pma_gis_point.php +++ b/libraries/gis/pma_gis_point.php @@ -59,6 +59,7 @@ class PMA_GIS_Point extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $point_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($point_color, 1, 2)); $green = hexdec(substr($point_color, 3, 2)); $blue = hexdec(substr($point_color, 4, 2)); @@ -69,7 +70,13 @@ class PMA_GIS_Point extends PMA_GIS_Geometry $points_arr = $this->extractPoints($point, $scale_data); // draw a small circle to mark the point - imagearc($image, $points_arr[0][0], $points_arr[0][1], 7, 7, 0, 360, $color); + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + imagearc($image, $points_arr[0][0], $points_arr[0][1], 7, 7, 0, 360, $color); + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring($image, 1, $points_arr[0][0], $points_arr[0][1], trim($label), $black); + } + } return $image; } @@ -97,7 +104,15 @@ class PMA_GIS_Point extends PMA_GIS_Geometry $points_arr = $this->extractPoints($point, $scale_data); // draw a small circle to mark the point - $pdf->Circle($points_arr[0][0], $points_arr[0][1], 2, 0, 360, 'D', $line); + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $pdf->Circle($points_arr[0][0], $points_arr[0][1], 2, 0, 360, 'D', $line); + // print label if applicable + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[0][0], $points_arr[0][1]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } + } return $pdf; } @@ -126,11 +141,14 @@ class PMA_GIS_Point extends PMA_GIS_Geometry $point = substr($spatial, 6, (strlen($spatial) - 7)); $points_arr = $this->extractPoints($point, $scale_data); - $row = ' $val) { - $row .= ' ' . $option . '="' . trim($val) . '"'; + $row = ''; + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $row .= ' $val) { + $row .= ' ' . $option . '="' . trim($val) . '"'; + } + $row .= '/>'; } - $row .= '/>'; return $row; } @@ -167,12 +185,76 @@ class PMA_GIS_Point extends PMA_GIS_Geometry $point = substr($spatial, 6, (strlen($spatial) - 7)); $points_arr = $this->extractPoints($point, null); - $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector((' - . 'new OpenLayers.Geometry.Point(' . $points_arr[0][0] . ', ' - . $points_arr[0][1] . ').transform(new OpenLayers.Projection("EPSG:' - . $srid . '"), map.getProjectionObject())), null, ' - . json_encode($style_options) . '));'; + if ($points_arr[0][0] != '' && $points_arr[0][1] != '') { + $result .= 'vectorLayer.addFeatures(new OpenLayers.Feature.Vector((' + . 'new OpenLayers.Geometry.Point(' . $points_arr[0][0] . ', ' + . $points_arr[0][1] . ').transform(new OpenLayers.Projection("EPSG:' + . $srid . '"), map.getProjectionObject())), null, ' + . json_encode($style_options) . '));'; + } return $result; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Point deos not adhere to this parameter + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + return 'POINT(' + . ((isset($gis_data[$index]['POINT']['x']) && trim($gis_data[$index]['POINT']['x']) != '') + ? $gis_data[$index]['POINT']['x'] : '') . ' ' + . ((isset($gis_data[$index]['POINT']['y']) && trim($gis_data[$index]['POINT']['y']) != '') + ? $gis_data[$index]['POINT']['y'] : '') . ')'; + } + + /** + * Generate the WKT for the data from ESRI shape files. + * + * @param array $row_data GIS data + * + * @return the WKT for the data from ESRI shape files + */ + public function getShape($row_data) + { + return 'POINT(' . (isset($row_data['x']) ? $row_data['x'] : '') + . ' ' . (isset($row_data['y']) ? $row_data['y'] : '') . ')'; + } + + /** + * Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'POINT'; + $wkt = $value; + } + + // Trim to remove leading 'POINT(' and trailing ')' + $point = substr($wkt, 6, (strlen($wkt) - 7)); + $points_arr = $this->extractPoints($point, null); + + $params[$index]['POINT']['x'] = $points_arr[0][0]; + $params[$index]['POINT']['y'] = $points_arr[0][1]; + + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_polygon.php b/libraries/gis/pma_gis_polygon.php index 02f01738cb..8f461ad73e 100644 --- a/libraries/gis/pma_gis_polygon.php +++ b/libraries/gis/pma_gis_polygon.php @@ -77,6 +77,7 @@ class PMA_GIS_Polygon extends PMA_GIS_Geometry public function prepareRowAsPng($spatial, $label, $fill_color, $scale_data, $image) { // allocate colors + $black = imagecolorallocate($image, 0, 0, 0); $red = hexdec(substr($fill_color, 1, 2)); $green = hexdec(substr($fill_color, 3, 2)); $blue = hexdec(substr($fill_color, 4, 2)); @@ -105,6 +106,10 @@ class PMA_GIS_Polygon extends PMA_GIS_Geometry // draw polygon imagefilledpolygon($image, $points_arr, sizeof($points_arr) / 2, $color); + // print label if applicable + if (isset($label) && trim($label) != '') { + imagestring($image, 1, $points_arr[2], $points_arr[3], trim($label), $black); + } return $image; } @@ -150,6 +155,12 @@ class PMA_GIS_Polygon extends PMA_GIS_Geometry // draw polygon $pdf->Polygon($points_arr, 'F*', array(), $color, true); + // print label if applicable + if (isset($label) && trim($label) != '') { + $pdf->SetXY($points_arr[2], $points_arr[3]); + $pdf->SetFontSize(5); + $pdf->Cell(0, 0, trim($label)); + } return $pdf; } @@ -262,5 +273,248 @@ class PMA_GIS_Polygon extends PMA_GIS_Geometry return $row; } + + /** + * Generate the WKT with the set of parameters passed by the GIS editor. + * + * @param array $gis_data GIS data + * @param int $index Index into the parameter object + * @param string $empty Value for empty points + * + * @return WKT with the set of parameters passed by the GIS editor + */ + public function generateWkt($gis_data, $index, $empty = '') + { + $no_of_lines = isset($gis_data[$index]['POLYGON']['no_of_lines']) + ? $gis_data[$index]['POLYGON']['no_of_lines'] : 1; + if ($no_of_lines < 1) { + $no_of_lines = 1; + } + $wkt = 'POLYGON('; + for ($i = 0; $i < $no_of_lines; $i++) { + $no_of_points = isset($gis_data[$index]['POLYGON'][$i]['no_of_points']) + ? $gis_data[$index]['POLYGON'][$i]['no_of_points'] : 4; + if ($no_of_points < 4) { + $no_of_points = 4; + } + $wkt .= '('; + for ($j = 0; $j < $no_of_points; $j++) { + $wkt .= ((isset($gis_data[$index]['POLYGON'][$i][$j]['x']) + && trim($gis_data[$index]['POLYGON'][$i][$j]['x']) != '') + ? $gis_data[$index]['POLYGON'][$i][$j]['x'] : $empty) + . ' ' . ((isset($gis_data[$index]['POLYGON'][$i][$j]['y']) + && trim($gis_data[$index]['POLYGON'][$i][$j]['y']) != '') + ? $gis_data[$index]['POLYGON'][$i][$j]['y'] : $empty) .','; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= '),'; + } + $wkt = substr($wkt, 0, strlen($wkt) - 1); + $wkt .= ')'; + return $wkt; + } + + /** + * Calculates the area of a closed simple polygon. + * + * @param array $ring array of points forming the ring + * + * @return the area of a closed simple polygon. + */ + public static function area($ring) + { + + $no_of_points = count($ring); + + // If the last point is same as the first point ignore it + $last = count($ring) - 1; + if (($ring[0]['x'] == $ring[$last]['x']) + && ($ring[0]['y'] == $ring[$last]['y']) + ) { + $no_of_points--; + } + + // _n-1 + // A = _1_ \ (X(i) * Y(i+1)) - (Y(i) * X(i+1)) + // 2 /__ + // i=0 + $area = 0; + for ($i = 0; $i < $no_of_points; $i++) { + $j = ($i + 1) % $no_of_points; + $area += $ring[$i]['x'] * $ring[$j]['y']; + $area -= $ring[$i]['y'] * $ring[$j]['x']; + } + $area /= 2.0; + + return $area; + } + + /** + * Determines whether a set of points represents an outer ring. + * If points are in clockwise orientation then, they form an outer ring. + * + * @param array $ring array of points forming the ring + * + * @return whether a set of points represents an outer ring. + */ + public static function isOuterRing($ring) + { + // If area is negative then it's in clockwise orientation, + // i.e. it's an outer ring + if (PMA_GIS_Polygon::area($ring) < 0) { + return true; + } + return false; + } + + /** + * Determines whether a given point is inside a given polygon. + * + * @param array $point x, y coordinates of the point + * @param array $polygon array of points forming the ring + * + * @return whether a given point is inside a given polygon + */ + public static function isPointInsidePolygon($point, $polygon) + { + // If first point is repeated at the end remove it + $last = count($polygon) - 1; + if (($polygon[0]['x'] == $polygon[$last]['x']) + && ($polygon[0]['y'] == $polygon[$last]['y']) + ) { + $polygon = array_slice($polygon, 0, $last); + } + + $no_of_points = count($polygon); + $counter = 0; + + // Use ray casting algorithm + $p1 = $polygon[0]; + for ($i = 1; $i <= $no_of_points; $i++) { + $p2 = $polygon[$i % $no_of_points]; + if ($point['y'] > min(array($p1['y'], $p2['y']))) { + if ($point['y'] <= max(array($p1['y'], $p2['y']))) { + if ($point['x'] <= max(array($p1['x'], $p2['x']))) { + if ($p1['y'] != $p2['y']) { + $xinters = ($point['y'] - $p1['y']) + * ($p2['x'] - $p1['x']) + / ($p2['y'] - $p1['y']) + $p1['x']; + if ($p1['x'] == $p2['x'] || $point['x'] <= $xinters) { + $counter++; + } + } + } + } + } + $p1 = $p2; + } + + if ($counter % 2 == 0) { + return false; + } else { + return true; + } + } + + /** + * Returns a point that is guaranteed to be on the surface of the ring. + * (for simple closed rings) + * + * @param array $ring array of points forming the ring + * + * @return a point on the surface of the ring + */ + public static function getPointOnSurface($ring) + { + // Find two consecutive distinct points. + for ($i = 0; $i < count($ring) - 1; $i++) { + if ($ring[$i]['y'] != $ring[$i + 1]['y']) { + $x0 = $ring[$i]['x']; + $x1 = $ring[$i + 1]['x']; + $y0 = $ring[$i]['y']; + $y1 = $ring[$i + 1]['y']; + break; + } + } + + if (! isset($x0)) { + return false; + } + + // Find the mid point + $x2 = ($x0 + $x1) / 2; + $y2 = ($y0 + $y1) / 2; + + // Always keep $epsilon < 1 to go with the reduction logic down here + $epsilon = 0.1; + $denominator = sqrt(pow(($y1 - $y0), 2) + pow(($x0 - $x1), 2)); + $pointA = array(); $pointB = array(); + + while (true) { + // Get the points on either sides of the line + // with a distance of epsilon to the mid point + $pointA['x'] = $x2 + ($epsilon * ($y1 - $y0)) / $denominator; + $pointA['y'] = $y2 + ($pointA['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0); + + $pointB['x'] = $x2 + ($epsilon * ($y1 - $y0)) / (0 - $denominator); + $pointB['y'] = $y2 + ($pointB['x'] - $x2) * ($x0 - $x1) / ($y1 - $y0); + + // One of the points should be inside the polygon, + // unless epcilon chosen is too large + if (PMA_GIS_Polygon::isPointInsidePolygon($pointA, $ring)) { + return $pointA; + } elseif (PMA_GIS_Polygon::isPointInsidePolygon($pointB, $ring)) { + return $pointB; + } else { + //If both are outside the polygon reduce the epsilon and + //recalculate the points(reduce exponentially for faster convergance) + $epsilon = pow($epsilon, 2); + if ($epsilon == 0) { + return false; + } + } + + } + } + + /** Generate parameters for the GIS data editor from the value of the GIS column. + * + * @param string $value of the GIS column + * @param index $index of the geometry + * + * @return parameters for the GIS data editor from the value of the GIS column + */ + public function generateParams($value, $index = -1) + { + if ($index == -1) { + $index = 0; + $params = array(); + $data = PMA_GIS_Geometry::generateParams($value); + $params['srid'] = $data['srid']; + $wkt = $data['wkt']; + } else { + $params[$index]['gis_type'] = 'POLYGON'; + $wkt = $value; + } + + // Trim to remove leading 'POLYGON((' and trailing '))' + $polygon = substr($wkt, 9, (strlen($wkt) - 11)); + // Seperate each linestring + $linerings = explode("),(", $polygon); + $params[$index]['POLYGON']['no_of_lines'] = count($linerings); + + $j = 0; + foreach ($linerings as $linering) { + $points_arr = $this->extractPoints($linering, null); + $no_of_points = count($points_arr); + $params[$index]['POLYGON'][$j]['no_of_points'] = $no_of_points; + for ($i = 0; $i < $no_of_points; $i++) { + $params[$index]['POLYGON'][$j][$i]['x'] = $points_arr[$i][0]; + $params[$index]['POLYGON'][$j][$i]['y'] = $points_arr[$i][1]; + } + $j++; + } + return $params; + } } ?> diff --git a/libraries/gis/pma_gis_visualization.php b/libraries/gis/pma_gis_visualization.php index 2817a23c4f..29f2e1c11c 100644 --- a/libraries/gis/pma_gis_visualization.php +++ b/libraries/gis/pma_gis_visualization.php @@ -18,15 +18,14 @@ class PMA_GIS_Visualization // Array of colors to be used for GIS visualizations. 'colors' => array( - '#BCE02E', + '#B02EE0', '#E0642E', '#E0D62E', '#2E97E0', - '#B02EE0', + '#BCE02E', '#E02E75', '#5CE02E', '#E0B02E', - '#000000', '#0022E0', '#726CB1', '#481A36', @@ -153,7 +152,7 @@ class PMA_GIS_Visualization $output .= ''; $scale_data = $this->_scaleDataSet($this->_data); - $output .= $this->_prepareDataSet($this->_data, 0, $scale_data, 'svg', ''); + $output .= $this->_prepareDataSet($this->_data, $scale_data, 'svg', ''); $output .= ''; $output .= ''; @@ -206,7 +205,7 @@ class PMA_GIS_Visualization ); $scale_data = $this->_scaleDataSet($this->_data); - $image = $this->_prepareDataSet($this->_data, 0, $scale_data, 'png', $image); + $image = $this->_prepareDataSet($this->_data, $scale_data, 'png', $image); return $image; } @@ -256,7 +255,33 @@ class PMA_GIS_Visualization { $this->init(); $scale_data = $this->_scaleDataSet($this->_data); - $output = $this->_prepareDataSet($this->_data, 0, $scale_data, 'ol', ''); + $output = + '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 layerMapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");' + . 'var layerOsmarender = new OpenLayers.Layer.OSM.Osmarender("Osmarender");' + . 'var layerCycleMap = new OpenLayers.Layer.OSM.CycleMap("CycleMap");' + . 'map.addLayers([layerMapnik, layerOsmarender, layerCycleMap, 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; } @@ -274,7 +299,7 @@ class PMA_GIS_Visualization include_once './libraries/tcpdf/tcpdf.php'; // create pdf - $pdf = new TCPDF('', 'pt', 'A4', true, 'UTF-8', false); + $pdf = new TCPDF('', 'pt', $GLOBALS['cfg']['PDFDefaultPageSize'], true, 'UTF-8', false); // disable header and footer $pdf->setPrintHeader(false); @@ -287,7 +312,7 @@ class PMA_GIS_Visualization $pdf->AddPage(); $scale_data = $this->_scaleDataSet($this->_data); - $pdf = $this->_prepareDataSet($this->_data, 0, $scale_data, 'pdf', $pdf); + $pdf = $this->_prepareDataSet($this->_data, $scale_data, 'pdf', $pdf); // sanitize file name $file_name = $this->_sanitizeName($file_name, 'pdf'); @@ -319,6 +344,9 @@ class PMA_GIS_Visualization $type = substr($ref_data, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $scale_data = $gis_obj->scaleRow($row[$this->_settings['spatialColumn']]); // Upadate minimum/maximum values for x and y cordinates. @@ -377,19 +405,19 @@ class PMA_GIS_Visualization /** * Prepares and return the dataset as needed by the visualization. * - * @param array $data Raw data - * @param int $color_number Start index to the color array - * @param array $scale_data Data related to scaling - * @param string $format Format of the visulaization - * @param image $results Image object in the case of png + * @param array $data Raw data + * @param array $scale_data Data related to scaling + * @param string $format Format of the visulaization + * @param image $results Image object in the case of png * * @return the formatted array of data. */ - private function _prepareDataSet($data, $color_number, $scale_data, $format, $results) + private function _prepareDataSet($data, $scale_data, $format, $results) { + $color_number = 0; + // loop through the rows foreach ($data as $row) { - $index = $color_number % sizeof($this->_settings['colors']); // Figure out the data type @@ -398,6 +426,9 @@ class PMA_GIS_Visualization $type = substr($ref_data, 0, $type_pos); $gis_obj = PMA_GIS_Factory::factory($type); + if (! $gis_obj) { + continue; + } $label = ''; if (isset($this->_settings['labelColumn']) && isset($row[$this->_settings['labelColumn']]) @@ -432,4 +463,3 @@ class PMA_GIS_Visualization } } ?> - diff --git a/libraries/gis_visualization.lib.php b/libraries/gis_visualization.lib.php index cc15dc984a..a35af807ef 100644 --- a/libraries/gis_visualization.lib.php +++ b/libraries/gis_visualization.lib.php @@ -16,7 +16,7 @@ * * @return the modified sql query. */ -function PMA_GIS_modify_query($sql_query, $visualizationSettings) +function PMA_GIS_modifyQuery($sql_query, $visualizationSettings) { $modified_query = 'SELECT '; @@ -75,7 +75,9 @@ function PMA_GIS_modify_query($sql_query, $visualizationSettings) // If select cluase is * } else { // If label column is chosen add it to the query - if ($visualizationSettings['labelColumn'] != '') { + if (isset($visualizationSettings['labelColumn']) + && $visualizationSettings['labelColumn'] != '' + ) { $modified_query .= '`' . $visualizationSettings['labelColumn'] .'`, '; } @@ -84,7 +86,8 @@ function PMA_GIS_modify_query($sql_query, $visualizationSettings) . '`) AS `' . $visualizationSettings['spatialColumn'] . '`, '; // Get the SRID - $modified_query .= 'SRID(`' . $visualizationSettings['spatialColumn'] . '`) AS `srid` '; + $modified_query .= 'SRID(`' . $visualizationSettings['spatialColumn'] + . '`) AS `srid` '; } // Append the rest of the query @@ -98,14 +101,16 @@ function PMA_GIS_modify_query($sql_query, $visualizationSettings) function sanitize($select) { $table_col = $select['table_name'] . "." . $select['column']; - $db_table_col = $select['db'] . "." . $select['table_name'] . "." . $select['column']; + $db_table_col = $select['db'] . "." . $select['table_name'] + . "." . $select['column']; if ($select['expr'] == $select['column']) { return "`" . $select['column'] . "`"; } elseif ($select['expr'] == $table_col) { return "`" . $select['table_name'] . "`.`" . $select['column'] . "`"; } elseif ($select['expr'] == $db_table_col) { - return "`" . $select['db'] . "`.`" . $select['table_name'] . "`.`" . $select['column'] . "`"; + return "`" . $select['db'] . "`.`" . $select['table_name'] + . "`.`" . $select['column'] . "`"; } return $select['expr']; } @@ -119,7 +124,7 @@ function sanitize($select) * * @return string HTML and JS code for the GIS visualization */ -function PMA_GIS_visualization_results($data, &$visualizationSettings, $format) +function PMA_GIS_visualizationResults($data, &$visualizationSettings, $format) { include_once './libraries/gis/pma_gis_visualization.php'; include_once './libraries/gis/pma_gis_factory.php'; @@ -156,7 +161,7 @@ function PMA_GIS_visualization_results($data, &$visualizationSettings, $format) * * @return file File containing the visualization */ -function PMA_GIS_save_to_file($data, $visualizationSettings, $format, $fileName) +function PMA_GIS_saveToFile($data, $visualizationSettings, $format, $fileName) { include_once './libraries/gis/pma_gis_visualization.php'; include_once './libraries/gis/pma_gis_factory.php'; diff --git a/libraries/import.lib.php b/libraries/import.lib.php index 0d0239c33f..df4290eb0d 100644 --- a/libraries/import.lib.php +++ b/libraries/import.lib.php @@ -425,6 +425,7 @@ define("VARCHAR", 1); define("INT", 2); define("DECIMAL", 3); define("BIGINT", 4); +define("GEOMETRY", 5); /* Decimal size defs */ define("M", 0); @@ -437,8 +438,9 @@ define("COL_NAMES", 1); define("ROWS", 2); /* Analysis array defs */ -define("TYPES", 0); -define("SIZES", 1); +define("TYPES", 0); +define("SIZES", 1); +define("FORMATTEDSQL", 2); /** * Obtains the precision (total # of digits) from a size of type decimal @@ -916,7 +918,7 @@ function PMA_buildSQL($db_name, &$tables, &$analyses = null, &$additional_sql = } if ($analyses != null) { - $type_array = array(NONE => "NULL", VARCHAR => "varchar", INT => "int", DECIMAL => "decimal", BIGINT => "bigint"); + $type_array = array(NONE => "NULL", VARCHAR => "varchar", INT => "int", DECIMAL => "decimal", BIGINT => "bigint", GEOMETRY => 'geometry'); /* TODO: Do more checking here to make sure they really are matched */ if (count($tables) != count($analyses)) { @@ -935,7 +937,10 @@ function PMA_buildSQL($db_name, &$tables, &$analyses = null, &$additional_sql = $size = 10; } - $tempSQLStr .= PMA_backquote($tables[$i][COL_NAMES][$j]) . " " . $type_array[$analyses[$i][TYPES][$j]] . "(" . $size . ")"; + $tempSQLStr .= PMA_backquote($tables[$i][COL_NAMES][$j]) . " " . $type_array[$analyses[$i][TYPES][$j]]; + if ($analyses[$i][TYPES][$j] != GEOMETRY) { + $tempSQLStr .= "(" . $size . ")"; + } if ($j != (count($tables[$i][COL_NAMES]) - 1)) { $tempSQLStr .= ", "; @@ -980,20 +985,28 @@ function PMA_buildSQL($db_name, &$tables, &$analyses = null, &$additional_sql = $tempSQLStr .= "("; for ($k = 0; $k < $num_cols; ++$k) { - if ($analyses != null) { - $is_varchar = ($analyses[$i][TYPES][$col_count] === VARCHAR); + // If fully formatted SQL, no need to enclose with aphostrophes, add shalshes etc. + if ($analyses != null + && isset($analyses[$i][FORMATTEDSQL][$col_count]) + && $analyses[$i][FORMATTEDSQL][$col_count] == true + ) { + $tempSQLStr .= (string) $tables[$i][ROWS][$j][$k]; } else { - $is_varchar = !is_numeric($tables[$i][ROWS][$j][$k]); - } + if ($analyses != null) { + $is_varchar = ($analyses[$i][TYPES][$col_count] === VARCHAR); + } else { + $is_varchar = !is_numeric($tables[$i][ROWS][$j][$k]); + } - /* Don't put quotes around NULL fields */ - if (! strcmp($tables[$i][ROWS][$j][$k], 'NULL')) { - $is_varchar = false; - } + /* Don't put quotes around NULL fields */ + if (! strcmp($tables[$i][ROWS][$j][$k], 'NULL')) { + $is_varchar = false; + } - $tempSQLStr .= (($is_varchar) ? "'" : ""); - $tempSQLStr .= PMA_sqlAddSlashes((string)$tables[$i][ROWS][$j][$k]); - $tempSQLStr .= (($is_varchar) ? "'" : ""); + $tempSQLStr .= (($is_varchar) ? "'" : ""); + $tempSQLStr .= PMA_sqlAddSlashes((string)$tables[$i][ROWS][$j][$k]); + $tempSQLStr .= (($is_varchar) ? "'" : ""); + } if ($k != ($num_cols - 1)) { $tempSQLStr .= ", "; diff --git a/libraries/import/shp.php b/libraries/import/shp.php new file mode 100644 index 0000000000..7ee62fdce3 --- /dev/null +++ b/libraries/import/shp.php @@ -0,0 +1,399 @@ + __('ESRI Shape File'), + 'extension' => 'shp', + 'options' => array(), + 'options_text' => __('Options'), + ); +} else { + + if ((int) ini_get('memory_limit') < 512) { + ini_set('memory_limit', '512M'); + } + set_time_limit(300); + + + // Append the bfShapeFiles directory to the include path variable + set_include_path(get_include_path() . PATH_SEPARATOR . getcwd() . '/libraries/bfShapeFiles/'); + require_once './libraries/bfShapeFiles/ShapeFile.lib.php'; + + $GLOBALS['finished'] = false; + $buffer = ''; + $eof = false; + + // Returns specified number of bytes from the buffer. + // Buffer automatically fetches next chunk of data when the buffer falls short. + // Sets $eof when $GLOBALS['finished'] is set and the buffer falls short. + function readFromBuffer($length){ + global $buffer, $eof; + + if (strlen($buffer) < $length) { + if ($GLOBALS['finished']) { + $eof = true; + } else { + $buffer .= PMA_importGetNextChunk(); + } + } + $result = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + return $result; + } + + /** + * This class extends ShapeFile class to cater following phpMyAdmin specific requirements. + * 1) To load data from .dbf file only when the dBase extension is available. + * 2) To use PMA_importGetNextChunk() functionality to read data, rather than reading directly from a file. + * Using readFromBuffer() in place of fread(). This makes it possible to use compressions. + */ + class PMA_ShapeFile extends ShapeFile { + + function _isDbaseLoaded() + { + return extension_loaded('dbase'); + } + + function loadFromFile($FileName) + { + $this->_loadHeaders(); + $this->_loadRecords(); + if ($this->_isDbaseLoaded()) { + $this->_closeDBFFile(); + } + } + + function _loadHeaders() + { + readFromBuffer(24); + $this->fileLength = loadData("N", readFromBuffer(4)); + + readFromBuffer(4); + $this->shapeType = loadData("V", readFromBuffer(4)); + + $this->boundingBox = array(); + $this->boundingBox["xmin"] = loadData("d", readFromBuffer(8)); + $this->boundingBox["ymin"] = loadData("d", readFromBuffer(8)); + $this->boundingBox["xmax"] = loadData("d", readFromBuffer(8)); + $this->boundingBox["ymax"] = loadData("d", readFromBuffer(8)); + + if ($this->_isDbaseLoaded() && $this->_openDBFFile()) { + $this->DBFHeader = $this->_loadDBFHeader(); + } + } + + function _loadRecords() + { + global $eof; + readFromBuffer(32); + while (true) { + $record = new PMA_ShapeRecord(-1); + $record->loadFromFile($this->SHPFile, $this->DBFFile); + if ($record->lastError != "") { + return false; + } + if ($eof) { + break; + } + + $this->records[] = $record; + } + } + } + + /** + * This class extends ShapeRecord class to cater following phpMyAdmin specific requirements. + * 1) To load data from .dbf file only when the dBase extension is available. + * 2) To use PMA_importGetNextChunk() functionality to read data, rather than reading directly from a file. + * Using readFromBuffer() in place of fread(). This makes it possible to use compressions. + */ + class PMA_ShapeRecord extends ShapeRecord { + + function loadFromFile(&$SHPFile, &$DBFFile) + { + $this->DBFFile = $DBFFile; + $this->_loadHeaders(); + + switch ($this->shapeType) { + case 0: + $this->_loadNullRecord(); + break; + case 1: + $this->_loadPointRecord(); + break; + case 3: + $this->_loadPolyLineRecord(); + break; + case 5: + $this->_loadPolygonRecord(); + break; + case 8: + $this->_loadMultiPointRecord(); + break; + default: + $this->setError(sprintf("The Shape Type '%s' is not supported.", $this->shapeType)); + break; + } + if (extension_loaded('dbase') && isset($this->DBFFile)) { + $this->_loadDBFData(); + } + } + + function _loadHeaders() + { + $this->recordNumber = loadData("N", readFromBuffer(4)); + $tmp = loadData("N", readFromBuffer(4)); //We read the length of the record + $this->shapeType = loadData("V", readFromBuffer(4)); + } + + function _loadPoint() + { + $data = array(); + + $data["x"] = loadData("d", readFromBuffer(8)); + $data["y"] = loadData("d", readFromBuffer(8)); + + return $data; + } + + function _loadMultiPointRecord() + { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", readFromBuffer(8)); + $this->SHPData["ymin"] = loadData("d", readFromBuffer(8)); + $this->SHPData["xmax"] = loadData("d", readFromBuffer(8)); + $this->SHPData["ymax"] = loadData("d", readFromBuffer(8)); + + $this->SHPData["numpoints"] = loadData("V", readFromBuffer(4)); + + for ($i = 0; $i <= $this->SHPData["numpoints"]; $i++) { + $this->SHPData["points"][] = $this->_loadPoint(); + } + } + + function _loadPolyLineRecord() + { + $this->SHPData = array(); + $this->SHPData["xmin"] = loadData("d", readFromBuffer(8)); + $this->SHPData["ymin"] = loadData("d", readFromBuffer(8)); + $this->SHPData["xmax"] = loadData("d", readFromBuffer(8)); + $this->SHPData["ymax"] = loadData("d", readFromBuffer(8)); + + $this->SHPData["numparts"] = loadData("V", readFromBuffer(4)); + $this->SHPData["numpoints"] = loadData("V", readFromBuffer(4)); + + for ($i = 0; $i < $this->SHPData["numparts"]; $i++) { + $this->SHPData["parts"][$i] = loadData("V", readFromBuffer(4)); + } + + $readPoints = 0; + reset($this->SHPData["parts"]); + while (list($partIndex, $partData) = each($this->SHPData["parts"])) { + if (!isset($this->SHPData["parts"][$partIndex]["points"]) || !is_array($this->SHPData["parts"][$partIndex]["points"])) { + $this->SHPData["parts"][$partIndex] = array(); + $this->SHPData["parts"][$partIndex]["points"] = array(); + } + while (!in_array($readPoints, $this->SHPData["parts"]) && ($readPoints < ($this->SHPData["numpoints"]))) { + $this->SHPData["parts"][$partIndex]["points"][] = $this->_loadPoint(); + $readPoints++; + } + } + } + } + + $shp = new PMA_ShapeFile(1); + // If the zip archive has more than one file, get the correct content to the buffer from .shp file. + if ($compression == 'application/zip' && PMA_getNoOfFilesInZip($import_file) > 1) { + $zip_content = PMA_getZipContents($import_file, '/^.*\.shp$/i'); + $GLOBALS['import_text'] = $zip_content['data']; + } + + $temp_dbf_file = false; + // We need dbase extension to handle .dbf file + if (extension_loaded('dbase')) { + // If we can extract the zip archive to 'TempDir' and use the files in it for import + if ($compression == 'application/zip' + && ! empty($cfg['TempDir']) + && is_writable($cfg['TempDir']) + ) { + $dbf_file_name = PMA_findFileFromZipArchive('/^.*\.dbf$/i', $import_file); + // If the corresponding .dbf file is in the zip archive + if ($dbf_file_name) { + // Extract the .dbf file and point to it. + $extracted = PMA_zipExtract($import_file, realpath($cfg['TempDir']), array($dbf_file_name)); + if ($extracted) { + $dbf_file_path = realpath($cfg['TempDir']) . (PMA_IS_WINDOWS ? '\\' : '/') . $dbf_file_name; + $temp_dbf_file = true; + // Replace the .dbf with .*, as required by the bsShapeFiles library. + $file_name = substr($dbf_file_path, 0, strlen($dbf_file_path) - 4) . '.*'; + $shp->FileName = $file_name; + } + } + } + // If file is in UploadDir, use .dbf file in the same UploadDir to load extra data. + elseif (! empty($local_import_file) && ! empty($cfg['UploadDir']) && $compression == 'none') { + // Replace the .shp with .*, so the bsShapeFiles library correctly locates .dbf file. + $file_name = substr($import_file, 0, strlen($import_file) - 4) . '.*'; + $shp->FileName = $file_name; + } + } + + // Load data + $shp->loadFromFile(''); + if ($shp->lastError != "") { + $error = true; + $message = PMA_Message::error(__('There was an error importing the ESRI shape file: "%s".')); + $message->addParam($shp->lastError); + return; + } + + // Delete the .dbf file extracted to 'TempDir' + if ($temp_dbf_file) { + unlink($dbf_file_path); + } + + $esri_types = array( + 0 => 'Null Shape', + 1 => 'Point', + 3 => 'PolyLine', + 5 => 'Polygon', + 8 => 'MultiPoint', + 11 => 'PointZ', + 13 => 'PolyLineZ', + 15 => 'PolygonZ', + 18 => 'MultiPointZ', + 21 => 'PointM', + 23 => 'PolyLineM', + 25 => 'PolygonM', + 28 => 'MultiPointM', + 31 => 'MultiPatch', + ); + + require_once './libraries/gis/pma_gis_geometry.php'; + switch ($shp->shapeType) { + // ESRI Null Shape + case 0: + $gis_obj = null; + break; + // ESRI Point + case 1: + require_once './libraries/gis/pma_gis_point.php'; + $gis_obj = PMA_GIS_Point::singleton(); + break; + // ESRI PolyLine + case 3: + require_once './libraries/gis/pma_gis_multilinestring.php'; + $gis_obj = PMA_GIS_Multilinestring::singleton(); + break; + // ESRI Polygon + case 5: + require_once './libraries/gis/pma_gis_multipolygon.php'; + $gis_obj = PMA_GIS_Multipolygon::singleton(); + break; + // ESRI MultiPoint + case 8: + require_once './libraries/gis/pma_gis_multipoint.php'; + $gis_obj = PMA_GIS_Multipoint::singleton(); + break; + default: + $error = true; + if (! isset($esri_types[$shp->shapeType])) { + $message = PMA_Message::error(__('You tried to import an invalid file or the imported file contains invalid data')); + } else { + $message = PMA_Message::error(__('MySQL Spatial Extension does not support ESRI type "%s".')); + $message->addParam($param); + } + return; + } + + $num_rows = count($shp->records); + // If .dbf file is loaded, the number of extra data columns + $num_data_cols = isset($shp->DBFHeader) ? count($shp->DBFHeader) : 0; + + $rows = array(); + $col_names = array(); + if ($num_rows != 0) { + foreach($shp->records as $record){ + $tempRow = array(); + if ($gis_obj == null) { + $tempRow[] = null; + } else { + $tempRow[] = "GeomFromText('" . $gis_obj->getShape($record->SHPData) . "')"; + } + + if (isset($shp->DBFHeader)) { + foreach ($shp->DBFHeader as $c) { + $cell = trim($record->DBFData[$c[0]]); + + if (! strcmp($cell, '')) { + $cell = 'NULL'; + } + + $tempRow[] = $cell; + } + } + $rows[] = $tempRow; + } + } + + if(count($rows) == 0) { + $error = true; + $message = PMA_Message::error(__('The imported file does not contain any data')); + return; + } + + // Column names for spatial column and the rest of the columns, if they are available + $col_names[] = 'SPATIAL'; + for ($n = 0; $n < $num_data_cols; $n++) { + $col_names[] = $shp->DBFHeader[$n][0]; + } + + // Set table name based on the number of tables + if (strlen($db)) { + $result = PMA_DBI_fetch_result('SHOW TABLES'); + $table_name = 'TABLE '.(count($result) + 1); + } else { + $table_name = 'TBL_NAME'; + } + $tables = array(array($table_name, $col_names, $rows)); + + // Use data from shape file to chose best-fit MySQL types for each column + $analyses = array(); + $analyses[] = PMA_analyzeTable($tables[0]); + + $table_no = 0; $spatial_col = 0; + $analyses[$table_no][TYPES][$spatial_col] = GEOMETRY; + $analyses[$table_no][FORMATTEDSQL][$spatial_col] = true; + + // Set database name to the currently selected one, if applicable + if (strlen($db)) { + $db_name = $db; + $options = array('create_db' => false); + } else { + $db_name = 'SHP_DB'; + $options = null; + } + + // Created and execute necessary SQL statements from data + $null_param = null; + PMA_buildSQL($db_name, $tables, $analyses, $null_param, $options); + + unset($tables); + unset($analyses); + + $finished = true; + $error = false; + + // Commit any possible data in buffers + PMA_importRunQuery(); +} +?> diff --git a/libraries/tbl_select.lib.php b/libraries/tbl_select.lib.php index 6384f60b16..af97aa434e 100644 --- a/libraries/tbl_select.lib.php +++ b/libraries/tbl_select.lib.php @@ -3,8 +3,8 @@ /** * Functions for the table-search page and zoom-search page * - * Funtion PMA_tbl_getFields : Returns the fields of a table - * Funtion PMA_tbl_search_getWhereClause : Returns the where clause for query generation + * Funtion PMA_tbl_getFields : Returns the fields of a table + * Funtion PMA_tbl_search_getWhereClause : Returns the where clause for query generation * * @package phpMyAdmin */ @@ -13,7 +13,7 @@ require_once 'url_generating.lib.php'; /** * PMA_tbl_setTitle() sets the title for foreign keys display link - * + * * @param $propertiesIconic Type of icon property * @param $themeImage Icon Image * @return string $str Value of the Title @@ -51,20 +51,26 @@ function PMA_tbl_setTitle($propertiesIconic,$pmaThemeImage){ * @param $db Selected database * @param $table Selected table * - * @return array($fields_list,$fields_type,$fields_collation,$fields_null) Array containing the field list, field types, collations and null constatint + * @return array($fields_list,$fields_type,$fields_collation,$fields_null) Array containing the field list, field types, collations and null constatint * */ function PMA_tbl_getFields($table,$db) { - + // Gets the list and number of fields $result = PMA_DBI_query('SHOW FULL FIELDS FROM ' . PMA_backquote($table) . ' FROM ' . PMA_backquote($db) . ';', null, PMA_DBI_QUERY_STORE); $fields_cnt = PMA_DBI_num_rows($result); $fields_list = $fields_null = $fields_type = $fields_collation = array(); + $geom_column_present = false; + $geom_types = PMA_getGISDatatypes(); while ($row = PMA_DBI_fetch_assoc($result)) { $fields_list[] = $row['Field']; $type = $row['Type']; + // check whether table contains geometric columns + if (in_array($type, $geom_types)) { + $geom_column_present = true; + } // reformat mysql query output if (strncasecmp($type, 'set', 3) == 0 || strncasecmp($type, 'enum', 4) == 0) { @@ -93,20 +99,26 @@ function PMA_tbl_getFields($table,$db) { PMA_DBI_free_result($result); unset($result, $type); - return array($fields_list,$fields_type,$fields_collation,$fields_null); - + return array($fields_list,$fields_type,$fields_collation,$fields_null, $geom_column_present); + } /* PMA_tbl_setTableHeader() sets the table header for displaying a table in query-by-example format * - * @return HTML content, the tags and content for table header + * @return HTML content, the tags and content for table header * */ -function PMA_tbl_setTableHeader(){ +function PMA_tbl_setTableHeader($geom_column_present = false){ + + // Display the Function column only if there is alteast one geomety colum + $func = ''; + if ($geom_column_present) { + $func = '' . __('Function') . ''; + } return ' - ' . __('Column') . ' + ' . $func . '' . __('Column') . ' ' . __('Type') . ' ' . __('Collation') . ' ' . __('Operator') . ' @@ -117,9 +129,9 @@ return ' } -/* PMA_tbl_getSubTabs() returns an array with necessary configrations to create sub-tabs(Table Search and Zoom Search) in the table_select page +/* PMA_tbl_getSubTabs() returns an array with necessary configrations to create sub-tabs(Table Search and Zoom Search) in the table_select page * - * @return array $subtabs Array containing configuration (icon,text,link,id,args) of sub-tabs for Table Search and Zoom search + * @return array $subtabs Array containing configuration (icon,text,link,id,args) of sub-tabs for Table Search and Zoom search * */ @@ -137,7 +149,7 @@ function PMA_tbl_getSubTabs(){ $subtabs['zoom']['link'] = 'tbl_zoom_select.php'; $subtabs['zoom']['text'] = __('Zoom Search'); $subtabs['zoom']['id'] = 'zoom_search_id'; - + return $subtabs; } @@ -164,12 +176,13 @@ function PMA_tbl_getSubTabs(){ * @param $titles Selected title * @param $foreignMaxLimit Max limit of displaying foreign elements * @param $fields Array of search criteria inputs + * @param $in_fbs In function based search * - * @return string $str HTML content for viewing foreing data and elements for search criteria input. + * @return string $str HTML content for viewing foreing data and elements for search criteria input. * */ -function PMA_getForeignFields_Values($foreigners, $foreignData, $field, $tbl_fields_type, $i, $db, $table,$titles,$foreignMaxLimit, $fields){ +function PMA_getForeignFields_Values($foreigners, $foreignData, $field, $tbl_fields_type, $i, $db, $table, $titles, $foreignMaxLimit, $fields, $in_fbs = false){ $str = ''; @@ -185,29 +198,40 @@ function PMA_getForeignFields_Values($foreigners, $foreignData, $field, $tbl_fie $foreignData['foreign_display'], '', $foreignMaxLimit); $str .= ' ' . "\n"; - } + } elseif ($foreignData['foreign_link'] == true) { if(isset($fields[$i]) && is_string($fields[$i])){ $str .= '' ; + 'id="field_' . md5($field) . '[' . $i .']" + class="textfield"/>' ; } else{ $str .= '' ; + 'id="field_' . md5($field) . '[' . $i .']" + class="textfield" />' ; } ?> '; // ' . str_replace("'", "\'", $titles['Browse']) . ''; // ]] $str .= ''; - } - elseif (strncasecmp($tbl_fields_type[$i], 'enum', 4) == 0) { + } elseif (in_array($tbl_fields_type[$i], PMA_getGISDatatypes())) { + // g e o m e t r y + $str .= '' . "\n"; + + if ($in_fbs) { + $edit_url = 'gis_data_editor.php?' . PMA_generate_common_url(); + $edit_str = PMA_getIcon('b_edit.png', __('Edit/Insert'), true); + $str .= ''; + $str .= PMA_linkOrButton($edit_url, $edit_str, array(), false, false, '_blank'); + $str .= ''; + } + } elseif (strncasecmp($tbl_fields_type[$i], 'enum', 4) == 0) { // e n u m s $enum_value=explode(', ', str_replace("'", '', substr($tbl_fields_type[$i], 5, -1))); $cnt_enum_value = count($enum_value); @@ -224,7 +248,7 @@ EOT; } } // end for $str .= ' ' . "\n"; - } + } else { // o t h e r c a s e s $the_class = 'textfield'; @@ -267,17 +291,61 @@ EOT; * @param $func_type Search fucntion/operator * @param $unaryFlag Whether operator unary or not * - * @return string $str HTML content for viewing foreing data and elements for search criteria input. + * @return string $str HTML content for viewing foreing data and elements for search criteria input. * */ -function PMA_tbl_search_getWhereClause($fields, $names, $types, $collations, $func_type, $unaryFlag){ - +function PMA_tbl_search_getWhereClause($fields, $names, $types, $collations, $func_type, $unaryFlag, $geom_func = null){ + + /** + * @todo move this to a more apropriate place + */ + $geom_unary_functions = array( + 'IsEmpty' => 1, + 'IsSimple' => 1, + 'IsRing' => 1, + 'IsClosed' => 1, + ); + + $w = ''; + + // If geometry function is set apply it to the field name + if ($geom_func != null && trim($geom_func) != '') { + // Get details about the geometry fucntions + $geom_funcs = PMA_getGISFunctions($types, true, false); + + // If the function takes a single parameter + if ($geom_funcs[$geom_func]['params'] == 1) { + $backquoted_name = $geom_func . '(' . PMA_backquote($names) . ')'; + // If the function takes two parameters + } else { + // create gis data from the string + $gis_data = PMA_createGISData($fields); + + $w = $geom_func . '(' . PMA_backquote($names) . ',' . $gis_data . ')'; + return $w; + } + + // New output type is the output type of the function being applied + $types = $geom_funcs[$geom_func]['type']; + + // If the intended where clause is something like 'IsEmpty(`spatial_col_name`)' + if (isset($geom_unary_functions[$geom_func]) && trim($fields) == '') { + $w = $backquoted_name; + return $w; + } + } else { + $backquoted_name = PMA_backquote($names); + } - $w = ''; if($unaryFlag){ $fields = ''; - $w = PMA_backquote($names) . ' ' . $func_type; + $w = $backquoted_name . ' ' . $func_type; + + } elseif (in_array($types, PMA_getGISDatatypes())) { + // create gis data from the string + $gis_data = PMA_createGISData($fields); + $w = $backquoted_name . ' ' . $func_type . ' ' . $gis_data; } elseif (strncasecmp($types, 'enum', 4) == 0) { if (!empty($fields)) { @@ -304,7 +372,7 @@ function PMA_tbl_search_getWhereClause($fields, $names, $types, $collations, $fu $enum_where .= ', \'' . PMA_sqlAddslashes($fields[$e]) . '\''; } - $w = PMA_backquote($names) . ' ' . $func_type . ' ' . $parens_open . $enum_where . $parens_close; + $w = $backquoted_name . ' ' . $func_type . ' ' . $parens_open . $enum_where . $parens_close; } } elseif ($fields != '') { @@ -336,12 +404,12 @@ function PMA_tbl_search_getWhereClause($fields, $names, $types, $collations, $fu $value = $quot . PMA_sqlAddslashes(trim($value)) . $quot; if ($func_type == 'BETWEEN' || $func_type == 'NOT BETWEEN') - $w = PMA_backquote($names) . ' ' . $func_type . ' ' . (isset($values[0]) ? $values[0] : '') . ' AND ' . (isset($values[1]) ? $values[1] : ''); + $w = $backquoted_name . ' ' . $func_type . ' ' . (isset($values[0]) ? $values[0] : '') . ' AND ' . (isset($values[1]) ? $values[1] : ''); else - $w = PMA_backquote($names) . ' ' . $func_type . ' (' . implode(',', $values) . ')'; + $w = $backquoted_name . ' ' . $func_type . ' (' . implode(',', $values) . ')'; } else { - $w = PMA_backquote($names) . ' ' . $func_type . ' ' . $quot . PMA_sqlAddslashes($fields) . $quot;; + $w = $backquoted_name . ' ' . $func_type . ' ' . $quot . PMA_sqlAddslashes($fields) . $quot;; } } // end if diff --git a/libraries/zip_extension.lib.php b/libraries/zip_extension.lib.php index b665b74e5b..7bfa84875f 100644 --- a/libraries/zip_extension.lib.php +++ b/libraries/zip_extension.lib.php @@ -9,12 +9,12 @@ /** * Gets zip file contents * - * @param string $file + * @param string $specific_entry regular expression to match a file * @return array ($error_message, $file_data); $error_message * is empty if no error */ -function PMA_getZipContents($file) +function PMA_getZipContents($file, $specific_entry = null) { $error_message = ''; $file_data = ''; @@ -28,11 +28,15 @@ function PMA_getZipContents($file) $read = zip_entry_read($first_zip_entry); $ods_mime = 'application/vnd.oasis.opendocument.spreadsheet'; if (!strcmp($ods_mime, $read)) { + $specific_entry = '/^content\.xml$/'; + } + + if (isset($specific_entry)) { /* Return the correct contents, not just the first entry */ for ( ; ; ) { $entry = zip_read($zip_handle); if (is_resource($entry)) { - if (!strcmp('content.xml', zip_entry_name($entry))) { + if (preg_match($specific_entry, zip_entry_name($entry))) { zip_entry_open($zip_handle, $entry, 'r'); $file_data = zip_entry_read($entry, zip_entry_filesize($entry)); zip_entry_close($entry); @@ -41,15 +45,15 @@ function PMA_getZipContents($file) } else { /** * Either we have reached the end of the zip and still - * haven't found 'content.xml' or there was a parsing + * haven't found $specific_entry or there was a parsing * error that we must display */ if ($entry === false) { - $error_message = __('Error in ZIP archive:') . ' Could not find "content.xml"'; + $error_message = __('Error in ZIP archive:') . ' Could not find "' . $specific_entry . '"'; } else { $error_message = __('Error in ZIP archive:') . ' ' . PMA_getZipError($zip_handle); } - + break; } } @@ -68,6 +72,69 @@ function PMA_getZipContents($file) return (array('error' => $error_message, 'data' => $file_data)); } +/** + * Returns the file name of the first file that matches the given $file_regexp. + * + * @param string $file_regexp regular expression for the file name to match + * @param string $file zip archive + */ +function PMA_findFileFromZipArchive ($file_regexp, $file) +{ + $zip_handle = zip_open($file); + $found = false; + if (is_resource($zip_handle)) { + $entry = zip_read($zip_handle); + while (is_resource($entry)) { + if (preg_match($file_regexp, zip_entry_name($entry))) { + $file_name = zip_entry_name($entry); + zip_close($zip_handle); + return $file_name; + } + $entry = zip_read($zip_handle); + } + } + zip_close($zip_handle); + return false; +} + +/** + * Returns the number of files in the zip archive. + * + * @param string $file + */ +function PMA_getNoOfFilesInZip($file) +{ + $count = 0; + $zip_handle = zip_open($file); + $found = false; + if (is_resource($zip_handle)) { + $entry = zip_read($zip_handle); + while (is_resource($entry)) { + $count++; + $entry = zip_read($zip_handle); + } + } + zip_close($zip_handle); + return $count; +} + +/** + * Extracts a set of files from the given zip archive to a given destinations. + * + * @param string $zip_path + * @param string $destination + * @param array $entries + */ +function PMA_zipExtract($zip_path, $destination, $entries) { + $zip = new ZipArchive; + if ($zip->open($zip_path) === true) { + $zip->extractTo($destination, $entries); + $zip->close(); + return true; + } + return false; +} + /** * Gets zip error message * diff --git a/sql.php b/sql.php index 6e5a05d82f..e2d956d0a1 100644 --- a/sql.php +++ b/sql.php @@ -18,6 +18,15 @@ $GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js'; $GLOBALS['js_include'][] = 'jquery/timepicker.js'; $GLOBALS['js_include'][] = 'tbl_change.js'; +// required for GIS editor loaded via AJAX +$GLOBALS['js_include'][] = 'gis_data_editor.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.svg.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.mousewheel.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.event.drag-2.0.min.js'; +$GLOBALS['js_include'][] = 'tbl_gis_visualization.js'; +$GLOBALS['js_include'][] = 'openlayers/OpenLayers.js'; +$GLOBALS['js_include'][] = 'OpenStreetMap.js'; + if (isset($_SESSION['profiling'])) { $GLOBALS['js_include'][] = 'highcharts/highcharts.js'; /* Files required for chart exporting */ diff --git a/tbl_change.php b/tbl_change.php index 9148ef32f3..5329f159a1 100644 --- a/tbl_change.php +++ b/tbl_change.php @@ -118,6 +118,16 @@ $GLOBALS['js_include'][] = 'functions.js'; $GLOBALS['js_include'][] = 'tbl_change.js'; $GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js'; $GLOBALS['js_include'][] = 'jquery/timepicker.js'; + +// required for GIS editor +$GLOBALS['js_include'][] = 'gis_data_editor.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.svg.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.mousewheel.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.event.drag-2.0.min.js'; +$GLOBALS['js_include'][] = 'tbl_gis_visualization.js'; +$GLOBALS['js_include'][] = 'openlayers/OpenLayers.js'; +$GLOBALS['js_include'][] = 'OpenStreetMap.js'; + /** * HTTP and HTML headers */ @@ -472,6 +482,9 @@ foreach ($rows as $row_id => $vrow) { $vrow) { $data = $vrow[$field['Field']]; } elseif ($field['True_Type'] == 'bit') { $special_chars = PMA_printable_bit_value($vrow[$field['Field']], $extracted_fieldspec['spec_in_brackets']); + } elseif (in_array($field['True_Type'], $gis_data_types)) { + // Convert gis data to Well Know Text format + $vrow[$field['Field']] = PMA_asWKT($vrow[$field['Field']], true); + $special_chars = htmlspecialchars($vrow[$field['Field']]); } else { // special binary "characters" if ($field['is_binary'] || ($field['is_blob'] && ! $cfg['ProtectBinary'])) { @@ -897,7 +914,6 @@ foreach ($rows as $row_id => $vrow) { } } // end if (web-server upload directory) } // end elseif (binary or blob) - elseif (in_array($field['pma_type'], $no_support_types)) { // ignore this column to avoid changing it } else { @@ -965,6 +981,21 @@ foreach ($rows as $row_id => $vrow) { } } } + if (in_array($field['pma_type'], $gis_data_types)) { + $data_val = isset($vrow[$field['Field']]) ? $vrow[$field['Field']] : ''; + $_url_params = array( + 'field' => $field['Field_title'], + 'value' => $data_val, + ); + if ($field['pma_type'] != 'geometry') { + $_url_params = $_url_params + array('gis_data[gis_type]' => strtoupper($field['pma_type'])); + } + $edit_url = 'gis_data_editor.php' . PMA_generate_common_url($_url_params); + $edit_str = PMA_getIcon('b_edit.png', __('Edit/Insert'), true); + echo(''); + echo(PMA_linkOrButton($edit_url, $edit_str, array(), false, false, '_blank')); + echo(''); + } ?> @@ -975,8 +1006,8 @@ foreach ($rows as $row_id => $vrow) { echo '
'; } // end foreach on multi-edit ?> +

-
diff --git a/tbl_gis_visualization.php b/tbl_gis_visualization.php index 5c54b2f984..31bcd81e4f 100644 --- a/tbl_gis_visualization.php +++ b/tbl_gis_visualization.php @@ -20,6 +20,7 @@ $GLOBALS['js_include'][] = 'jquery/jquery.svg.js'; $GLOBALS['js_include'][] = 'jquery/jquery.mousewheel.js'; $GLOBALS['js_include'][] = 'jquery/jquery.event.drag-2.0.min.js'; $GLOBALS['js_include'][] = 'tbl_gis_visualization.js'; +$GLOBALS['js_include'][] = 'OpenStreetMap.js'; // Allows for resending headers even after sending some data ob_start(); @@ -63,7 +64,7 @@ if (! isset($visualizationSettings['spatialColumn'])) { } // Convert geometric columns from bytes to text. -$modified_query = PMA_GIS_modify_query($sql_query, $visualizationSettings); +$modified_query = PMA_GIS_modifyQuery($sql_query, $visualizationSettings); $modified_result = PMA_DBI_try_query($modified_query); $data = array(); @@ -89,7 +90,7 @@ if (isset($_REQUEST['saveToFile'])) { } $save_format = $_REQUEST['fileFormat']; - PMA_GIS_save_to_file($data, $visualizationSettings, $save_format, $file_name); + PMA_GIS_saveToFile($data, $visualizationSettings, $save_format, $file_name); exit(); } @@ -97,7 +98,7 @@ $svg_support = (PMA_USR_BROWSER_AGENT == 'IE' && PMA_USR_BROWSER_VER <= 8) ? fal $format = $svg_support ? 'svg' : 'png'; // get the chart and settings after chart generation -$visualization = PMA_GIS_visualization_results($data, $visualizationSettings, $format); +$visualization = PMA_GIS_visualizationResults($data, $visualizationSettings, $format); /** * Displays the page @@ -115,39 +116,9 @@ $visualization = PMA_GIS_visualization_results($data, $visualizationSettings, $f
- diff --git a/tbl_replace.php b/tbl_replace.php index a14574a879..460908e350 100644 --- a/tbl_replace.php +++ b/tbl_replace.php @@ -153,6 +153,28 @@ $func_optional_param = array( 'UNIX_TIMESTAMP', ); +$gis_from_text_functions = array( + 'GeomFromText', + 'GeomCollFromText', + 'LineFromText', + 'MLineFromText', + 'PointFromText', + 'MPointFromText', + 'PolyFromText', + 'MPolyFromText', +); + +$gis_from_wkb_functions = array( + 'GeomFromWKB', + 'GeomCollFromWKB', + 'LineFromWKB', + 'MLineFromWKB', + 'PointFromWKB', + 'MPointFromWKB', + 'PolyFromWKB', + 'MPolyFromWKB', +); + foreach ($loop_array as $rownumber => $where_clause) { // skip fields to be ignored if (! $using_key && isset($_REQUEST['insert_ignore_' . $where_clause])) { @@ -238,10 +260,19 @@ foreach ($loop_array as $rownumber => $where_clause) { /* This way user will know what UUID new row has */ $uuid = PMA_DBI_fetch_value('SELECT UUID()'); $cur_value = "'" . $uuid . "'"; - } elseif (!in_array($me_funcs[$key], $func_no_param) + } elseif ((in_array($me_funcs[$key], $gis_from_text_functions) + && substr($val, 0, 3) == "'''") + || in_array($me_funcs[$key], $gis_from_wkb_functions) + ) { + // Remove enclosing apostrophes + $val = substr($val, 1, strlen($val) - 2); + // Remove escaping apostrophes + $val = str_replace("''", "'", $val); + $cur_value = $me_funcs[$key] . '(' . $val . ')'; + } elseif (! in_array($me_funcs[$key], $func_no_param) || ($val != "''" && in_array($me_funcs[$key], $func_optional_param))) { $cur_value = $me_funcs[$key] . '(' . $val . ')'; - } else { + } else { $cur_value = $me_funcs[$key] . '()'; } diff --git a/tbl_select.php b/tbl_select.php index e9b1eaec11..1191c3a09f 100644 --- a/tbl_select.php +++ b/tbl_select.php @@ -24,8 +24,18 @@ $GLOBALS['js_include'][] = 'tbl_change.js'; $GLOBALS['js_include'][] = 'jquery/jquery-ui-1.8.custom.js'; $GLOBALS['js_include'][] = 'jquery/timepicker.js'; +// required for GIS editor loaded via AJAX +$GLOBALS['js_include'][] = 'gis_data_editor.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.svg.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.mousewheel.js'; +$GLOBALS['js_include'][] = 'jquery/jquery.event.drag-2.0.min.js'; +$GLOBALS['js_include'][] = 'tbl_gis_visualization.js'; +$GLOBALS['js_include'][] = 'openlayers/OpenLayers.js'; +$GLOBALS['js_include'][] = 'OpenStreetMap.js'; + $titles['Browse'] = PMA_tbl_setTitle($GLOBALS['cfg']['PropertiesIconic'], $pmaThemeImage); +$geom_types = PMA_getGISDatatypes(); /** * Not selection yet required -> displays the selection form */ @@ -52,8 +62,7 @@ if (! isset($param) || $param[0] == '') { $err_url = $goto . '?' . PMA_generate_common_url($db, $table); // Gets the list and number of fields - - list($fields_list, $fields_type, $fields_collation, $fields_null) = PMA_tbl_getFields($table,$db); + list($fields_list, $fields_type, $fields_collation, $fields_null, $geom_column_present) = PMA_tbl_getFields($table,$db); $fields_cnt = count($fields_list); // retrieve keys into foreign fields, if any @@ -82,7 +91,7 @@ echo PMA_generate_html_tabs(PMA_tbl_getSubTabs(), $url_params);
- + + '); + // if a geometry column + if (in_array($fields_type[$i], $geom_types)) { + echo(''); + } else { + echo(' '); + } + echo(''); + } + ?> @@ -131,8 +161,8 @@ echo PMA_generate_html_tabs(PMA_tbl_getSubTabs(), $url_params); $foreignData = PMA_getForeignData($foreigners, $field, false, '', ''); - echo PMA_getForeignFields_Values($foreigners, $foreignData, $field, $fields_type, $i, $db, $table, $titles,$GLOBALS['cfg']['ForeignKeyMaxLimit'], '' ); - + echo PMA_getForeignFields_Values($foreigners, $foreignData, $field, $fields_type, $i, $db, $table, $titles,$GLOBALS['cfg']['ForeignKeyMaxLimit'], '', true); + ?> @@ -147,6 +177,7 @@ echo PMA_generate_html_tabs(PMA_tbl_getSubTabs(), $url_params); ?>
+
do the work */ else { - echo "ZZ"; // Builds the query $sql_query = 'SELECT ' . (isset($distinct) ? 'DISTINCT ' : ''); @@ -258,14 +288,16 @@ else { $cnt_func = count($func); reset($func); while (list($i, $func_type) = each($func)) { - - list($charsets[$i]) = explode('_', $collations[$i]); + + list($charsets[$i]) = explode('_', $collations[$i]); $unaryFlag = (isset($GLOBALS['cfg']['UnaryOperators'][$func_type]) && $GLOBALS['cfg']['UnaryOperators'][$func_type] == 1) ? true : false; - $whereClause = PMA_tbl_search_getWhereClause($fields[$i],$names[$i], $types[$i], $collations[$i], $func_type, $unaryFlag); - if($whereClause) - $w[] = $whereClause; - - } // end for + + $tmp_geom_func = isset($geom_func[$i]) ? $geom_func[$i] : null; + $whereClause = PMA_tbl_search_getWhereClause($fields[$i],$names[$i], $types[$i], $collations[$i], $func_type, $unaryFlag, $tmp_geom_func); + + if($whereClause) + $w[] = $whereClause; + } // end for //print_r($w); if ($w) { $sql_query .= ' WHERE ' . implode(' AND ', $w); @@ -278,4 +310,4 @@ else { require './sql.php'; } -?> +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Factory_test.php b/test/classes/gis/PMA_GIS_Factory_test.php new file mode 100644 index 0000000000..6d778ab799 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Factory_test.php @@ -0,0 +1,80 @@ +assertInstanceOf($geom, PMA_GIS_Factory::factory($type)); + } + + /** + * data provider for testFactory + * + * @return data for testFactory + */ + public function providerForTestFactory() + { + return array( + array( + 'MULTIPOLYGON', + 'PMA_GIS_Multipolygon' + ), + array( + 'POLYGON', + 'PMA_GIS_Polygon' + ), + array( + 'MULTILINESTRING', + 'PMA_GIS_Multilinestring' + ), + array( + 'LINESTRING', + 'PMA_GIS_Linestring' + ), + array( + 'MULTIPOINT', + 'PMA_GIS_Multipoint' + ), + array( + 'POINT', + 'PMA_GIS_Point' + ), + array( + 'GEOMETRYCOLLECTION', + 'PMA_GIS_Geometrycollection' + ), + ); + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Geometry_test.php b/test/classes/gis/PMA_GIS_Geometry_test.php new file mode 100644 index 0000000000..37d4c423c5 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Geometry_test.php @@ -0,0 +1,60 @@ + test classes + * + * @package phpMyAdmin-test + */ + +require_once 'libraries/gis/pma_gis_geometry.php'; + +/** + * Abstract parent class for all PMA_GIS_ test classes + */ +abstract class PMA_GIS_GeometryTest extends PHPUnit_Framework_TestCase +{ + /** + * test generateWkt method + * + * @param array $gis_data array of GIS data + * @param int $index index + * @param string $empty string to be insterted in place of missing values + * @param string $wkt expected WKT + * + * @return nothing + * @dataProvider providerForTestGenerateWkt + */ + public function testGenerateWkt($gis_data, $index, $empty, $wkt) + { + if ($empty == null) { + $this->assertEquals($this->object->generateWkt($gis_data, $index), $wkt); + } else { + $this->assertEquals( + $this->object->generateWkt($gis_data, $index, $empty), + $wkt + ); + } + } + + /** + * test generateParams method + * + * @param string $wkt point in WKT form + * @param index $index index + * @param array $params expected output array + * + * @dataProvider providerForTestGenerateParams + * @return nothing + */ + public function testGenerateParams($wkt, $index, $params) + { + if ($index == null) { + $this->assertEquals($this->object->generateParams($wkt), $params); + } else { + $this->assertEquals( + $this->object->generateParams($wkt, $index), + $params + ); + } + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Linestring_test.php b/test/classes/gis/PMA_GIS_Linestring_test.php new file mode 100644 index 0000000000..4bef75c3d9 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Linestring_test.php @@ -0,0 +1,142 @@ +object = PMA_GIS_Linestring::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + $temp1 = array( + 0 => array( + 'LINESTRING' => array( + 'no_of_points' => 2, + 0 => array('x' => 5.02, 'y' => 8.45), + 1 => array('x' => 6.14, 'y' => 0.15) + ) + ) + ); + + $temp2 = $temp1; + $temp2[0]['LINESTRING']['no_of_points'] = 3; + $temp2[0]['LINESTRING'][2] = array('x' => 1.56); + + $temp3 = $temp2; + $temp3[0]['LINESTRING']['no_of_points'] = -1; + + $temp4 = $temp3; + $temp4[0]['LINESTRING']['no_of_points'] = 3; + unset($temp4[0]['LINESTRING'][2]['x']); + + return array( + array( + $temp1, + 0, + null, + 'LINESTRING(5.02 8.45,6.14 0.15)' + ), + // if a coordinate is missing, default is empty string + array( + $temp2, + 0, + null, + 'LINESTRING(5.02 8.45,6.14 0.15,1.56 )' + ), + // if no_of_points is not valid, it is considered as 2 + array( + $temp3, + 0, + null, + 'LINESTRING(5.02 8.45,6.14 0.15)' + ), + // missing coordinates are replaced with provided values (3rd parameter) + array( + $temp4, + 0, + '0', + 'LINESTRING(5.02 8.45,6.14 0.15,0 0)' + ) + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + $temp = array( + 'LINESTRING' => array( + 'no_of_points' => 2, + 0 => array('x' => '5.02', 'y' => '8.45'), + 1 => array('x' => '6.14', 'y' => '0.15') + ) + ); + $temp1 = $temp; + $temp1['gis_type'] = 'LINESTRING'; + + return array( + array( + "'LINESTRING(5.02 8.45,6.14 0.15)',124", + null, + array( + 'srid' => '124', + 0 => $temp + ) + ), + array( + 'LINESTRING(5.02 8.45,6.14 0.15)', + 2, + array( + 2 => $temp1 + ) + ) + ); + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Multilinestring_test.php b/test/classes/gis/PMA_GIS_Multilinestring_test.php new file mode 100644 index 0000000000..cef0cefbbc --- /dev/null +++ b/test/classes/gis/PMA_GIS_Multilinestring_test.php @@ -0,0 +1,182 @@ +object = PMA_GIS_Multilinestring::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + $temp = array( + 0 => array( + 'MULTILINESTRING' => array( + 'no_of_lines' => 2, + 0 => array( + 'no_of_points' => 2, + 0 => array('x' => 5.02, 'y' => 8.45), + 1 => array('x' => 6.14, 'y' => 0.15) + ), + 1 => array( + 'no_of_points' => 2, + 0 => array('x' => 1.23, 'y' => 4.25), + 1 => array('x' => 9.15, 'y' => 0.47) + ) + ) + ) + ); + + $temp1 = $temp; + unset($temp1[0]['MULTILINESTRING'][1][1]['y']); + + return array( + array( + $temp, + 0, + null, + 'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 0.47))' + ), + // values at undefined index + array( + $temp, + 1, + null, + 'MULTILINESTRING(( , ))' + ), + // if a coordinate is missing, default is empty string + array( + $temp1, + 0, + null, + 'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 ))' + ), + // missing coordinates are replaced with provided values (3rd parameter) + array( + $temp1, + 0, + '0', + 'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 0))' + ) + ); + } + + /** + * test getShape method + * + * @return nothing + */ + public function testGetShape() + { + $row_data = array( + 'numparts' => 2, + 'parts' => array( + 0 => array( + 'points' => array( + 0 => array('x' => 5.02, 'y' => 8.45), + 1 => array('x' => 6.14, 'y' => 0.15), + ), + ), + 1 => array( + 'points' => array( + 0 => array('x' => 1.23, 'y' => 4.25), + 1 => array('x' => 9.15, 'y' => 0.47), + ), + ), + ), + ); + + $this->assertEquals( + $this->object->getShape($row_data), + 'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 0.47))' + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + $temp = array( + 'MULTILINESTRING' => array( + 'no_of_lines' => 2, + 0 => array( + 'no_of_points' => 2, + 0 => array('x' => 5.02, 'y' => 8.45), + 1 => array('x' => 6.14, 'y' => 0.15), + ), + 1 => array( + 'no_of_points' => 2, + 0 => array('x' => 1.23, 'y' => 4.25), + 1 => array('x' => 9.15, 'y' => 0.47), + ) + ) + ); + + $temp1 = $temp; + $temp1['gis_type'] = 'MULTILINESTRING'; + + return array( + array( + "'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 0.47))',124", + null, + array( + 'srid' => '124', + 0 => $temp + ) + ), + array( + 'MULTILINESTRING((5.02 8.45,6.14 0.15),(1.23 4.25,9.15 0.47))', + 2, + array( + 2 => $temp1 + ) + ) + ); + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Multipoint_test.php b/test/classes/gis/PMA_GIS_Multipoint_test.php new file mode 100644 index 0000000000..8ab0aef928 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Multipoint_test.php @@ -0,0 +1,146 @@ +object = PMA_GIS_Multipoint::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + $gis_data1 = array( + 0 => array( + 'MULTIPOINT' => array( + 'no_of_points' => 2, + 0 => array( + 'x' => 5.02, + 'y' => 8.45 + ), + 1 => array( + 'x' => 1.56, + 'y' => 4.36 + ) + ) + ) + ); + + $gis_data2 = $gis_data1; + $gis_data2[0]['MULTIPOINT']['no_of_points'] = -1; + + return array( + array( + $gis_data1, + 0, + null, + 'MULTIPOINT(5.02 8.45,1.56 4.36)' + ), + array( + $gis_data2, + 0, + null, + 'MULTIPOINT(5.02 8.45)' + ) + ); + } + + /** + * test getShape method + * + * @return nothing + */ + public function testGetShape() + { + $gis_data = array( + 'numpoints' => 2, + 'points' => array( + 0 => array('x' => 5.02, 'y' => 8.45), + 1 => array('x' => 6.14, 'y' => 0.15) + ) + ); + + $this->assertEquals( + $this->object->getShape($gis_data), + 'MULTIPOINT(5.02 8.45,6.14 0.15)' + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + $temp1 = array( + 'MULTIPOINT' => array( + 'no_of_points' => 2, + 0 => array('x' => '5.02', 'y' => '8.45'), + 1 => array('x' => '6.14', 'y' => '0.15') + ) + ); + $temp2 = $temp1; + $temp2['gis_type'] = 'MULTIPOINT'; + + return array( + array( + "'MULTIPOINT(5.02 8.45,6.14 0.15)',124", + null, + array( + 'srid' => '124', + 0 => $temp1 + ) + ), + array( + 'MULTIPOINT(5.02 8.45,6.14 0.15)', + 2, + array( + 2 => $temp2 + ) + ) + ); + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Multipolygon_test.php b/test/classes/gis/PMA_GIS_Multipolygon_test.php new file mode 100644 index 0000000000..1a43555b56 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Multipolygon_test.php @@ -0,0 +1,139 @@ +object = PMA_GIS_Multipolygon::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + private function _getData() + { + return array( + 'MULTIPOLYGON' => array( + 'no_of_polygons' => 2, + 0 => array( + 'no_of_lines' => 2, + 0 => array( + 'no_of_points' => 5, + 0 => array('x' => 35, 'y' => 10), + 1 => array('x' => 10, 'y' => 20), + 2 => array('x' => 15, 'y' => 40), + 3 => array('x' => 45, 'y' => 45), + 4 => array('x' => 35, 'y' => 10), + ), + 1 => array( + 'no_of_points' => 4, + 0 => array('x' => 20, 'y' => 30), + 1 => array('x' => 35, 'y' => 32), + 2 => array('x' => 30, 'y' => 20), + 3 => array('x' => 20, 'y' => 30), + ) + ), + 1 => array( + 'no_of_lines' => 1, + 0 => array( + 'no_of_points' => 4, + 0 => array('x' => 123, 'y' => 0), + 1 => array('x' => 23, 'y' => 30), + 2 => array('x' => 17, 'y' => 63), + 3 => array('x' => 123, 'y' => 0), + ) + ) + ) + ); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + $temp = array( + 0 => $this->_getData() + ); + + return array( + array( + $temp, + 0, + null, + 'MULTIPOLYGON(((35 10,10 20,15 40,45 45,35 10)' + . ',(20 30,35 32,30 20,20 30)),((123 0,23 30,17 63,123 0)))' + ), + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + $temp = $this->_getData(); + + $temp1 = $this->_getData(); + $temp1['gis_type'] = 'MULTIPOLYGON'; + + return array( + array( + "'MULTIPOLYGON(((35 10,10 20,15 40,45 45,35 10)," + . "(20 30,35 32,30 20,20 30)),((123 0,23 30,17 63,123 0)))',124", + null, + array( + 'srid' => '124', + 0 => $temp + ) + ), + array( + 'MULTIPOLYGON(((35 10,10 20,15 40,45 45,35 10)' + . ',(20 30,35 32,30 20,20 30)),((123 0,23 30,17 63,123 0)))', + 2, + array( + 2 => $temp1 + ) + ) + ); + } +} +?> diff --git a/test/classes/gis/PMA_GIS_Point_test.php b/test/classes/gis/PMA_GIS_Point_test.php new file mode 100644 index 0000000000..a25a66ab95 --- /dev/null +++ b/test/classes/gis/PMA_GIS_Point_test.php @@ -0,0 +1,148 @@ +object = PMA_GIS_Point::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + return array( + array( + array(0 => array('POINT' => array('x' => 5.02, 'y' => 8.45))), + 0, + null, + 'POINT(5.02 8.45)' + ), + array( + array(0 => array('POINT' => array('x' => 5.02, 'y' => 8.45))), + 1, + null, + 'POINT( )' + ), + array( + array(0 => array('POINT' => array('x' => 5.02))), + 0, + null, + 'POINT(5.02 )' + ), + array( + array(0 => array('POINT' => array('y' => 8.45))), + 0, + null, + 'POINT( 8.45)' + ), + array( + array(0 => array('POINT' => array())), + 0, + null, + 'POINT( )' + ), + ); + } + + /** + * test getShape method + * + * @param array $row_data array of GIS data + * @param string $shape expected shape in WKT + * + * @dataProvider providerForTestGetShape + * @return nothing + */ + public function testGetShape($row_data, $shape) + { + $this->assertEquals($this->object->getShape($row_data), $shape); + } + + /** + * data provider for testGetShape + * + * @return data for testGetShape + */ + public function providerForTestGetShape() + { + return array( + array( + array('x' => 5.02, 'y' => 8.45), + 'POINT(5.02 8.45)' + ) + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + return array( + array( + "'POINT(5.02 8.45)',124", + null, + array( + 'srid' => '124', + 0 => array( + 'POINT' => array('x' => '5.02', 'y' => '8.45') + ), + ) + ), + array( + 'POINT(5.02 8.45)', + 2, + array( + 2 => array( + 'gis_type' => 'POINT', + 'POINT' => array('x' => '5.02', 'y' => '8.45') + ), + ) + ) + ); + } +} +?> \ No newline at end of file diff --git a/test/classes/gis/PMA_GIS_Polygon_test.php b/test/classes/gis/PMA_GIS_Polygon_test.php new file mode 100644 index 0000000000..b504acc12e --- /dev/null +++ b/test/classes/gis/PMA_GIS_Polygon_test.php @@ -0,0 +1,299 @@ +object = PMA_GIS_Polygon::singleton(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + * + * @access protected + * @return nothing + */ + protected function tearDown() + { + unset($this->object); + } + + private function _getData() + { + return array( + 'POLYGON' => array( + 'no_of_lines' => 2, + 0 => array( + 'no_of_points' => 5, + 0 => array('x' => 35, 'y' => 10), + 1 => array('x' => 10, 'y' => 20), + 2 => array('x' => 15, 'y' => 40), + 3 => array('x' => 45, 'y' => 45), + 4 => array('x' => 35, 'y' => 10), + ), + 1 => array( + 'no_of_points' => 4, + 0 => array('x' => 20, 'y' => 30), + 1 => array('x' => 35, 'y' => 32), + 2 => array('x' => 30, 'y' => 20), + 3 => array('x' => 20, 'y' => 30), + ) + ) + ); + } + + /** + * data provider for testGenerateWkt + * + * @return data for testGenerateWkt + */ + public function providerForTestGenerateWkt() + { + $temp = array( + 0 => $this->_getData() + ); + + $temp1 = $temp; + unset($temp1[0]['POLYGON'][1][3]['y']); + + return array( + array( + $temp, + 0, + null, + 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 32,30 20,20 30))' + ), + // values at undefined index + array( + $temp, + 1, + null, + 'POLYGON(( , , , ))' + ), + // if a coordinate is missing, default is empty string + array( + $temp1, + 0, + null, + 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 32,30 20,20 ))' + ), + // missing coordinates are replaced with provided values (3rd parameter) + array( + $temp1, + 0, + '0', + 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 32,30 20,20 0))' + ) + ); + } + + /** + * data provider for testGenerateParams + * + * @return data for testGenerateParams + */ + public function providerForTestGenerateParams() + { + $temp = $this->_getData(); + + $temp1 = $temp; + $temp1['gis_type'] = 'POLYGON'; + + return array( + array( + "'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 32,30 20,20 30))',124", + null, + array( + 'srid' => '124', + 0 => $temp + ) + ), + array( + 'POLYGON((35 10,10 20,15 40,45 45,35 10),(20 30,35 32,30 20,20 30))', + 2, + array( + 2 => $temp1 + ) + ) + ); + } + + /** + * test for Area + * + * @param array $ring array of points forming the ring + * @param fload $area area of the ring + * + * @dataProvider providerForTestArea + * @return nothing + */ + public function testArea($ring, $area) + { + $this->assertEquals($this->object->area($ring), $area); + } + + /** + * data provider for testArea + * + * @return data for testArea + */ + public function providerForTestArea() + { + return array( + array( + array( + 0 => array('x' => 35, 'y' => 10), + 1 => array('x' => 10, 'y' => 10), + 2 => array('x' => 15, 'y' => 40) + ), + -375.00 + ), + // first point of the ring repeated as the last point + array( + array( + 0 => array('x' => 35, 'y' => 10), + 1 => array('x' => 10, 'y' => 10), + 2 => array('x' => 15, 'y' => 40), + 3 => array('x' => 35, 'y' => 10) + ), + -375.00 + ), + // anticlockwise gives positive area + array( + array( + 0 => array('x' => 15, 'y' => 40), + 1 => array('x' => 10, 'y' => 10), + 2 => array('x' => 35, 'y' => 10) + ), + 375.00 + ) + ); + } + + /** + * test for isPointInsidePolygon + * + * @param array $point x, y coordinates of the point + * @param array $polygon array of points forming the ring + * @param bool $isInside output + * + * @dataProvider providerForTestIsPointInsidePolygon + * @return nothing + */ + public function testIsPointInsidePolygon($point, $polygon, $isInside) + { + $this->assertEquals( + $this->object->isPointInsidePolygon($point, $polygon), + $isInside + ); + } + + /** + * data provider for testIsPointInsidePolygon + * + * @return data for testIsPointInsidePolygon + */ + public function providerForTestIsPointInsidePolygon() + { + $ring = array( + 0 => array('x' => 35, 'y' => 10), + 1 => array('x' => 10, 'y' => 10), + 2 => array('x' => 15, 'y' => 40), + 3 => array('x' => 35, 'y' => 10) + ); + + return array( + // point inside the ring + array( + array('x' => 20, 'y' => 15), + $ring, + true + ), + // point on an edge of the ring + array( + array('x' => 20, 'y' => 10), + $ring, + false + ), + // point on a vertex of the ring + array( + array('x' => 10, 'y' => 10), + $ring, + false + ), + // point outside the ring + array( + array('x' => 5, 'y' => 10), + $ring, + false + ), + ); + } + + /** + * test for getPointOnSurface + * + * @param array $ring array of points forming the ring + * + * @dataProvider providerForTestGetPointOnSurface + * @return nothing + */ + public function testGetPointOnSurface($ring) + { + $this->assertEquals( + $this->object->isPointInsidePolygon( + $this->object->getPointOnSurface($ring), + $ring + ), + true + ); + } + + /** + * data provider for testGetPointOnSurface + * + * @return data for testGetPointOnSurface + */ + public function providerForTestGetPointOnSurface() + { + $temp = $this->_getData(); + unset($temp['POLYGON'][0]['no_of_points']); + unset($temp['POLYGON'][1]['no_of_points']); + + return array( + array( + $temp['POLYGON'][0] + ), + array( + $temp['POLYGON'][1] + ) + ); + } +} +?> \ No newline at end of file diff --git a/test/libraries/PMA_GIS_modifyQuery_test.php b/test/libraries/PMA_GIS_modifyQuery_test.php new file mode 100644 index 0000000000..04e9e1adad --- /dev/null +++ b/test/libraries/PMA_GIS_modifyQuery_test.php @@ -0,0 +1,94 @@ +assertEquals( + PMA_GIS_modifyQuery($sql_query, $settings), + $modified_query + ); + } + + /** + * data provider for testModifyQuery + * + * @return data for testModifyQuery + */ + public function provider() + { + return array( + // select * + array( + 'SELECT * FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `noo`, ASTEXT(`moo`) AS `moo`, SRID(`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // select * with no label column + array( + 'SELECT * FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo'), + 'SELECT ASTEXT(`moo`) AS `moo`, SRID(`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // more columns + array( + 'SELECT `aaa`, `moo`, `bbb`, `noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `noo`, ASTEXT(`moo`) AS `moo`, SRID(`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // no labelColumn defined + array( + 'SELECT `moo`, `noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo'), + 'SELECT ASTEXT(`moo`) AS `moo`, SRID(`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // alias for spatialColumn + array( + 'SELECT `aaa` AS `moo`, `noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `noo`, ASTEXT(`aaa`) AS `moo`, SRID(`aaa`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // alias for labelColumn + array( + 'SELECT `moo`, `bbb` AS `noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `bbb` AS `noo`, ASTEXT(`moo`) AS `moo`, SRID(`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // with database names + array( + 'SELECT `db`.`moo`, `db`.`noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `db`.`noo`, ASTEXT(`db`.`moo`) AS `moo`, SRID(`db`.`moo`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + // database names plus alias + array( + 'SELECT `db`.`aaa` AS `moo`, `noo` FROM `foo` WHERE `bar` = `zoo`', + array('spatialColumn' => 'moo', 'labelColumn' => 'noo'), + 'SELECT `noo`, ASTEXT(`db`.`aaa`) AS `moo`, SRID(`db`.`aaa`) AS `srid` FROM `foo` WHERE `bar` = `zoo`' + ), + ); + } +} +?> \ No newline at end of file diff --git a/themes/original/css/theme_right.css.php b/themes/original/css/theme_right.css.php index 64e64e49a7..6cb423363f 100644 --- a/themes/original/css/theme_right.css.php +++ b/themes/original/css/theme_right.css.php @@ -1739,6 +1739,44 @@ input#input_import_file { margin: 5px 0px 5px 0px; } +/** + * GIS data editor styles + */ +a.close_gis_editor { + float: right; +} + +#gis_editor { + display: none; + position: fixed; + _position: absolute; /* hack for IE */ + z-index: 101; + overflow-y: auto; + overflow-x: hidden; +} + +#gis_data { + min-height: 230px; +} + +#gis_data_textarea { + height: 6em; +} + +#gis_data_editor { + background: #D0DCE0; + padding: 15px; + min-height: 500px; +} + +#gis_data_editor .choice { + display: none; +} + +#gis_data_editor input[type="text"] { + width: 75px; +} + /** * ENUM/SET editor styles */ diff --git a/themes/pmahomme/css/theme_right.css.php b/themes/pmahomme/css/theme_right.css.php index 982e744a7d..1103bfa770 100644 --- a/themes/pmahomme/css/theme_right.css.php +++ b/themes/pmahomme/css/theme_right.css.php @@ -2134,6 +2134,44 @@ input#input_import_file { margin: 5px 0px 5px 0px; } +/** + * GIS data editor styles + */ +a.close_gis_editor { + float: right; +} + +#gis_editor { + display: none; + position: fixed; + _position: absolute; /* hack for IE */ + z-index: 101; + overflow-y: auto; + overflow-x: hidden; +} + +#gis_data { + min-height: 230px; +} + +#gis_data_textarea { + height: 6em; +} + +#gis_data_editor { + background: #D0DCE0; + padding: 15px; + min-height: 500px; +} + +#gis_data_editor .choice { + display: none; +} + +#gis_data_editor input[type="text"] { + width: 75px; +} + /** * ENUM/SET editor styles */