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