692 lines
22 KiB
C++
692 lines
22 KiB
C++
|
/*
|
||
|
* Copyright (C) 2018 Open Source Robotics Foundation
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <curl/curl.h>
|
||
|
#include <boost/filesystem.hpp>
|
||
|
|
||
|
#include <ignition/math/Angle.hh>
|
||
|
#include <ignition/math/Helpers.hh>
|
||
|
#include <ignition/math/SphericalCoordinates.hh>
|
||
|
#include <ignition/math/Vector2.hh>
|
||
|
#include <gazebo/physics/physics.hh>
|
||
|
#include <gazebo/common/CommonIface.hh>
|
||
|
#include <gazebo/transport/Node.hh>
|
||
|
|
||
|
#include "StaticMapPlugin.hh"
|
||
|
|
||
|
namespace gazebo
|
||
|
{
|
||
|
/// \brief Class to provide helper functions for Web Mercator projection
|
||
|
class MercatorProjection
|
||
|
{
|
||
|
/// \brief Convert point in world coordinates to latitude and longitude
|
||
|
/// \param[in] _point Point in world coordinates
|
||
|
/// \return Latitude and longitude coorindates
|
||
|
public: static ignition::math::SphericalCoordinates PointToLatLon(
|
||
|
const ignition::math::Vector2d &_point);
|
||
|
|
||
|
/// \brief Convert latitdue and longitude to point in world coordinates
|
||
|
/// \param[in] Latitude and longitude coorindates
|
||
|
/// \return Point in world coordinates
|
||
|
public: static ignition::math::Vector2d LatLonToPoint(
|
||
|
const ignition::math::SphericalCoordinates &_latLon);
|
||
|
|
||
|
/// \brief Google map base level tile size
|
||
|
public: static const unsigned int TILE_SIZE;
|
||
|
};
|
||
|
|
||
|
|
||
|
/// \brief Private data class for StaticMapPlugin
|
||
|
class StaticMapPluginPrivate
|
||
|
{
|
||
|
/// \brief Download map tiles.
|
||
|
/// \param[in] _centerLat Latitude of center point of map
|
||
|
/// \param[in] _centerLon Longitude of center point of map
|
||
|
/// \param[in] _zoom Map zoom level between 0 (entire world) and 21+
|
||
|
/// (streets).
|
||
|
/// \param[in] _tileSizePx Size of each map tile in pixels. Tiles will be
|
||
|
/// square.
|
||
|
/// \param[in] _worldSize Size of map in the world in meters.
|
||
|
/// \param[in] _mapType Type of map to download: roadmap, satellite,
|
||
|
/// terrain, hybrid
|
||
|
/// \param[in] _apiKey Google API key
|
||
|
/// \param[in] _saveDirPath Location in local filesystem to save tile
|
||
|
/// images.
|
||
|
public: std::vector<std::string> DownloadMapTiles(const double _centerLat,
|
||
|
const double _centerLon, const unsigned int _zoom,
|
||
|
const unsigned int _tileSizePx,
|
||
|
const ignition::math::Vector2d &_worldSize,
|
||
|
const std::string &_mapType, const std::string &_apiKey,
|
||
|
const std::string &_saveDirPath);
|
||
|
|
||
|
/// \brief Create textured map model and save it in specified path.
|
||
|
/// \param[in] _name Name of map model
|
||
|
/// \param[in] _tileWorldSize Size of map tiles in meters
|
||
|
/// \param[in] _xNumTiles Number of tiles in x direction
|
||
|
/// \param[in] _yNumTiles Number of tiles in y direction
|
||
|
/// \param[in] _tiles Tile image filenames
|
||
|
/// \param[in] _modelPath Path to model directory
|
||
|
/// \return True if map tile model has been successfully created.
|
||
|
public: bool CreateMapTileModel(
|
||
|
const std::string &_name,
|
||
|
const double _tileWorldSize,
|
||
|
const unsigned int xNumTiles, const unsigned int yNumTiles,
|
||
|
const std::vector<std::string> &_tiles, const std::string &_modelPath);
|
||
|
|
||
|
/// \brief Get the ground resolution at the specified latitude and zoom
|
||
|
/// level.
|
||
|
/// \param[in] _lat Latitude
|
||
|
/// \param[in] _zoom Map zoom Level
|
||
|
/// \return Ground resolution in meters per pixel.
|
||
|
public: double GroundResolution(const double _lat,
|
||
|
const unsigned int _zoom) const;
|
||
|
|
||
|
/// \brief Spawn a model into the world
|
||
|
/// \param[in] _name Name of model
|
||
|
/// \param[in] _pose Pose of model
|
||
|
public: void SpawnModel(const std::string &_name,
|
||
|
const ignition::math::Pose3d &_pose);
|
||
|
|
||
|
/// \brief Pointer to world.
|
||
|
public: physics::WorldPtr world;
|
||
|
|
||
|
/// \brief Name of map model
|
||
|
public: std::string modelName;
|
||
|
|
||
|
/// \brief Pose of map model
|
||
|
public: ignition::math::Pose3d modelPose;
|
||
|
|
||
|
/// \brief Latitude and Longitude of map center
|
||
|
public: ignition::math::Vector2d center;
|
||
|
|
||
|
/// \brief Target size of world to be covered by map in meters.
|
||
|
public: ignition::math::Vector2d worldSize;
|
||
|
|
||
|
/// \brief Map zoom level. From 0 (entire world) to 21+ (streets)
|
||
|
public: unsigned int zoom = 21u;
|
||
|
|
||
|
/// \brief Size of map tile in pixels. 640 is max resolution for users of
|
||
|
/// standard API
|
||
|
public: unsigned int tileSizePx = 640u;
|
||
|
|
||
|
/// \brief Type of map to use as texture: roadmap, satellite, terrain,
|
||
|
/// hybrid
|
||
|
public: std::string mapType = "satellite";
|
||
|
|
||
|
/// \brief True to use cached model and image data from gazebo model path.
|
||
|
/// False to redownload image tiles and recreate model sdf and config
|
||
|
/// files.
|
||
|
public: bool useCache = false;
|
||
|
|
||
|
/// \brief Google API key
|
||
|
public: std::string apiKey;
|
||
|
|
||
|
/// \brief Filenames of map tile images
|
||
|
public: std::vector<std::string> mapTileFilenames;
|
||
|
|
||
|
/// \brief Pointer to a node for communication.
|
||
|
public: transport::NodePtr node;
|
||
|
|
||
|
/// \brief Factory publisher.
|
||
|
public: transport::PublisherPtr factoryPub;
|
||
|
|
||
|
/// \brief True if the plugin is loaded successfully
|
||
|
public: bool loaded = false;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
using namespace gazebo;
|
||
|
|
||
|
const unsigned int MercatorProjection::TILE_SIZE = 256;
|
||
|
|
||
|
GZ_REGISTER_WORLD_PLUGIN(StaticMapPlugin)
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
size_t WriteData(void *_ptr, size_t _size, size_t _nmemb, FILE *_stream)
|
||
|
{
|
||
|
return fwrite(_ptr, _size, _nmemb, _stream);
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
bool DownloadFile(const std::string &_url, const std::string &_outputFile)
|
||
|
{
|
||
|
if (_url.empty())
|
||
|
return false;
|
||
|
|
||
|
CURL *curl = curl_easy_init();
|
||
|
|
||
|
curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
|
||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData);
|
||
|
|
||
|
FILE *fp = fopen(_outputFile.c_str(), "wb");
|
||
|
if (!fp)
|
||
|
{
|
||
|
gzerr << "Could not download model[" << _url << "] because we were"
|
||
|
<< "unable to write to file[" << _outputFile << "]."
|
||
|
<< "Please fix file permissions.";
|
||
|
return false;
|
||
|
}
|
||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||
|
|
||
|
char errbuf[CURL_ERROR_SIZE];
|
||
|
// provide a buffer to store errors in
|
||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||
|
// set the error buffer as empty before performing a request
|
||
|
errbuf[0] = 0;
|
||
|
|
||
|
CURLcode success = curl_easy_perform(curl);
|
||
|
if (success != CURLE_OK)
|
||
|
{
|
||
|
gzerr << "Error in REST request" << std::endl;
|
||
|
size_t len = strlen(errbuf);
|
||
|
fprintf(stderr, "\nlibcurl: (%d) ", success);
|
||
|
if (len)
|
||
|
{
|
||
|
fprintf(stderr, "%s%s", errbuf,
|
||
|
((errbuf[len - 1] != '\n') ? "\n" : ""));
|
||
|
}
|
||
|
else
|
||
|
fprintf(stderr, "%s\n", curl_easy_strerror(success));
|
||
|
}
|
||
|
fclose(fp);
|
||
|
|
||
|
// Update the status code.
|
||
|
int statusCode = 0;
|
||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
|
||
|
|
||
|
// Cleaning.
|
||
|
curl_easy_cleanup(curl);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
ignition::math::Vector2d MercatorProjection::LatLonToPoint(
|
||
|
const ignition::math::SphericalCoordinates &_latLon)
|
||
|
{
|
||
|
// Adapted from:
|
||
|
// https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
|
||
|
|
||
|
ignition::math::Vector2d point;
|
||
|
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
|
||
|
// about a third of a tile past the edge of the world tile.
|
||
|
double siny = std::min(std::max(std::sin(
|
||
|
_latLon.LatitudeReference().Radian()), -0.9999), 0.9999);
|
||
|
|
||
|
point.X() = TILE_SIZE * (0.5 + _latLon.LongitudeReference().Degree()/ 360.0);
|
||
|
point.Y() = TILE_SIZE * (0.5 - std::log((1 + siny) / (1 - siny)) /
|
||
|
(4 * IGN_PI));
|
||
|
return point;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
ignition::math::SphericalCoordinates MercatorProjection::PointToLatLon(
|
||
|
const ignition::math::Vector2d &_point)
|
||
|
{
|
||
|
ignition::math::SphericalCoordinates latLon;
|
||
|
ignition::math::Angle lonAngle;
|
||
|
ignition::math::Angle latAngle;
|
||
|
|
||
|
double lonDegrees = (_point.X() / TILE_SIZE - 0.5) * 360;
|
||
|
double latRadians = (_point.Y() - TILE_SIZE/2.0) /
|
||
|
-(TILE_SIZE/(2*IGN_PI));
|
||
|
|
||
|
lonAngle.Degree(lonDegrees);
|
||
|
latAngle.Radian(2 * std::atan(std::exp(latRadians)) - IGN_PI / 2.0);
|
||
|
|
||
|
latLon.SetLongitudeReference(lonAngle);
|
||
|
latLon.SetLatitudeReference(latAngle);
|
||
|
return latLon;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
StaticMapPlugin::StaticMapPlugin()
|
||
|
: dataPtr(new StaticMapPluginPrivate)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void StaticMapPlugin::Load(physics::WorldPtr _world, sdf::ElementPtr _sdf)
|
||
|
{
|
||
|
this->dataPtr->world = _world;
|
||
|
|
||
|
if (!_sdf->HasElement("api_key"))
|
||
|
{
|
||
|
gzerr << "Missing Google API key needed to download map tiles" << std::endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!_sdf->HasElement("center"))
|
||
|
{
|
||
|
gzerr << "Please specify latitude and longitude coordinates of map center"
|
||
|
<< std::endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!_sdf->HasElement("world_size"))
|
||
|
{
|
||
|
gzerr << "Please specify size of map to cover in meters" << std::endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this->dataPtr->apiKey = _sdf->Get<std::string>("api_key");
|
||
|
this->dataPtr->center =
|
||
|
_sdf->Get<ignition::math::Vector2d>("center");
|
||
|
double wSize = _sdf->Get<double>("world_size");
|
||
|
if (wSize > 0)
|
||
|
{
|
||
|
// support only square map for now
|
||
|
this->dataPtr->worldSize.X() = wSize;
|
||
|
this->dataPtr->worldSize.Y() = wSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gzerr << "World size must be greater than 0 meters" << std::endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// optional params
|
||
|
if (_sdf->HasElement("zoom"))
|
||
|
this->dataPtr->zoom = _sdf->Get<unsigned int>("zoom");
|
||
|
|
||
|
if (_sdf->HasElement("tile_size"))
|
||
|
{
|
||
|
this->dataPtr->tileSizePx = _sdf->Get<unsigned int>("tile_size");
|
||
|
if (this->dataPtr->tileSizePx > 640u)
|
||
|
{
|
||
|
gzerr << "Tile size exceeds standard API usage limit. Setting to 640px."
|
||
|
<< std::endl;
|
||
|
this->dataPtr->tileSizePx = 640u;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_sdf->HasElement("map_type"))
|
||
|
this->dataPtr->mapType= _sdf->Get<std::string>("map_type");
|
||
|
|
||
|
if (_sdf->HasElement("use_cache"))
|
||
|
this->dataPtr->useCache = _sdf->Get<bool>("use_cache");
|
||
|
|
||
|
if (_sdf->HasElement("pose"))
|
||
|
this->dataPtr->modelPose = _sdf->Get<ignition::math::Pose3d>("pose");
|
||
|
|
||
|
if (_sdf->HasElement("model_name"))
|
||
|
this->dataPtr->modelName = _sdf->Get<std::string>("model_name");
|
||
|
else
|
||
|
{
|
||
|
// generate name based on input
|
||
|
std::stringstream name;
|
||
|
name << "map_" << this->dataPtr->mapType << "_" << std::setprecision(9)
|
||
|
<< this->dataPtr->center.X() << "_" << this->dataPtr->center.Y()
|
||
|
<< "_" << this->dataPtr->worldSize.X()
|
||
|
<< "_" << this->dataPtr->worldSize.Y();
|
||
|
this->dataPtr->modelName = name.str();
|
||
|
}
|
||
|
|
||
|
this->dataPtr->loaded = true;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void StaticMapPlugin::Init()
|
||
|
{
|
||
|
// don't init if params are not loaded successfully
|
||
|
if (!this->dataPtr->loaded)
|
||
|
return;
|
||
|
|
||
|
// check if model exists locally
|
||
|
auto basePath = common::SystemPaths::Instance()->GetLogPath() /
|
||
|
boost::filesystem::path("models");
|
||
|
|
||
|
this->dataPtr->node = transport::NodePtr(new transport::Node());
|
||
|
this->dataPtr->node->Init();
|
||
|
this->dataPtr->factoryPub =
|
||
|
this->dataPtr->node->Advertise<msgs::Factory>("~/factory");
|
||
|
|
||
|
boost::filesystem::path modelPath = basePath / this->dataPtr->modelName;
|
||
|
if (this->dataPtr->useCache && common::exists(modelPath.string()))
|
||
|
{
|
||
|
gzmsg << "Model: '" << this->dataPtr->modelName << "' exists. "
|
||
|
<< "Spawning existing model.." << std::endl;
|
||
|
this->dataPtr->SpawnModel("model://" + this->dataPtr->modelName,
|
||
|
this->dataPtr->modelPose);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
// create tmp dir to save model files
|
||
|
boost::filesystem::path tmpModelPath =
|
||
|
boost::filesystem::temp_directory_path() / this->dataPtr->modelName;
|
||
|
boost::filesystem::path scriptsPath(tmpModelPath / "materials" / "scripts");
|
||
|
boost::filesystem::create_directories(scriptsPath);
|
||
|
boost::filesystem::path texturesPath(tmpModelPath / "materials" / "textures");
|
||
|
boost::filesystem::create_directories(texturesPath);
|
||
|
|
||
|
// download map tile images into model/materials/textures
|
||
|
std::vector<std::string> tiles = this->dataPtr->DownloadMapTiles(
|
||
|
this->dataPtr->center.X(),
|
||
|
this->dataPtr->center.Y(),
|
||
|
this->dataPtr->zoom,
|
||
|
this->dataPtr->tileSizePx,
|
||
|
this->dataPtr->worldSize,
|
||
|
this->dataPtr->mapType,
|
||
|
this->dataPtr->apiKey,
|
||
|
texturesPath.string());
|
||
|
|
||
|
// assume square model for now
|
||
|
unsigned int xNumTiles = std::sqrt(tiles.size());
|
||
|
unsigned int yNumTiles = xNumTiles;
|
||
|
|
||
|
double tileWorldSize = this->dataPtr->GroundResolution(
|
||
|
IGN_DTOR(this->dataPtr->center.X()), this->dataPtr->zoom)
|
||
|
* this->dataPtr->tileSizePx;
|
||
|
|
||
|
// create model and spawn it into the world
|
||
|
if (this->dataPtr->CreateMapTileModel(
|
||
|
this->dataPtr->modelName, tileWorldSize,
|
||
|
xNumTiles, yNumTiles, tiles, tmpModelPath.string()))
|
||
|
{
|
||
|
// verify model dir is created
|
||
|
if (common::exists(tmpModelPath.string()))
|
||
|
{
|
||
|
// remove existing map model
|
||
|
if (common::exists(modelPath.string()))
|
||
|
boost::filesystem::remove_all(modelPath);
|
||
|
|
||
|
// move new map model to gazebo model path
|
||
|
boost::filesystem::rename(tmpModelPath, modelPath);
|
||
|
|
||
|
// spawn the model
|
||
|
this->dataPtr->SpawnModel("model://" + this->dataPtr->modelName,
|
||
|
this->dataPtr->modelPose);
|
||
|
}
|
||
|
else
|
||
|
gzerr << "Failed to create model: " << tmpModelPath.string() << std::endl;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
double StaticMapPluginPrivate::GroundResolution(const double _lat,
|
||
|
const unsigned int _zoom) const
|
||
|
{
|
||
|
double earthEquatorialRadius = 6378137;
|
||
|
double metersPerPx = 2 * IGN_PI * earthEquatorialRadius *
|
||
|
std::cos(_lat) / (MercatorProjection::TILE_SIZE * std::pow(2, _zoom));
|
||
|
return metersPerPx;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
std::vector<std::string> StaticMapPluginPrivate::DownloadMapTiles(
|
||
|
const double _centerLat, const double _centerLon,
|
||
|
const unsigned int _zoom, const unsigned int _tileSizePx,
|
||
|
const ignition::math::Vector2d &_worldSize, const std::string &_mapType,
|
||
|
const std::string &_apiKey, const std::string &_saveDirPath)
|
||
|
{
|
||
|
ignition::math::Angle lonAngle;
|
||
|
ignition::math::Angle latAngle;
|
||
|
latAngle.Degree(_centerLat);
|
||
|
lonAngle.Degree(_centerLon);
|
||
|
ignition::math::SphericalCoordinates centerLatLon;
|
||
|
centerLatLon.SetLatitudeReference(latAngle);
|
||
|
centerLatLon.SetLongitudeReference(lonAngle);
|
||
|
|
||
|
// ground resolution - varies by latitude and zoom level
|
||
|
double metersPerPx =
|
||
|
this->GroundResolution(centerLatLon.LatitudeReference().Radian(), _zoom);
|
||
|
|
||
|
// determine number of tiles necessary to cover specified world size
|
||
|
unsigned int xNumTiles = static_cast<unsigned int>(
|
||
|
std::ceil(_worldSize.X() / metersPerPx /
|
||
|
_tileSizePx));
|
||
|
// y is only approximate because ground resolution is based on latitude
|
||
|
unsigned int yNumTiles = static_cast<unsigned int>(
|
||
|
std::ceil(_worldSize.Y() / metersPerPx /
|
||
|
_tileSizePx));
|
||
|
|
||
|
// scale for converting between pixel and world point
|
||
|
double scale = std::pow(2, _zoom);
|
||
|
|
||
|
ignition::math::Vector2d centerPx =
|
||
|
MercatorProjection::LatLonToPoint(centerLatLon) * scale;
|
||
|
|
||
|
// compute starting x, y values in pixel coordinates
|
||
|
double halfWidthPx = _tileSizePx * std::floor(xNumTiles / 2);
|
||
|
double halfHeightPx = _tileSizePx * std::floor(yNumTiles / 2);
|
||
|
double halfTileSize = _tileSizePx / 2.0;
|
||
|
double x = centerPx.X() - halfWidthPx;
|
||
|
double y = centerPx.Y() - halfHeightPx;
|
||
|
if (xNumTiles % 2 == 0u)
|
||
|
x += halfTileSize;
|
||
|
if (yNumTiles % 2 == 0u)
|
||
|
y += halfTileSize;
|
||
|
double startx = x;
|
||
|
|
||
|
// download map tiles using google static map API
|
||
|
std::string url = "https://maps.googleapis.com/maps/api/staticmap";
|
||
|
for (unsigned int i = 0; i < yNumTiles; ++i)
|
||
|
{
|
||
|
for (unsigned int j = 0; j < xNumTiles; ++j)
|
||
|
{
|
||
|
// convert from pixels to world point
|
||
|
auto px = ignition::math::Vector2d(x, y);
|
||
|
auto point = px / scale;
|
||
|
// convert world point to lat lon
|
||
|
auto latLon = MercatorProjection::PointToLatLon(point);
|
||
|
|
||
|
// download tile image
|
||
|
std::stringstream query;
|
||
|
query << "?center="
|
||
|
<< std::setprecision(9)
|
||
|
<< latLon.LatitudeReference().Degree() << ","
|
||
|
<< latLon.LongitudeReference().Degree()
|
||
|
<< "&zoom=" << _zoom
|
||
|
<< "&size=" << _tileSizePx << "x" << _tileSizePx
|
||
|
<< "&maptype=" << _mapType
|
||
|
<< "&key=" << _apiKey;
|
||
|
std::string fullURL = url + query.str();
|
||
|
std::stringstream filename;
|
||
|
filename << "tile_"
|
||
|
<< std::setprecision(9) << latLon.LatitudeReference().Degree()
|
||
|
<< "_" << latLon.LongitudeReference().Degree() << ".png";
|
||
|
std::string fullPath = _saveDirPath + "/" + filename.str();
|
||
|
DownloadFile(fullURL, fullPath);
|
||
|
gzmsg << "Downloading map tile: " << filename.str() << std::endl;
|
||
|
mapTileFilenames.push_back(filename.str());
|
||
|
|
||
|
x += _tileSizePx;
|
||
|
}
|
||
|
x = startx;
|
||
|
y += _tileSizePx;
|
||
|
}
|
||
|
return mapTileFilenames;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
bool StaticMapPluginPrivate::CreateMapTileModel(
|
||
|
const std::string &_name,
|
||
|
const double _tileWorldSize,
|
||
|
const unsigned int _xNumTiles, const unsigned int _yNumTiles,
|
||
|
const std::vector<std::string> &_tiles, const std::string &_modelPath)
|
||
|
{
|
||
|
// create material script
|
||
|
std::stringstream materialScriptStr;
|
||
|
for (unsigned int i = 0; i < _yNumTiles; ++i)
|
||
|
{
|
||
|
for (unsigned int j = 0; j < _xNumTiles; ++j)
|
||
|
{
|
||
|
materialScriptStr <<
|
||
|
"material " << _name << "/" << i << "_" << j << "\n"
|
||
|
"{\n"
|
||
|
" technique\n"
|
||
|
" {\n"
|
||
|
" pass\n"
|
||
|
" {\n"
|
||
|
" texture_unit\n"
|
||
|
" {\n"
|
||
|
" texture " << _tiles[j + i * _xNumTiles] << "\n"
|
||
|
" }\n"
|
||
|
" }\n"
|
||
|
" }\n"
|
||
|
"}\n\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// save material script file to disk
|
||
|
boost::filesystem::path scriptFilePath(_modelPath);
|
||
|
scriptFilePath = scriptFilePath / "materials" / "scripts"
|
||
|
/ "map_tiles.material";
|
||
|
std::ofstream scriptFile;
|
||
|
scriptFile.open(scriptFilePath.string().c_str());
|
||
|
if (!scriptFile.is_open())
|
||
|
{
|
||
|
gzerr << "Couldn't open file for writing: " << scriptFilePath.string()
|
||
|
<< std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
scriptFile << materialScriptStr.str();
|
||
|
scriptFile.close();
|
||
|
|
||
|
// create model.sdf file
|
||
|
double sizeX = _tileWorldSize;
|
||
|
double sizeY = _tileWorldSize;
|
||
|
double sizeZ = 1.0;
|
||
|
|
||
|
ignition::math::Vector2d wCenter(0, 0);
|
||
|
double halfTileWorldWidth = sizeX * std::floor(_xNumTiles / 2);
|
||
|
double halfTileWorldHeight = sizeY * std::floor(_yNumTiles / 2);
|
||
|
double x = wCenter.X() - halfTileWorldWidth;
|
||
|
double y = wCenter.Y() + halfTileWorldHeight;
|
||
|
double halfTileWorldSizeX = sizeX / 2.0;
|
||
|
double halfTileWorldSizeY = sizeY / 2.0;
|
||
|
if (_xNumTiles % 2 == 0u)
|
||
|
x += halfTileWorldSizeX;
|
||
|
if (_yNumTiles % 2 == 0u)
|
||
|
y -= halfTileWorldSizeY;
|
||
|
double startx = x;
|
||
|
double zPos = -sizeZ / 2.0;
|
||
|
|
||
|
// rotate around z to line up textures
|
||
|
ignition::math::Vector3d tileRot(0, 0, IGN_PI / 2.0);
|
||
|
ignition::math::Vector3d colSize(sizeX * _xNumTiles, sizeY * _yNumTiles,
|
||
|
sizeZ);
|
||
|
|
||
|
// Model will have boxed-shaped tiles with z size of 1.0
|
||
|
// Surface of model will be at z=0.0
|
||
|
std::stringstream newModelStr;
|
||
|
newModelStr << "<sdf version='" << SDF_VERSION << "'>\n"
|
||
|
"<model name='" << _name << "'>\n"
|
||
|
" <static>true</static>\n"
|
||
|
" <link name='link'>\n"
|
||
|
" <collision name='collision'>\n"
|
||
|
" <pose>0 0 " << zPos << " 0 0 0</pose>\n"
|
||
|
" <geometry>\n"
|
||
|
" <box>\n"
|
||
|
" <size>" << colSize << "</size>\n"
|
||
|
" </box>\n"
|
||
|
" </geometry>\n"
|
||
|
" </collision>\n";
|
||
|
for (unsigned int i = 0; i < _yNumTiles; ++i)
|
||
|
{
|
||
|
for (unsigned int j = 0; j < _xNumTiles; ++j)
|
||
|
{
|
||
|
newModelStr <<
|
||
|
" <visual name='visual" << i << "_" << j <<"'>\n"
|
||
|
" <pose>" << x << " " << y << " " << zPos
|
||
|
<< " " << tileRot << "</pose>\n"
|
||
|
" <geometry>\n"
|
||
|
" <box>\n"
|
||
|
" <size>" << sizeX << " " << sizeY << " " << sizeZ
|
||
|
<< "</size>\n"
|
||
|
" </box>\n"
|
||
|
" </geometry>\n"
|
||
|
" <material>\n"
|
||
|
" <script>\n"
|
||
|
" <uri>model://" << _name << "/materials/scripts</uri>\n"
|
||
|
" <uri>model://" << _name << "/materials/textures</uri>\n"
|
||
|
" <name>" << _name << "/" << i << "_" << j << "</name>\n"
|
||
|
" </script>\n"
|
||
|
" </material>\n"
|
||
|
" </visual>\n";
|
||
|
x += sizeX;
|
||
|
}
|
||
|
x = startx;
|
||
|
y -= sizeY;
|
||
|
}
|
||
|
newModelStr <<
|
||
|
" </link>\n"
|
||
|
"</model>\n"
|
||
|
"</sdf>";
|
||
|
|
||
|
// save model.sdf file to disk
|
||
|
boost::filesystem::path modelSDFFilePath(_modelPath);
|
||
|
modelSDFFilePath /= "model.sdf";
|
||
|
std::ofstream modelSDFFile;
|
||
|
modelSDFFile.open(modelSDFFilePath.string().c_str());
|
||
|
if (!modelSDFFile.is_open())
|
||
|
{
|
||
|
gzerr << "Couldn't open file for writing: " << modelSDFFilePath.string()
|
||
|
<< std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
modelSDFFile << newModelStr.str();
|
||
|
modelSDFFile.close();
|
||
|
|
||
|
// create model.config file
|
||
|
std::ostringstream modelConfigStr;
|
||
|
modelConfigStr << "<?xml version=\"1.0\"?>\n"
|
||
|
<< "<model>\n"
|
||
|
<< " <name>" << _name << "</name>\n"
|
||
|
<< " <version>1.0</version>\n"
|
||
|
<< " <sdf version=\"" << SDF_VERSION << "\">model.sdf</sdf>\n"
|
||
|
<< " <author>\n"
|
||
|
<< " <name>gazebo</name>\n"
|
||
|
<< " <email></email>\n"
|
||
|
<< " </author>\n"
|
||
|
<< " <description>\n"
|
||
|
<< " Made with Gazebo using Google Static Map API. "
|
||
|
<< "https://developers.google.com/maps/documentation/static-maps\n"
|
||
|
<< " </description>\n"
|
||
|
<< "</model>";
|
||
|
|
||
|
// save model.config file to disk
|
||
|
boost::filesystem::path modelConfigFilePath(_modelPath);
|
||
|
modelConfigFilePath /= "model.config";
|
||
|
std::ofstream modelConfigFile;
|
||
|
modelConfigFile.open(modelConfigFilePath.string().c_str());
|
||
|
if (!modelConfigFile.is_open())
|
||
|
{
|
||
|
gzerr << "Couldn't open file for writing: "
|
||
|
<< modelConfigFilePath.string() << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
modelConfigFile << modelConfigStr.str();
|
||
|
modelConfigFile.close();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////
|
||
|
void StaticMapPluginPrivate::SpawnModel(const std::string &_uri,
|
||
|
const ignition::math::Pose3d &_pose)
|
||
|
{
|
||
|
// publish to factory topic to spawn the model
|
||
|
msgs::Factory msg;
|
||
|
msg.set_sdf_filename(_uri);
|
||
|
msgs::Set(msg.mutable_pose(), _pose);
|
||
|
this->factoryPub->Publish(msg);
|
||
|
}
|