Merge remote-tracking branch 'origin/trouble-with-triplets' into gaussian-splats

This commit is contained in:
Kevin Ring 2025-10-23 17:43:39 +11:00
commit 9c41f33c83
19 changed files with 1099 additions and 330 deletions

View File

@ -5,6 +5,12 @@
##### Breaking Changes :mega:
- Restored vcpkg commit update to `2025.09.17`.
- Removed `refreshTileProviderWithNewKey` from `BingMapsRasterOverlay` and `refreshTileProviderWithNewUrlAndHeaders` from `TileMapServiceRasterOverlay`. These were no longer used after the raster overlay refactor in `v0.52.0`.
##### Additions :tada:
- Added `AzureMapsRasterOverlay`.
- Added `Uri::ensureTrailingSlash`, which is helpful when the `Uri` represents a base URL.
##### Additions :tada:
@ -14,6 +20,7 @@
##### Fixes :wrench:
- Fixed a bug in `GoogleMapTilesRasterOverlay` that tried to parse credits from an erroneous viewport service response.
- Fixed a bug with credits not showing on-screen when `showCreditsOnScreen` was enabled on `GoogleMapTilesRasterOverlay`.
### v0.52.1 - 2025-10-01

View File

@ -53,9 +53,16 @@ if (NOT VCPKG_TRIPLET)
else()
set(VCPKG_TRIPLET "${DETECTED_VCPKG_TRIPLET}")
endif()
if (NOT CESIUM_USE_EZVCPKG)
set(VCPKG_TARGET_TRIPLET "${VCPKG_TRIPLET}")
endif()
# If we're using ezvcpkg, ezvcpkg will update CMAKE_TOOLCHAIN_FILE to point to the vcpkg toolchain.
# Which means that when we hit the `project` function call below, cmake will load the vcpkg
# toolchain file. If VCPKG_TARGET_TRIPLET isn't set by that time, vcpkg will set it itself, and
# maybe not to what we want. So set VCPKG_TARGET_TRIPLET explicit here so that we're sure to get
# the right one.
#
# If we're _not_ using ezvcpkg, then we also must set VCPKG_TARGET_TRIPLET, but for a different reason.
# VCPKG_TRIPLET is only an ezvcpkg thing, vcpkg itself only knows about VCPKG_TARGET_TRIPLET.
set(VCPKG_TARGET_TRIPLET "${VCPKG_TRIPLET}")
endif()
message(STATUS "VCPKG_TRIPLET ${VCPKG_TRIPLET}")
@ -295,46 +302,42 @@ list(APPEND CMAKE_PREFIX_PATH "${PACKAGE_BUILD_DIR}")
# they won't be visible in this scope nor any of the subdirectories for the actual libraries.
#
# However, for some of the vcpkg built libraries where they don't provide a prope cmake config file, we have to declare
# and imporeted library target ourselves. This is the case for modp_b64::modp_b64, picosha2::picosha2 and earcut. In
# an imported library target ourselves. This is the case for modp_b64::modp_b64, picosha2::picosha2 and earcut. In
# these cases, we *do* have the somewhat ugly and verbose details in the extern/CMakeLists.txt file.
#
# XXX Above comment should be obsoleted by these first calls to
# find_package, which resolve to our own modules that provide
# targets. If needed, they can be installed with CMake config files
# etc.
find_package(zlib-ng REQUIRED)
find_package(ZLIB REQUIRED)
find_package(modp_b64 REQUIRED)
find_package(ada CONFIG REQUIRED)
find_package(Async++ CONFIG REQUIRED)
find_package(blend2d CONFIG REQUIRED)
find_package(doctest CONFIG REQUIRED)
find_package(draco CONFIG REQUIRED)
find_package(expected-lite CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
find_package(httplib CONFIG REQUIRED)
find_package(Ktx CONFIG REQUIRED)
find_package(libmorton CONFIG REQUIRED)
find_package(libjpeg-turbo CONFIG REQUIRED)
find_package(libmorton CONFIG REQUIRED)
find_package(meshoptimizer CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(s2 CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(spz CONFIG REQUIRED)
find_package(tinyxml2 CONFIG REQUIRED)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
find_package(WebP CONFIG REQUIRED)
find_package(blend2d CONFIG REQUIRED)
find_package(spz CONFIG REQUIRED)
find_package(zlib-ng CONFIG REQUIRED)
# asmjit should not be included with iOS builds as iOS doesn't support JIT compilation.
if(NOT IOS AND NOT VCPKG_CMAKE_SYSTEM_NAME MATCHES "iOS")
find_package(asmjit CONFIG REQUIRED)
endif()
if(NOT CESIUM_DISABLE_CURL)
find_package(CURL REQUIRED)
endif()
if(NOT CESIUM_DISABLE_CURL)
find_package(CURL REQUIRED)
endif()
@ -388,7 +391,6 @@ if(CESIUM_INSTALL_STATIC_LIBS AND CESIUM_INSTALL_HEADERS)
DESTINATION ${CMAKE_INSTALL_DATADIR}/cesium-native/cmake)
install(FILES
"${CMAKE_CURRENT_LIST_DIR}/cmake/modules/Findzlib-ng.cmake"
"${CMAKE_CURRENT_LIST_DIR}/cmake/modules/Findmodp_b64.cmake"
DESTINATION ${CMAKE_INSTALL_DATADIR}/cesium-native/cmake)

View File

@ -0,0 +1,225 @@
#pragma once
#include <CesiumAsync/IAssetRequest.h>
#include <CesiumGeospatial/Ellipsoid.h>
#include <CesiumRasterOverlays/Library.h>
#include <CesiumRasterOverlays/RasterOverlay.h>
#include <functional>
#include <memory>
namespace CesiumRasterOverlays {
/**
* @brief Supported values for @ref AzureMapsSessionParameters::tilesetId. See
* the [official
* documentation](https://learn.microsoft.com/en-us/rest/api/maps/render/get-map-tile?tabs=HTTP#tilesetid)
* for all standard values. Note that some tileset IDs return vector data, which
* is currently unsupported.
*/
struct AzureMapsTilesetId {
/**
* @brief All layers with Azure Maps' main style.
*/
inline static const std::string baseRoad = "microsoft.base.road";
/**
* @brief All layers with Azure Maps' dark grey style.
*/
inline static const std::string baseDarkGrey = "microsoft.base.darkgrey";
/**
* @brief Label data in Azure Maps' main style.
*/
inline static const std::string baseLabelsRoad = "microsoft.base.labels.road";
/**
* @brief Label data in Azure Maps' dark grey style.
*/
inline static const std::string baseLabelsDarkGrey =
"microsoft.base.labels.darkgrey";
/**
* @brief Road, boundary and label data in Azure Maps' main style.
*/
inline static const std::string baseHybridRoad = "microsoft.base.hybrid.road";
/**
* @brief Road, boundary and label data in Azure Maps' dark grey style.
*/
inline static const std::string baseHybridDarkGrey =
"microsoft.base.hybrid.darkgrey";
/**
* @brief A combination of satellite or aerial imagery. Only available in S1
* and G2 pricing SKU.
*/
inline static const std::string imagery = "microsoft.imagery";
/**
* @brief Shaded relief and terra layers.
*/
inline static const std::string terra = "microsoft.terra.main";
/**
* @brief Weather radar tiles. Latest weather radar images including areas of
* rain, snow, ice and mixed conditions. For more information on the Azure
* Maps Weather service coverage, see [Azure Maps weather services
* coverage](https://learn.microsoft.com/en-us/azure/azure-maps/weather-coverage).
* For more information on Radar data, see [Weather services in Azure
* Maps](https://learn.microsoft.com/en-us/azure/azure-maps/weather-services-concepts#radar-images).
*/
inline static const std::string weatherRadar = "microsoft.weather.radar.main";
/**
* @brief Weather infrared tiles. Latest Infrared Satellite images show
* clouds by their temperature. For more information on the Azure Maps Weather
* service coverage, see [Azure Maps weather services
* coverage](https://learn.microsoft.com/en-us/azure/azure-maps/weather-coverage).
* For more information on Radar data, see [Weather services in Azure
* Maps](https://learn.microsoft.com/en-us/azure/azure-maps/weather-services-concepts#radar-images).
*/
inline static const std::string weatherInfrared =
"microsoft.weather.infrared.main";
/**
* @brief Absolute traffic tiles in Azure Maps' main style.
*/
inline static const std::string trafficAbsolute =
"microsoft.traffic.absolute.main";
/**
* @brief Relative traffic tiles in Azure Maps' main style.
*/
inline static const std::string trafficRelativeMain =
"microsoft.traffic.relative.main";
/**
* @brief Relative traffic tiles in Azure Maps' dark style.
*/
inline static const std::string trafficRelativeDark =
"microsoft.traffic.relative.dark";
/**
* @brief Delay traffic tiles in Azure Maps' main style.
*/
inline static const std::string trafficDelay = "microsoft.traffic.delay.main";
/**
* @brief Reduced traffic tiles in Azure Maps' main style.
*/
inline static const std::string trafficReduced =
"microsoft.traffic.reduced.main";
};
/**
* @brief Session parameters for an Azure Maps overlay.
*/
struct AzureMapsSessionParameters {
/**
* @brief The Azure Maps subscription key to use.
*/
std::string key;
/**
* @brief The version number of Azure Maps API.
*/
std::string apiVersion{"2024-04-01"};
/**
* @brief A tileset is a collection of raster or vector data broken up into a
* uniform grid of square tiles at preset zoom levels. Every tileset has a
* tilesetId to use when making requests. The tilesetId for tilesets created
* using [Azure Maps Creator](https://aka.ms/amcreator) are generated through
* the [Tileset Create
* API](https://learn.microsoft.com/en-us/rest/api/maps-creator/tileset).
*
* The supported ready-to-use tilesets supplied by Azure Maps are listed
* [here](https://learn.microsoft.com/en-us/rest/api/maps/render/get-map-tile?view=rest-maps-2025-01-01&tabs=HTTP#tilesetid).
*/
std::string tilesetId{AzureMapsTilesetId::baseRoad};
/**
* @brief The language in which search results should be returned. Should be
* one of the supported [IETF language
* tags](https://en.wikipedia.org/wiki/IETF_language_tag), case insensitive.
* When data in the specified language is not available for a specific field,
* default language is used.
*
* Refer to [Supported
* Languages](https://learn.microsoft.com/en-us/azure/azure-maps/supported-languages)
* for details.
*/
std::string language{"en-US"};
/**
* @brief The view (also called the "user region") of a certain country/region
* for which to show the correct maps. Quoting the Azure Maps' documentation:
*
* "Different countries/regions have different views of such regions, and the
* View parameter allows your application to comply with the view required by
* the country/region your application will be serving. By default, the View
* parameter is set to "Unified" even if you haven't defined it in the
* request. It is your responsibility to determine the location of your users,
* and then set the View parameter correctly for that location. Alternatively,
* you have the option to set 'View=Auto', which will return the map data
* based on the IP address of the request. The View parameter in Azure Maps
* must be used in compliance with applicable laws, including those regarding
* mapping, of the country/region where maps, images and other data and third
* party content that you are authorized to access via Azure Maps is made
* available. Example: view=IN."
*
* Refer to [Supported Views](https://aka.ms/AzureMapsLocalizationViews) for
* details and to see the available Views.
*/
std::string view{"US"};
/**
* @brief Whether or not the @ref AzureMapsRasterOverlay should show the
* Azure Maps logo.
*
* Azure requires the logo to be shown, so setting this to false is only
* valid when something else is already showing the logo.
*/
bool showLogo = true;
/**
* @brief The base URL for the Azure Maps API.
*/
std::string apiBaseUrl{"https://atlas.microsoft.com/"};
};
/**
* @brief A @ref RasterOverlay that retrieves imagery from the Azure Maps API.
*/
class CESIUMRASTEROVERLAYS_API AzureMapsRasterOverlay final
: public RasterOverlay {
public:
/**
* @brief Creates a new instance.
*
* @param name The user-given name of this overlay layer.
* @param sessionParameters The session parameters for this overlay.
* @param overlayOptions The @ref RasterOverlayOptions for this instance.
*/
AzureMapsRasterOverlay(
const std::string& name,
const AzureMapsSessionParameters& sessionParameters,
const RasterOverlayOptions& overlayOptions = {});
virtual ~AzureMapsRasterOverlay() override;
virtual CesiumAsync::Future<CreateTileProviderResult> createTileProvider(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CesiumUtility::CreditSystem>& pCreditSystem,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner)
const override;
private:
AzureMapsSessionParameters _sessionParameters;
};
} // namespace CesiumRasterOverlays

View File

@ -1,7 +1,6 @@
#pragma once
#include <CesiumAsync/IAssetRequest.h>
#include <CesiumGeospatial/Ellipsoid.h>
#include <CesiumRasterOverlays/Library.h>
#include <CesiumRasterOverlays/RasterOverlay.h>
@ -116,20 +115,6 @@ public:
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner)
const override;
/**
* @brief Refresh a previously-created tile provider using a new key.
*
* Calling this method on a tile provider that was not created by this \ref
* BingMapsRasterOverlay will lead to undefined behavior.
*
* @param pProvider The previously-created tile provider.
* @param newKey The new key to use.
*/
CesiumAsync::Future<void> refreshTileProviderWithNewKey(
const CesiumUtility::IntrusivePointer<RasterOverlayTileProvider>&
pProvider,
const std::string& newKey);
private:
static const std::string BING_LOGO_HTML;

View File

@ -252,8 +252,8 @@ struct GoogleMapTilesNewSessionParameters {
};
/**
* @brief A `RasterOverlay` that retrieves imagery from the [Google Maps Tiles
* API](https://developers.google.com/maps/documentation/tile).
* @brief A @ref RasterOverlay that retrieves imagery from the [Google Maps
* Tiles API](https://developers.google.com/maps/documentation/tile).
*/
class CESIUMRASTEROVERLAYS_API GoogleMapTilesRasterOverlay
: public RasterOverlay {

View File

@ -126,11 +126,10 @@ private:
};
/** @private */
struct Bing {
std::string key;
struct Azure2D {
std::string url;
std::string mapStyle;
std::string culture;
std::string tilesetId;
std::string key;
};
/** @private */
@ -144,7 +143,21 @@ private:
uint32_t tileHeight;
};
std::variant<std::monostate, TileMapService, Bing, Google2D> options{};
/** @private */
struct Bing {
std::string key;
std::string url;
std::string mapStyle;
std::string culture;
};
std::variant<std::monostate, TileMapService, Azure2D, Google2D, Bing>
options{};
void parseAzure2DOptions(const rapidjson::Document& ionResponse);
void parseGoogle2DOptions(const rapidjson::Document& ionResponse);
void parseBingOptions(const rapidjson::Document& ionResponse);
void parseTileMapServiceOptions(const rapidjson::Document& ionResponse);
};
static std::unordered_map<std::string, ExternalAssetEndpoint> endpointCache;

View File

@ -115,27 +115,6 @@ public:
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner)
const override;
/**
* @brief Refresh a previously-created tile provider using a new URL and
* request headers. This is primarily useful to refresh an access token after
* it expires.
*
* Calling this method on a tile provider that was not created by this \ref
* TileMapServiceRasterOverlay will lead to undefined behavior.
*
* @param pProvider The previously-created tile provider.
* @param newUrl The new URL to use. If `std::nullopt`, the existing URL is
* unchanged.
* @param newHeaders The new request headers to use. If `std::nullopt`, the
* existing headers are unchanged.
*/
CesiumAsync::Future<void> refreshTileProviderWithNewUrlAndHeaders(
const CesiumUtility::IntrusivePointer<RasterOverlayTileProvider>&
pProvider,
const std::optional<std::string>& newUrl,
const std::optional<std::vector<CesiumAsync::IAssetAccessor::THeader>>&
newHeaders);
private:
std::string _url;
std::vector<CesiumAsync::IAssetAccessor::THeader> _headers;

View File

@ -0,0 +1,632 @@
#include <CesiumAsync/Future.h>
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumGeometry/QuadtreeTileID.h>
#include <CesiumGeometry/Rectangle.h>
#include <CesiumGeospatial/Projection.h>
#include <CesiumGeospatial/WebMercatorProjection.h>
#include <CesiumJsonReader/JsonObjectJsonHandler.h>
#include <CesiumJsonReader/JsonReader.h>
#include <CesiumRasterOverlays/AzureMapsRasterOverlay.h>
#include <CesiumRasterOverlays/QuadtreeRasterOverlayTileProvider.h>
#include <CesiumRasterOverlays/RasterOverlay.h>
#include <CesiumRasterOverlays/RasterOverlayLoadFailureDetails.h>
#include <CesiumRasterOverlays/RasterOverlayTile.h>
#include <CesiumRasterOverlays/RasterOverlayTileProvider.h>
#include <CesiumUtility/CreditReferencer.h>
#include <CesiumUtility/CreditSystem.h>
#include <CesiumUtility/ErrorList.h>
#include <CesiumUtility/IntrusivePointer.h>
#include <CesiumUtility/JsonHelpers.h>
#include <CesiumUtility/JsonValue.h>
#include <CesiumUtility/Uri.h>
#include <CesiumUtility/joinToString.h>
#include <fmt/format.h>
#include <nonstd/expected.hpp>
#include <rapidjson/document.h>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
using namespace CesiumAsync;
using namespace CesiumGeometry;
using namespace CesiumGeospatial;
using namespace CesiumJsonReader;
using namespace CesiumUtility;
namespace {
std::unordered_map<std::string, std::vector<std::byte>> sessionCache;
} // namespace
namespace CesiumRasterOverlays {
const std::string AZURE_MAPS_LOGO_HTML =
"<img alt=\"Microsoft Azure\" "
"src=\"data:image/"
"png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAAM0AAAAnCAYAAACsY8yNAAAAAXNSR0IB2cksfwAAAAlwSFlzAA"
"ALEwAACxMBAJqcGAAACaFJREFUeJztWweMVUUU3QUUUURBUIqBFZBFUbGgQQFDlCi2KAr2Hl2w"
"oRRFxQgaVDYrxIINBCyASrHFFRCU2KMIlgixF4wVdBELosD1HuaO7/757/333vJ1/"
"8c5ycnfd+fOvGl35s6dtyUlHh4eHh4eHh4eHh4eHh4e/2/8ckL7mWm4pl/"
"7GT+d2q7lmLklZ4x6rmRmGt4wv+"
"TYum6vh8dmgw2B0pCNZh0bze5sNJVsCJSSV9Z1ez08NhveaDw8UsIbjYdHSnij2TJBRE2ZBzB7"
"MTvVdX22KGzpRsMTphVzMLPDf/"
"3uugK39RLmV8yNZPBVXddpi0IhGQ0P7vHMvxRXMJvFtYF1Zjr5hom8DfM7mTjfM1vnq98KFdzG"
"E5l/Uia+yVPZw1Uf/46dLB/"
"lFh0KzGj6UTb6x+"
"RpK4OocaWk7cfcILL1zL3z2XeFBm5fPebr0l7sMmczd2R2ZjZg7sMcz5xci7KR/y2nnyv/"
"jXYUPIrAaGZjMuTIMyEkjzWarZnXM99g3oTnfPdfIYHMOeYb6YP3nbRy5jpJW1CLsrsx10p+"
"uxAtYzbKXwuKBAVsNHaA4FZ1idBvwvxC9Fa7RvN/"
"A7e7BfMH6YNFTloX1T+1MZp7Vf5n5RduYI/8taBIUMBGM40C33xchP4pauW71TUa/"
"m3IPJNZwTyN2cDJD5ejB3MM80HmI8xK5uFWV95RIb9wf/"
"qKzlXMrVRZcH2GMCcxHybjBp3D3C6i7nsyR4rudCmzd4jetlL3KtGdLO/"
"e39E7nzmMuUb64EOpt+Vo1T/LlbxrrjGRsrdn/"
"ip5n8O7tQFF5NnLeX8onT4GG4a036adpOTHKDl22AOZN0o/"
"7qr0OjCHMu8m45VcziyLa3NOFLDRnM58W/"
"7+lFnf0S1lzpH0xWQiRhbWaODPfy2yz0lNYP67GXMq82fKxk/"
"MtqL3ocg+Zl6kJs+m8pjbMK+jIOCggXPUIuYhTt37q3ppzHf0Dma+TNkHe2AVGSPdVnR/"
"C9FJgthdmTLH5Swyi80KeYZHsFNInssTvHut08dAU6ecVirtHSVfoOSXkvFILHqKDuqqo4gWH5"
"EywNQoYKPBSjJcPR/q6JZRMOHRaYmNhswONIcy8RnzRSkTE6G9M6BrKXAZ/"
"ymPeZdTzifMJZQ50b8lWd3IBC5+FDkCGLOYj4lsgWpfRzLGa/"
"EHmbPZCiXDDlsl+v+m0cwSXdSxlchuUmVUhOTBmKwLoZ3A+"
"B3o9DFQG6OpcdrUk8wCZwNEcN3hXt5PwbigLgfGtT0UP/"
"ffbUoarh5QNnHVGW13GVtdOmDMvJIpKXlUzOC4RrM7BSs7ttd6Sne8yDGRd6Z0RoMVer3IsRL1"
"sGWTcQcGosyQAYXu4TKQuDTcV5WD95yo6gejfkLlnSjyQUo2QunDkHvL3whgLFJ69zCbS1p9yt"
"zxYOS7Sb3bMVeK/CUyRm3ZTZX3gpL/"
"42JGjElz9S6cZUpFrscm0RmJ9Y6mIBhRTRKYoc03Giwu8Exaow/JuI/"
"2bIdFpovK14OCxfahJPXOxvSaNmlZb9qqBmvm1G/"
"y+5OlrVOycUynukZTSsGhEx1TLnqNKPDdbxVZGqNZLjIM+"
"pExdbIDmnXo5ecH1DvDVttmFKxsG6Q+2m2ZSM45S/IhVG6NETtXYycd/"
"VKpyhkk8rwHAihzRznPqcNLqm05vzogs4jYuqFPy1Ta5hrNSCdPhUrrF1KX+ZJWy/"
"ur6TWrU3Ils+"
"Nfz5SO3ji3pCYlL4vp2AyjERlWELuljxDZafKMVaubyBIZjchsyPQVCpm0Tp3sgGLFaqLk2A0+"
"UPXoHJF/iapXdzIBALvSbZA6XEiyk0ies1SeaRHlHq107hVZXo2GzOK0TPLA/"
"cMq3UbxFlXmhBzlNCZzdWD7qndEHwO1MRrXddeRPgRZ7nP4iaRtTNIP2ZheQyn5J7PT+"
"uqSSprH9UvHVGcakbWkYHK+SuYQ+"
"qQ8v8fcRvSSGk250ns0rnvUgOLwuLWS70DGXQPgM7eJyD9Pva8PmRUad0f6QhaLAi4Ou0qewSo"
"tKnJ4kNKZLbJ8Gw1cOrtTor5fSj9a6sM3AiVRkcJrVXun5OhjoDZG08XJM5sSIkk/"
"ZKPAjUbkQ0WGAcQtt3VdhimdpEbTUelVx3UPRRsNVs8vJA2rcFlE/"
"lfU+7oreVcyQYRfVToCBjsxL1ay+yLKPczVofwbzXhKDhh+"
"35AysDtZg3mNHFfT6WPANZrWKi2p0Tyi0hBMGhzFJP2QjeIwmnYUrHir5BeTbQ+"
"lk9RocGC2h1FM+"
"rKYOkUZDe5sXo2ZMA1VfTFxWjjpONTDZXtX1f08ynS9MNHCzj1DlM4lIktqNAtztVn0cR6zrhm"
"CLaMjOEOVe49TBiKFb0oaIm/"
"7RrxruSqjzEk7QKUlNRp93su4z8oPisBoJG0+"
"ZQIumo6mpQkEVCtdBBrcS08cxJvK36FGI2nnqnKwo7RQaTAqHTKfJvJDyPkIlZ+"
"vVnowBkQD7eSHsZ3s6MMA7C6HxaOTyHMZTWf1jo9yjYPoX0DBjj43h14zpYdzXzuRY0F4XL0z8"
"jzLaQuV3mAnbZJKS2o0fVXapkhhyDsRFcy6X0qG4jEaHI713UdPJz2N0eDSUF9GYqWHK4KV82n"
"mL5R9TxNmNLjY1KskJnKVlIOzjJ1M2G06Sx58BQA3bILUeQQFB1PsgDZCOFCVC/"
"cPExBnodskvwVC8TYMnMtosMvqeyYckPElROglH5mbf4uLY8btedHDjjtcZOeq9q+R+"
"s8IIXbj8ykI9qDvx5H5uuExyryYTGo08CYWq/"
"S3pJ8RVELIfyqZYEzo51nxKB6jwQS1W33WSknpvwg4lII7DRf47D3WaCQddyTLIsoBECzoo/"
"RHRughkjbcKRufhYR9DWD1MbkaKf1Io5F0HMjd2/GsMSETfNFfh+f8lwrKNPClInO/PI/"
"Cprsi5lMRbbxNPScyGtUXb1M00K7yXO2KRmEZDb5XqhJmhXBZdpykZa2OZNwem7e3yBAyvU5k1"
"1D2TlEucqyqCA3j/PAQmW+h7MXbVZIfv/"
"Xd94oOImmYODiALpaycLEJt6tVyDtvJuNuLiXj1iEMegQ5X3OTcfGwK95OZjXHJMDdyB1kXBD3"
"0yIEJ0ZJfQeF1BP9MVTKQh2rKfx7t71VX14R1mZHv6XSHyvvqUpI28/oQ5zn8A0gvta4U/"
"qkqdIdot5ZoeS7RNQLruOlUh7GBTvOM9L/"
"vaLGMx4FZDQeHsUBbzQeHinhjcbDIyW80Xh4pIQ3Gg8PDw8PDw8PDw8PDw8PDw8PDw8Pj9T4G6"
"U/URjRT3IrAAAAAElFTkSuQmCC\"/>";
namespace {
Rectangle createRectangle(
const CesiumUtility::IntrusivePointer<const RasterOverlay>& pOwner) {
return WebMercatorProjection::computeMaximumProjectedRectangle(
pOwner->getOptions().ellipsoid);
}
QuadtreeTilingScheme createTilingScheme(
const CesiumUtility::IntrusivePointer<const RasterOverlay>& pOwner) {
return QuadtreeTilingScheme(createRectangle(pOwner), 1, 1);
}
} // namespace
class AzureMapsRasterOverlayTileProvider final
: public QuadtreeRasterOverlayTileProvider {
public:
AzureMapsRasterOverlayTileProvider(
const IntrusivePointer<const RasterOverlay>& pOwner,
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
std::optional<CesiumUtility::Credit> credit,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
const std::string& baseUrl,
const std::string& apiVersion,
const std::string& tilesetId,
const std::string& key,
const std::string& tileEndpoint,
uint32_t minimumLevel,
uint32_t maximumLevel,
uint32_t imageSize,
bool showLogo);
virtual ~AzureMapsRasterOverlayTileProvider() = default;
void update(const AzureMapsRasterOverlayTileProvider& newProvider) {
this->_baseUrl = newProvider._baseUrl;
this->_apiVersion = newProvider._apiVersion;
this->_tilesetId = newProvider._tilesetId;
this->_key = newProvider._key;
this->_tileEndpoint = newProvider._tileEndpoint;
}
CesiumAsync::Future<void> loadCredits();
virtual void addCredits(
CesiumUtility::CreditReferencer& creditReferencer) noexcept override;
protected:
virtual CesiumAsync::Future<LoadedRasterOverlayImage> loadQuadtreeTileImage(
const CesiumGeometry::QuadtreeTileID& tileID) const override;
private:
std::string _baseUrl;
std::string _apiVersion;
std::string _tilesetId;
std::string _key;
std::string _tileEndpoint;
std::optional<Credit> _azureCredit;
std::vector<Credit> _credits;
bool _showCreditsOnScreen;
};
AzureMapsRasterOverlay::AzureMapsRasterOverlay(
const std::string& name,
const AzureMapsSessionParameters& sessionParameters,
const RasterOverlayOptions& overlayOptions)
: RasterOverlay(name, overlayOptions),
_sessionParameters(sessionParameters) {
Uri baseUrl(this->_sessionParameters.apiBaseUrl);
baseUrl.ensureTrailingSlash();
this->_sessionParameters.apiBaseUrl = baseUrl.toString();
}
AzureMapsRasterOverlay::~AzureMapsRasterOverlay() = default;
Future<RasterOverlay::CreateTileProviderResult>
AzureMapsRasterOverlay::createTileProvider(
const AsyncSystem& asyncSystem,
const std::shared_ptr<IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
IntrusivePointer<const RasterOverlay> pOwner) const {
Uri tilesetUri(this->_sessionParameters.apiBaseUrl, "map/tileset");
UriQuery tilesetQuery(tilesetUri);
tilesetQuery.setValue("api-version", this->_sessionParameters.apiVersion);
tilesetQuery.setValue("tilesetId", this->_sessionParameters.tilesetId);
tilesetQuery.setValue("subscription-key", this->_sessionParameters.key);
tilesetQuery.setValue("language", this->_sessionParameters.language);
tilesetQuery.setValue("view", this->_sessionParameters.view);
tilesetUri.setQuery(tilesetQuery.toQueryString());
std::string tilesetUrl = std::string(tilesetUri.toString());
pOwner = pOwner ? pOwner : this;
auto handleResponse =
[pOwner,
asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
sessionParameters = this->_sessionParameters](
const std::shared_ptr<IAssetRequest>& pRequest,
const std::span<const std::byte>& data) -> CreateTileProviderResult {
JsonObjectJsonHandler handler{};
ReadJsonResult<JsonValue> response = JsonReader::readJson(data, handler);
if (!response.value) {
ErrorList errorList{};
errorList.errors = std::move(response.errors);
errorList.warnings = std::move(response.warnings);
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = errorList.format("Failed to parse response from Azure "
"Maps tileset API service:")});
}
if (!response.value->isObject()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = "Response from Azure Maps tileset API service was not a "
"JSON object."});
}
const JsonValue::Object& responseObject = response.value->getObject();
uint32_t tileSize = 256u;
uint32_t minimumLevel = 0u;
uint32_t maximumLevel = 22u;
// "tileSize" is an optional enum property that is delivered as a string.
std::string tileSizeEnum = "256";
auto it = responseObject.find("tileSize");
if (it != responseObject.end() && it->second.isString()) {
tileSizeEnum = it->second.getString();
}
try {
tileSize = static_cast<uint32_t>(std::stoul(tileSizeEnum));
} catch (std::exception&) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = "Response from Azure Maps tileset API service did not "
"contain a valid "
"'tileSize' property."});
}
it = responseObject.find("minzoom");
if (it != responseObject.end() && it->second.isNumber()) {
minimumLevel = it->second.getSafeNumberOrDefault<uint32_t>(0);
} else if (it != responseObject.end()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = "Response from Azure Maps tileset API service did not "
"contain a valid "
"'minzoom' property."});
}
it = responseObject.find("maxzoom");
if (it != responseObject.end() && it->second.isNumber()) {
maximumLevel = it->second.getSafeNumberOrDefault<uint32_t>(0);
} else if (it != responseObject.end()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = "Response from Azure Maps tileset API service did not "
"contain a valid "
"'maxzoom' property."});
}
std::vector<std::string> tiles;
it = responseObject.find("tiles");
if (it != responseObject.end()) {
tiles = it->second.getArrayOfStrings(std::string());
}
if (tiles.empty()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = "Response from Azure Maps tileset API service did not "
"contain a valid "
"'tiles' property."});
}
std::string tileEndpoint;
for (const std::string& endpoint : tiles) {
// Azure Maps documentation: "If multiple endpoints are specified, clients
// may use any combination of endpoints. All endpoints MUST return the
// same content for the same URL."
// This just picks the first non-empty string endpoint.
if (!endpoint.empty()) {
tileEndpoint = endpoint;
break;
}
}
if (tileEndpoint.empty()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::TileProvider,
pRequest,
"Azure Maps returned no valid endpoints for the given tilesetId."});
}
std::optional<Credit> topLevelCredit;
it = responseObject.find("attribution");
if (it != responseObject.end() && it->second.isString()) {
topLevelCredit = pCreditSystem->createCredit(
it->second.getString(),
pOwner->getOptions().showCreditsOnScreen);
}
auto* pProvider = new AzureMapsRasterOverlayTileProvider(
pOwner,
asyncSystem,
pAssetAccessor,
pCreditSystem,
topLevelCredit,
pPrepareRendererResources,
pLogger,
sessionParameters.apiBaseUrl,
sessionParameters.apiVersion,
sessionParameters.tilesetId,
sessionParameters.key,
tileEndpoint,
minimumLevel,
maximumLevel,
tileSize,
sessionParameters.showLogo);
// Start loading credits, but don't wait for the load to finish.
pProvider->loadCredits();
return pProvider;
};
auto cacheResultIt = sessionCache.find(tilesetUrl);
if (cacheResultIt != sessionCache.end()) {
return asyncSystem.createResolvedFuture(
handleResponse(nullptr, std::span<std::byte>(cacheResultIt->second)));
}
return pAssetAccessor->get(asyncSystem, tilesetUrl)
.thenInMainThread(
[tilesetUrl,
handleResponse](std::shared_ptr<IAssetRequest>&& pRequest)
-> CreateTileProviderResult {
const IAssetResponse* pResponse = pRequest->response();
if (!pResponse) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::TileProvider,
pRequest,
"No response received from Azure Maps imagery tileset "
"service."});
}
if (pResponse->statusCode() < 200 ||
pResponse->statusCode() >= 300) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
.type = RasterOverlayLoadType::TileProvider,
.pRequest = pRequest,
.message = fmt::format(
"Azure Maps API tileset request service returned "
"HTTP status code {}.",
pResponse->statusCode())});
}
CreateTileProviderResult handleResponseResult =
handleResponse(pRequest, pResponse->data());
// If the response successfully created a tile provider, cache it.
if (handleResponseResult) {
sessionCache[tilesetUrl] = std::vector<std::byte>(
pResponse->data().begin(),
pResponse->data().end());
}
return handleResponseResult;
});
}
namespace {
CesiumAsync::Future<rapidjson::Document> fetchAttributionData(
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<spdlog::logger>& pLogger,
const std::string& apiBaseUrl,
const std::string& apiVersion,
const std::string& tilesetId,
const std::string& key,
uint32_t zoom,
const Rectangle& rectangleDegrees) {
std::vector<std::string> bounds(4);
bounds[0] = std::to_string(rectangleDegrees.minimumX); // west
bounds[1] = std::to_string(rectangleDegrees.minimumY); // south
bounds[2] = std::to_string(rectangleDegrees.maximumX); // east
bounds[3] = std::to_string(rectangleDegrees.maximumY); // north
Uri attributionUri(apiBaseUrl, "map/attribution", true);
UriQuery query(attributionUri);
query.setValue("api-version", apiVersion);
query.setValue("subscription-key", key);
query.setValue("tilesetId", tilesetId);
query.setValue("zoom", std::to_string(zoom));
query.setValue("bounds", joinToString(bounds, ","));
attributionUri.setQuery(query.toQueryString());
return pAssetAccessor
->get(asyncSystem, std::string(attributionUri.toString()))
.thenInMainThread(
[pLogger](const std::shared_ptr<IAssetRequest>& pRequest)
-> rapidjson::Document {
const IAssetResponse* pResponse = pRequest->response();
rapidjson::Document document;
if (!pResponse) {
SPDLOG_LOGGER_ERROR(
pLogger,
"No response received from Azure Maps API attribution "
"service.");
return document;
}
if (pResponse->statusCode() < 200 ||
pResponse->statusCode() >= 300) {
SPDLOG_LOGGER_ERROR(
pLogger,
"Error response {} received from Azure Maps API attribution "
"service URL {}.",
pResponse->statusCode(),
pRequest->url());
return document;
}
document.Parse(
reinterpret_cast<const char*>(pResponse->data().data()),
pResponse->data().size());
if (document.HasParseError()) {
SPDLOG_LOGGER_ERROR(
pLogger,
"Error when parsing Azure Maps API attribution service JSON "
"from URL {}, error code {} at byte offset {}.",
pRequest->url(),
document.GetParseError(),
document.GetErrorOffset());
}
return document;
});
}
} // namespace
AzureMapsRasterOverlayTileProvider::AzureMapsRasterOverlayTileProvider(
const IntrusivePointer<const RasterOverlay>& pOwner,
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
std::optional<CesiumUtility::Credit> credit,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
const std::string& baseUrl,
const std::string& apiVersion,
const std::string& tilesetId,
const std::string& key,
const std::string& tileEndpoint,
uint32_t minimumLevel,
uint32_t maximumLevel,
uint32_t imageSize,
bool showLogo)
: QuadtreeRasterOverlayTileProvider(
pOwner,
asyncSystem,
pAssetAccessor,
pCreditSystem,
credit,
pPrepareRendererResources,
pLogger,
WebMercatorProjection(pOwner->getOptions().ellipsoid),
createTilingScheme(pOwner),
createRectangle(pOwner),
minimumLevel,
maximumLevel,
imageSize,
imageSize),
_baseUrl(baseUrl),
_apiVersion(apiVersion),
_tilesetId(tilesetId),
_key(key),
_tileEndpoint(tileEndpoint),
_azureCredit(),
_credits(),
_showCreditsOnScreen(pOwner->getOptions().showCreditsOnScreen) {
if (pCreditSystem && showLogo) {
this->_azureCredit =
pCreditSystem->createCredit(AZURE_MAPS_LOGO_HTML, true);
}
}
CesiumAsync::Future<LoadedRasterOverlayImage>
AzureMapsRasterOverlayTileProvider::loadQuadtreeTileImage(
const CesiumGeometry::QuadtreeTileID& tileID) const {
Uri uri(CesiumUtility::Uri::substituteTemplateParameters(
this->_tileEndpoint,
[this, &tileID](const std::string& key) {
if (key == "z") {
return std::to_string(tileID.level);
}
if (key == "x") {
return std::to_string(tileID.x);
}
if (key == "y") {
return std::to_string(
tileID.computeInvertedY(this->getTilingScheme()));
}
return key;
}));
UriQuery query(uri);
query.setValue("subscription-key", this->_key);
uri.setQuery(query.toQueryString());
std::string url = std::string(uri.toString());
LoadTileImageFromUrlOptions options;
options.allowEmptyImages = true;
options.moreDetailAvailable = tileID.level < this->getMaximumLevel();
options.rectangle = this->getTilingScheme().tileToRectangle(tileID);
return this->loadTileImageFromUrl(url, {}, std::move(options));
}
Future<void> AzureMapsRasterOverlayTileProvider::loadCredits() {
const uint32_t maximumZoomLevel = this->getMaximumLevel();
IntrusivePointer thiz = this;
std::vector<Future<std::vector<std::string>>> creditFutures;
creditFutures.reserve(maximumZoomLevel + 1);
for (size_t i = 0; i <= maximumZoomLevel; ++i) {
creditFutures.emplace_back(
fetchAttributionData(
this->getAssetAccessor(),
this->getAsyncSystem(),
this->getLogger(),
this->_baseUrl,
this->_apiVersion,
this->_tilesetId,
this->_key,
uint32_t(i),
Rectangle(-180.0, -90.0, 180.0, 90.0))
.thenInMainThread([](rapidjson::Document&& document) {
if (document.HasParseError() || !document.IsObject()) {
return std::vector<std::string>();
}
return JsonHelpers::getStrings(document, "copyrights");
}));
}
return this->getAsyncSystem()
.all(std::move(creditFutures))
.thenInMainThread(
[thiz](std::vector<std::vector<std::string>>&& results) {
std::set<std::string> uniqueCredits;
for (size_t i = 0; i < results.size(); i++) {
const std::vector<std::string>& credits = results[i];
for (size_t j = 0; j < credits.size(); j++) {
if (!credits[j].empty()) {
uniqueCredits.insert(credits[j]);
}
}
}
for (const std::string& credit : uniqueCredits) {
thiz->_credits.emplace_back(thiz->getCreditSystem()->createCredit(
credit,
thiz->_showCreditsOnScreen));
}
});
}
void AzureMapsRasterOverlayTileProvider::addCredits(
CesiumUtility::CreditReferencer& creditReferencer) noexcept {
QuadtreeRasterOverlayTileProvider::addCredits(creditReferencer);
if (this->_azureCredit) {
creditReferencer.addCreditReference(*this->_azureCredit);
}
for (const Credit& credit : this->_credits) {
creditReferencer.addCreditReference(credit);
}
}
} // namespace CesiumRasterOverlays

View File

@ -2,7 +2,7 @@
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumGeometry/QuadtreeTileID.h>
#include <CesiumGeospatial/Ellipsoid.h>
#include <CesiumGeometry/Rectangle.h>
#include <CesiumGeospatial/GlobeRectangle.h>
#include <CesiumGeospatial/Projection.h>
#include <CesiumGeospatial/WebMercatorProjection.h>
@ -22,7 +22,6 @@
#include <rapidjson/document.h>
#include <rapidjson/pointer.h>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
#include <cstddef>
#include <cstdint>
@ -94,6 +93,19 @@ const std::string BingMapsRasterOverlay::BING_LOGO_HTML =
"OXfbBoeDOo8wHpy8lKpvoafRoG6YgXFYKP4GSj63gtwWfhHzl7Skq9JTshAAAAAElFTkSuQmCC"
"\" title=\"Bing Imagery\"/></a>";
namespace {
Rectangle createRectangle(
const CesiumUtility::IntrusivePointer<const RasterOverlay>& pOwner) {
return WebMercatorProjection::computeMaximumProjectedRectangle(
pOwner->getOptions().ellipsoid);
}
QuadtreeTilingScheme createTilingScheme(
const CesiumUtility::IntrusivePointer<const RasterOverlay>& pOwner) {
return QuadtreeTilingScheme(createRectangle(pOwner), 2, 2);
}
} // namespace
class BingMapsTileProvider final : public QuadtreeRasterOverlayTileProvider {
public:
BingMapsTileProvider(
@ -113,8 +125,7 @@ public:
uint32_t height,
uint32_t minimumLevel,
uint32_t maximumLevel,
const std::string& culture,
const CesiumGeospatial::Ellipsoid& ellipsoid)
const std::string& culture)
: QuadtreeRasterOverlayTileProvider(
pOwner,
asyncSystem,
@ -123,13 +134,9 @@ public:
bingCredit,
pPrepareRendererResources,
pLogger,
WebMercatorProjection(ellipsoid),
QuadtreeTilingScheme(
WebMercatorProjection::computeMaximumProjectedRectangle(
ellipsoid),
2,
2),
WebMercatorProjection::computeMaximumProjectedRectangle(ellipsoid),
WebMercatorProjection(pOwner->getOptions().ellipsoid),
createTilingScheme(pOwner),
createRectangle(pOwner),
minimumLevel,
maximumLevel,
width,
@ -379,8 +386,6 @@ BingMapsRasterOverlay::createTileProvider(
pOwner = pOwner ? pOwner : this;
const CesiumGeospatial::Ellipsoid& ellipsoid = this->getOptions().ellipsoid;
auto handleResponse =
[pOwner,
asyncSystem,
@ -388,7 +393,6 @@ BingMapsRasterOverlay::createTileProvider(
pCreditSystem,
pPrepareRendererResources,
pLogger,
ellipsoid,
baseUrl = this->_url,
culture = this->_culture](
const std::shared_ptr<IAssetRequest>& pRequest,
@ -470,8 +474,7 @@ BingMapsRasterOverlay::createTileProvider(
height,
0,
maximumLevel,
culture,
ellipsoid);
culture);
};
auto cacheResultIt = sessionCache.find(metadataUrl);
@ -509,55 +512,4 @@ BingMapsRasterOverlay::createTileProvider(
});
}
Future<void> BingMapsRasterOverlay::refreshTileProviderWithNewKey(
const IntrusivePointer<RasterOverlayTileProvider>& pProvider,
const std::string& newKey) {
this->_key = newKey;
return this
->createTileProvider(
pProvider->getAsyncSystem(),
pProvider->getAssetAccessor(),
pProvider->getCreditSystem(),
pProvider->getPrepareRendererResources(),
pProvider->getLogger(),
&pProvider->getOwner())
.thenInMainThread([pProvider](CreateTileProviderResult&& result) {
if (!result) {
SPDLOG_LOGGER_WARN(
pProvider->getLogger(),
"Could not refresh Bing Maps raster overlay with a new key: {}.",
result.error().message);
return;
}
// Use static_cast instead of dynamic_cast here to avoid the need for
// RTTI, and because we are certain of the type.
// NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast)
BingMapsTileProvider* pOldBing =
static_cast<BingMapsTileProvider*>(pProvider.get());
BingMapsTileProvider* pNewBing =
static_cast<BingMapsTileProvider*>(result->get());
// NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast)
if (pOldBing->getCoverageRectangle().getLowerLeft() !=
pNewBing->getCoverageRectangle().getLowerLeft() ||
pOldBing->getCoverageRectangle().getUpperRight() !=
pNewBing->getCoverageRectangle().getUpperRight() ||
pOldBing->getHeight() != pNewBing->getHeight() ||
pOldBing->getWidth() != pNewBing->getWidth() ||
pOldBing->getMinimumLevel() != pNewBing->getMinimumLevel() ||
pOldBing->getMaximumLevel() != pNewBing->getMaximumLevel() ||
pOldBing->getProjection() != pNewBing->getProjection()) {
SPDLOG_LOGGER_WARN(
pProvider->getLogger(),
"Could not refresh Bing Maps raster overlay with a new key "
"because some metadata properties changed unexpectedly upon "
"refresh.");
return;
}
pOldBing->update(*pNewBing);
});
}
} // namespace CesiumRasterOverlays

View File

@ -144,7 +144,6 @@ public:
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CesiumUtility::CreditSystem>& pCreditSystem,
std::optional<CesiumUtility::Credit> credit,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
@ -177,19 +176,12 @@ private:
std::string _key;
std::optional<Credit> _googleCredit;
std::optional<Credit> _credits;
bool _showCreditsOnScreen;
mutable QuadtreeRectangleAvailability _availableTiles;
mutable QuadtreeRectangleAvailability _availableAvailability;
};
void ensureTrailingSlash(std::string& url) {
Uri uri(url);
std::string_view path = uri.getPath();
if (path.empty() || path.back() != '/') {
uri.setPath(std::string(path) + '/');
url = uri.toString();
}
}
} // namespace
namespace CesiumRasterOverlays {
@ -201,7 +193,9 @@ GoogleMapTilesRasterOverlay::GoogleMapTilesRasterOverlay(
: RasterOverlay(name, overlayOptions),
_newSessionParameters(newSessionParameters),
_existingSession(std::nullopt) {
ensureTrailingSlash(this->_newSessionParameters->apiBaseUrl);
Uri baseUrl(this->_newSessionParameters->apiBaseUrl);
baseUrl.ensureTrailingSlash();
this->_newSessionParameters->apiBaseUrl = baseUrl.toString();
}
GoogleMapTilesRasterOverlay::GoogleMapTilesRasterOverlay(
@ -211,7 +205,9 @@ GoogleMapTilesRasterOverlay::GoogleMapTilesRasterOverlay(
: RasterOverlay(name, overlayOptions),
_newSessionParameters(std::nullopt),
_existingSession(existingSession) {
ensureTrailingSlash(this->_existingSession->apiBaseUrl);
Uri baseUrl(this->_existingSession->apiBaseUrl);
baseUrl.ensureTrailingSlash();
this->_existingSession->apiBaseUrl = baseUrl.toString();
}
Future<RasterOverlay::CreateTileProviderResult>
@ -242,7 +238,6 @@ GoogleMapTilesRasterOverlay::createTileProvider(
asyncSystem,
pAssetAccessor,
pCreditSystem,
std::nullopt,
pPrepareRendererResources,
pLogger,
session.apiBaseUrl,
@ -342,13 +337,14 @@ GoogleMapTilesRasterOverlay::createNewSession(
{{"Content-Type", "application/json"}},
requestPayloadBytes)
.thenInMainThread(
[this,
asyncSystem,
[asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
pOwner](std::shared_ptr<IAssetRequest>&& pRequest)
pOwner,
newSessionParameters = this->_newSessionParameters](
std::shared_ptr<IAssetRequest>&& pRequest)
-> Future<CreateTileProviderResult> {
const IAssetResponse* pResponse = pRequest->response();
if (!pResponse) {
@ -453,12 +449,11 @@ GoogleMapTilesRasterOverlay::createNewSession(
asyncSystem,
pAssetAccessor,
pCreditSystem,
std::nullopt,
pPrepareRendererResources,
pLogger,
this->_newSessionParameters->apiBaseUrl,
newSessionParameters->apiBaseUrl,
session,
this->_newSessionParameters->key,
newSessionParameters->key,
maximumZoomLevel,
static_cast<uint32_t>(tileWidth),
static_cast<uint32_t>(tileHeight),
@ -498,7 +493,6 @@ GoogleMapTilesRasterOverlayTileProvider::
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CesiumUtility::CreditSystem>& pCreditSystem,
std::optional<CesiumUtility::Credit> credit,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
@ -514,7 +508,7 @@ GoogleMapTilesRasterOverlayTileProvider::
asyncSystem,
pAssetAccessor,
pCreditSystem,
credit,
std::nullopt,
pPrepareRendererResources,
pLogger,
WebMercatorProjection(pOwner->getOptions().ellipsoid),
@ -529,6 +523,7 @@ GoogleMapTilesRasterOverlayTileProvider::
_key(key),
_googleCredit(),
_credits(),
_showCreditsOnScreen(pOwner->getOptions().showCreditsOnScreen),
_availableTiles(createTilingScheme(pOwner), maximumLevel),
_availableAvailability(createTilingScheme(pOwner), maximumLevel) {
if (pCreditSystem && showLogo) {
@ -977,7 +972,9 @@ Future<void> GoogleMapTilesRasterOverlayTileProvider::loadCredits() {
}
// Create a single credit from this giant string.
thiz->_credits = thiz->getCreditSystem()->createCredit(joined, false);
thiz->_credits = thiz->getCreditSystem()->createCredit(
joined,
thiz->_showCreditsOnScreen);
});
}

View File

@ -3,6 +3,7 @@
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumAsync/NetworkAssetDescriptor.h>
#include <CesiumAsync/SharedAssetDepot.h>
#include <CesiumRasterOverlays/AzureMapsRasterOverlay.h>
#include <CesiumRasterOverlays/BingMapsRasterOverlay.h>
#include <CesiumRasterOverlays/GoogleMapTilesRasterOverlay.h>
#include <CesiumRasterOverlays/IonRasterOverlay.h>
@ -315,6 +316,74 @@ IonRasterOverlay::createTileProvider(
return TileProvider::create(externals, descriptor, pOwner);
}
void IonRasterOverlay::ExternalAssetEndpoint::parseAzure2DOptions(
const rapidjson::Document& ionResponse) {
const auto optionsIt = ionResponse.FindMember("options");
if (optionsIt == ionResponse.MemberEnd() || !optionsIt->value.IsObject()) {
return;
}
const auto& ionOptions = optionsIt->value;
ExternalAssetEndpoint::Azure2D& azure2D =
this->options.emplace<ExternalAssetEndpoint::Azure2D>();
azure2D.url = JsonHelpers::getStringOrDefault(ionOptions, "url", "");
azure2D.tilesetId =
JsonHelpers::getStringOrDefault(ionOptions, "tilesetId", "");
azure2D.key =
JsonHelpers::getStringOrDefault(ionOptions, "subscription-key", "");
}
void IonRasterOverlay::ExternalAssetEndpoint::parseGoogle2DOptions(
const rapidjson::Document& ionResponse) {
const auto optionsIt = ionResponse.FindMember("options");
if (optionsIt == ionResponse.MemberEnd() || !optionsIt->value.IsObject()) {
return;
}
const auto& ionOptions = optionsIt->value;
ExternalAssetEndpoint::Google2D& google2D =
this->options.emplace<ExternalAssetEndpoint::Google2D>();
google2D.url =
JsonHelpers::getStringOrDefault(ionOptions, "url", google2D.url);
google2D.key = JsonHelpers::getStringOrDefault(ionOptions, "key", "");
google2D.session = JsonHelpers::getStringOrDefault(ionOptions, "session", "");
google2D.expiry = JsonHelpers::getStringOrDefault(ionOptions, "expiry", "");
google2D.tileWidth =
JsonHelpers::getUint32OrDefault(ionOptions, "tileWidth", 256);
google2D.tileHeight =
JsonHelpers::getUint32OrDefault(ionOptions, "tileHeight", 256);
google2D.imageFormat = JsonHelpers::getStringOrDefault(
ionOptions,
"imageFormat",
GoogleMapTilesImageFormat::jpeg);
}
void IonRasterOverlay::ExternalAssetEndpoint::parseBingOptions(
const rapidjson::Document& ionResponse) {
const auto optionsIt = ionResponse.FindMember("options");
if (optionsIt == ionResponse.MemberEnd() || !optionsIt->value.IsObject()) {
return;
}
const auto& ionOptions = optionsIt->value;
ExternalAssetEndpoint::Bing& bing =
this->options.emplace<ExternalAssetEndpoint::Bing>();
bing.url = JsonHelpers::getStringOrDefault(ionOptions, "url", "");
bing.key = JsonHelpers::getStringOrDefault(ionOptions, "key", "");
bing.mapStyle =
JsonHelpers::getStringOrDefault(ionOptions, "mapStyle", "AERIAL");
bing.culture = JsonHelpers::getStringOrDefault(ionOptions, "culture", "");
}
void IonRasterOverlay::ExternalAssetEndpoint::parseTileMapServiceOptions(
const rapidjson::Document& ionResponse) {
ExternalAssetEndpoint::TileMapService& tileMapService =
this->options.emplace<ExternalAssetEndpoint::TileMapService>();
tileMapService.url = JsonHelpers::getStringOrDefault(ionResponse, "url", "");
tileMapService.accessToken =
JsonHelpers::getStringOrDefault(ionResponse, "accessToken", "");
}
/* static */ CesiumUtility::IntrusivePointer<IonRasterOverlay::EndpointDepot>
IonRasterOverlay::getEndpointCache() {
static CesiumUtility::IntrusivePointer<EndpointDepot> pDepot =
@ -393,36 +462,25 @@ IonRasterOverlay::getEndpointCache() {
"externalType",
"unknown");
if (endpoint.externalType == "BING") {
const auto optionsIt = response.FindMember("options");
if (optionsIt == response.MemberEnd() ||
!optionsIt->value.IsObject()) {
if (endpoint.externalType == "AZURE_MAPS") {
endpoint.parseAzure2DOptions(response);
if (!std::holds_alternative<ExternalAssetEndpoint::Azure2D>(
endpoint.options)) {
endpoint.pRequestThatFailed = std::move(pRequest);
return ResultPointer<ExternalAssetEndpoint>(
new ExternalAssetEndpoint(std::move(endpoint)),
ErrorList::error(fmt::format(
"Cesium ion Bing Maps raster overlay metadata "
"Cesium ion Azure Maps raster overlay metadata "
"response does not contain 'options' or it is "
"not an object.")));
}
const auto& options = optionsIt->value;
ExternalAssetEndpoint::Bing& bing =
endpoint.options.emplace<ExternalAssetEndpoint::Bing>();
bing.url =
JsonHelpers::getStringOrDefault(options, "url", "");
bing.key =
JsonHelpers::getStringOrDefault(options, "key", "");
bing.mapStyle = JsonHelpers::getStringOrDefault(
options,
"mapStyle",
"AERIAL");
bing.culture =
JsonHelpers::getStringOrDefault(options, "culture", "");
} else if (endpoint.externalType == "GOOGLE_2D_MAPS") {
const auto optionsIt = response.FindMember("options");
if (optionsIt == response.MemberEnd() ||
!optionsIt->value.IsObject()) {
endpoint.parseGoogle2DOptions(response);
if (!std::holds_alternative<
ExternalAssetEndpoint::Google2D>(
endpoint.options)) {
endpoint.pRequestThatFailed = std::move(pRequest);
return ResultPointer<ExternalAssetEndpoint>(
new ExternalAssetEndpoint(std::move(endpoint)),
@ -431,45 +489,21 @@ IonRasterOverlay::getEndpointCache() {
"metadata response does not contain 'options' or "
"it is not an object.")));
}
} else if (endpoint.externalType == "BING") {
endpoint.parseBingOptions(response);
const auto& options = optionsIt->value;
ExternalAssetEndpoint::Google2D& google2D =
endpoint.options
.emplace<ExternalAssetEndpoint::Google2D>();
google2D.url = JsonHelpers::getStringOrDefault(
options,
"url",
google2D.url);
google2D.key =
JsonHelpers::getStringOrDefault(options, "key", "");
google2D.session =
JsonHelpers::getStringOrDefault(options, "session", "");
google2D.expiry =
JsonHelpers::getStringOrDefault(options, "expiry", "");
google2D.tileWidth = JsonHelpers::getUint32OrDefault(
options,
"tileWidth",
256);
google2D.tileHeight = JsonHelpers::getUint32OrDefault(
options,
"tileHeight",
256);
google2D.imageFormat = JsonHelpers::getStringOrDefault(
options,
"imageFormat",
GoogleMapTilesImageFormat::jpeg);
if (!std::holds_alternative<ExternalAssetEndpoint::Bing>(
endpoint.options)) {
endpoint.pRequestThatFailed = std::move(pRequest);
return ResultPointer<ExternalAssetEndpoint>(
new ExternalAssetEndpoint(std::move(endpoint)),
ErrorList::error(fmt::format(
"Cesium ion Bing Maps raster overlay metadata "
"response does not contain 'options' or it is "
"not an object.")));
}
} else {
ExternalAssetEndpoint::TileMapService& tileMapService =
endpoint.options
.emplace<ExternalAssetEndpoint::TileMapService>();
tileMapService.url =
JsonHelpers::getStringOrDefault(response, "url", "");
tileMapService.accessToken =
JsonHelpers::getStringOrDefault(
response,
"accessToken",
"");
endpoint.parseTileMapServiceOptions(response);
}
const auto attributionsIt =
@ -549,17 +583,19 @@ IonRasterOverlay::TileProvider::CreateTileProvider::operator()(
}
IntrusivePointer<RasterOverlay> pOverlay = nullptr;
if (pEndpoint->externalType == "BING") {
CESIUM_ASSERT(std::holds_alternative<ExternalAssetEndpoint::Bing>(
if (pEndpoint->externalType == "AZURE_MAPS") {
CESIUM_ASSERT(std::holds_alternative<ExternalAssetEndpoint::Azure2D>(
pEndpoint->options));
ExternalAssetEndpoint::Bing& bing =
std::get<ExternalAssetEndpoint::Bing>(pEndpoint->options);
pOverlay = new BingMapsRasterOverlay(
ExternalAssetEndpoint::Azure2D& azure2D =
std::get<ExternalAssetEndpoint::Azure2D>(pEndpoint->options);
pOverlay = new AzureMapsRasterOverlay(
this->pOwner->getName(),
bing.url,
bing.key,
bing.mapStyle,
bing.culture,
AzureMapsSessionParameters{
.key = azure2D.key,
.tilesetId = azure2D.tilesetId,
.showLogo = false,
.apiBaseUrl = azure2D.url,
},
this->pOwner->getOptions());
} else if (pEndpoint->externalType == "GOOGLE_2D_MAPS") {
CESIUM_ASSERT(std::holds_alternative<ExternalAssetEndpoint::Google2D>(
@ -579,6 +615,18 @@ IonRasterOverlay::TileProvider::CreateTileProvider::operator()(
.apiBaseUrl = google2D.url,
},
this->pOwner->getOptions());
} else if (pEndpoint->externalType == "BING") {
CESIUM_ASSERT(std::holds_alternative<ExternalAssetEndpoint::Bing>(
pEndpoint->options));
ExternalAssetEndpoint::Bing& bing =
std::get<ExternalAssetEndpoint::Bing>(pEndpoint->options);
pOverlay = new BingMapsRasterOverlay(
this->pOwner->getName(),
bing.url,
bing.key,
bing.mapStyle,
bing.culture,
this->pOwner->getOptions());
} else {
CESIUM_ASSERT(std::holds_alternative<ExternalAssetEndpoint::TileMapService>(
pEndpoint->options));
@ -629,5 +677,4 @@ IonRasterOverlay::TileProvider::CreateTileProvider::operator()(
})
.share();
}
} // namespace CesiumRasterOverlays

View File

@ -37,11 +37,14 @@ RasterOverlayTile::RasterOverlayTile(
RasterOverlayTile::~RasterOverlayTile() {
this->_pActivatedOverlay->removeTile(this);
RasterOverlayTileProvider& tileProvider =
*this->_pActivatedOverlay->getTileProvider();
RasterOverlayTileProvider* pTileProvider =
this->_pActivatedOverlay->getTileProvider();
if (!pTileProvider)
return;
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources = tileProvider.getPrepareRendererResources();
pPrepareRendererResources = pTileProvider->getPrepareRendererResources();
if (pPrepareRendererResources) {
void* pLoadThreadResult =

View File

@ -24,7 +24,6 @@
#include <glm/common.hpp>
#include <nonstd/expected.hpp>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
#include <tinyxml2.h>
#include <cstddef>
@ -520,63 +519,4 @@ TileMapServiceRasterOverlay::createTileProvider(
});
}
Future<void>
TileMapServiceRasterOverlay::refreshTileProviderWithNewUrlAndHeaders(
const IntrusivePointer<RasterOverlayTileProvider>& pProvider,
const std::optional<std::string>& newUrl,
const std::optional<std::vector<CesiumAsync::IAssetAccessor::THeader>>&
newHeaders) {
if (newUrl) {
this->_url = *newUrl;
}
if (newHeaders) {
this->_headers = *newHeaders;
}
return this
->createTileProvider(
pProvider->getAsyncSystem(),
pProvider->getAssetAccessor(),
pProvider->getCreditSystem(),
pProvider->getPrepareRendererResources(),
pProvider->getLogger(),
&pProvider->getOwner())
.thenInMainThread([pProvider](CreateTileProviderResult&& result) {
if (!result) {
SPDLOG_LOGGER_WARN(
pProvider->getLogger(),
"Could not refresh Bing Maps raster overlay with a new key: {}.",
result.error().message);
return;
}
// Use static_cast instead of dynamic_cast here to avoid the need for
// RTTI, and because we are certain of the type.
// NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast)
TileMapServiceTileProvider* pOld =
static_cast<TileMapServiceTileProvider*>(pProvider.get());
TileMapServiceTileProvider* pNew =
static_cast<TileMapServiceTileProvider*>(result->get());
// NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast)
if (pOld->getCoverageRectangle().getLowerLeft() !=
pNew->getCoverageRectangle().getLowerLeft() ||
pOld->getCoverageRectangle().getUpperRight() !=
pNew->getCoverageRectangle().getUpperRight() ||
pOld->getHeight() != pNew->getHeight() ||
pOld->getWidth() != pNew->getWidth() ||
pOld->getMinimumLevel() != pNew->getMinimumLevel() ||
pOld->getMaximumLevel() != pNew->getMaximumLevel() ||
pOld->getProjection() != pNew->getProjection()) {
SPDLOG_LOGGER_WARN(
pProvider->getLogger(),
"Could not refresh Tile Map Service raster overlay with a new "
"URL and request headers because some metadata properties "
"changed unexpectedly upon refresh.");
return;
}
pOld->update(*pNew);
});
}
} // namespace CesiumRasterOverlays

View File

@ -41,7 +41,7 @@ cesium_target_include_directories(
target_link_libraries(
CesiumUtility
PUBLIC
zlib-ng::zlib-ng
zlib-ng::zlib
spdlog::spdlog
glm::glm
ada::ada

View File

@ -356,6 +356,12 @@ public:
static std::string
setPath(const std::string& uri, const std::string& newPath);
/**
* @brief Ensures that the Uri's path ends with a slash, modifying itself if
* necessary. Useful when the Uri is used as a base URL.
*/
void ensureTrailingSlash();
private:
std::optional<ada::url_aggregator> _url = std::nullopt;
bool _hasScheme = false;

View File

@ -385,4 +385,11 @@ std::string Uri::setPath(const std::string& uri, const std::string& newPath) {
return std::string(parsedUri.toString());
}
void Uri::ensureTrailingSlash() {
std::string_view path = this->getPath();
if (path.empty() || path.back() != '/') {
this->setPath(std::string(path) + '/');
}
}
} // namespace CesiumUtility

View File

@ -294,4 +294,14 @@ TEST_CASE("Uri::getExtension") {
CHECK(Uri("http://example.com/a/b/c/.hidden").getExtension() == "");
CHECK(Uri("http://example.com/a/b/c/.").getExtension() == "");
CHECK(Uri("http://example.com/a/b/c/..").getExtension() == "");
}
}
TEST_CASE("Uri::ensureTrailingSlash") {
Uri uri("http://www.example.com");
uri.ensureTrailingSlash();
CHECK_EQ(uri.toString(), "http://www.example.com/");
// Ensure nothing changes when a trailing slash is present.
uri.ensureTrailingSlash();
CHECK_EQ(uri.toString(), "http://www.example.com/");
}

View File

@ -4,25 +4,35 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
include(CMakeFindDependencyMacro)
find_dependency(zlib-ng REQUIRED)
find_dependency(modp_b64 REQUIRED)
find_dependency(ada CONFIG REQUIRED)
find_dependency(Async++ CONFIG REQUIRED)
find_dependency(blend2d CONFIG REQUIRED)
find_dependency(doctest CONFIG REQUIRED)
find_dependency(draco CONFIG REQUIRED)
find_dependency(expected-lite CONFIG REQUIRED)
find_dependency(glm CONFIG REQUIRED)
find_dependency(meshoptimizer CONFIG REQUIRED)
find_dependency(httplib CONFIG REQUIRED)
find_dependency(Ktx CONFIG REQUIRED)
find_dependency(libmorton CONFIG REQUIRED)
find_dependency(libjpeg-turbo CONFIG REQUIRED)
find_dependency(libmorton CONFIG REQUIRED)
find_dependency(meshoptimizer CONFIG REQUIRED)
find_dependency(OpenSSL REQUIRED)
find_dependency(s2 CONFIG REQUIRED)
find_dependency(spdlog CONFIG REQUIRED)
find_dependency(tinyxml2 CONFIG REQUIRED)
find_dependency(unofficial-sqlite3 CONFIG REQUIRED)
find_dependency(ada CONFIG REQUIRED)
find_dependency(WebP CONFIG REQUIRED)
find_dependency(zlib-ng CONFIG REQUIRED)
# asmjit should not be included with iOS builds as iOS doesn't support JIT compilation.
if(NOT IOS AND NOT VCPKG_CMAKE_SYSTEM_NAME MATCHES "iOS")
find_dependency(asmjit CONFIG REQUIRED)
endif()
if(NOT CESIUM_DISABLE_CURL)
find_dependency(CURL REQUIRED)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/cesium-nativeTargets.cmake")

View File

@ -1,46 +0,0 @@
find_library(zlib-ng_LIBRARIES NAMES zlibstatic-ng z-ng zlib-ng)
find_library(zlib-ng_DEBUG_LIBRARIES
NAMES
"zlibstatic-ngd"
"z-ngd"
"zlib-ngd"
)
# vcpkg specific locations for debug libraries if they are not already found
set(zlibngSavePrefixPath ${CMAKE_PREFIX_PATH})
list(FILTER CMAKE_PREFIX_PATH INCLUDE REGEX "/debug")
find_library(zlib-ng_DEBUG_LIBRARIES
NAMES
zlibstatic
z-ng
zlib-ng
)
set(CMAKE_PREFIX_PATH ${zlibngSavePrefixPath})
find_path(zlib-ng_INCLUDE_DIRS NAMES zlib-ng.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(zlib-ng
FOUND_VAR
zlib-ng_FOUND
REQUIRED_VARS
zlib-ng_LIBRARIES
zlib-ng_INCLUDE_DIRS
)
mark_as_advanced(zlib-ng_LIBRARIES zlib-ng_INCLUDE_DIRS)
if(zlib-ng_FOUND AND NOT TARGET zlib-ng::zlib-ng)
add_library(zlib-ng::zlib-ng UNKNOWN IMPORTED)
set_target_properties(zlib-ng::zlib-ng PROPERTIES
IMPORTED_LOCATION "${zlib-ng_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${zlib-ng_INCLUDE_DIRS}"
)
if(zlib-ng_DEBUG_LIBRARIES)
set_target_properties(zlib-ng::zlib-ng PROPERTIES
IMPORTED_LOCATION_DEBUG "${zlib-ng_DEBUG_LIBRARIES}"
)
endif()
endif()