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\"", &deg, &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\"", &deg, &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); }