cesium-native/CesiumRasterOverlays/src/IonRasterOverlay.cpp

383 lines
14 KiB
C++

#include <CesiumAsync/CesiumIonAssetAccessor.h>
#include <CesiumAsync/Future.h>
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumRasterOverlays/BingMapsRasterOverlay.h>
#include <CesiumRasterOverlays/IonRasterOverlay.h>
#include <CesiumRasterOverlays/RasterOverlay.h>
#include <CesiumRasterOverlays/RasterOverlayLoadFailureDetails.h>
#include <CesiumRasterOverlays/RasterOverlayTile.h>
#include <CesiumRasterOverlays/RasterOverlayTileProvider.h>
#include <CesiumRasterOverlays/TileMapServiceRasterOverlay.h>
#include <CesiumUtility/CreditSystem.h>
#include <CesiumUtility/IntrusivePointer.h>
#include <CesiumUtility/JsonHelpers.h>
#include <fmt/format.h>
#include <nonstd/expected.hpp>
#include <rapidjson/document.h>
#include <spdlog/logger.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
using namespace CesiumAsync;
using namespace CesiumUtility;
namespace CesiumRasterOverlays {
IonRasterOverlay::IonRasterOverlay(
const std::string& name,
int64_t ionAssetID,
const std::string& ionAccessToken,
const RasterOverlayOptions& overlayOptions,
const std::string& ionAssetEndpointUrl)
: IonRasterOverlay(
name,
fmt::format(
"{}v1/assets/{}/endpoint?access_token={}",
ionAssetEndpointUrl,
ionAssetID,
ionAccessToken),
ionAccessToken,
false,
overlayOptions) {}
IonRasterOverlay::IonRasterOverlay(
const std::string& name,
const std::string& overlayUrl,
const std::string& ionAccessToken,
bool needsAuthHeader,
const RasterOverlayOptions& overlayOptions)
: RasterOverlay(name, overlayOptions),
_overlayUrl(overlayUrl),
_ionAccessToken(ionAccessToken),
_needsAuthHeader(needsAuthHeader) {}
IonRasterOverlay::~IonRasterOverlay() = default;
std::unordered_map<std::string, IonRasterOverlay::ExternalAssetEndpoint>
IonRasterOverlay::endpointCache;
Future<RasterOverlay::CreateTileProviderResult>
IonRasterOverlay::createTileProvider(
const ExternalAssetEndpoint& endpoint,
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner) const {
IntrusivePointer<RasterOverlay> pOverlay = nullptr;
bool isBing;
if (endpoint.externalType == "BING") {
isBing = true;
pOverlay = new BingMapsRasterOverlay(
this->getName(),
endpoint.url,
endpoint.key,
endpoint.mapStyle,
endpoint.culture,
this->getOptions());
} else {
isBing = false;
pOverlay = new TileMapServiceRasterOverlay(
this->getName(),
endpoint.url,
std::vector<CesiumAsync::IAssetAccessor::THeader>{
std::make_pair("Authorization", "Bearer " + endpoint.accessToken)},
TileMapServiceRasterOverlayOptions(),
this->getOptions());
}
if (pCreditSystem) {
std::vector<Credit>& credits = pOverlay->getCredits();
for (const auto& attribution : endpoint.attributions) {
credits.emplace_back(pCreditSystem->createCredit(
attribution.html,
!attribution.collapsible || this->getOptions().showCreditsOnScreen));
}
}
// CesiumIonAssetAccessor must be created first to be given to
// createTileProvider. But the "new token" callback needs to modify the
// created tile provider, which can't be known when that callback is created.
// Thus, a "holder" is created ahead of time and filled with the provider
// once it's created.
struct ProviderHolder {
RasterOverlayTileProvider* pProvider = nullptr;
};
std::shared_ptr<ProviderHolder> pHolder = std::make_shared<ProviderHolder>();
std::vector<IAssetAccessor::THeader> requestHeaders;
if (this->_needsAuthHeader) {
requestHeaders.emplace_back(
"Authorization",
fmt::format("Bearer {}", this->_ionAccessToken));
}
std::shared_ptr<CesiumIonAssetAccessor> pIonAccessor =
std::make_shared<CesiumIonAssetAccessor>(
pLogger,
pAssetAccessor,
this->_overlayUrl,
requestHeaders,
[asyncSystem, url = this->_overlayUrl, pHolder, isBing, pOverlay](
const CesiumIonAssetAccessor::UpdatedToken& update) {
// update cache with new access token
auto cacheIt = endpointCache.find(url);
if (cacheIt != endpointCache.end()) {
cacheIt->second.accessToken = update.token;
// For Bing Maps endpoints, the access token is stored in the key
// field instead.
cacheIt->second.key = update.token;
}
if (pHolder->pProvider) {
// Use static_cast instead of dynamic_cast below to avoid the
// need for RTTI, and because we are certain of the type.
// NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast)
if (isBing) {
BingMapsRasterOverlay* pBing =
static_cast<BingMapsRasterOverlay*>(pOverlay.get());
return pBing->refreshTileProviderWithNewKey(
pHolder->pProvider,
update.token);
} else {
TileMapServiceRasterOverlay* pTMS =
static_cast<TileMapServiceRasterOverlay*>(pOverlay.get());
return pTMS->refreshTileProviderWithNewUrlAndHeaders(
pHolder->pProvider,
std::nullopt,
std::vector<CesiumAsync::IAssetAccessor::THeader>{
std::make_pair(
"Authorization",
update.authorizationHeader)});
}
// NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast)
}
return asyncSystem.createResolvedFuture();
});
return pOverlay
->createTileProvider(
asyncSystem,
pIonAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
std::move(pOwner))
.thenInMainThread(
[pHolder, pIonAccessor](
CreateTileProviderResult&& result) -> CreateTileProviderResult {
if (result) {
RasterOverlayTileProvider* pProvider = result->get();
pHolder->pProvider = pProvider;
// When the tile provider is destroyed, notify the
// CesiumIonAssetAccessor.
pProvider->getAsyncDestructionCompleteEvent().thenImmediately(
[pHolder, pIonAccessor]() {
pIonAccessor->notifyOwnerIsBeingDestroyed();
pHolder->pProvider = nullptr;
});
}
return std::move(result);
});
}
Future<RasterOverlay::CreateTileProviderResult>
IonRasterOverlay::createTileProvider(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
const std::shared_ptr<IPrepareRasterOverlayRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner) const {
pOwner = pOwner ? pOwner : this;
auto cacheIt = IonRasterOverlay::endpointCache.find(this->_overlayUrl);
if (cacheIt != IonRasterOverlay::endpointCache.end()) {
IntrusivePointer<const IonRasterOverlay> pThis = this;
return createTileProvider(
cacheIt->second,
asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
pOwner)
.thenInMainThread(
[pThis,
asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
pOwner](RasterOverlay::CreateTileProviderResult&& result) {
if (!result) {
const RasterOverlayLoadFailureDetails& error = result.error();
if (error.pRequest && error.pRequest->response() &&
error.pRequest->response()->statusCode() == 401) {
// Cached endpoint response is no good, so remove it and
// retry.
endpointCache.erase(pThis->_overlayUrl);
return pThis->createTileProvider(
asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
pOwner);
}
}
return asyncSystem.createResolvedFuture(std::move(result));
});
}
std::vector<IAssetAccessor::THeader> headers;
if (this->_needsAuthHeader) {
headers.emplace_back(
"Authorization",
fmt::format("Bearer {}", this->_ionAccessToken));
}
return pAssetAccessor->get(asyncSystem, this->_overlayUrl, headers)
.thenImmediately(
[](std::shared_ptr<IAssetRequest>&& pRequest)
-> nonstd::expected<
ExternalAssetEndpoint,
RasterOverlayLoadFailureDetails> {
const IAssetResponse* pResponse = pRequest->response();
rapidjson::Document response;
response.Parse(
reinterpret_cast<const char*>(pResponse->data().data()),
pResponse->data().size());
if (response.HasParseError()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::CesiumIon,
std::move(pRequest),
fmt::format(
"Error while parsing Cesium ion raster overlay "
"response, "
"error code {} at byte offset {}",
response.GetParseError(),
response.GetErrorOffset())});
}
std::string type =
JsonHelpers::getStringOrDefault(response, "type", "unknown");
if (type != "IMAGERY") {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::CesiumIon,
std::move(pRequest),
fmt::format(
"Assets used with a raster overlay must have type "
"'IMAGERY', but instead saw '{}'.",
type)});
}
ExternalAssetEndpoint endpoint;
endpoint.externalType = JsonHelpers::getStringOrDefault(
response,
"externalType",
"unknown");
if (endpoint.externalType == "BING") {
const auto optionsIt = response.FindMember("options");
if (optionsIt == response.MemberEnd() ||
!optionsIt->value.IsObject()) {
return nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::CesiumIon,
std::move(pRequest),
fmt::format("Cesium ion Bing Maps raster overlay metadata "
"response "
"does not contain 'options' or it is not an "
"object.")});
}
const auto attributionsIt = response.FindMember("attributions");
if (attributionsIt != response.MemberEnd() &&
attributionsIt->value.IsArray()) {
for (const rapidjson::Value& attribution :
attributionsIt->value.GetArray()) {
AssetEndpointAttribution& endpointAttribution =
endpoint.attributions.emplace_back();
const auto html = attribution.FindMember("html");
if (html != attribution.MemberEnd() &&
html->value.IsString()) {
endpointAttribution.html = html->value.GetString();
}
auto collapsible = attribution.FindMember("collapsible");
if (collapsible != attribution.MemberEnd() &&
collapsible->value.IsBool()) {
endpointAttribution.collapsible =
collapsible->value.GetBool();
}
}
}
const auto& options = optionsIt->value;
endpoint.url =
JsonHelpers::getStringOrDefault(options, "url", "");
endpoint.key =
JsonHelpers::getStringOrDefault(options, "key", "");
endpoint.mapStyle = JsonHelpers::getStringOrDefault(
options,
"mapStyle",
"AERIAL");
endpoint.culture =
JsonHelpers::getStringOrDefault(options, "culture", "");
} else {
endpoint.url =
JsonHelpers::getStringOrDefault(response, "url", "");
endpoint.accessToken =
JsonHelpers::getStringOrDefault(response, "accessToken", "");
}
return endpoint;
})
.thenInMainThread(
[asyncSystem,
pOwner,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
this,
pLogger](nonstd::expected<
ExternalAssetEndpoint,
RasterOverlayLoadFailureDetails>&& result)
-> Future<CreateTileProviderResult> {
if (result) {
IonRasterOverlay::endpointCache[this->_overlayUrl] = *result;
return this->createTileProvider(
*result,
asyncSystem,
pAssetAccessor,
pCreditSystem,
pPrepareRendererResources,
pLogger,
pOwner);
} else {
return asyncSystem.createResolvedFuture<CreateTileProviderResult>(
nonstd::make_unexpected(std::move(result).error()));
}
});
}
} // namespace CesiumRasterOverlays