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

471 lines
18 KiB
C++
Raw Permalink Normal View History

#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>
2021-03-05 00:22:38 +08:00
#include <cstddef>
2020-12-03 07:23:17 +08:00
using namespace CesiumAsync;
using namespace CesiumUtility;
namespace Cesium3DTilesSelection {
2022-07-30 02:39:43 +08:00
namespace {
struct TileMapServiceTileset {
2022-07-19 01:40:31 +08:00
std::string url;
uint32_t level;
2022-07-15 05:47:15 +08:00
};
2022-07-30 02:39:43 +08:00
} // namespace
2022-07-15 05:47:15 +08:00
class TileMapServiceTileProvider final
: public QuadtreeRasterOverlayTileProvider {
2021-03-09 08:37:45 +08:00
public:
TileMapServiceTileProvider(
const IntrusivePointer<const RasterOverlay>& pOwner,
2021-03-09 08:37:45 +08:00
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,
2021-03-09 08:37:45 +08:00
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,
2022-07-15 05:47:15 +08:00
uint32_t maximumLevel,
2022-07-30 02:39:43 +08:00
const std::vector<TileMapServiceTileset>& tileSets)
: QuadtreeRasterOverlayTileProvider(
pOwner,
asyncSystem,
2021-02-21 20:13:42 +08:00
pAssetAccessor,
credit,
pPrepareRendererResources,
2020-12-17 13:53:46 +08:00
pLogger,
2021-03-09 08:37:45 +08:00
projection,
tilingScheme,
coverageRectangle,
minimumLevel,
maximumLevel,
width,
height),
_url(url),
_headers(headers),
2022-07-30 02:39:43 +08:00
_fileExtension(fileExtension),
_tileSets(tileSets) {}
2021-03-09 08:37:45 +08:00
virtual ~TileMapServiceTileProvider() {}
protected:
virtual CesiumAsync::Future<LoadedRasterOverlayImage> loadQuadtreeTileImage(
const CesiumGeometry::QuadtreeTileID& tileID) const override {
2021-03-09 08:37:45 +08:00
LoadTileImageFromUrlOptions options;
options.rectangle = this->getTilingScheme().tileToRectangle(tileID);
options.moreDetailAvailable = tileID.level < this->getMaximumLevel();
2022-07-15 05:47:15 +08:00
2022-07-30 02:39:43 +08:00
uint32_t level = tileID.level - this->getMinimumLevel();
if (level < _tileSets.size()) {
const TileMapServiceTileset& tileset = _tileSets[level];
2022-07-15 05:47:15 +08:00
std::string url = CesiumUtility::Uri::resolve(
this->_url,
2022-07-30 02:39:43 +08:00
tileset.url + "/" + std::to_string(tileID.x) + "/" +
2022-07-15 05:47:15 +08:00
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,
{},
2022-07-30 07:02:35 +08:00
{"Failed to load image from TMS."},
2022-07-15 05:47:15 +08:00
{},
options.moreDetailAvailable});
}
2021-03-09 08:37:45 +08:00
}
private:
std::string _url;
std::vector<IAssetAccessor::THeader> _headers;
std::string _fileExtension;
2022-07-30 02:39:43 +08:00
std::vector<TileMapServiceTileset> _tileSets;
2021-03-09 08:37:45 +08:00
};
TileMapServiceRasterOverlay::TileMapServiceRasterOverlay(
const std::string& name,
2021-03-09 08:37:45 +08:00
const std::string& url,
const std::vector<IAssetAccessor::THeader>& headers,
2021-11-03 15:46:03 +08:00
const TileMapServiceRasterOverlayOptions& tmsOptions,
const RasterOverlayOptions& overlayOptions)
: RasterOverlay(name, overlayOptions),
_url(url),
_headers(headers),
_options(tmsOptions) {}
2021-03-09 08:37:45 +08:00
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;
}
2022-10-18 14:59:23 +08:00
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,
2022-10-18 14:59:23 +08:00
const std::vector<IAssetAccessor::THeader>& headers) {
return pAssetAccessor->get(asyncSystem, url, headers)
.thenInWorkerThread(
2022-10-18 14:59:23 +08:00
[asyncSystem, pAssetAccessor, url, headers](
std::shared_ptr<IAssetRequest>&& pRequest)
-> Future<GetXmlDocumentResult> {
const IAssetResponse* pResponse = pRequest->response();
if (!pResponse) {
2022-10-18 14:59:23 +08:00
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());
2022-07-21 03:45:31 +08:00
bool hasError = false;
2022-07-30 07:02:35 +08:00
std::string errorMessage;
2022-07-21 03:45:31 +08:00
if (error != tinyxml2::XMLError::XML_SUCCESS) {
hasError = true;
2022-07-30 07:02:35 +08:00
errorMessage = "Unable to parse Tile map service XML document.";
} else {
2022-07-19 01:40:31 +08:00
tinyxml2::XMLElement* pRoot = pDoc->RootElement();
2022-07-21 03:45:31 +08:00
if (!pRoot) {
hasError = true;
2022-07-30 07:02:35 +08:00
errorMessage =
"Tile map service XML document does not have a root "
"element.";
2022-07-21 03:45:31 +08:00
} else {
tinyxml2::XMLElement* pTilesets =
pRoot->FirstChildElement("TileSets");
2022-07-21 03:45:31 +08:00
if (!pTilesets) {
hasError = true;
2022-07-30 07:02:35 +08:00
errorMessage = "Tile map service XML document does not have "
"any tilesets.";
2022-07-21 03:45:31 +08:00
}
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) {
2022-10-18 14:59:23 +08:00
hasError = true;
errorMessage = srsText + " is not supported.";
2022-07-21 03:45:31 +08:00
}
2022-07-30 07:02:35 +08:00
} else {
hasError = true;
errorMessage =
"Tile map service XML document does not have an SRS.";
2022-07-21 03:45:31 +08:00
}
}
2022-07-21 04:36:45 +08:00
}
if (hasError) {
2022-07-30 07:02:35 +08:00
if (url.find("tilemapresource.xml") == std::string::npos) {
2022-07-21 04:36:45 +08:00
std::string baseUrl = url;
2022-07-30 07:02:35 +08:00
if (baseUrl.size() > 0 && baseUrl[baseUrl.size() - 1] != '/') {
2022-07-21 04:36:45 +08:00
baseUrl += '/';
2022-07-21 03:45:31 +08:00
}
2022-07-21 04:36:45 +08:00
return getXmlDocument(
2022-10-18 14:59:23 +08:00
asyncSystem,
pAssetAccessor,
CesiumUtility::Uri::resolve(baseUrl, "tilemapresource.xml"),
headers);
2022-07-21 04:36:45 +08:00
} else {
2022-10-18 14:59:23 +08:00
return asyncSystem.createResolvedFuture<GetXmlDocumentResult>(
nonstd::make_unexpected(RasterOverlayLoadFailureDetails{
RasterOverlayLoadType::TileProvider,
std::move(pRequest),
errorMessage}));
2022-07-19 01:40:31 +08:00
}
}
2022-10-18 14:59:23 +08:00
return asyncSystem.createResolvedFuture<GetXmlDocumentResult>(
std::move(pDoc));
});
}
2022-10-18 14:59:23 +08:00
} // namespace
Future<RasterOverlay::CreateTileProviderResult>
2021-03-09 08:37:45 +08:00
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,
2022-10-18 14:59:23 +08:00
CesiumUtility::IntrusivePointer<const RasterOverlay> pOwner) const {
std::string xmlUrl = this->_url;
2021-03-09 08:37:45 +08:00
pOwner = pOwner ? pOwner : this;
const std::optional<Credit> credit =
2021-03-09 08:37:45 +08:00
this->_options.credit ? std::make_optional(pCreditSystem->createCredit(
2022-03-08 08:47:36 +08:00
this->_options.credit.value(),
pOwner->getOptions().showCreditsOnScreen))
2021-03-09 08:37:45 +08:00
: std::nullopt;
2022-10-18 14:59:23 +08:00
return getXmlDocument(asyncSystem, pAssetAccessor, xmlUrl, this->_headers)
.thenInMainThread(
2021-03-09 08:37:45 +08:00
[pOwner,
asyncSystem,
pAssetAccessor,
credit,
pPrepareRendererResources,
pLogger,
options = this->_options,
url = this->_url,
2022-10-18 14:59:23 +08:00
headers = this->_headers](
GetXmlDocumentResult&& xml) -> CreateTileProviderResult {
if (!xml) {
return nonstd::make_unexpected(std::move(xml).error());
}
2022-10-18 14:59:23 +08:00
std::unique_ptr<tinyxml2::XMLDocument> pDoc = std::move(*xml);
2022-07-19 01:40:31 +08:00
tinyxml2::XMLElement* pRoot = pDoc->RootElement();
2021-03-09 08:37:45 +08:00
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;
2022-07-30 02:39:43 +08:00
std::vector<TileMapServiceTileset> tileSets;
2022-07-15 05:47:15 +08:00
2021-03-09 08:37:45 +08:00
tinyxml2::XMLElement* pTilesets =
pRoot->FirstChildElement("TileSets");
if (pTilesets) {
2021-03-09 08:37:45 +08:00
tinyxml2::XMLElement* pTileset =
pTilesets->FirstChildElement("TileSet");
while (pTileset) {
2022-07-30 02:39:43 +08:00
TileMapServiceTileset& tileSet = tileSets.emplace_back();
2022-07-30 07:02:35 +08:00
tileSet.level =
getAttributeUint32(pTileset, "order").value_or(0);
minimumLevel = glm::min(minimumLevel, tileSet.level);
maximumLevel = glm::max(maximumLevel, tileSet.level);
2022-07-19 01:40:31 +08:00
tileSet.url = getAttributeString(pTileset, "href")
2022-07-30 07:02:35 +08:00
.value_or(std::to_string(tileSet.level));
2021-03-09 08:37:45 +08:00
pTileset = pTileset->NextSiblingElement("TileSet");
}
}
if (maximumLevel < minimumLevel && maximumLevel == 0) {
// Min and max levels unknown, so use defaults.
minimumLevel = 0;
maximumLevel = 25;
}
2021-03-09 08:37:45 +08:00
CesiumGeospatial::GlobeRectangle tilingSchemeRectangle =
CesiumGeospatial::GeographicProjection::MAXIMUM_GLOBE_RECTANGLE;
CesiumGeospatial::Projection projection;
uint32_t rootTilesX = 1;
bool isRectangleInDegrees = false;
if (options.projection) {
2021-03-09 08:37:45 +08:00
projection = options.projection.value();
} else {
2021-03-09 08:37:45 +08:00
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();
2022-07-19 05:47:21 +08:00
if (srsText.find("4326") != std::string::npos) {
projection = CesiumGeospatial::GeographicProjection();
tilingSchemeRectangle = CesiumGeospatial::
GeographicProjection::MAXIMUM_GLOBE_RECTANGLE;
rootTilesX = 2;
isRectangleInDegrees = true;
2022-07-21 03:45:31 +08:00
} 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;
}
}
2021-03-09 08:37:45 +08:00
}
}
minimumLevel = glm::min(minimumLevel, maximumLevel);
minimumLevel = options.minimumLevel.value_or(minimumLevel);
maximumLevel = options.maximumLevel.value_or(maximumLevel);
2021-03-09 08:37:45 +08:00
CesiumGeometry::Rectangle coverageRectangle =
projectRectangleSimple(projection, tilingSchemeRectangle);
if (options.coverageRectangle) {
2021-03-09 08:37:45 +08:00
coverageRectangle = options.coverageRectangle.value();
} else {
2021-03-09 08:37:45 +08:00
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());
}
2021-03-09 08:37:45 +08:00
}
}
CesiumGeometry::QuadtreeTilingScheme tilingScheme(
projectRectangleSimple(projection, tilingSchemeRectangle),
rootTilesX,
2021-03-09 08:37:45 +08:00
1);
2022-07-21 04:36:45 +08:00
std::string baseUrl = url;
2022-07-30 07:02:35 +08:00
if (!(baseUrl.size() < 4)) {
if (baseUrl.substr(baseUrl.size() - 4, 4) != ".xml") {
if (baseUrl[baseUrl.size() - 1] != '/') {
baseUrl += "/";
}
2022-07-21 04:36:45 +08:00
}
}
return new TileMapServiceTileProvider(
pOwner,
asyncSystem,
2021-02-21 20:13:42 +08:00
pAssetAccessor,
credit,
pPrepareRendererResources,
2021-01-08 20:10:27 +08:00
pLogger,
projection,
tilingScheme,
coverageRectangle,
2022-07-21 04:36:45 +08:00
baseUrl,
headers,
!fileExtension.empty() ? "." + fileExtension : fileExtension,
tileWidth,
tileHeight,
minimumLevel,
2022-07-15 05:47:15 +08:00
maximumLevel,
tileSets);
2021-03-09 08:37:45 +08:00
});
}
2021-03-09 08:37:45 +08:00
} // namespace Cesium3DTilesSelection