cesium-native/Cesium3DTilesSelection/src/TileMapServiceRasterOverlay...

471 lines
18 KiB
C++

#include "Cesium3DTilesSelection/TileMapServiceRasterOverlay.h"
#include "Cesium3DTilesSelection/CreditSystem.h"
#include "Cesium3DTilesSelection/QuadtreeRasterOverlayTileProvider.h"
#include "Cesium3DTilesSelection/RasterOverlayLoadFailureDetails.h"
#include "Cesium3DTilesSelection/RasterOverlayTile.h"
#include "Cesium3DTilesSelection/TilesetExternals.h"
#include "Cesium3DTilesSelection/spdlog-cesium.h"
#include "Cesium3DTilesSelection/tinyxml-cesium.h"
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumAsync/IAssetResponse.h>
#include <CesiumGeospatial/GlobeRectangle.h>
#include <CesiumGeospatial/WebMercatorProjection.h>
#include <CesiumUtility/Uri.h>
#include <cstddef>
using namespace CesiumAsync;
using namespace CesiumUtility;
namespace Cesium3DTilesSelection {
namespace {
struct TileMapServiceTileset {
std::string url;
uint32_t level;
};
} // namespace
class TileMapServiceTileProvider final
: public QuadtreeRasterOverlayTileProvider {
public:
TileMapServiceTileProvider(
const IntrusivePointer<const RasterOverlay>& pOwner,
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<IAssetAccessor>& pAssetAccessor,
std::optional<Credit> credit,
const std::shared_ptr<IPrepareRendererResources>&
pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
const CesiumGeospatial::Projection& projection,
const CesiumGeometry::QuadtreeTilingScheme& tilingScheme,
const CesiumGeometry::Rectangle& coverageRectangle,
const std::string& url,
const std::vector<IAssetAccessor::THeader>& headers,
const std::string& fileExtension,
uint32_t width,
uint32_t height,
uint32_t minimumLevel,
uint32_t maximumLevel,
const std::vector<TileMapServiceTileset>& tileSets)
: QuadtreeRasterOverlayTileProvider(
pOwner,
asyncSystem,
pAssetAccessor,
credit,
pPrepareRendererResources,
pLogger,
projection,
tilingScheme,
coverageRectangle,
minimumLevel,
maximumLevel,
width,
height),
_url(url),
_headers(headers),
_fileExtension(fileExtension),
_tileSets(tileSets) {}
virtual ~TileMapServiceTileProvider() {}
protected:
virtual CesiumAsync::Future<LoadedRasterOverlayImage> loadQuadtreeTileImage(
const CesiumGeometry::QuadtreeTileID& tileID) const override {
LoadTileImageFromUrlOptions options;
options.rectangle = this->getTilingScheme().tileToRectangle(tileID);
options.moreDetailAvailable = tileID.level < this->getMaximumLevel();
uint32_t level = tileID.level - this->getMinimumLevel();
if (level < _tileSets.size()) {
const TileMapServiceTileset& tileset = _tileSets[level];
std::string url = CesiumUtility::Uri::resolve(
this->_url,
tileset.url + "/" + std::to_string(tileID.x) + "/" +
std::to_string(tileID.y) + this->_fileExtension,
true);
return this->loadTileImageFromUrl(
url,
this->_headers,
std::move(options));
} else {
return this->getAsyncSystem()
.createResolvedFuture<LoadedRasterOverlayImage>(
{std::nullopt,
options.rectangle,
{},
{"Failed to load image from TMS."},
{},
options.moreDetailAvailable});
}
}
private:
std::string _url;
std::vector<IAssetAccessor::THeader> _headers;
std::string _fileExtension;
std::vector<TileMapServiceTileset> _tileSets;
};
TileMapServiceRasterOverlay::TileMapServiceRasterOverlay(
const std::string& name,
const std::string& url,
const std::vector<IAssetAccessor::THeader>& headers,
const TileMapServiceRasterOverlayOptions& tmsOptions,
const RasterOverlayOptions& overlayOptions)
: RasterOverlay(name, overlayOptions),
_url(url),
_headers(headers),
_options(tmsOptions) {}
TileMapServiceRasterOverlay::~TileMapServiceRasterOverlay() {}
static std::optional<std::string> getAttributeString(
const tinyxml2::XMLElement* pElement,
const char* attributeName) {
if (!pElement) {
return std::nullopt;
}
const char* pAttrValue = pElement->Attribute(attributeName);
if (!pAttrValue) {
return std::nullopt;
}
return std::string(pAttrValue);
}
static std::optional<uint32_t> getAttributeUint32(
const tinyxml2::XMLElement* pElement,
const char* attributeName) {
std::optional<std::string> s = getAttributeString(pElement, attributeName);
if (s) {
return std::stoul(s.value());
}
return std::nullopt;
}
static std::optional<double> getAttributeDouble(
const tinyxml2::XMLElement* pElement,
const char* attributeName) {
std::optional<std::string> s = getAttributeString(pElement, attributeName);
if (s) {
return std::stod(s.value());
}
return std::nullopt;
}
namespace {
using GetXmlDocumentResult = nonstd::expected<
std::unique_ptr<tinyxml2::XMLDocument>,
RasterOverlayLoadFailureDetails>;
Future<GetXmlDocumentResult> getXmlDocument(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<IAssetAccessor>& pAssetAccessor,
const std::string& url,
const std::vector<IAssetAccessor::THeader>& headers) {
return pAssetAccessor->get(asyncSystem, url, headers)
.thenInWorkerThread(
[asyncSystem, pAssetAccessor, url, headers](
std::shared_ptr<IAssetRequest>&& pRequest)
-> Future<GetXmlDocumentResult> {
const IAssetResponse* pResponse = pRequest->response();
if (!pResponse) {
return asyncSystem.createResolvedFuture<GetXmlDocumentResult>(
nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::TileProvider,
std::move(pRequest),
"No response received from Tile Map Service."}));
}
const gsl::span<const std::byte> data = pResponse->data();
std::unique_ptr<tinyxml2::XMLDocument> pDoc =
std::make_unique<tinyxml2::XMLDocument>(
new tinyxml2::XMLDocument());
const tinyxml2::XMLError error = pDoc->Parse(
reinterpret_cast<const char*>(data.data()),
data.size_bytes());
bool hasError = false;
std::string errorMessage;
if (error != tinyxml2::XMLError::XML_SUCCESS) {
hasError = true;
errorMessage = "Unable to parse Tile map service XML document.";
} else {
tinyxml2::XMLElement* pRoot = pDoc->RootElement();
if (!pRoot) {
hasError = true;
errorMessage =
"Tile map service XML document does not have a root "
"element.";
} else {
tinyxml2::XMLElement* pTilesets =
pRoot->FirstChildElement("TileSets");
if (!pTilesets) {
hasError = true;
errorMessage = "Tile map service XML document does not have "
"any tilesets.";
}
tinyxml2::XMLElement* srs = pRoot->FirstChildElement("SRS");
if (srs) {
std::string srsText = srs->GetText();
if (srsText.find("4326") == std::string::npos &&
srsText.find("3857") == std::string::npos &&
srsText.find("900913") == std::string::npos) {
hasError = true;
errorMessage = srsText + " is not supported.";
}
} else {
hasError = true;
errorMessage =
"Tile map service XML document does not have an SRS.";
}
}
}
if (hasError) {
if (url.find("tilemapresource.xml") == std::string::npos) {
std::string baseUrl = url;
if (baseUrl.size() > 0 && baseUrl[baseUrl.size() - 1] != '/') {
baseUrl += '/';
}
return getXmlDocument(
asyncSystem,
pAssetAccessor,
CesiumUtility::Uri::resolve(baseUrl, "tilemapresource.xml"),
headers);
} else {
return asyncSystem.createResolvedFuture<GetXmlDocumentResult>(
nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::TileProvider,
std::move(pRequest),
errorMessage}));
}
}
return asyncSystem.createResolvedFuture<GetXmlDocumentResult>(
std::move(pDoc));
});
}
} // namespace
Future<RasterOverlay::CreateTileProviderResult>
TileMapServiceRasterOverlay::createTileProvider(
const CesiumAsync::AsyncSystem& asyncSystem,
const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
const std::shared_ptr<CreditSystem>& pCreditSystem,
const std::shared_ptr<IPrepareRendererResources>& pPrepareRendererResources,
const std::shared_ptr<spdlog::logger>& pLogger,
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner) const {
std::string xmlUrl = this->_url;
pOwner = pOwner ? pOwner : this;
const std::optional<Credit> credit =
this->_options.credit ? std::make_optional(pCreditSystem->createCredit(
this->_options.credit.value(),
pOwner->getOptions().showCreditsOnScreen))
: std::nullopt;
return getXmlDocument(asyncSystem, pAssetAccessor, xmlUrl, this->_headers)
.thenInMainThread(
[pOwner,
asyncSystem,
pAssetAccessor,
credit,
pPrepareRendererResources,
pLogger,
options = this->_options,
url = this->_url,
headers = this->_headers](
GetXmlDocumentResult&& xml) -> CreateTileProviderResult {
if (!xml) {
return nonstd::make_unexpected(std::move(xml).error());
}
std::unique_ptr<tinyxml2::XMLDocument> pDoc = std::move(*xml);
tinyxml2::XMLElement* pRoot = pDoc->RootElement();
tinyxml2::XMLElement* pTileFormat =
pRoot->FirstChildElement("TileFormat");
std::string fileExtension = options.fileExtension.value_or(
getAttributeString(pTileFormat, "extension").value_or("png"));
uint32_t tileWidth = options.tileWidth.value_or(
getAttributeUint32(pTileFormat, "width").value_or(256));
uint32_t tileHeight = options.tileHeight.value_or(
getAttributeUint32(pTileFormat, "height").value_or(256));
uint32_t minimumLevel = std::numeric_limits<uint32_t>::max();
uint32_t maximumLevel = 0;
std::vector<TileMapServiceTileset> tileSets;
tinyxml2::XMLElement* pTilesets =
pRoot->FirstChildElement("TileSets");
if (pTilesets) {
tinyxml2::XMLElement* pTileset =
pTilesets->FirstChildElement("TileSet");
while (pTileset) {
TileMapServiceTileset& tileSet = tileSets.emplace_back();
tileSet.level =
getAttributeUint32(pTileset, "order").value_or(0);
minimumLevel = glm::min(minimumLevel, tileSet.level);
maximumLevel = glm::max(maximumLevel, tileSet.level);
tileSet.url = getAttributeString(pTileset, "href")
.value_or(std::to_string(tileSet.level));
pTileset = pTileset->NextSiblingElement("TileSet");
}
}
if (maximumLevel < minimumLevel && maximumLevel == 0) {
// Min and max levels unknown, so use defaults.
minimumLevel = 0;
maximumLevel = 25;
}
CesiumGeospatial::GlobeRectangle tilingSchemeRectangle =
CesiumGeospatial::GeographicProjection::MAXIMUM_GLOBE_RECTANGLE;
CesiumGeospatial::Projection projection;
uint32_t rootTilesX = 1;
bool isRectangleInDegrees = false;
if (options.projection) {
projection = options.projection.value();
} else {
std::string projectionName =
getAttributeString(pTilesets, "profile").value_or("mercator");
if (projectionName == "mercator" ||
projectionName == "global-mercator") {
projection = CesiumGeospatial::WebMercatorProjection();
tilingSchemeRectangle = CesiumGeospatial::
WebMercatorProjection::MAXIMUM_GLOBE_RECTANGLE;
// Determine based on the profile attribute if this tileset was
// generated by gdal2tiles.py, which uses 'mercator' and
// 'geodetic' profiles, or by a tool compliant with the TMS
// standard, which is 'global-mercator' and 'global-geodetic'
// profiles. In the gdal2Tiles case, X and Y are always in
// geodetic degrees.
isRectangleInDegrees = projectionName.find("global-") != 0;
} else if (
projectionName == "geodetic" ||
projectionName == "global-geodetic") {
projection = CesiumGeospatial::GeographicProjection();
tilingSchemeRectangle = CesiumGeospatial::GeographicProjection::
MAXIMUM_GLOBE_RECTANGLE;
rootTilesX = 2;
// The geodetic profile is always in degrees.
isRectangleInDegrees = true;
} else {
tinyxml2::XMLElement* srs = pRoot->FirstChildElement("SRS");
if (srs) {
std::string srsText = srs->GetText();
if (srsText.find("4326") != std::string::npos) {
projection = CesiumGeospatial::GeographicProjection();
tilingSchemeRectangle = CesiumGeospatial::
GeographicProjection::MAXIMUM_GLOBE_RECTANGLE;
rootTilesX = 2;
isRectangleInDegrees = true;
} else if (
srsText.find("3857") != std::string::npos ||
srsText.find("900913") != std::string::npos) {
projection = CesiumGeospatial::WebMercatorProjection();
tilingSchemeRectangle = CesiumGeospatial::
WebMercatorProjection::MAXIMUM_GLOBE_RECTANGLE;
isRectangleInDegrees = true;
}
}
}
}
minimumLevel = glm::min(minimumLevel, maximumLevel);
minimumLevel = options.minimumLevel.value_or(minimumLevel);
maximumLevel = options.maximumLevel.value_or(maximumLevel);
CesiumGeometry::Rectangle coverageRectangle =
projectRectangleSimple(projection, tilingSchemeRectangle);
if (options.coverageRectangle) {
coverageRectangle = options.coverageRectangle.value();
} else {
tinyxml2::XMLElement* pBoundingBox =
pRoot->FirstChildElement("BoundingBox");
std::optional<double> west =
getAttributeDouble(pBoundingBox, "minx");
std::optional<double> south =
getAttributeDouble(pBoundingBox, "miny");
std::optional<double> east =
getAttributeDouble(pBoundingBox, "maxx");
std::optional<double> north =
getAttributeDouble(pBoundingBox, "maxy");
if (west && south && east && north) {
if (isRectangleInDegrees) {
coverageRectangle = projectRectangleSimple(
projection,
CesiumGeospatial::GlobeRectangle::fromDegrees(
west.value(),
south.value(),
east.value(),
north.value()));
} else {
coverageRectangle = CesiumGeometry::Rectangle(
west.value(),
south.value(),
east.value(),
north.value());
}
}
}
CesiumGeometry::QuadtreeTilingScheme tilingScheme(
projectRectangleSimple(projection, tilingSchemeRectangle),
rootTilesX,
1);
std::string baseUrl = url;
if (!(baseUrl.size() < 4)) {
if (baseUrl.substr(baseUrl.size() - 4, 4) != ".xml") {
if (baseUrl[baseUrl.size() - 1] != '/') {
baseUrl += "/";
}
}
}
return new TileMapServiceTileProvider(
pOwner,
asyncSystem,
pAssetAccessor,
credit,
pPrepareRendererResources,
pLogger,
projection,
tilingScheme,
coverageRectangle,
baseUrl,
headers,
!fileExtension.empty() ? "." + fileExtension : fileExtension,
tileWidth,
tileHeight,
minimumLevel,
maximumLevel,
tileSets);
});
}
} // namespace Cesium3DTilesSelection