00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include <tile_map/tile_map_plugin.h>
00031
00032 #include <boost/algorithm/string/trim.hpp>
00033
00034 #include <tile_map/tile_source.h>
00035 #include <tile_map/bing_source.h>
00036 #include <tile_map/wmts_source.h>
00037
00038
00039 #include <QGLWidget>
00040 #include <QInputDialog>
00041 #include <QMessageBox>
00042 #include <QPalette>
00043
00044
00045 #include <ros/ros.h>
00046 #include <tf/transform_datatypes.h>
00047
00048 #include <swri_transform_util/frames.h>
00049 #include <swri_yaml_util/yaml_util.h>
00050
00051
00052 #include <pluginlib/class_list_macros.h>
00053 PLUGINLIB_EXPORT_CLASS(tile_map::TileMapPlugin, mapviz::MapvizPlugin)
00054
00055 namespace tile_map
00056 {
00057 std::string TileMapPlugin::BASE_URL_KEY = "base_url";
00058 std::string TileMapPlugin::BING_API_KEY = "bing_api_key";
00059 std::string TileMapPlugin::CUSTOM_SOURCES_KEY = "custom_sources";
00060 std::string TileMapPlugin::MAX_ZOOM_KEY = "max_zoom";
00061 std::string TileMapPlugin::NAME_KEY = "name";
00062 std::string TileMapPlugin::SOURCE_KEY = "source";
00063 std::string TileMapPlugin::TYPE_KEY = "type";
00064 QString TileMapPlugin::BING_NAME = "Bing Maps (terrain)";
00065 QString TileMapPlugin::STAMEN_TERRAIN_NAME = "Stamen (terrain)";
00066 QString TileMapPlugin::STAMEN_TONER_NAME = "Stamen (toner)";
00067 QString TileMapPlugin::STAMEN_WATERCOLOR_NAME = "Stamen (watercolor)";
00068
00069 TileMapPlugin::TileMapPlugin() :
00070 config_widget_(new QWidget()),
00071 transformed_(false),
00072 last_center_x_(0.0),
00073 last_center_y_(0.0),
00074 last_scale_(0.0),
00075 last_height_(0),
00076 last_width_(0)
00077 {
00078 ui_.setupUi(config_widget_);
00079
00080 tile_sources_[STAMEN_TERRAIN_NAME] =
00081 boost::make_shared<WmtsSource>(STAMEN_TERRAIN_NAME,
00082 "http://tile.stamen.com/terrain/{level}/{x}/{y}.png",
00083 false,
00084 15);
00085 tile_sources_[STAMEN_TONER_NAME] =
00086 boost::make_shared<WmtsSource>(STAMEN_TONER_NAME,
00087 "http://tile.stamen.com/toner/{level}/{x}/{y}.png",
00088 false,
00089 19);
00090 tile_sources_[STAMEN_WATERCOLOR_NAME] =
00091 boost::make_shared<WmtsSource>(STAMEN_WATERCOLOR_NAME,
00092 "http://tile.stamen.com/watercolor/{level}/{x}/{y}.jpg",
00093 false,
00094 19);
00095 boost::shared_ptr<BingSource> bing = boost::make_shared<BingSource>(BING_NAME);
00096 tile_sources_[BING_NAME] = bing;
00097
00098 QPalette p(config_widget_->palette());
00099 p.setColor(QPalette::Background, Qt::white);
00100 config_widget_->setPalette(p);
00101
00102 QPalette p2(ui_.status->palette());
00103 p2.setColor(QPalette::Text, Qt::red);
00104 ui_.status->setPalette(p2);
00105
00106 source_frame_ = swri_transform_util::_wgs84_frame;
00107
00108 QObject::connect(bing.get(), SIGNAL(ErrorMessage(const std::string&)),
00109 this, SLOT(PrintError(const std::string&)));
00110 QObject::connect(bing.get(), SIGNAL(InfoMessage(const std::string&)),
00111 this, SLOT(PrintInfo(const std::string&)));
00112 QObject::connect(ui_.delete_button, SIGNAL(clicked()), this, SLOT(DeleteTileSource()));
00113 QObject::connect(ui_.source_combo, SIGNAL(activated(const QString&)), this, SLOT(SelectSource(const QString&)));
00114 QObject::connect(ui_.save_button, SIGNAL(clicked()), this, SLOT(SaveCustomSource()));
00115 QObject::connect(ui_.reset_cache_button, SIGNAL(clicked()), this, SLOT(ResetTileCache()));
00116 }
00117
00118 TileMapPlugin::~TileMapPlugin()
00119 {
00120 }
00121
00122 void TileMapPlugin::DeleteTileSource()
00123 {
00124 int source_index = ui_.source_combo->currentIndex();
00125 QString current_name = ui_.source_combo->currentText();
00126
00127 QMessageBox mbox;
00128 mbox.setText("Are you sure you want to delete the source \"" + current_name + "\"?");
00129 mbox.setIcon(QMessageBox::Warning);
00130 mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
00131 mbox.setDefaultButton(QMessageBox::Cancel);
00132 int ret = mbox.exec();
00133
00134 if (ret == QMessageBox::Ok)
00135 {
00136 ui_.source_combo->removeItem(source_index);
00137 tile_sources_.erase(current_name);
00138 ui_.source_combo->setCurrentIndex(0);
00139 SelectSource(ui_.source_combo->currentText());
00140 }
00141 }
00142
00143 void TileMapPlugin::SelectSource(const QString& source)
00144 {
00145 if (source == STAMEN_TERRAIN_NAME ||
00146 source == STAMEN_WATERCOLOR_NAME ||
00147 source == STAMEN_TONER_NAME ||
00148 source == BING_NAME)
00149 {
00150 stopCustomEditing();
00151 }
00152 else
00153 {
00154 startCustomEditing();
00155 }
00156
00157 std::map<QString, boost::shared_ptr<TileSource> >::iterator iter = tile_sources_.find(source);
00158
00159
00160
00161
00162 ui_.url_label->setText("Base URL:");
00163 ui_.save_button->setText("Save...");
00164 if (iter != tile_sources_.end())
00165 {
00166 selectTileSource(iter->second);
00167 initialized_ = true;
00168
00169
00170
00171 if (iter->second->GetType() == BingSource::BING_TYPE)
00172 {
00173 ui_.url_label->setText("API Key:");
00174 ui_.save_button->setText("Save");
00175 ui_.base_url_text->setEnabled(true);
00176 ui_.save_button->setEnabled(true);
00177 }
00178 }
00179 else
00180 {
00181 ui_.delete_button->setEnabled(false);
00182 }
00183 }
00184
00185 void TileMapPlugin::SaveCustomSource()
00186 {
00187
00188
00189
00190
00191 QString current_source = ui_.source_combo->currentText();
00192 QString default_name = "";
00193
00194 std::map<QString, boost::shared_ptr<TileSource> >::iterator iter = tile_sources_.find(current_source);
00195 if (iter != tile_sources_.end())
00196 {
00197 if (iter->second->IsCustom())
00198 {
00199 default_name = current_source;
00200 }
00201 else if (iter->second->GetType() == BingSource::BING_TYPE)
00202 {
00203
00204
00205 BingSource* bing_source = static_cast<BingSource*>(iter->second.get());
00206 bing_source->SetApiKey(ui_.base_url_text->text());
00207 return;
00208 }
00209 }
00210
00211 bool ok;
00212 QString name = QInputDialog::getText(config_widget_,
00213 tr("Save New Tile Source"),
00214 tr("Tile Source Name:"),
00215 QLineEdit::Normal,
00216 default_name,
00217 &ok);
00218 name = name.trimmed();
00219 if (ok && !name.isEmpty())
00220 {
00221 boost::shared_ptr<WmtsSource> source = boost::make_shared<WmtsSource>(name,
00222 ui_.base_url_text->text(),
00223 true,
00224 ui_.max_zoom_spin_box->value());
00225 int existing_index = ui_.source_combo->findText(name);
00226 if (existing_index != -1)
00227 {
00228 ui_.source_combo->removeItem(existing_index);
00229 }
00230 tile_sources_[name] = source;
00231 ui_.source_combo->addItem(name);
00232 int new_index = ui_.source_combo->findText(name);
00233 ui_.source_combo->setCurrentIndex(new_index);
00234 SelectSource(name);
00235 }
00236 }
00237
00238 void TileMapPlugin::ResetTileCache()
00239 {
00240 tile_map_.ResetCache();
00241 }
00242
00243 void TileMapPlugin::PrintError(const std::string& message)
00244 {
00245 if (message == ui_.status->text().toStdString())
00246 return;
00247
00248 ROS_ERROR("Error: %s", message.c_str());
00249 QPalette p(ui_.status->palette());
00250 p.setColor(QPalette::Text, Qt::red);
00251 ui_.status->setPalette(p);
00252 ui_.status->setText(message.c_str());
00253 }
00254
00255 void TileMapPlugin::PrintInfo(const std::string& message)
00256 {
00257 if (message == ui_.status->text().toStdString())
00258 return;
00259
00260 ROS_INFO("%s", message.c_str());
00261 QPalette p(ui_.status->palette());
00262 p.setColor(QPalette::Text, Qt::green);
00263 ui_.status->setPalette(p);
00264 ui_.status->setText(message.c_str());
00265 }
00266
00267 void TileMapPlugin::PrintWarning(const std::string& message)
00268 {
00269 if (message == ui_.status->text().toStdString())
00270 return;
00271
00272 ROS_WARN("%s", message.c_str());
00273 QPalette p(ui_.status->palette());
00274 p.setColor(QPalette::Text, Qt::darkYellow);
00275 ui_.status->setPalette(p);
00276 ui_.status->setText(message.c_str());
00277 }
00278
00279 QWidget* TileMapPlugin::GetConfigWidget(QWidget* parent)
00280 {
00281 config_widget_->setParent(parent);
00282
00283 return config_widget_;
00284 }
00285
00286 bool TileMapPlugin::Initialize(QGLWidget* canvas)
00287 {
00288 canvas_ = canvas;
00289
00290 SelectSource(STAMEN_TERRAIN_NAME);
00291
00292 return true;
00293 }
00294
00295 void TileMapPlugin::Draw(double x, double y, double scale)
00296 {
00297 swri_transform_util::Transform to_wgs84;
00298 if (tf_manager_.GetTransform(source_frame_, target_frame_, to_wgs84))
00299 {
00300 tf::Vector3 center(x, y, 0);
00301 center = to_wgs84 * center;
00302 if (center.y() != last_center_y_ ||
00303 center.x() != last_center_x_ ||
00304 scale != last_scale_ ||
00305 canvas_->width() != last_width_ ||
00306 canvas_->height() != last_height_)
00307 {
00308
00309
00310 last_center_y_ = center.y();
00311 last_center_x_ = center.x();
00312 last_scale_ = scale;
00313 last_width_ = canvas_->width();
00314 last_height_ = canvas_->height();
00315 tile_map_.SetView(center.y(), center.x(), scale, canvas_->width(), canvas_->height());
00316 }
00317 tile_map_.Draw();
00318 }
00319 }
00320
00321 void TileMapPlugin::Transform()
00322 {
00323 swri_transform_util::Transform to_target;
00324 if (tf_manager_.GetTransform(target_frame_, source_frame_, to_target))
00325 {
00326 tile_map_.SetTransform(to_target);
00327 PrintInfo("OK");
00328 }
00329 else
00330 {
00331 PrintError("No transform between " + source_frame_ + " and " + target_frame_);
00332 }
00333 }
00334
00335 void TileMapPlugin::LoadConfig(const YAML::Node& node, const std::string& path)
00336 {
00337 if (swri_yaml_util::FindValue(node, CUSTOM_SOURCES_KEY))
00338 {
00339 const YAML::Node& sources = node[CUSTOM_SOURCES_KEY];
00340 YAML::Node::const_iterator source_iter;
00341 for (source_iter = sources.begin(); source_iter != sources.end(); source_iter++)
00342 {
00343 std::string type = "";
00344 if (swri_yaml_util::FindValue(*source_iter, TYPE_KEY))
00345 {
00346
00347 (*source_iter)[TYPE_KEY] >> type;
00348 }
00349 boost::shared_ptr<TileSource> source;
00350 if (type == "wmts" || type.empty())
00351 {
00352 std::string name;
00353 std::string base_url;
00354 int max_zoom;
00355 (*source_iter)[NAME_KEY] >> name;
00356 (*source_iter)[BASE_URL_KEY] >> base_url;
00357 (*source_iter)[MAX_ZOOM_KEY] >> max_zoom;
00358 source = boost::make_shared<WmtsSource>(
00359 QString::fromStdString(name),
00360 QString::fromStdString(base_url),
00361 true,
00362 max_zoom);
00363 }
00364 else if (type == "bing")
00365 {
00366 std::string name;
00367 (*source_iter)[NAME_KEY] >> name;
00368 source = boost::make_shared<BingSource>(QString::fromStdString(name));
00369 }
00370 tile_sources_[source->GetName()] = source;
00371 ui_.source_combo->addItem(source->GetName());
00372 }
00373 }
00374
00375 if (swri_yaml_util::FindValue(node, BING_API_KEY))
00376 {
00377 std::string key;
00378 node[BING_API_KEY] >> key;
00379 BingSource* source = static_cast<BingSource*>(tile_sources_[BING_NAME].get());
00380 source->SetApiKey(QString::fromStdString(key));
00381 }
00382
00383 if (swri_yaml_util::FindValue(node, SOURCE_KEY))
00384 {
00385 std::string source;
00386 node[SOURCE_KEY] >> source;
00387
00388 int index = ui_.source_combo->findText(QString::fromStdString(source), Qt::MatchExactly);
00389
00390 if (index >= 0)
00391 {
00392 ui_.source_combo->setCurrentIndex(index);
00393 }
00394
00395 SelectSource(QString::fromStdString(source));
00396 }
00397 }
00398
00399 void TileMapPlugin::SaveConfig(YAML::Emitter& emitter, const std::string& path)
00400 {
00401 emitter << YAML::Key << CUSTOM_SOURCES_KEY << YAML::Value << YAML::BeginSeq;
00402
00403 std::map<QString, boost::shared_ptr<TileSource> >::iterator iter;
00404 for (iter = tile_sources_.begin(); iter != tile_sources_.end(); iter++)
00405 {
00406 if (iter->second->IsCustom())
00407 {
00408 emitter << YAML::BeginMap;
00409 emitter << YAML::Key << BASE_URL_KEY << YAML::Value << iter->second->GetBaseUrl().toStdString();
00410 emitter << YAML::Key << MAX_ZOOM_KEY << YAML::Value << iter->second->GetMaxZoom();
00411 emitter << YAML::Key << NAME_KEY << YAML::Value << iter->second->GetName().toStdString();
00412 emitter << YAML::Key << TYPE_KEY << YAML::Value << iter->second->GetType().toStdString();
00413 emitter << YAML::EndMap;
00414 }
00415 }
00416 emitter << YAML::EndSeq;
00417
00418 BingSource* bing_source = static_cast<BingSource*>(tile_sources_[BING_NAME].get());
00419 emitter << YAML::Key << BING_API_KEY <<
00420 YAML::Value << boost::trim_copy(bing_source->GetApiKey().toStdString());
00421
00422 emitter << YAML::Key << SOURCE_KEY <<
00423 YAML::Value << boost::trim_copy(ui_.source_combo->currentText().toStdString());
00424 }
00425
00426 void TileMapPlugin::selectTileSource(const boost::shared_ptr<TileSource>& tile_source)
00427 {
00428 last_height_ = 0;
00429 tile_map_.SetTileSource(tile_source);
00430 if (tile_source->GetType() == BingSource::BING_TYPE)
00431 {
00432 BingSource* bing_source = static_cast<BingSource*>(tile_source.get());
00433 ui_.base_url_text->setText(bing_source->GetApiKey());
00434 }
00435 else
00436 {
00437 ui_.base_url_text->setText(tile_source->GetBaseUrl());
00438 }
00439 ui_.max_zoom_spin_box->setValue(tile_source->GetMaxZoom());
00440 }
00441
00442 void TileMapPlugin::startCustomEditing()
00443 {
00444 ui_.base_url_text->setEnabled(true);
00445 ui_.delete_button->setEnabled(true);
00446 ui_.max_zoom_spin_box->setEnabled(true);
00447 ui_.save_button->setEnabled(true);
00448 }
00449
00450 void TileMapPlugin::stopCustomEditing()
00451 {
00452 ui_.base_url_text->setEnabled(false);
00453 ui_.delete_button->setEnabled(false);
00454 ui_.max_zoom_spin_box->setEnabled(false);
00455 ui_.save_button->setEnabled(false);
00456 }
00457 }
00458