248 lines
8.3 KiB
C++
248 lines
8.3 KiB
C++
#include <CesiumAsync/AsyncSystem.h>
|
|
#include <CesiumAsync/Future.h>
|
|
#include <CesiumAsync/IAssetAccessor.h>
|
|
#include <CesiumAsync/ITaskProcessor.h>
|
|
#include <CesiumGeometry/QuadtreeTileID.h>
|
|
#include <CesiumGeometry/QuadtreeTilingScheme.h>
|
|
#include <CesiumGeometry/Rectangle.h>
|
|
#include <CesiumGeospatial/Ellipsoid.h>
|
|
#include <CesiumGeospatial/GeographicProjection.h>
|
|
#include <CesiumGeospatial/Projection.h>
|
|
#include <CesiumGeospatial/WebMercatorProjection.h>
|
|
#include <CesiumNativeTests/SimpleAssetAccessor.h>
|
|
#include <CesiumNativeTests/SimpleAssetRequest.h>
|
|
#include <CesiumRasterOverlays/ActivatedRasterOverlay.h>
|
|
#include <CesiumRasterOverlays/CreateRasterOverlayTileProviderParameters.h>
|
|
#include <CesiumRasterOverlays/QuadtreeRasterOverlayTileProvider.h>
|
|
#include <CesiumRasterOverlays/RasterOverlay.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayTile.h>
|
|
#include <CesiumRasterOverlays/RasterOverlayTileProvider.h>
|
|
#include <CesiumUtility/IntrusivePointer.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <glm/ext/vector_double2.hpp>
|
|
#include <spdlog/logger.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace CesiumAsync;
|
|
using namespace CesiumGeometry;
|
|
using namespace CesiumGeospatial;
|
|
using namespace CesiumGltf;
|
|
using namespace CesiumUtility;
|
|
using namespace CesiumNativeTests;
|
|
using namespace CesiumRasterOverlays;
|
|
|
|
namespace {
|
|
|
|
class TestTileProvider : public QuadtreeRasterOverlayTileProvider {
|
|
public:
|
|
TestTileProvider(
|
|
const IntrusivePointer<const RasterOverlay>& pCreator,
|
|
const CreateRasterOverlayTileProviderParameters& parameters,
|
|
const CesiumGeospatial::Projection& projection,
|
|
const CesiumGeometry::QuadtreeTilingScheme& tilingScheme,
|
|
const CesiumGeometry::Rectangle& coverageRectangle,
|
|
uint32_t minimumLevel,
|
|
uint32_t maximumLevel,
|
|
uint32_t imageWidth,
|
|
uint32_t imageHeight) noexcept
|
|
: QuadtreeRasterOverlayTileProvider(
|
|
pCreator,
|
|
parameters,
|
|
projection,
|
|
tilingScheme,
|
|
coverageRectangle,
|
|
minimumLevel,
|
|
maximumLevel,
|
|
imageWidth,
|
|
imageHeight) {}
|
|
|
|
// The tiles that will return an error from loadQuadtreeTileImage.
|
|
std::vector<QuadtreeTileID> errorTiles;
|
|
|
|
virtual CesiumAsync::Future<LoadedRasterOverlayImage>
|
|
loadQuadtreeTileImage(const QuadtreeTileID& tileID) const {
|
|
LoadedRasterOverlayImage result;
|
|
result.rectangle = this->getTilingScheme().tileToRectangle(tileID);
|
|
|
|
if (std::find(errorTiles.begin(), errorTiles.end(), tileID) !=
|
|
errorTiles.end()) {
|
|
result.errorList.emplaceError("Tile errored.");
|
|
} else {
|
|
// Return an image where every component of every pixel is equal to the
|
|
// tile level.
|
|
result.pImage.emplace();
|
|
result.pImage->width = int32_t(this->getWidth());
|
|
result.pImage->height = int32_t(this->getHeight());
|
|
result.pImage->bytesPerChannel = 1;
|
|
result.pImage->channels = 4;
|
|
result.pImage->pixelData.resize(
|
|
static_cast<size_t>(this->getWidth() * this->getHeight() * 4),
|
|
std::byte(tileID.level));
|
|
}
|
|
|
|
return this->getAsyncSystem().createResolvedFuture(std::move(result));
|
|
}
|
|
};
|
|
|
|
class TestRasterOverlay : public RasterOverlay {
|
|
public:
|
|
TestRasterOverlay(
|
|
const std::string& name,
|
|
const RasterOverlayOptions& options = RasterOverlayOptions())
|
|
: RasterOverlay(name, options) {}
|
|
|
|
virtual CesiumAsync::Future<CreateTileProviderResult> createTileProvider(
|
|
const CreateRasterOverlayTileProviderParameters& parameters)
|
|
const override {
|
|
return parameters.externals.asyncSystem
|
|
.createResolvedFuture<CreateTileProviderResult>(new TestTileProvider(
|
|
this,
|
|
parameters,
|
|
WebMercatorProjection(Ellipsoid::WGS84),
|
|
QuadtreeTilingScheme(
|
|
WebMercatorProjection::computeMaximumProjectedRectangle(
|
|
Ellipsoid::WGS84),
|
|
1,
|
|
1),
|
|
WebMercatorProjection::computeMaximumProjectedRectangle(
|
|
Ellipsoid::WGS84),
|
|
0,
|
|
10,
|
|
256,
|
|
256));
|
|
}
|
|
};
|
|
|
|
class MockTaskProcessor : public ITaskProcessor {
|
|
public:
|
|
virtual void startTask(std::function<void()> f) { std::thread(f).detach(); }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("QuadtreeRasterOverlayTileProvider getTile") {
|
|
auto pTaskProcessor = std::make_shared<MockTaskProcessor>();
|
|
auto pAssetAccessor = std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>());
|
|
|
|
AsyncSystem asyncSystem(pTaskProcessor);
|
|
IntrusivePointer<TestRasterOverlay> pOverlay = new TestRasterOverlay("Test");
|
|
|
|
IntrusivePointer<ActivatedRasterOverlay> pActivated = pOverlay->activate(
|
|
RasterOverlayExternals{
|
|
pAssetAccessor,
|
|
nullptr,
|
|
asyncSystem,
|
|
nullptr,
|
|
spdlog::default_logger()},
|
|
Ellipsoid::WGS84);
|
|
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
|
|
REQUIRE(pActivated->getTileProvider() != nullptr);
|
|
|
|
SUBCASE("uses root tile for a large area") {
|
|
Rectangle rectangle =
|
|
GeographicProjection::computeMaximumProjectedRectangle(
|
|
Ellipsoid::WGS84);
|
|
IntrusivePointer<RasterOverlayTile> pTile =
|
|
pActivated->getTile(rectangle, glm::dvec2(256));
|
|
pActivated->loadTile(*pTile);
|
|
|
|
while (pTile->getState() != RasterOverlayTile::LoadState::Loaded) {
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
}
|
|
|
|
CHECK(pTile->getState() == RasterOverlayTile::LoadState::Loaded);
|
|
|
|
REQUIRE(pTile->getImage());
|
|
|
|
const ImageAsset& image = *pTile->getImage();
|
|
CHECK(image.width > 0);
|
|
CHECK(image.height > 0);
|
|
CHECK(image.pixelData.size() > 0);
|
|
CHECK(std::all_of(
|
|
image.pixelData.begin(),
|
|
image.pixelData.end(),
|
|
[](std::byte b) { return b == std::byte(0); }));
|
|
}
|
|
|
|
SUBCASE("uses a mix of levels when a tile returns an error") {
|
|
glm::dvec2 center(0.1, 0.2);
|
|
|
|
TestTileProvider* pTestProvider =
|
|
static_cast<TestTileProvider*>(pActivated->getTileProvider());
|
|
|
|
// Select a rectangle that spans four tiles at tile level 8.
|
|
const uint32_t expectedLevel = 8;
|
|
std::optional<QuadtreeTileID> centerTileID =
|
|
pTestProvider->getTilingScheme().positionToTile(center, expectedLevel);
|
|
REQUIRE(centerTileID);
|
|
|
|
Rectangle centerRectangle =
|
|
pTestProvider->getTilingScheme().tileToRectangle(*centerTileID);
|
|
Rectangle tileRectangle(
|
|
centerRectangle.minimumX - centerRectangle.computeWidth() * 0.5,
|
|
centerRectangle.minimumY - centerRectangle.computeHeight() * 0.5,
|
|
centerRectangle.maximumX + centerRectangle.computeWidth() * 0.5,
|
|
centerRectangle.maximumY + centerRectangle.computeHeight() * 0.5);
|
|
|
|
uint32_t rasterSSE = 2;
|
|
glm::dvec2 targetScreenPixels = glm::dvec2(
|
|
pTestProvider->getWidth() * 2 * rasterSSE,
|
|
pTestProvider->getHeight() * 2 * rasterSSE);
|
|
|
|
// The tile in the southeast corner will fail to load.
|
|
std::optional<QuadtreeTileID> southeastID =
|
|
pTestProvider->getTilingScheme().positionToTile(
|
|
tileRectangle.getLowerRight(),
|
|
expectedLevel);
|
|
REQUIRE(southeastID);
|
|
|
|
pTestProvider->errorTiles.emplace_back(*southeastID);
|
|
|
|
IntrusivePointer<RasterOverlayTile> pTile =
|
|
pActivated->getTile(tileRectangle, targetScreenPixels);
|
|
pActivated->loadTile(*pTile);
|
|
|
|
while (pTile->getState() != RasterOverlayTile::LoadState::Loaded) {
|
|
asyncSystem.dispatchMainThreadTasks();
|
|
}
|
|
|
|
CHECK(pTile->getState() == RasterOverlayTile::LoadState::Loaded);
|
|
|
|
REQUIRE(pTile->getImage());
|
|
|
|
const ImageAsset& image = *pTile->getImage();
|
|
CHECK(image.width > 0);
|
|
CHECK(image.height > 0);
|
|
CHECK(image.pixelData.size() > 0);
|
|
|
|
// We should have pixels from both level 7 and level 8.
|
|
CHECK(std::all_of(
|
|
image.pixelData.begin(),
|
|
image.pixelData.end(),
|
|
[](std::byte b) { return b == std::byte(7) || b == std::byte(8); }));
|
|
CHECK(std::any_of(
|
|
image.pixelData.begin(),
|
|
image.pixelData.end(),
|
|
[](std::byte b) { return b == std::byte(7); }));
|
|
CHECK(std::any_of(
|
|
image.pixelData.begin(),
|
|
image.pixelData.end(),
|
|
[](std::byte b) { return b == std::byte(8); }));
|
|
}
|
|
}
|