backend/wbprivate/sqlide/spatial_data_view.cpp (732 lines of code) (raw):
/*
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms, as
* designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "base/log.h"
#include "base/file_utilities.h"
#include "spatial_data_view.h"
#include "spatial_draw_box.h"
#include "grt/spatial_handler.h"
#include "wb_sql_editor_form.h"
#include "wb_sql_editor_result_panel.h"
#include <algorithm>
#include <cstdlib>
#include "mforms/app.h"
#include "mforms/toolbar.h"
#include "mforms/menubar.h"
#include "mforms/checkbox.h"
#include "mforms/treeview.h"
#include "mforms/label.h"
#include "mforms/textbox.h"
#include "mforms/filechooser.h"
#include "mdc.h"
DEFAULT_LOG_DOMAIN("spatial");
class RecordsetLayer : public spatial::Layer {
Recordset::Ptr _rset;
int _geom_column;
bool _loaded;
public:
RecordsetLayer(int layer_id, base::Color color, Recordset::Ptr rset, int column)
: spatial::Layer(layer_id, color), _rset(rset), _geom_column(column), _loaded(false) {
}
virtual void load_data() {
Recordset::Ref rs(recordset());
if (rs && !_loaded) {
_loaded = true;
logInfo("Loading %li rows/features from resultset\n", (long)rs->row_count());
_render_progress = 0.0;
ssize_t row_count = rs->row_count();
float step = 1.0f / row_count;
for (ssize_t c = row_count, row = 0; row < c; row++) {
std::string geom_data; // data in MySQL internal binary geometry format.. this is neither WKT nor WKB
// but the internal format seems to be 4 bytes of SRID followed by WKB data
if (rs->get_raw_field(row, _geom_column, geom_data) && !geom_data.empty())
add_feature((int)row, geom_data, false);
_render_progress += step;
}
}
}
Recordset::Ref recordset() {
return _rset.lock();
}
};
class GridLayer : public spatial::Layer {
public:
GridLayer(int layer_id, base::Color color)
: spatial::Layer(layer_id, color) // the color is the background color, the grid color is always gray
{
_show = true;
set_fill_polygons(true);
std::string data =
"GEOMETRYCOLLECTION(LINESTRING(-179 -89,-165 -89,-150 -89,-135 -89,-120 -89,-105 -89,-89 -89,-75 -89,-60 -89,-45 "
"-89,-30 -89,-15 -89,0 -89,15 -89,30 -89,45 -89,60 -89,75 -89,89 -89,105 -89,120 -89,135 -89,150 -89,165 -89,179 "
"-89),LINESTRING(-179 -75,-165 -75,-150 -75,-135 -75,-120 -75,-105 -75,-89 -75,-75 -75,-60 -75,-45 -75,-30 "
"-75,-15 -75,0 -75,15 -75,30 -75,45 -75,60 -75,75 -75,89 -75,105 -75,120 -75,135 -75,150 -75,165 -75,179 "
"-75),LINESTRING(-179 -60,-165 -60,-150 -60,-135 -60,-120 -60,-105 -60,-89 -60,-75 -60,-60 -60,-45 -60,-30 "
"-60,-15 -60,0 -60,15 -60,30 -60,45 -60,60 -60,75 -60,89 -60,105 -60,120 -60,135 -60,150 -60,165 -60,179 "
"-60),LINESTRING(-179 -45,-165 -45,-150 -45,-135 -45,-120 -45,-105 -45,-89 -45,-75 -45,-60 -45,-45 -45,-30 "
"-45,-15 -45,0 -45,15 -45,30 -45,45 -45,60 -45,75 -45,89 -45,105 -45,120 -45,135 -45,150 -45,165 -45,179 "
"-45),LINESTRING(-179 -30,-165 -30,-150 -30,-135 -30,-120 -30,-105 -30,-89 -30,-75 -30,-60 -30,-45 -30,-30 "
"-30,-15 -30,0 -30,15 -30,30 -30,45 -30,60 -30,75 -30,89 -30,105 -30,120 -30,135 -30,150 -30,165 -30,179 "
"-30),LINESTRING(-179 -15,-165 -15,-150 -15,-135 -15,-120 -15,-105 -15,-89 -15,-75 -15,-60 -15,-45 -15,-30 "
"-15,-15 -15,0 -15,15 -15,30 -15,45 -15,60 -15,75 -15,89 -15,105 -15,120 -15,135 -15,150 -15,165 -15,179 "
"-15),LINESTRING(-179 0,-165 0,-150 0,-135 0,-120 0,-105 0,-89 0,-75 0,-60 0,-45 0,-30 0,-15 0,0 0,15 0,30 0,45 "
"0,60 0,75 0,89 0,105 0,120 0,135 0,150 0,165 0,179 0),LINESTRING(-179 15,-165 15,-150 15,-135 15,-120 15,-105 "
"15,-89 15,-75 15,-60 15,-45 15,-30 15,-15 15,0 15,15 15,30 15,45 15,60 15,75 15,89 15,105 15,120 15,135 15,150 "
"15,165 15,179 15),LINESTRING(-179 30,-165 30,-150 30,-135 30,-120 30,-105 30,-89 30,-75 30,-60 30,-45 30,-30 "
"30,-15 30,0 30,15 30,30 30,45 30,60 30,75 30,89 30,105 30,120 30,135 30,150 30,165 30,179 30),LINESTRING(-179 "
"45,-165 45,-150 45,-135 45,-120 45,-105 45,-89 45,-75 45,-60 45,-45 45,-30 45,-15 45,0 45,15 45,30 45,45 45,60 "
"45,75 45,89 45,105 45,120 45,135 45,150 45,165 45,179 45),LINESTRING(-179 60,-165 60,-150 60,-135 60,-120 "
"60,-105 60,-89 60,-75 60,-60 60,-45 60,-30 60,-15 60,0 60,15 60,30 60,45 60,60 60,75 60,89 60,105 60,120 60,135 "
"60,150 60,165 60,179 60),LINESTRING(-179 75,-165 75,-150 75,-135 75,-120 75,-105 75,-89 75,-75 75,-60 75,-45 "
"75,-30 75,-15 75,0 75,15 75,30 75,45 75,60 75,75 75,89 75,105 75,120 75,135 75,150 75,165 75,179 "
"75),LINESTRING(-179 89,-165 89,-150 89,-135 89,-120 89,-105 89,-89 89,-75 89,-60 89,-45 89,-30 89,-15 89,0 "
"89,15 89,30 89,45 89,60 89,75 89,89 89,105 89,120 89,135 89,150 89,165 89,179 89),LINESTRING(-179 -89,-179 "
"-75,-179 -60,-179 -45,-179 -30,-179 -15,-179 0,-179 15,-179 30,-179 45,-179 60,-179 75,-179 89),LINESTRING(-165 "
"-89,-165 -75,-165 -60,-165 -45,-165 -30,-165 -15,-165 0,-165 15,-165 30,-165 45,-165 60,-165 75,-165 "
"89),LINESTRING(-150 -89,-150 -75,-150 -60,-150 -45,-150 -30,-150 -15,-150 0,-150 15,-150 30,-150 45,-150 "
"60,-150 75,-150 89),LINESTRING(-135 -89,-135 -75,-135 -60,-135 -45,-135 -30,-135 -15,-135 0,-135 15,-135 "
"30,-135 45,-135 60,-135 75,-135 89),LINESTRING(-120 -89,-120 -75,-120 -60,-120 -45,-120 -30,-120 -15,-120 "
"0,-120 15,-120 30,-120 45,-120 60,-120 75,-120 89),LINESTRING(-105 -89,-105 -75,-105 -60,-105 -45,-105 -30,-105 "
"-15,-105 0,-105 15,-105 30,-105 45,-105 60,-105 75,-105 89),LINESTRING(-89 -89,-89 -75,-89 -60,-89 -45,-89 "
"-30,-89 -15,-89 0,-89 15,-89 30,-89 45,-89 60,-89 75,-89 89),LINESTRING(-75 -89,-75 -75,-75 -60,-75 -45,-75 "
"-30,-75 -15,-75 0,-75 15,-75 30,-75 45,-75 60,-75 75,-75 89),LINESTRING(-60 -89,-60 -75,-60 -60,-60 -45,-60 "
"-30,-60 -15,-60 0,-60 15,-60 30,-60 45,-60 60,-60 75,-60 89),LINESTRING(-45 -89,-45 -75,-45 -60,-45 -45,-45 "
"-30,-45 -15,-45 0,-45 15,-45 30,-45 45,-45 60,-45 75,-45 89),LINESTRING(-30 -89,-30 -75,-30 -60,-30 -45,-30 "
"-30,-30 -15,-30 0,-30 15,-30 30,-30 45,-30 60,-30 75,-30 89),LINESTRING(-15 -89,-15 -75,-15 -60,-15 -45,-15 "
"-30,-15 -15,-15 0,-15 15,-15 30,-15 45,-15 60,-15 75,-15 89),LINESTRING(0 -89,0 -75,0 -60,0 -45,0 -30,0 -15,0 "
"0,0 15,0 30,0 45,0 60,0 75,0 89),LINESTRING(15 -89,15 -75,15 -60,15 -45,15 -30,15 -15,15 0,15 15,15 30,15 45,15 "
"60,15 75,15 89),LINESTRING(30 -89,30 -75,30 -60,30 -45,30 -30,30 -15,30 0,30 15,30 30,30 45,30 60,30 75,30 "
"89),LINESTRING(45 -89,45 -75,45 -60,45 -45,45 -30,45 -15,45 0,45 15,45 30,45 45,45 60,45 75,45 "
"89),LINESTRING(60 -89,60 -75,60 -60,60 -45,60 -30,60 -15,60 0,60 15,60 30,60 45,60 60,60 75,60 "
"89),LINESTRING(75 -89,75 -75,75 -60,75 -45,75 -30,75 -15,75 0,75 15,75 30,75 45,75 60,75 75,75 "
"89),LINESTRING(89 -89,89 -75,89 -60,89 -45,89 -30,89 -15,89 0,89 15,89 30,89 45,89 60,89 75,89 "
"89),LINESTRING(105 -89,105 -75,105 -60,105 -45,105 -30,105 -15,105 0,105 15,105 30,105 45,105 60,105 75,105 "
"89),LINESTRING(120 -89,120 -75,120 -60,120 -45,120 -30,120 -15,120 0,120 15,120 30,120 45,120 60,120 75,120 "
"89),LINESTRING(135 -89,135 -75,135 -60,135 -45,135 -30,135 -15,135 0,135 15,135 30,135 45,135 60,135 75,135 "
"89),LINESTRING(150 -89,150 -75,150 -60,150 -45,150 -30,150 -15,150 0,150 15,150 30,150 45,150 60,150 75,150 "
"89),LINESTRING(165 -89,165 -75,165 -60,165 -45,165 -30,165 -15,165 0,165 15,165 30,165 45,165 60,165 75,165 "
"89),LINESTRING(179 -89,179 -75,179 -60,179 -45,179 -30,179 -15,179 0,179 15,179 30,179 45,179 60,179 75,179 "
"89))";
add_feature(0, data, true);
}
virtual void repaint(mdc::CairoCtx &cr, float scale, const base::Rect &clip_area) {
std::deque<spatial::ShapeContainer>::const_iterator it;
cr.save();
cr.set_line_width(0.5);
cr.set_color(base::Color(0.4, 0.4, 0.4));
for (std::deque<spatial::Feature *>::iterator it = _features.begin(); it != _features.end() && !_interrupt; ++it)
(*it)->repaint(cr, scale, clip_area);
cr.restore();
}
};
SpatialDataView::SpatialDataView(SqlEditorResult *owner) : mforms::Box(false), _owner(owner), _activated(false) {
_splitter = mforms::manage(new mforms::Splitter(true, true));
_rendering = false;
_main_box = mforms::manage(new mforms::Box(true));
_viewer = mforms::manage(new SpatialDrawBox());
_viewer->position_changed_cb = std::bind(&SpatialDataView::update_coordinates, this, std::placeholders::_1);
_viewer->position_clicked_cb = std::bind(&SpatialDataView::handle_click, this, std::placeholders::_1);
_viewer->work_started = std::bind(&SpatialDataView::work_started, this, std::placeholders::_1, std::placeholders::_2);
_viewer->work_finished = std::bind(&SpatialDataView::work_finished, this, std::placeholders::_1);
_viewer->get_option = std::bind(&SpatialDataView::get_option, this, std::placeholders::_1, std::placeholders::_2);
_viewer->area_selected = std::bind(&SpatialDataView::area_selected, this);
_active_layer = 0;
_toolbar = mforms::manage(new mforms::ToolBar(mforms::SecondaryToolBar));
{
mforms::ToolBarItem *item;
item = mforms::manage(new mforms::ToolBarItem(mforms::TitleItem));
item->set_text("Spatial View");
_toolbar->add_item(item);
_toolbar->add_separator_item();
item = mforms::manage(new mforms::ToolBarItem(mforms::LabelItem));
item->set_text("Projection:");
_toolbar->add_item(item);
std::vector<std::string> projection_types;
projection_types.push_back("Robinson");
projection_types.push_back("Mercator");
projection_types.push_back("Equirectangular");
projection_types.push_back("Bonne");
_projection_picker = mforms::manage(new mforms::ToolBarItem(mforms::SelectorItem));
_projection_picker->set_selector_items(projection_types);
scoped_connect(_projection_picker->signal_activated(),
std::bind(&SpatialDataView::projection_item_activated, this, std::placeholders::_1));
_toolbar->add_item(_projection_picker);
_toolbar->add_separator_item();
item = mforms::manage(new mforms::ToolBarItem(mforms::LabelItem));
item->set_text("Tool:");
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
item->set_name("Reset Tool");
item->setInternalName("reset_tool");
item->set_icon(mforms::App::get()->get_resource_path("wb_arrow.png"));
item->set_tooltip("Pan map and select feature to view");
item->signal_activated()->connect(std::bind(&SpatialDataView::change_tool, this, item));
_toolbar->add_item(item);
item->set_checked(true);
item = mforms::manage(new mforms::ToolBarItem(mforms::ToggleItem));
item->set_name("Zoom to Area");
item->setInternalName("zoom_to_area");
item->set_icon(mforms::App::get()->get_resource_path("qe_sql-editor-tb-icon_zoom-area.png"));
item->set_tooltip("Zoom to area. Click and drag in the map to select an area to be zoomed into.");
item->signal_activated()->connect(std::bind(&SpatialDataView::change_tool, this, item));
_toolbar->add_item(item);
_toolbar->add_separator_item();
item = mforms::manage(new mforms::ToolBarItem(mforms::LabelItem));
item->set_text("Zoom:");
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
item->set_icon(mforms::App::get()->get_resource_path("qe_sql-editor-tb-icon_zoom-out.png"));
item->set_tooltip("Zoom out one step");
item->signal_activated()->connect(std::bind(&SpatialDrawBox::zoom_out, _viewer));
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
item->set_icon(mforms::App::get()->get_resource_path("qe_sql-editor-tb-icon_zoom-in.png"));
item->set_tooltip("Zoom in one step");
item->signal_activated()->connect(std::bind(&SpatialDrawBox::zoom_in, _viewer));
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
item->set_icon(mforms::App::get()->get_resource_path("qe_sql-editor-tb-icon_zoom-reset.png"));
item->set_tooltip("Reset zoom to the outermost zoom level");
item->signal_activated()->connect(std::bind(&SpatialDrawBox::reset_view, _viewer));
_toolbar->add_item(item);
_toolbar->add_separator_item();
item = mforms::manage(new mforms::ToolBarItem(mforms::LabelItem));
item->set_text("Jump To:");
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
item->set_icon(mforms::App::get()->get_resource_path("qe_sql-editor-tb-icon_zoom-jump.png"));
item->set_tooltip("Specify coordinates to center screen on.");
item->signal_activated()->connect(std::bind(&SpatialDataView::jump_to, this));
_toolbar->add_item(item);
_toolbar->add_separator_item();
item = mforms::manage(new mforms::ToolBarItem(mforms::LabelItem));
item->set_text("Export:");
_toolbar->add_item(item);
item = mforms::manage(new mforms::ToolBarItem(mforms::ActionItem));
item->set_icon(mforms::App::get()->get_resource_path("record_export.png"));
item->set_tooltip(_("Export visible area as PNG image."));
item->signal_activated()->connect(std::bind(&SpatialDataView::export_image, this));
_toolbar->add_item(item);
}
add(_toolbar, false, true);
_splitter->add(_viewer, 100);
_option_box = mforms::manage(new mforms::Box(false));
_option_box->set_spacing(4);
_option_box->set_padding(8);
#if defined(__APPLE__) || defined(_MSC_VER)
_option_box->set_back_color("#f0f0f0");
#endif
_map_menu = new mforms::ContextMenu();
_map_menu->add_item_with_title("Copy Coordinates", std::bind(&SpatialDataView::copy_coordinates, this), "Copy Coordinates", "");
_map_menu->add_item_with_title("Copy Record for Feature", std::bind(&SpatialDataView::copy_record, this), "Copy Record for Feature", "");
_map_menu->add_item_with_title("View Record for Feature", std::bind(&SpatialDataView::view_record, this), "View Record for Feature", "");
_map_menu->signal_will_show()->connect(std::bind(&SpatialDataView::map_menu_will_show, this));
_viewer->set_context_menu(_map_menu);
_layer_menu = new mforms::ContextMenu();
// _layer_menu->add_item_with_title("Set Color...", std::bind(&SpatialDataView::activate, this));
// _layer_menu->add_item_with_title("Properties...", std::bind(&SpatialDataView::activate, this));
mforms::MenuItem *mitem = mforms::manage(new mforms::MenuItem("Fill Polygons", mforms::CheckedMenuItem));
mitem->set_name("Fillup Polygon");
mitem->setInternalName("fillup_polygon");
mitem->signal_clicked()->connect(std::bind(&SpatialDataView::fillup_polygon, this, mitem));
_layer_menu->add_item(mitem);
_layer_menu->add_separator();
_layer_menu->add_item_with_title("Refresh", std::bind(&SpatialDataView::refresh_layers, this), "Refresh", "refresh");
_layer_menu->add_separator();
_layer_menu->add_item_with_title("Move Layer Up", std::bind(&SpatialDataView::layer_menu_action, this, "layer_up"),
"Move Layer Up", "layer_up");
_layer_menu->add_item_with_title("Move Layer Down",
std::bind(&SpatialDataView::layer_menu_action, this, "layer_down"), "Move Layer Down", "layer_down");
_layer_menu->signal_will_show()->connect(std::bind(&SpatialDataView::layer_menu_will_show, this));
_layer_tree = mforms::manage(new mforms::TreeView(mforms::TreeFlatList));
_layer_tree->add_column(mforms::CheckColumnType, "", 25, true);
_layer_tree->add_column(mforms::IconStringColumnType, "Layer", 120, false, true);
_layer_tree->add_column(mforms::StringColumnType, "Source", 200, false, true);
_layer_tree->end_columns();
_layer_tree->set_cell_edit_handler(
std::bind(&SpatialDataView::tree_toggled, this, std::placeholders::_1, std::placeholders::_3));
_layer_tree->set_context_menu(_layer_menu);
_layer_tree->signal_node_activated()->connect(
std::bind(&SpatialDataView::activate_layer, this, std::placeholders::_1, std::placeholders::_2));
_layer_tree->signal_changed()->connect(
std::bind(&SpatialDataView::activate_layer, this, mforms::TreeNodeRef(),
-42)); // unused dummy value... should just not conflict with possibly valid values
_layer_tree->set_row_overlay_handler(std::bind(&SpatialDataView::layer_overlay_handler, this, std::placeholders::_1));
_layer_tree->set_size(-1, 150);
_option_box->add(_layer_tree, true, true);
_mouse_pos_label = mforms::manage(new mforms::Label("Lat:\nLon:"));
_option_box->add(_mouse_pos_label, false, true);
_info_box = mforms::manage(new mforms::TextBox(mforms::VerticalScrollBar));
_option_box->add(_info_box, true, true);
_info_box->set_value("Click a feature to view its record");
_option_box->set_size(220, -1);
_splitter->add(_option_box, 200);
_splitter->signal_position_changed()->connect(std::bind(&SpatialDataView::call_refresh_viewer, this));
add(_splitter, true, true);
}
std::vector<std::string> SpatialDataView::layer_overlay_handler(mforms::TreeNodeRef node) {
std::vector<std::string> icons;
icons.push_back(mforms::App::get()->get_resource_path("wb_item_overlay_autozoom.png"));
return icons;
}
void SpatialDataView::call_refresh_viewer() {
if (!_rendering) {
if (_spliter_change_timeout != 0) {
mforms::Utilities::cancel_timeout(_spliter_change_timeout);
_spliter_change_timeout = 0;
}
_spliter_change_timeout = mforms::Utilities::add_timeout(0.5, std::bind(&SpatialDataView::refresh_viewer, this));
}
}
bool SpatialDataView::refresh_viewer() {
if (_rendering)
return false;
_spliter_change_timeout = 0;
_viewer->invalidate(true);
return false;
}
void SpatialDataView::change_tool(mforms::ToolBarItem *item) {
item->set_checked(true);
if (item->getInternalName() == "reset_tool") {
_toolbar->set_item_checked("zoom_to_area", false);
_viewer->select_area(false);
} else {
_viewer->select_area(true);
_toolbar->set_item_checked("reset_tool", false);
}
}
int SpatialDataView::get_option(const char *opt_name, int default_value) {
return bec::GRTManager::get()->get_app_option_int(opt_name, default_value) != 0;
}
void SpatialDataView::area_selected() {
_toolbar->set_item_checked("zoom_to_area", false);
_toolbar->set_item_checked("reset_tool", true);
_viewer->select_area(false);
}
void SpatialDataView::fillup_polygon(mforms::MenuItem *mitem) {
if (_layer_tree->is_enabled()) {
spatial::Layer *layer = _viewer->get_layer(get_selected_layer_id());
if (layer)
layer->set_fill_polygons(mitem->get_checked());
_viewer->invalidate();
}
}
void SpatialDataView::projection_item_activated(mforms::ToolBarItem *item) {
std::string action = item->get_text();
if (action == "Mercator")
_viewer->set_projection(spatial::ProjMercator);
else if (action == "Equirectangular")
_viewer->set_projection(spatial::ProjEquirectangular);
else if (action == "Robinson")
_viewer->set_projection(spatial::ProjRobinson);
else if (action == "Bonne")
_viewer->set_projection(spatial::ProjBonne);
}
SpatialDataView::~SpatialDataView() {
if (_spliter_change_timeout != 0) {
mforms::Utilities::cancel_timeout(_spliter_change_timeout);
_spliter_change_timeout = 0;
}
delete _layer_menu;
}
static double parse_latitude(const std::string &s) {
double parsed = 0.0;
if (s.empty())
throw std::invalid_argument("Invalid value");
// check if in degrees
if (s.find("\xc2\xb0") != std::string::npos) // look for degree sign in utf8
{
int deg = 0, min = 0;
float sec = 0;
char o = *s.rbegin();
if (o != 'N' && o != 'S' && o != '"' && !isdigit(o))
throw std::invalid_argument("Latitude value must be N or S");
if (sscanf(s.c_str(), "%i\xc2\xb0%i'%f\"", °, &min, &sec) == 0)
throw std::invalid_argument("Unable to parse latitude value " + s);
parsed = deg + (min / 60.0) + (sec / 3600.0);
if (o == 'S')
parsed = -parsed;
} else
parsed = strtod(s.c_str(), NULL);
return parsed;
}
static double parse_longitude(const std::string &s) {
double parsed = 0.0;
if (s.empty())
throw std::invalid_argument("Invalid value");
// check if in degrees
if (s.find("\xc2\xb0") != std::string::npos) // look for degree sign in utf8
{
int deg = 0, min = 0;
float sec = 0;
char o = *s.rbegin();
if (o != 'E' && o != 'W' && o != '"' && !isdigit(o))
throw std::invalid_argument("Longitude value must be E or W");
if (sscanf(s.c_str(), "%i\xc2\xb0%i'%f\"", °, &min, &sec) == 0)
throw std::invalid_argument("Unable to parse longitude value " + s);
parsed = deg + (min / 60.0) + (sec / 3600.0);
if (o == 'W')
parsed = -parsed;
} else
parsed = strtod(s.c_str(), NULL);
return parsed;
}
void SpatialDataView::jump_to() {
std::string ret;
bool badformat = false;
if (mforms::Utilities::request_input("Jump to Coordinates", "Enter coordinates in Lat, Lon:", "", ret)) {
std::string lat, lon;
if (base::partition(ret, ",", lat, lon)) {
double plat = parse_latitude(base::strip_text(lat));
double plon = parse_longitude(base::strip_text(lon));
_viewer->center_on(plat, plon);
} else
badformat = true;
}
if (badformat) {
mforms::Utilities::show_message(
"Jump to Coordinates",
"Coordinates must be in Lat, Lon format.\nEx.: 40.32321312, -120.3232131 or 54°50'26.7\"N 98°23'51.0\"E", "OK");
}
}
void SpatialDataView::export_image() {
mforms::FileChooser fc(mforms::SaveFile);
fc.set_title("Save Spatial View Image to File");
fc.set_extensions("PNG Files (*.png)|*.png", "png");
if (fc.run_modal()) {
try {
_viewer->save_to_png(fc.get_path());
mforms::Utilities::show_message(
_("Save to File"), base::strfmt(_("Image has been succesfully saved to '%s'"), fc.get_path().c_str()), _("OK"));
} catch (std::exception &exc) {
mforms::Utilities::show_error(
_("Save to File"), base::strfmt(_("Could not save to file '%s': %s"), fc.get_path().c_str(), exc.what()),
_("OK"));
}
}
}
spatial::LayerId SpatialDataView::get_selected_layer_id() {
mforms::TreeNodeRef node(_layer_tree->get_selected_node());
if (node)
return base::atoi<int>(node->get_tag(), 0);
return 0;
}
void SpatialDataView::auto_zoom(LayerId layer) {
_viewer->clear_pins();
_viewer->auto_zoom(layer);
_viewer->invalidate(true);
}
void SpatialDataView::copy_coordinates() {
std::pair<double, double> p = _viewer->clicked_coordinates();
mforms::Utilities::set_clipboard_text(base::strfmt("%.6f, %.6f", p.first, p.second));
}
RecordsetLayer *SpatialDataView::active_layer() {
std::deque<spatial::Layer *> layers(_viewer->get_layers());
for (std::deque<spatial::Layer *>::const_iterator l = layers.begin(); l != layers.end(); ++l) {
if ((*l)->layer_id() == _active_layer)
return dynamic_cast<RecordsetLayer *>(*l);
}
return NULL;
}
int SpatialDataView::row_id_for_action(RecordsetLayer *&layer) {
layer = active_layer();
if (layer)
return _viewer->clicked_row_id();
return -1;
}
void SpatialDataView::map_menu_will_show() {
}
void SpatialDataView::layer_menu_will_show() {
spatial::Layer *layer = _viewer->get_layer(get_selected_layer_id());
_layer_menu->set_item_enabled("set_active", layer && layer->layer_id() != _grid_layer);
_layer_menu->set_item_checked("fillup_polygon", layer && layer->fill());
mforms::TreeNodeRef node = _layer_tree->get_selected_node();
spatial::LayerId bg_layer_id = _viewer->get_background()->layer_id();
if (node.is_valid() && base::atoi<int>(node->get_tag(), 0) != bg_layer_id) {
mforms::TreeNodeRef pnode = node->previous_sibling(), nnode = node->next_sibling();
_layer_menu->set_item_enabled("layer_up", pnode.is_valid() && base::atoi<int>(pnode->get_tag(), 0) != bg_layer_id);
_layer_menu->set_item_enabled("layer_down",
nnode.is_valid() && base::atoi<int>(nnode->get_tag(), 0) != bg_layer_id);
} else {
_layer_menu->set_item_enabled("layer_up", false);
_layer_menu->set_item_enabled("layer_down", false);
}
}
void SpatialDataView::copy_record() {
RecordsetLayer *layer = NULL;
int row_id = row_id_for_action(layer);
if (layer) {
bool flag = false;
if (row_id >= 0) {
Recordset::Ref rs(layer->recordset());
if (rs) {
std::string text;
std::string value;
for (size_t i = 0; i < rs->get_column_count(); i++) {
if (i > 0)
text.append(",");
if (rs->get_field(row_id, i, value))
text.append(value);
}
mforms::Utilities::set_clipboard_text(text);
flag = true;
}
}
if (!flag)
mforms::App::get()->set_status_text("No row found for clicked coordinates.");
} else
mforms::App::get()->set_status_text("No visible layers.");
}
void SpatialDataView::view_record() {
RecordsetLayer *layer = NULL;
int row_id = row_id_for_action(layer);
if (layer) {
if (row_id >= 0) {
_owner->view_record_in_form(row_id);
} else
mforms::App::get()->set_status_text("No row found for clicked coordinates.");
} else
mforms::App::get()->set_status_text("No visible layers.");
}
void SpatialDataView::work_started(mforms::View *progress_panel, bool reprojecting) {
_rendering = true;
_layer_tree->set_enabled(false);
_layer_menu->set_item_enabled("refresh", false);
if (reprojecting) {
progress_panel->set_size(500, 150);
_viewer->add(progress_panel, mforms::MiddleCenter);
// this is causing a loop in the Mac, where the relayout causes the splitter to be resized
// which then triggers a re-render and so on... commenting out the relayout() seems to
// not have any effects, so let's try that...
// relayout();
}
}
void SpatialDataView::work_finished(mforms::View *progress_panel) {
_rendering = false;
_layer_tree->set_enabled(true);
_layer_menu->set_item_enabled("refresh", true);
_viewer->remove(progress_panel);
_main_box->show(true);
}
void SpatialDataView::activate() {
if (!_activated) {
_activated = true;
if (_splitter->get_divider_position() != this->get_width() - 200)
_splitter->set_divider_position(this->get_width() - 200);
}
_viewer->activate();
}
void SpatialDataView::refresh_layers() {
std::vector<SpatialDataView::SpatialDataSource> spatial_columns; // = _owner->get_spatial_columns();
for (int c = _owner->owner()->owner()->sql_editor_count(), editor = 0; editor < c; editor++) {
SqlEditorPanel *panel = _owner->owner()->owner()->sql_editor_panel(editor);
if (panel != NULL) {
for (size_t i = 0; i < panel->result_panel_count(); ++i) {
SqlEditorResult *result = panel->result_panel((int)i);
if (result) {
std::vector<SpatialDataView::SpatialDataSource> tmp(result->get_spatial_columns());
std::copy(tmp.begin(), tmp.end(), std::back_inserter(spatial_columns));
}
}
}
}
set_geometry_columns(spatial_columns);
if (get_option("SqlEditor::SpatialAutoZoom", 1) >= 1)
_viewer->auto_zoom(_active_layer);
}
mforms::TreeNodeRef static move_node_to(mforms::TreeNodeRef &node, mforms::TreeNodeRef &new_parent, int index) {
mforms::TreeNodeRef new_node = new_parent->insert_child(index);
new_node->set_bool(0, node->get_bool(0));
new_node->set_string(1, node->get_string(1));
new_node->set_string(2, node->get_string(2));
new_node->set_tag(node->get_tag());
new_node->set_data(node->get_data());
node->remove_from_parent();
return new_node;
}
void SpatialDataView::layer_menu_action(const std::string &action) {
mforms::TreeNodeRef node = _layer_tree->get_selected_node();
mforms::TreeNodeRef group_node = node->get_parent();
size_t node_index = node->get_child_index(node), new_index = node_index;
if (action == "layer_up") {
if (node->previous_sibling().is_valid())
new_index = node_index - 1;
} else if (action == "layer_down") {
if (node->next_sibling().is_valid())
new_index = node_index + 2;
}
node = move_node_to(node, group_node, (int)new_index);
spatial::Layer *layer = _viewer->get_layer(base::atoi<int>(node->get_tag(), 0));
if (layer)
set_color_icon(node, 1, layer->color());
std::vector<int> order;
order.reserve(_layer_tree->count());
for (int i = 0; i < _layer_tree->count(); ++i) {
spatial::LayerId layer_id = base::atoi<int>(_layer_tree->node_at_row(i)->get_tag(), 0);
if (layer_id != _viewer->get_background()->layer_id())
order.push_back(layer_id);
}
_viewer->change_layer_order(order);
_layer_tree->select_node(node);
_viewer->invalidate(false);
}
void SpatialDataView::set_color_icon(mforms::TreeNodeRef node, int column, const base::Color &color) {
static std::string path;
if (path.empty()) {
path = mforms::Utilities::get_special_folder(mforms::ApplicationData) + "/tmpicons";
base::create_directory(path, 0700);
}
std::string p = path + "/" + base::strfmt("%02x%02x%02x.png", (unsigned char)(color.red * 255),
(unsigned char)(color.green * 255), (unsigned char)(color.blue * 255));
if (!base::file_exists(p)) {
cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 16, 16);
cairo_t *cr = cairo_create(surf);
cairo_set_source_rgb(cr, color.red, color.green, color.blue);
cairo_paint(cr);
cairo_destroy(cr);
cairo_surface_write_to_png(surf, p.c_str());
cairo_surface_destroy(surf);
}
node->set_icon_path(column, p);
}
void SpatialDataView::tree_toggled(const mforms::TreeNodeRef &node, const std::string &value) {
if (_layer_tree->is_enabled()) {
bool show = value == "1";
node->set_bool(0, show);
_viewer->show_layer(base::atoi<int>(node->get_tag(), 0), show);
}
}
void SpatialDataView::activate_layer(mforms::TreeNodeRef node, int column) {
if (!node)
node = _layer_tree->get_selected_node();
if (node) {
if (column == -1)
auto_zoom(base::atoi<int>(node->get_tag(), 0));
else
set_active_layer(base::atoi<int>(node->get_tag(), 0));
}
}
static spatial::Layer *find_layer_for(std::deque<spatial::Layer *> &layers, Recordset::Ref rset, int column) {
for (std::deque<spatial::Layer *>::iterator l = layers.begin(); l != layers.end(); ++l) {
RecordsetLayer *rsl = dynamic_cast<RecordsetLayer *>(*l);
if (rsl && rsl->recordset() == rset)
return *l;
}
return NULL;
}
void SpatialDataView::set_active_layer(spatial::LayerId layer) {
if (_grid_layer == layer)
return;
_active_layer = layer;
mforms::TreeNodeTextAttributes plain;
for (int i = 0; i < _layer_tree->count(); i++) {
mforms::TreeNodeRef node(_layer_tree->node_at_row(i));
if (node) {
if (base::atoi<int>(node->get_tag(), -1) == _active_layer) {
mforms::TreeNodeTextAttributes attribs;
attribs.bold = true;
node->set_attributes(1, attribs);
node->set_attributes(2, attribs);
} else {
node->set_attributes(1, plain);
node->set_attributes(2, plain);
}
}
}
}
void SpatialDataView::set_geometry_columns(const std::vector<SpatialDataSource> &sources) {
static base::Color layer_colors[] = {
base::Color::parse("#b8ddf3"), // background color
base::Color(0.9, 1, 0.9), base::Color(1, 0.9, 1.0),
base::Color(0.8, 0.8, 0.4), base::Color(0.4, 0.8, 0.8), base::Color(0.8, 0.4, 0.8),
base::Color(0.8, 0.4, 0.4), base::Color(0.4, 0.8, 0.4), base::Color(0.4, 0.4, 0.8),
base::Color(0.0, 0.6, 0.6), base::Color(0.6, 0.0, 0.6), base::Color(0.6, 0.6, 0.0),
base::Color(0.6, 0.0, 0.0), base::Color(0.0, 0.6, 0.0), base::Color(0.0, 0.0, 0.6)};
if (_layer_tree->count() == 0) {
base::Color color(layer_colors[0]);
mforms::TreeNodeRef node = _layer_tree->add_node();
node->set_string(1, "Grid");
set_color_icon(node, 1, color);
node->set_bool(0, true);
_grid_layer = spatial::new_layer_id();
node->set_tag(base::strfmt("%i", _grid_layer));
_viewer->set_background(new GridLayer(_grid_layer, color));
}
std::deque<spatial::Layer *> layers(_viewer->get_layers());
// remove layers that are gone
for (std::deque<spatial::Layer *>::iterator l = layers.begin(); l != layers.end(); ++l) {
RecordsetLayer *rsl = dynamic_cast<RecordsetLayer *>(*l);
if (rsl) {
Recordset::Ref rset(rsl->recordset());
bool found = false;
if (rset) {
for (std::vector<SpatialDataSource>::const_iterator iter = sources.begin(); iter != sources.end(); ++iter) {
if (!iter->resultset.expired() && iter->resultset.lock() == rset) {
found = true;
break;
}
}
}
if (!found) {
// find the node for the layer
for (int i = 0; i < _layer_tree->count(); i++) {
mforms::TreeNodeRef node;
if (base::atoi<int>((node = _layer_tree->node_at_row(i))->get_tag(), 0) == (*l)->layer_id()) {
node->remove_from_parent();
break;
}
}
_viewer->remove_layer(*l);
delete *l;
*l = NULL;
}
}
}
int idx = 1;
for (std::vector<SpatialDataSource>::const_iterator iter = sources.begin(); iter != sources.end(); ++iter) {
// check if already exists
if (!iter->resultset.expired() && find_layer_for(layers, iter->resultset.lock(), iter->column_index))
continue;
int layer_id = spatial::new_layer_id();
base::Color color(layer_colors[(idx++) % (sizeof(layer_colors) / sizeof(base::Color))]);
mforms::TreeNodeRef node = _layer_tree->add_node();
node->set_bool(0, false);
node->set_string(1, iter->column);
node->set_string(2, iter->source);
node->set_tag(base::strfmt("%i", layer_id));
set_color_icon(node, 1, color);
spatial::Layer *layer = NULL;
if (iter->column_index >= 0) {
layer = new RecordsetLayer(layer_id, color, iter->resultset, iter->column_index);
if (_owner->recordset()->key() == iter->resultset.lock()->key()) {
layer->set_show(true);
node->set_bool(0, true);
set_active_layer(layer_id);
}
} else {
// from file
}
if (layer) {
_viewer->add_layer(layer);
}
}
}
void SpatialDataView::update_coordinates(base::Point p) {
double lat, lon;
if (_viewer->screen_to_world((int)p.x, (int)p.y, lat, lon))
_mouse_pos_label->set_text(base::strfmt("Lat: %s\nLon: %s",
spatial::Converter::dec_to_dms(lat, spatial::AxisLat, 2).c_str(),
spatial::Converter::dec_to_dms(lon, spatial::AxisLon, 2).c_str()));
else
_mouse_pos_label->set_text("Lat: \nLon: ");
}
void SpatialDataView::handle_click(base::Point p) {
RecordsetLayer *layer = active_layer();
std::string text;
_viewer->clear_pins();
if (layer) {
spatial::Feature *feature = layer->feature_closest(_viewer->apply_cairo_transformation(p));
if (feature) {
int row_id = feature->row_id();
if (row_id >= 0) {
Recordset::Ref rs(layer->recordset());
if (rs) {
std::string value;
_viewer->place_pin(mforms::Utilities::load_icon("qe_sql-editor-resultset-tb-pinned.png"), p);
for (size_t i = 0; i < rs->get_column_count(); i++) {
if (i > 0)
text.append("\n");
text.append(rs->get_column_caption(i)).append(": ");
if (rs->get_field(row_id, i, value))
text.append(value);
}
}
}
}
}
_info_box->set_value(text);
}