From 0c73de0817c951b31ac0cd7d61a529325400ba01 Mon Sep 17 00:00:00 2001 From: "P. J. Reed" Date: Thu, 21 Jul 2016 16:24:04 -0500 Subject: [PATCH] Adding support for Bing Maps This makes a number of changes in the `tile_map` plugin in order to support different types of tile servers, including Bing Maps. Notable changes include: - TileSource is now an abstract class - WMTS server-specific behavior has been moved into a new WmtsSource class - BingSource provides support for obtaining tiles from Bing Maps - The UI for specifying server URLs has changed - Prefix and coordinate order are no longer separate fields - In URLs for WMTS sources, the variables {level}, {x}, and {y} will be substituted with appropriate values when tiles are requested - Rather than generating hashes for image tiles based on their URLs, hashes are now generated by the TileSource implementations in order to support sources that can pull tiles from multiple servers - Idle performance has been improved by removing redundant recalculations of the map view - Added a dependency on libjsoncpp Resolves #227 Conflicts: tile_map/CMakeLists.txt tile_map/package.xml --- README.md | 7 +- tile_map/CMakeLists.txt | 20 +- tile_map/include/tile_map/bing_source.h | 141 ++++++++++++++ tile_map/include/tile_map/image_cache.h | 17 +- tile_map/include/tile_map/texture_cache.h | 2 +- tile_map/include/tile_map/tile_map_plugin.h | 21 ++- tile_map/include/tile_map/tile_map_view.h | 14 +- tile_map/include/tile_map/tile_source.h | 87 +++++---- tile_map/include/tile_map/wmts_source.h | 86 +++++++++ tile_map/package.xml | 4 +- tile_map/src/bing_source.cpp | 184 ++++++++++++++++++ tile_map/src/image_cache.cpp | 25 +-- tile_map/src/texture_cache.cpp | 2 +- tile_map/src/tile_map_config.ui | 176 ++++++----------- tile_map/src/tile_map_plugin.cpp | 197 ++++++++++++++------ tile_map/src/tile_map_view.cpp | 31 +-- tile_map/src/tile_source.cpp | 149 +-------------- tile_map/src/wmts_source.cpp | 70 +++++++ 18 files changed, 816 insertions(+), 417 deletions(-) create mode 100644 tile_map/include/tile_map/bing_source.h create mode 100644 tile_map/include/tile_map/wmts_source.h create mode 100644 tile_map/src/bing_source.cpp create mode 100644 tile_map/src/wmts_source.cpp diff --git a/README.md b/README.md index d4bc6313..870cd6b2 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ Textured markers follow the same general approach as traditional markers, but ca * Topic: The textured marker topic ### Tile Map -Projects a geo-referenced multi-resolution image tile map into the scene. Data is automatically streamed from [OpenMapQuest](http://open.mapquest.com/) (satellite and roads) or [Stamen Design] (http://maps.stamen.com/) (terrain, watercolor, and toner). Custom or local map servers can also be specified. Map data is cached to disk which enables some limited use completely offline. +Projects a geo-referenced multi-resolution image tile map into the scene. Map tiles can be obtained from [Bing Maps](https://www.bing.com/mapspreview) or any [WMTS Tile Service](http://www.opengeospatial.org/standards/wmts). Pre-defined services that access [Stamen Design](http://maps.stamen.com/) (terrain, watercolor, and toner) are provided. Custom or local WMTS map servers can also be specified. Map data is cached to disk which enables some limited use completely offline. @@ -255,7 +255,10 @@ Projects a geo-referenced multi-resolution image tile map into the scene. Data **Parameters** - * Source: The source of the tile data. + * Source: The name of source of the tile data. + * Base URL: A template URL used to obtain map tiles. When obtaining map tiles, parameters labeled `{level}`, `{x}`, and `{y}` in the URL will be replaced with appropriate values. For example, `http://tile.stamen.com/terrain/{level}/{x}/{y}.png` is appropriate for retrieving terrain tiles from Stamen Design. + * API Key: When the `Bing Maps (terrain)` source is selected, you must enter a Bing Maps access key here and click the `Save` button in order for tiles to be available. You can get a Bing Maps Key from the [Microsoft Developer Network](https://msdn.microsoft.com/en-us/library/ff428642.aspx). + * Max Zoom: The maximum zoom level that will be used when requesting tiles. ### TF Frame diff --git a/tile_map/CMakeLists.txt b/tile_map/CMakeLists.txt index 504064db..a9593a43 100644 --- a/tile_map/CMakeLists.txt +++ b/tile_map/CMakeLists.txt @@ -38,8 +38,12 @@ add_definitions( ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") +find_package(PkgConfig REQUIRED) + find_package(GLU REQUIRED) +pkg_check_modules(JSONCPP REQUIRED jsoncpp) + catkin_package( INCLUDE_DIRS include DEPENDS Qt5Core Qt5Gui Qt5OpenGL Qt5Widgets @@ -55,7 +59,7 @@ catkin_package( # Fix conflict between Boost signals used by tf and QT signals used by mapviz add_definitions(-DQT_NO_KEYWORDS) -include_directories(include ${catkin_INCLUDE_DIRS}) +include_directories(include ${catkin_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) @@ -63,14 +67,20 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) file (GLOB TILE_SRC_FILES src/image_cache.cpp src/texture_cache.cpp + src/bing_source.cpp + src/tile_source.cpp + src/wmts_source.cpp src/tile_map_view.cpp) -qt5_wrap_cpp(TILE_SRC_FILES include/tile_map/image_cache.h) +qt5_wrap_cpp(TILE_SRC_FILES + include/tile_map/image_cache.h + include/tile_map/tile_source.h + include/tile_map/wmts_source.h + include/tile_map/bing_source.h) add_library(${PROJECT_NAME} ${TILE_SRC_FILES}) -target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES} ${QT_QTOPENGL_LIBRARIES} ${GLU_LIBRARY} ${catkin_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES} ${QT_QTOPENGL_LIBRARIES} ${GLU_LIBRARY} ${JSONCPP_LIBRARIES} ${catkin_LIBRARIES}) file (GLOB PLUGIN_SRC_FILES - src/tile_map_plugin.cpp - src/tile_source.cpp) + src/tile_map_plugin.cpp) file (GLOB PLUGIN_UI_FILES src/tile_map_config.ui) qt5_wrap_ui(PLUGIN_SRC_FILES ${PLUGIN_UI_FILES}) diff --git a/tile_map/include/tile_map/bing_source.h b/tile_map/include/tile_map/bing_source.h new file mode 100644 index 00000000..a2e3cc57 --- /dev/null +++ b/tile_map/include/tile_map/bing_source.h @@ -0,0 +1,141 @@ +// ***************************************************************************** +// +// Copyright (c) 2015, Southwest Research Institute® (SwRI®) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Southwest Research Institute® (SwRI®) nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +// +// ***************************************************************************** + +#ifndef TILE_MAP_BING_SOURCE_H +#define TILE_MAP_BING_SOURCE_H + +#include "tile_source.h" + +#include +#include + +#include + +#include +#include +#include + +namespace tile_map +{ + class BingSource : public TileSource + { + Q_OBJECT + public: + /** + * Initializes a Bing map source with a given name. + * + * Note that currently, only a single, hard-coded Bing map source is supported. + * There's only one Bing Maps, after all. + * In the future, though, it would probably make sense to extend its + * functionality to allow pulling different tile sets from Bing. + * @param name The name the source will appear as in the combo box. + */ + explicit BingSource(const QString& name); + + /** + * Generates a unique hash that identifies the tile as the given coordinates. + * + * Note that Bing Maps tiles could potentially be pulled from one of many + * different servers, depending on the subdomain list given to us after we + * authenticate with our API Key. That means the exact URL to any given tile + * should not be used as part of the hash, because there are many valid URLs + * for a tile. + * @param level The zoom level + * @param x The X coordinate + * @param y The Y coordinate + * @return A hash that uniquely identifies this tile + */ + virtual size_t GenerateTileHash(int32_t level, int64_t x, int64_t y); + + /** + * Generates a URL that will retrieve a tile for the given coordinates. + * + * Since Bing can give us a list of subdomains to pull tiles from, the + * exact subdomain for a tile is chosen at random every time this function + * is called. That means you are not guaranteed to get the same URL for a + * tile every time you call this function. + * @param level The zoom level + * @param x The X coordinate + * @param y The Y coordinate + * @return A URL that points to this tile + */ + virtual QString GenerateTileUrl(int32_t level, int64_t x, int64_t y); + + virtual QString GetType() const; + + QString GetApiKey() const; + + /** + * Bing requires an API key in order to access its tiles. The key provided + * will determine the URL we use to retrieve map tiles, so setting the API + * key will also cause this object to make a network request to the Bing Map + * server to get the appropriate URL. + * + * More information about getting an API key: + * https://msdn.microsoft.com/en-us/library/ff428642.aspx + * @param api_key A valid Bing Maps key + */ + void SetApiKey(const QString& api_key); + + static const QString BING_TYPE; + + protected Q_SLOTS: + void ReplyFinished(QNetworkReply* reply); + + protected: + /** + * Bing Maps identifies tiles using a quadkey that is generated from the zoom + * level and x and y coordinates. Details on how the quadkey is generated can + * be found here: + * https://msdn.microsoft.com/en-us/library/bb259689.aspx + * + * @param level The zoom level + * @param x The X coordinate + * @param y The Y coordinate + * @return The quadkey that represents the tile at the requested location + */ + QString GenerateQuadKey(int32_t level, int64_t x, int64_t y) const; + + QString api_key_; + boost::hash hash_; + QNetworkAccessManager network_manager_; + boost::random::mt19937 rng_; + std::vector subdomains_; + QString tile_url_; + + static const std::string BING_IMAGE_URL_KEY; + static const std::string BING_IMAGE_URL_SUBDOMAIN_KEY; + static const std::string BING_RESOURCE_SET_KEY; + static const std::string BING_RESOURCE_KEY; + static const std::string BING_STATUS_CODE_KEY; + }; +} + +#endif //TILE_MAP_BING_SOURCE_H diff --git a/tile_map/include/tile_map/image_cache.h b/tile_map/include/tile_map/image_cache.h index ba25b7e5..342968d6 100644 --- a/tile_map/include/tile_map/image_cache.h +++ b/tile_map/include/tile_map/image_cache.h @@ -32,8 +32,7 @@ #include -#include - +#include #include #include @@ -51,10 +50,10 @@ namespace tile_map class Image { public: - Image(const std::string& uri, size_t uri_hash, uint64_t priority = 0); + Image(const QString& uri, size_t uri_hash, uint64_t priority = 0); ~Image(); - - std::string Uri() const { return uri_; } + + QString Uri() const { return uri_; } size_t UriHash() const { return uri_hash_; } boost::shared_ptr GetImage() { return image_; } @@ -72,7 +71,7 @@ namespace tile_map void SetLoading(bool loading) { loading_ = loading; } private: - std::string uri_; + QString uri_; size_t uri_hash_; @@ -93,7 +92,7 @@ namespace tile_map explicit ImageCache(const QString& cache_dir, size_t size = 4096); ~ImageCache(); - ImagePtr GetImage(size_t uri_hash, const std::string& uri, int32_t priority = 0); + ImagePtr GetImage(size_t uri_hash, const QString& uri, int32_t priority = 0); public Q_SLOTS: void ProcessRequest(QString uri); @@ -105,10 +104,10 @@ namespace tile_map QNetworkAccessManager network_manager_; QString cache_dir_; - - boost::hash hash_function_; + QCache cache_; QMap unprocessed_; + QMap uri_to_hash_map_; QMutex cache_mutex_; QMutex unprocessed_mutex_; diff --git a/tile_map/include/tile_map/texture_cache.h b/tile_map/include/tile_map/texture_cache.h index 9bb4b1a1..e03beaef 100644 --- a/tile_map/include/tile_map/texture_cache.h +++ b/tile_map/include/tile_map/texture_cache.h @@ -54,7 +54,7 @@ namespace tile_map public: explicit TextureCache(ImageCachePtr image_cache, size_t size = 512); - TexturePtr GetTexture(size_t url_hash, const std::string& url, bool& failed); + TexturePtr GetTexture(size_t url_hash, const QString& url, bool& failed); void AddTexture(const TexturePtr& texture); void Clear(); diff --git a/tile_map/include/tile_map/tile_map_plugin.h b/tile_map/include/tile_map/tile_map_plugin.h index 36ac74c2..e717ffda 100644 --- a/tile_map/include/tile_map/tile_map_plugin.h +++ b/tile_map/include/tile_map/tile_map_plugin.h @@ -36,6 +36,7 @@ // Boost libraries #include +#include #include @@ -74,19 +75,18 @@ namespace tile_map QWidget* GetConfigWidget(QWidget* parent); - protected: + protected Q_SLOTS: void PrintError(const std::string& message); void PrintInfo(const std::string& message); void PrintWarning(const std::string& message); - protected Q_SLOTS: void DeleteTileSource(); - void SelectSource(QString source_name); + void SelectSource(const QString& source_name); void SaveCustomSource(); void ResetTileCache(); private: - void selectTileSource(const TileSource& tile_source); + void selectTileSource(const boost::shared_ptr& tile_source); void startCustomEditing(); void stopCustomEditing(); @@ -99,15 +99,22 @@ namespace tile_map bool transformed_; TileMapView tile_map_; - std::map tile_sources_; + std::map > tile_sources_; + + double last_center_x_; + double last_center_y_; + double last_scale_; + int32_t last_height_; + int32_t last_width_; static std::string BASE_URL_KEY; - static std::string COORD_ORDER_KEY; + static std::string BING_API_KEY; static std::string CUSTOM_SOURCES_KEY; static std::string MAX_ZOOM_KEY; static std::string NAME_KEY; static std::string SOURCE_KEY; - static std::string SUFFIX_KEY; + static std::string TYPE_KEY; + static QString BING_NAME; static QString STAMEN_TERRAIN_NAME; static QString STAMEN_TONER_NAME; static QString STAMEN_WATERCOLOR_NAME; diff --git a/tile_map/include/tile_map/tile_map_view.h b/tile_map/include/tile_map/tile_map_view.h index c294a350..429dd462 100644 --- a/tile_map/include/tile_map/tile_map_view.h +++ b/tile_map/include/tile_map/tile_map_view.h @@ -32,11 +32,10 @@ #include -#include - -#include +#include #include +#include #include @@ -47,7 +46,7 @@ namespace tile_map struct Tile { public: - std::string url; + QString url; size_t url_hash; int32_t level; int32_t subdiv_count; @@ -66,7 +65,7 @@ namespace tile_map void ResetCache(); - void SetTileSource(const TileSource& tile_source); + void SetTileSource(const boost::shared_ptr& tile_source); void SetTransform(const swri_transform_util::Transform& transform); @@ -80,7 +79,7 @@ namespace tile_map void Draw(); private: - TileSource tile_source_; + boost::shared_ptr tile_source_; swri_transform_util::Transform transform_; @@ -96,8 +95,7 @@ namespace tile_map std::vector tiles_; std::vector precache_; - - boost::hash hash_function_; + TextureCachePtr tile_cache_; void ToLatLon(int32_t level, double x, double y, double& latitude, double& longitude); diff --git a/tile_map/include/tile_map/tile_source.h b/tile_map/include/tile_map/tile_source.h index cf98793e..32873b84 100644 --- a/tile_map/include/tile_map/tile_source.h +++ b/tile_map/include/tile_map/tile_source.h @@ -31,6 +31,7 @@ #ifndef TILE_MAP_TILE_SOURCE_H #define TILE_MAP_TILE_SOURCE_H +#include #include namespace tile_map @@ -39,70 +40,74 @@ namespace tile_map * Represents a network source for map tiles; contains information about how to * connect to the source and how to retrieve tiles from it. * - * Currently this implementation is fairly WMTS-centric, but there's no reason - * this couldn't be made a bit more generate for supporting other service formats - * in the future. + * Implementations of this should be specific to a type of map server such as + * WMS, WMTS, or TMS, and implement the appropriate methods for retrieiving tiles + * from those servers. */ - class TileSource + class TileSource : public QObject { + Q_OBJECT public: - enum COORD_ORDER - { - // The order of the listing here should match the order listed - // in the combo box in tile_map_config.ui - ZXY, - ZYX, - XYZ, - XZY, - YXZ, - YZX - }; + virtual ~TileSource() {}; - TileSource(); + virtual const QString& GetBaseUrl() const; - TileSource(const QString& name, - const QString& base_url, - COORD_ORDER coord_order, - bool is_custom, - int32_t max_zoom, - const QString& suffix - ); + virtual void SetBaseUrl(const QString& base_url); - TileSource(const TileSource& tile_source); + virtual bool IsCustom() const; - const QString& GetBaseUrl() const; + virtual void SetCustom(bool is_custom); - void SetBaseUrl(const QString& base_url); + virtual int32_t GetMaxZoom() const; - COORD_ORDER GetCoordOrder() const; + virtual void SetMaxZoom(int32_t max_zoom); - void SetCoordOrder(COORD_ORDER coord_order); + virtual int32_t GetMinZoom() const; - bool IsCustom() const; + virtual void SetMinZoom(int32_t min_zoom); - void SetCustom(bool is_custom); + virtual const QString& GetName() const; - int32_t GetMaxZoom() const; + virtual void SetName(const QString& name); - void SetMaxZoom(int32_t max_zoom); + /** + * Generates a hash that uniquely identifies the tile from this source at the + * specified level and coordinates. + * @param level The zoom level + * @param x The X coordinate + * @param y The Y coordinate + * @return A hash identifying the tile + */ + virtual size_t GenerateTileHash(int32_t level, int64_t x, int64_t y) = 0; - const QString& GetName() const; + /** + * Generates an HTTP or HTTPS URL that refers to a map tile from this source + * at the given level and x and y coordinates. + * @param level The zoom level + * @param x The x coordinate + * @param y The y coordinate + * @return A URL referring to the map tile + */ + virtual QString GenerateTileUrl(int32_t level, int64_t x, int64_t y) = 0; - void SetName(const QString& name); + /** + * Returns a string identifying the type of map source ("wmts", "bing", etc.) + * @return + */ + virtual QString GetType() const = 0; - const QString& GetSuffix() const; + Q_SIGNALS: + void ErrorMessage(const std::string& error_msg) const; + void InfoMessage(const std::string& info_msg) const; - void SetSuffix(const QString& suffix); + protected: + TileSource() {}; - std::string GenerateTileUrl(int32_t level, int64_t x, int64_t y) const; - - private: QString base_url_; - COORD_ORDER coord_order_; bool is_custom_; int32_t max_zoom_; + int32_t min_zoom_; QString name_; - QString suffix_; }; } diff --git a/tile_map/include/tile_map/wmts_source.h b/tile_map/include/tile_map/wmts_source.h new file mode 100644 index 00000000..b9d64582 --- /dev/null +++ b/tile_map/include/tile_map/wmts_source.h @@ -0,0 +1,86 @@ +// ***************************************************************************** +// +// Copyright (c) 2015, Southwest Research Institute® (SwRI®) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Southwest Research Institute® (SwRI®) nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +// +// ***************************************************************************** + +#ifndef TILE_MAP_WMTS_SOURCE_H +#define TILE_MAP_WMTS_SOURCE_H + +#include "tile_source.h" + +#include + +namespace tile_map +{ + class WmtsSource : public TileSource + { + Q_OBJECT + public: + /** + * Creates a new tile source from a set of known parameters. + * + * @param[in] name A user-friendly display name + * @param[in] base_url The base HTTP URL of the data source; e. g.: + * "http://tile.stamen.com/terrain/" + * @param[in] is_custom If this is a custom (i. e. not one of the default) + * tile source; custom sources are saved and loaded from our settings + * @param[in] max_zoom The maximum zoom level + */ + explicit WmtsSource(const QString& name, + const QString& base_url, + bool is_custom, + int32_t max_zoom); + + virtual size_t GenerateTileHash(int32_t level, int64_t x, int64_t y); + + /** + * Given a zoom level and x and y coordinates appropriate for the tile source's + * projection, this will generate a URL that points to an image tile for that + * location. + * + * This expects the URL to have three strings in it, "{level}", "{x}", and "{y}", + * which will be replaced with the passed values. See tile_map_plugin.cpp for + * example URLs. + * + * @param[in] level The zoom level + * @param[in] x The X coordinate of the tile + * @param[in] y The Y coordinate of the tile + * @return A URL that references that tile + */ + virtual QString GenerateTileUrl(int32_t level, int64_t x, int64_t y); + + virtual QString GetType() const; + + static const QString WMTS_TYPE; + + private: + boost::hash hash_; + }; +} + +#endif //TILE_MAP_WMTS_SOURCE_H diff --git a/tile_map/package.xml b/tile_map/package.xml index 5408692f..8089da2e 100644 --- a/tile_map/package.xml +++ b/tile_map/package.xml @@ -15,11 +15,12 @@ catkin + libjsoncpp-dev + libqt5-opengl-dev libqt5-core libqt5-gui libqt5-network libqt5-opengl - libqt5-opengl-dev libqt5-widgets mapviz pluginlib @@ -28,6 +29,7 @@ swri_transform_util swri_yaml_util tf + libjsoncpp diff --git a/tile_map/src/bing_source.cpp b/tile_map/src/bing_source.cpp new file mode 100644 index 00000000..bbf50147 --- /dev/null +++ b/tile_map/src/bing_source.cpp @@ -0,0 +1,184 @@ +// ***************************************************************************** +// +// Copyright (c) 2015, Southwest Research Institute® (SwRI®) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Southwest Research Institute® (SwRI®) nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +// +// ***************************************************************************** + +#include +#include +#include + +#include +#include + +#include + +namespace tile_map +{ + const QString BingSource::BING_TYPE = "bing"; + const std::string BingSource::BING_IMAGE_URL_KEY = "imageUrl"; + const std::string BingSource::BING_IMAGE_URL_SUBDOMAIN_KEY = "imageUrlSubdomains"; + const std::string BingSource::BING_RESOURCE_SET_KEY = "resourceSets"; + const std::string BingSource::BING_RESOURCE_KEY = "resources"; + const std::string BingSource::BING_STATUS_CODE_KEY = "statusCode"; + + BingSource::BingSource(const QString& name) : + network_manager_(this) + { + name_ = name; + is_custom_ = false; + max_zoom_ = 19; + base_url_ = "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?uriScheme=https&include=ImageryProviders&key={api_key}"; + tile_url_ = ""; + min_zoom_ = 2; + + QObject::connect(&network_manager_, SIGNAL(finished(QNetworkReply*)), + this, SLOT(ReplyFinished(QNetworkReply*))); + } + + size_t BingSource::GenerateTileHash(int32_t level, int64_t x, int64_t y) + { + size_t hash = hash_((base_url_ + api_key_ + GenerateQuadKey(level, x, y)).toStdString()); + return hash; + } + + QString BingSource::GenerateTileUrl(int32_t level, int64_t x, int64_t y) + { + QString url = tile_url_; + if (!subdomains_.empty()) + { + boost::random::uniform_int_distribution<> random(0, (int) subdomains_.size() - 1); + url.replace(QString::fromStdString("{subdomain}"), subdomains_[random(rng_)]); + } + url.replace(QString::fromStdString("{quadkey}"), GenerateQuadKey(level, x, y)); + return url; + } + + QString BingSource::GetType() const + { + return BING_TYPE; + } + + QString BingSource::GetApiKey() const + { + return api_key_; + } + + void BingSource::SetApiKey(const QString& api_key) + { + api_key_ = api_key.trimmed(); + if (!api_key_.isEmpty()) + { + QString url(base_url_); + url.replace(QString::fromStdString("{api_key}"), api_key_); + // Changing the API key will result in the tile URL changing; go ahead + // and blank it out so we don't make requests using the old one. + tile_url_= ""; + network_manager_.get(QNetworkRequest(QUrl(url))); + } + } + + QString BingSource::GenerateQuadKey(int32_t level, int64_t x, int64_t y) const + { + QString quadkey; + for (int32_t i = level; i > 0; i--) + { + int32_t bitmask = 1 << (i-1); + int32_t digit = 0; + if ((x & bitmask) != 0) + { + digit |= 1; + } + if ((y & bitmask) != 0) + { + digit |= 2; + } + quadkey.append(QString::number(digit)); + } + + return quadkey; + } + + void BingSource::ReplyFinished(QNetworkReply* reply) + { + QString reply_string(reply->readAll()); + Json::Reader reader; + Json::Value root; + reader.parse(reply_string.toStdString(), root); + + int status = root[BING_STATUS_CODE_KEY].asInt(); + if (status != 200) + { + Q_EMIT ErrorMessage("Bing authorization error: " + boost::lexical_cast(status)); + } + else + { + if (!root[BING_RESOURCE_SET_KEY].isArray() || + root[BING_RESOURCE_SET_KEY].size() == 0) + { + Q_EMIT ErrorMessage("No Bing resource sets found."); + return; + } + Json::Value firstResourceSet = root[BING_RESOURCE_SET_KEY][0]; + + if (!firstResourceSet[BING_RESOURCE_KEY].isArray() || + firstResourceSet[BING_RESOURCE_KEY].size() == 0) + { + Q_EMIT ErrorMessage("No Bing resources found."); + return; + } + + Json::Value first_resource = firstResourceSet[BING_RESOURCE_KEY][0]; + + std::string image_url = first_resource[BING_IMAGE_URL_KEY].asString(); + + if (image_url.empty()) + { + Q_EMIT ErrorMessage("No Bing image URL found."); + return; + } + + tile_url_ = QString::fromStdString(image_url); + SetMaxZoom(19); + + + if (!first_resource[BING_IMAGE_URL_SUBDOMAIN_KEY].isArray() || + first_resource[BING_IMAGE_URL_SUBDOMAIN_KEY].size() == 0) + { + Q_EMIT ErrorMessage("No image URL subdomains; maybe that's ok sometimes?"); + } + + for (int i = 0; i < first_resource[BING_IMAGE_URL_SUBDOMAIN_KEY].size(); i++) + { + Json::Value subdomain = first_resource[BING_IMAGE_URL_SUBDOMAIN_KEY][i]; + subdomains_.push_back(QString::fromStdString(subdomain.asString())); + } + + Q_EMIT InfoMessage("API Key Set."); + } + } +} diff --git a/tile_map/src/image_cache.cpp b/tile_map/src/image_cache.cpp index d365ff35..4dc28493 100644 --- a/tile_map/src/image_cache.cpp +++ b/tile_map/src/image_cache.cpp @@ -47,7 +47,7 @@ namespace tile_map return left->Priority() > right->Priority(); } - Image::Image(const std::string& uri, size_t uri_hash, uint64_t priority) : + Image::Image(const QString& uri, size_t uri_hash, uint64_t priority) : uri_(uri), uri_hash_(uri_hash), loading_(false), @@ -110,7 +110,7 @@ namespace tile_map network_manager_.cache()->clear(); } - ImagePtr ImageCache::GetImage(size_t uri_hash, const std::string& uri, int32_t priority) + ImagePtr ImageCache::GetImage(size_t uri_hash, const QString& uri, int32_t priority) { ImagePtr image; @@ -124,7 +124,7 @@ namespace tile_map image = *image_ptr; if (!cache_.insert(uri_hash, image_ptr)) { - ROS_ERROR("FAILED TO CREATE HANDLE: %s", uri.c_str()); + ROS_ERROR("FAILED TO CREATE HANDLE: %s", uri.toStdString().c_str()); image_ptr = 0; } } @@ -147,11 +147,12 @@ namespace tile_map { image->SetPriority(tick_++); unprocessed_[uri_hash] = image; + uri_to_hash_map_[uri] = uri_hash; } } else { - ROS_ERROR("To many failures for image: %s", uri.c_str()); + ROS_ERROR("To many failures for image: %s", uri.toStdString().c_str()); } } @@ -179,12 +180,12 @@ namespace tile_map void ImageCache::ProcessReply(QNetworkReply* reply) { - std::string url = reply->url().toString().toStdString(); - size_t hash = hash_function_(url); + QString url = reply->url().toString(); ImagePtr image; unprocessed_mutex_.lock(); - + + size_t hash = uri_to_hash_map_[url]; image = unprocessed_[hash]; if (image) { @@ -194,19 +195,20 @@ namespace tile_map image->InitializeImage(); if (!image->GetImage()->loadFromData(data)) { - ROS_ERROR("FAILED TO CREATE IMAGE FROM REPLY: %s", url.c_str()); + ROS_ERROR("FAILED TO CREATE IMAGE FROM REPLY: %s", url.toStdString().c_str()); image->ClearImage(); image->AddFailure(); } } else { - ROS_ERROR("============ AN ERROR OCCURRED ==============: %s", url.c_str()); + ROS_ERROR("============ AN ERROR OCCURRED ==============: %s", url.toStdString().c_str()); image->AddFailure(); } } unprocessed_.remove(hash); + uri_to_hash_map_.remove(url); if (image) { image->SetLoading(false); @@ -218,8 +220,6 @@ namespace tile_map reply->deleteLater(); - - // Condition variable? } @@ -253,7 +253,7 @@ namespace tile_map image->SetLoading(true); images.pop_front(); - Q_EMIT RequestImage(QString::fromStdString(image->Uri())); + Q_EMIT RequestImage(image->Uri()); p->pending_++; count++; @@ -272,6 +272,7 @@ namespace tile_map ImagePtr image = images.back(); images.pop_back(); p->unprocessed_.remove(image->UriHash()); + p->uri_to_hash_map_.remove(image->Uri()); } p->unprocessed_mutex_.unlock(); diff --git a/tile_map/src/texture_cache.cpp b/tile_map/src/texture_cache.cpp index da015a21..2dc92dd4 100644 --- a/tile_map/src/texture_cache.cpp +++ b/tile_map/src/texture_cache.cpp @@ -69,7 +69,7 @@ namespace tile_map } - TexturePtr TextureCache::GetTexture(size_t url_hash, const std::string& url, bool& failed) + TexturePtr TextureCache::GetTexture(size_t url_hash, const QString& url, bool& failed) { TexturePtr texture; diff --git a/tile_map/src/tile_map_config.ui b/tile_map/src/tile_map_config.ui index 8d5e90af..068e61d1 100644 --- a/tile_map/src/tile_map_config.ui +++ b/tile_map/src/tile_map_config.ui @@ -6,8 +6,8 @@ 0 0 - 294 - 178 + 305 + 141 @@ -24,7 +24,13 @@ 2 - + + + + Sans Serif + 8 + + Base URL: @@ -43,83 +49,42 @@ - - - - false + + + + + Sans Serif + 8 + - - - 0 - 0 - + + - - - z/x/y - - - - - z/y/x - - - - - x/y/z - - - - - x/z/y - - - - - y/x/z - - - - - y/z/x - - - - - - - Coord order: + Unconfigured - - - - - - Max Zoom: + + true - + + + + Sans Serif + 8 + + - Suffix: + Max Zoom: - - - - false - - - - 50 - 16777215 - - + + - .jpg + Reset Cache @@ -151,7 +116,12 @@ - Custom... + Bing Maps (terrain) + + + + + Custom WMTS Source... @@ -166,7 +136,27 @@ - + + + + false + + + Delete + + + + + + + false + + + Save... + + + + @@ -179,7 +169,7 @@ - + false @@ -201,52 +191,6 @@ - - - - Reset Cache - - - - - - - - Sans Serif - 8 - - - - - - - Unconfigured - - - true - - - - - - - false - - - Save... - - - - - - - false - - - Delete - - - diff --git a/tile_map/src/tile_map_plugin.cpp b/tile_map/src/tile_map_plugin.cpp index 9e4429cb..223068c8 100644 --- a/tile_map/src/tile_map_plugin.cpp +++ b/tile_map/src/tile_map_plugin.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include // QT libraries #include @@ -50,40 +52,45 @@ PLUGINLIB_DECLARE_CLASS(mapviz_plugins, tile_map, tile_map::TileMapPlugin, mapvi namespace tile_map { std::string TileMapPlugin::BASE_URL_KEY = "base_url"; - std::string TileMapPlugin::COORD_ORDER_KEY = "coord_order"; + std::string TileMapPlugin::BING_API_KEY = "bing_api_key"; std::string TileMapPlugin::CUSTOM_SOURCES_KEY = "custom_sources"; std::string TileMapPlugin::MAX_ZOOM_KEY = "max_zoom"; std::string TileMapPlugin::NAME_KEY = "name"; std::string TileMapPlugin::SOURCE_KEY = "source"; - std::string TileMapPlugin::SUFFIX_KEY = "suffix"; + std::string TileMapPlugin::TYPE_KEY = "type"; + QString TileMapPlugin::BING_NAME = "Bing Maps (terrain)"; QString TileMapPlugin::STAMEN_TERRAIN_NAME = "Stamen (terrain)"; QString TileMapPlugin::STAMEN_TONER_NAME = "Stamen (toner)"; QString TileMapPlugin::STAMEN_WATERCOLOR_NAME = "Stamen (watercolor)"; TileMapPlugin::TileMapPlugin() : config_widget_(new QWidget()), - transformed_(false) + transformed_(false), + last_center_x_(0.0), + last_center_y_(0.0), + last_scale_(0.0), + last_height_(0), + last_width_(0) { ui_.setupUi(config_widget_); - tile_sources_[STAMEN_TERRAIN_NAME] = TileSource(STAMEN_TERRAIN_NAME, - "http://tile.stamen.com/terrain/", - TileSource::ZXY, - false, - 15, - ".png"); - tile_sources_[STAMEN_TONER_NAME] = TileSource(STAMEN_TONER_NAME, - "http://tile.stamen.com/toner/", - TileSource::ZXY, - false, - 19, - ".png"); - tile_sources_[STAMEN_WATERCOLOR_NAME] = TileSource(STAMEN_WATERCOLOR_NAME, - "http://tile.stamen.com/watercolor/", - TileSource::ZXY, - false, - 19, - ".jpg"); + tile_sources_[STAMEN_TERRAIN_NAME] = + boost::make_shared(STAMEN_TERRAIN_NAME, + "http://tile.stamen.com/terrain/{level}/{x}/{y}.png", + false, + 15); + tile_sources_[STAMEN_TONER_NAME] = + boost::make_shared(STAMEN_TONER_NAME, + "http://tile.stamen.com/toner/{level}/{x}/{y}.png", + false, + 19); + tile_sources_[STAMEN_WATERCOLOR_NAME] = + boost::make_shared(STAMEN_WATERCOLOR_NAME, + "http://tile.stamen.com/watercolor/{level}/{x}/{y}.jpg", + false, + 19); + boost::shared_ptr bing = boost::make_shared(BING_NAME); + tile_sources_[BING_NAME] = bing; QPalette p(config_widget_->palette()); p.setColor(QPalette::Background, Qt::white); @@ -95,8 +102,12 @@ namespace tile_map source_frame_ = swri_transform_util::_wgs84_frame; + QObject::connect(bing.get(), SIGNAL(ErrorMessage(const std::string&)), + this, SLOT(PrintError(const std::string&))); + QObject::connect(bing.get(), SIGNAL(InfoMessage(const std::string&)), + this, SLOT(PrintInfo(const std::string&))); QObject::connect(ui_.delete_button, SIGNAL(clicked()), this, SLOT(DeleteTileSource())); - QObject::connect(ui_.source_combo, SIGNAL(activated(QString)), this, SLOT(SelectSource(QString))); + QObject::connect(ui_.source_combo, SIGNAL(activated(const QString&)), this, SLOT(SelectSource(const QString&))); QObject::connect(ui_.save_button, SIGNAL(clicked()), this, SLOT(SaveCustomSource())); QObject::connect(ui_.reset_cache_button, SIGNAL(clicked()), this, SLOT(ResetTileCache())); } @@ -126,11 +137,12 @@ namespace tile_map } } - void TileMapPlugin::SelectSource(QString source) + void TileMapPlugin::SelectSource(const QString& source) { if (source == STAMEN_TERRAIN_NAME || source == STAMEN_WATERCOLOR_NAME || - source == STAMEN_TONER_NAME) + source == STAMEN_TONER_NAME || + source == BING_NAME) { stopCustomEditing(); } @@ -139,10 +151,27 @@ namespace tile_map startCustomEditing(); } - if (tile_sources_.find(source) != tile_sources_.end()) + std::map >::iterator iter = tile_sources_.find(source); + + // If the previously selected source was Bing, these will have been changed, so + // they should be changed back. There's not an easy way to know here what the + // previously selected item was, so just always change them. + ui_.url_label->setText("Base URL:"); + ui_.save_button->setText("Save..."); + if (iter != tile_sources_.end()) { - selectTileSource(tile_sources_[source]); + selectTileSource(iter->second); initialized_ = true; + // For the Bing map type, change a couple of the fields to have more appropriate + // labels. There should probably be a cleaner way to do this if we end up adding + // more tile source types.... + if (iter->second->GetType() == BingSource::BING_TYPE) + { + ui_.url_label->setText("API Key:"); + ui_.save_button->setText("Save"); + ui_.base_url_text->setEnabled(true); + ui_.save_button->setEnabled(true); + } } else { @@ -158,13 +187,24 @@ namespace tile_map // should leave the default blank. QString current_source = ui_.source_combo->currentText(); QString default_name = ""; - if (tile_sources_.find(current_source) != tile_sources_.end()) + + std::map >::iterator iter = tile_sources_.find(current_source); + if (iter != tile_sources_.end()) { - if (tile_sources_[current_source].IsCustom()) + if (iter->second->IsCustom()) { default_name = current_source; } + else if (iter->second->GetType() == BingSource::BING_TYPE) + { + // If the user has picked Bing as they're source, we're not actually + // saving a custom map source, just updating the API key + BingSource* bing_source = static_cast(iter->second.get()); + bing_source->SetApiKey(ui_.base_url_text->text()); + return; + } } + bool ok; QString name = QInputDialog::getText(config_widget_, tr("Save New Tile Source"), @@ -175,12 +215,10 @@ namespace tile_map name = name.trimmed(); if (ok && !name.isEmpty()) { - TileSource source(name, + boost::shared_ptr source = boost::make_shared(name, ui_.base_url_text->text(), - static_cast(ui_.coord_order_combo_box->currentIndex()), true, - ui_.max_zoom_spin_box->value(), - ui_.suffix_text->text()); + ui_.max_zoom_spin_box->value()); int existing_index = ui_.source_combo->findText(name); if (existing_index != -1) { @@ -258,7 +296,21 @@ namespace tile_map { tf::Vector3 center(x, y, 0); center = to_wgs84 * center; - tile_map_.SetView(center.y(), center.x(), scale, canvas_->width(), canvas_->height()); + if (center.y() != last_center_y_ || + center.x() != last_center_x_ || + scale != last_scale_ || + canvas_->width() != last_width_ || + canvas_->height() != last_height_) + { + // Draw() is called very frequently, and SetView is a fairly expensive operation, so we + // can save some CPU time by only calling it when the relevant parameters have changed. + last_center_y_ = center.y(); + last_center_x_ = center.x(); + last_scale_ = scale; + last_width_ = canvas_->width(); + last_height_ = canvas_->height(); + tile_map_.SetView(center.y(), center.x(), scale, canvas_->width(), canvas_->height()); + } tile_map_.Draw(); } } @@ -285,17 +337,37 @@ namespace tile_map YAML::Node::const_iterator source_iter; for (source_iter = sources.begin(); source_iter != sources.end(); source_iter++) { - TileSource source(QString::fromStdString(((*source_iter)[NAME_KEY]).as()), - QString::fromStdString((*source_iter)[BASE_URL_KEY].as()), - static_cast((*source_iter)[COORD_ORDER_KEY].as()), - true, - (*source_iter)[MAX_ZOOM_KEY].as(), - QString::fromStdString((*source_iter)[SUFFIX_KEY].as()) - ); - tile_sources_[source.GetName()] = source; - ui_.source_combo->addItem(source.GetName()); + std::string type = ""; + if ((*source_iter)[TYPE_KEY]) + { + // If the type isn't set, we'll assume it's WMTS + type = ((*source_iter)[TYPE_KEY]).as(); + } + boost::shared_ptr source; + if (type == "wmts" || type.empty()) + { + source = boost::make_shared( + QString::fromStdString(((*source_iter)[NAME_KEY]).as()), + QString::fromStdString((*source_iter)[BASE_URL_KEY].as()), + true, + (*source_iter)[MAX_ZOOM_KEY].as()); + } + else if (type == "bing") + { + source = boost::make_shared( + QString::fromStdString(((*source_iter)[NAME_KEY]).as())); + } + tile_sources_[source->GetName()] = source; + ui_.source_combo->addItem(source->GetName()); } } + + if (node[BING_API_KEY]) + { + std::string key = node[BING_API_KEY].as(); + BingSource* source = static_cast(tile_sources_[BING_NAME].get()); + source->SetApiKey(QString::fromStdString(key)); + } if (node[SOURCE_KEY]) { @@ -316,39 +388,48 @@ namespace tile_map { emitter << YAML::Key << CUSTOM_SOURCES_KEY << YAML::Value << YAML::BeginSeq; - std::map::iterator iter; + std::map >::iterator iter; for (iter = tile_sources_.begin(); iter != tile_sources_.end(); iter++) { - if (iter->second.IsCustom()) + if (iter->second->IsCustom()) { emitter << YAML::BeginMap; - emitter << YAML::Key << BASE_URL_KEY << YAML::Value << iter->second.GetBaseUrl().toStdString(); - emitter << YAML::Key << COORD_ORDER_KEY << YAML::Value << (int) iter->second.GetCoordOrder(); - emitter << YAML::Key << MAX_ZOOM_KEY << YAML::Value << iter->second.GetMaxZoom(); - emitter << YAML::Key << NAME_KEY << YAML::Value << iter->second.GetName().toStdString(); - emitter << YAML::Key << SUFFIX_KEY << YAML::Value << iter->second.GetSuffix().toStdString(); + emitter << YAML::Key << BASE_URL_KEY << YAML::Value << iter->second->GetBaseUrl().toStdString(); + emitter << YAML::Key << MAX_ZOOM_KEY << YAML::Value << iter->second->GetMaxZoom(); + emitter << YAML::Key << NAME_KEY << YAML::Value << iter->second->GetName().toStdString(); + emitter << YAML::Key << TYPE_KEY << YAML::Value << iter->second->GetType().toStdString(); emitter << YAML::EndMap; } } emitter << YAML::EndSeq; - emitter << YAML::Key << SOURCE_KEY << YAML::Value << boost::trim_copy(ui_.source_combo->currentText().toStdString()); + BingSource* bing_source = static_cast(tile_sources_[BING_NAME].get()); + emitter << YAML::Key << BING_API_KEY << + YAML::Value << boost::trim_copy(bing_source->GetApiKey().toStdString()); + + emitter << YAML::Key << SOURCE_KEY << + YAML::Value << boost::trim_copy(ui_.source_combo->currentText().toStdString()); } - void TileMapPlugin::selectTileSource(const TileSource& tile_source) + void TileMapPlugin::selectTileSource(const boost::shared_ptr& tile_source) { + last_height_ = 0; // This will force us to recalculate our view tile_map_.SetTileSource(tile_source); - ui_.base_url_text->setText(tile_source.GetBaseUrl()); - ui_.suffix_text->setText(tile_source.GetSuffix()); - ui_.coord_order_combo_box->setCurrentIndex((int) tile_source.GetCoordOrder()); - ui_.max_zoom_spin_box->setValue(tile_source.GetMaxZoom()); + if (tile_source->GetType() == BingSource::BING_TYPE) + { + BingSource* bing_source = static_cast(tile_source.get()); + ui_.base_url_text->setText(bing_source->GetApiKey()); + } + else + { + ui_.base_url_text->setText(tile_source->GetBaseUrl()); + } + ui_.max_zoom_spin_box->setValue(tile_source->GetMaxZoom()); } void TileMapPlugin::startCustomEditing() { ui_.base_url_text->setEnabled(true); - ui_.suffix_text->setEnabled(true); - ui_.coord_order_combo_box->setEnabled(true); ui_.delete_button->setEnabled(true); ui_.max_zoom_spin_box->setEnabled(true); ui_.save_button->setEnabled(true); @@ -357,8 +438,6 @@ namespace tile_map void TileMapPlugin::stopCustomEditing() { ui_.base_url_text->setEnabled(false); - ui_.suffix_text->setEnabled(false); - ui_.coord_order_combo_box->setEnabled(false); ui_.delete_button->setEnabled(false); ui_.max_zoom_spin_box->setEnabled(false); ui_.save_button->setEnabled(false); diff --git a/tile_map/src/tile_map_view.cpp b/tile_map/src/tile_map_view.cpp index 01f51405..56cada4e 100644 --- a/tile_map/src/tile_map_view.cpp +++ b/tile_map/src/tile_map_view.cpp @@ -46,7 +46,6 @@ namespace tile_map { TileMapView::TileMapView() : - tile_source_("localhost", "localhost", TileSource::ZXY, true, 19, ".jpg"), level_(-1), width_(100), height_(100) @@ -60,7 +59,7 @@ namespace tile_map tile_cache_->Clear(); } - void TileMapView::SetTileSource(const TileSource& tile_source) + void TileMapView::SetTileSource(const boost::shared_ptr& tile_source) { tile_source_ = tile_source; level_ = -1; @@ -115,9 +114,8 @@ namespace tile_map // double lat_circumference = swri_transform_util::_earth_equator_circumference * std::cos(lat) / scale; - int32_t level = std::min(tile_source_.GetMaxZoom(), std::max(0, static_cast( - std::ceil(std::log(lat_circumference) / std::log(2) - 8)))); - + int32_t level = std::min(tile_source_->GetMaxZoom(), + std::max(tile_source_->GetMinZoom(), static_cast(std::ceil(std::log(lat_circumference) / std::log(2) - 8)))); int64_t max_size = std::pow(2, level); int64_t center_x = std::min(max_size - 1, static_cast( @@ -205,6 +203,11 @@ namespace tile_map void TileMapView::Draw() { + if (!tile_source_) + { + return; + } + glEnable(GL_TEXTURE_2D); for (size_t i = 0; i < precache_.size(); i++) @@ -217,8 +220,8 @@ namespace tile_map texture = tile_cache_->GetTexture(precache_[i].url_hash, precache_[i].url, failed); if (failed) { - tile_source_.SetMaxZoom(std::min(tile_source_.GetMaxZoom(), precache_[i].level - 1)); - ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_.GetMaxZoom()); + tile_source_->SetMaxZoom(std::min(tile_source_->GetMaxZoom(), precache_[i].level - 1)); + ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_->GetMaxZoom()); } } @@ -272,8 +275,8 @@ namespace tile_map texture = tile_cache_->GetTexture(tiles_[i].url_hash, tiles_[i].url, failed); if (failed) { - tile_source_.SetMaxZoom(std::min(tile_source_.GetMaxZoom(), tiles_[i].level - 1)); - ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_.GetMaxZoom()); + tile_source_->SetMaxZoom(std::min(tile_source_->GetMaxZoom(), tiles_[i].level - 1)); + ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_->GetMaxZoom()); } } @@ -331,18 +334,18 @@ namespace tile_map void TileMapView::InitializeTile(int32_t level, int64_t x, int64_t y, Tile& tile) { - tile.url = tile_source_.GenerateTileUrl(level, x, y); - - tile.url_hash = hash_function_(tile.url); + tile.url = tile_source_->GenerateTileUrl(level, x, y); + tile.url_hash = tile_source_->GenerateTileHash(level, x, y); + tile.level = level; bool failed; tile.texture = tile_cache_->GetTexture(tile.url_hash, tile.url, failed); if (failed) { - tile_source_.SetMaxZoom(std::min(tile_source_.GetMaxZoom(), level - 1)); - ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_.GetMaxZoom()); + tile_source_->SetMaxZoom(std::min(tile_source_->GetMaxZoom(), level - 1)); + ROS_WARN("===== SETTING MAX LEVEL TO %d =====", tile_source_->GetMaxZoom()); } int32_t subdivs = std::max(0, 4 - level); diff --git a/tile_map/src/tile_source.cpp b/tile_map/src/tile_source.cpp index 438a7c71..383186aa 100644 --- a/tile_map/src/tile_source.cpp +++ b/tile_map/src/tile_source.cpp @@ -29,65 +29,9 @@ // ***************************************************************************** #include -#include -#include namespace tile_map { - /** - * Default constructor. - * - * Not actually used in the code, this is only necessary so that this class can - * be used by STL data structures. - */ - TileSource::TileSource() : - coord_order_(ZYX), - is_custom_(false), - max_zoom_(-1) - { - } - - /** - * Creates a new tile source from a set of known parameters. - * - * @param[in] name A user-friendly display name - * @param[in] base_url The base HTTP URL of the data source; e. g.: - * "http://tile.stamen.com/terrain/" - * @param[in] coord_order The order in which coordinates should be appended - * to the URL - * @param is_custom If this is a custom (i. e. not one of the default) - * tile source; custom sources are saved and loaded from our settings - * @param[in] max_zoom The maximum zoom level - * @param[in] suffix A suffix that should be appended to the URL; i. e. ".jpg" - */ - TileSource::TileSource(const QString& name, - const QString& base_url, - COORD_ORDER coord_order, - bool is_custom, - int32_t max_zoom, - const QString& suffix) : - base_url_(base_url), - coord_order_(coord_order), - is_custom_(is_custom), - max_zoom_(max_zoom), - name_(name), - suffix_(suffix) - { - } - - /** - * Copy constructor - */ - TileSource::TileSource(const TileSource& tile_source) : - base_url_(tile_source.base_url_), - coord_order_(tile_source.coord_order_), - is_custom_(tile_source.is_custom_), - max_zoom_(tile_source.max_zoom_), - name_(tile_source.name_), - suffix_(tile_source.suffix_) - { - } - const QString& TileSource::GetBaseUrl() const { return base_url_; @@ -98,16 +42,6 @@ namespace tile_map base_url_ = base_url; } - TileSource::COORD_ORDER TileSource::GetCoordOrder() const - { - return coord_order_; - } - - void TileSource::SetCoordOrder(TileSource::COORD_ORDER coord_order) - { - coord_order_ = coord_order; - } - bool TileSource::IsCustom() const { return is_custom_; @@ -128,90 +62,23 @@ namespace tile_map max_zoom_ = max_zoom; } - const QString& TileSource::GetName() const - { - return name_; - } - - void TileSource::SetName(const QString& name) + int32_t TileSource::GetMinZoom() const { - name_ = name; + return min_zoom_; } - const QString& TileSource::GetSuffix() const + void TileSource::SetMinZoom(int32_t min_zoom) { - return suffix_; + min_zoom_ = min_zoom; } - void TileSource::SetSuffix(const QString& suffix) + const QString& TileSource::GetName() const { - suffix_ = suffix; + return name_; } - /** - * Given a zoom level and x and y coordinates appropriate for the tile source's - * projection, this will generate a URL that points to an image tile for that - * location. - * - * @param[in] level The zoom level - * @param[in] x The X coordinate of the tile - * @param[in] y The Y coordinate of the tile - * @return A URL that references that tile - */ - std::string TileSource::GenerateTileUrl(int32_t level, int64_t x, int64_t y) const + void TileSource::SetName(const QString& name) { - std::stringstream url; - url << base_url_.toStdString(); - - switch (coord_order_) - { - case TileSource::XYZ: - case TileSource::XZY: - url << boost::lexical_cast(x); - break; - case TileSource::YXZ: - case TileSource::YZX: - url << boost::lexical_cast(y); - break; - case TileSource::ZXY: - case TileSource::ZYX: - url << boost::lexical_cast(level); - break; - } - url << "/"; - switch (coord_order_) - { - case TileSource::ZXY: - case TileSource::YXZ: - url << boost::lexical_cast(x); - break; - case TileSource::ZYX: - case TileSource::XYZ: - url << boost::lexical_cast(y); - break; - case TileSource::XZY: - case TileSource::YZX: - url << boost::lexical_cast(level); - break; - } - url << "/"; - switch (coord_order_) - { - case TileSource::YZX: - case TileSource::ZYX: - url << boost::lexical_cast(x); - break; - case TileSource::ZXY: - case TileSource::XZY: - url << boost::lexical_cast(y); - break; - case TileSource::YXZ: - case TileSource::XYZ: - url << boost::lexical_cast(level); - break; - } - url << suffix_.toStdString(); - - return url.str(); + name_ = name; } } diff --git a/tile_map/src/wmts_source.cpp b/tile_map/src/wmts_source.cpp new file mode 100644 index 00000000..8ec06e6e --- /dev/null +++ b/tile_map/src/wmts_source.cpp @@ -0,0 +1,70 @@ +// ***************************************************************************** +// +// Copyright (c) 2015, Southwest Research Institute® (SwRI®) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Southwest Research Institute® (SwRI®) nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +// +// ***************************************************************************** + +#include + +#include + +namespace tile_map +{ + const QString WmtsSource::WMTS_TYPE = "wmts"; + + WmtsSource::WmtsSource(const QString& name, + const QString& base_url, + bool is_custom, + int32_t max_zoom) + { + name_ = name; + base_url_ = base_url; + is_custom_ = is_custom; + max_zoom_ = max_zoom; + min_zoom_ = 1; + } + + QString WmtsSource::GetType() const + { + return WMTS_TYPE; + } + + size_t WmtsSource::GenerateTileHash(int32_t level, int64_t x, int64_t y) + { + return hash_(GenerateTileUrl(level, x, y).toStdString()); + } + + QString WmtsSource::GenerateTileUrl(int32_t level, int64_t x, int64_t y) + { + QString url(base_url_); + url.replace(QString::fromStdString("{level}"), QString::number(level)); + url.replace(QString::fromStdString("{x}"), QString::number(x)); + url.replace(QString::fromStdString("{y}"), QString::number(y)); + + return url; + } +}