Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GlobeRectangle::splitAtAntiMeridian #848

Merged
merged 13 commits into from
Apr 23, 2024
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Added `NormalAccessorType`, which is a type definition for a normal accessor. It can be constructed using `getNormalAccessorView`.
- Added `Uri::getPath` and `Uri::setPath`.
- Added `TileTransform::setTransform`.
- Added `GlobeRectangle::splitAtAntiMeridian`.
- Added a new `CesiumQuantizedMeshTerrain` library and namespace, containing classes for working with terrain in the `quantized-mesh-1.0` format and its `layer.json` file.
- Added `BoundingRegionBuilder::toGlobeRectangle`.
- Added `GlobeRectangle::equals` and `GlobeRectangle::equalsEpsilon`.
Expand Down Expand Up @@ -44,6 +45,7 @@
- After a glTF has been Draco-decoded, the `KHR_draco_mesh_compression` extension is now removed from the primitives, as well as from `extensionsUsed` and `extensionsRequired`.
- For glTFs converted from quantized-mesh tiles, accessors created for the position attribute now have their minimum and maximum values set correctly to include the vertices that form the skirt around the edge of the tile.
- Fixed some glTF validation problems with the mode produced by `upsampleGltfForRasterOverlays`.
- `RasterOverlayUtilities::createRasterOverlayTextureCoordinates` no longer fails when the model spans the anti-meridian. However, only the larger part of the model on one side of the anti-meridian will have useful texture coordinates.

### v0.34.0 - 2024-04-01

Expand Down
14 changes: 14 additions & 0 deletions CesiumGeospatial/include/CesiumGeospatial/GlobeRectangle.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,19 @@ class CESIUMGEOSPATIAL_API GlobeRectangle final {
GlobeRectangle computeUnion(const GlobeRectangle& other) const noexcept;

/**
* @brief Splits this rectangle at the anti-meridian (180 degrees longitude),
* if necessary.
*
* If the rectangle does not cross the anti-meridian, the entire rectangle is
* returned in the `first` field of the pair and the `second` is std::nullopt.
* If it does cross the anti-meridian, this function returns two rectangles
* that touch but do not cross it. The larger of the two rectangles is
* returned in `first` and the smaller one is returned in `second`.
*/
std::pair<GlobeRectangle, std::optional<GlobeRectangle>>
splitAtAntiMeridian() const noexcept;

/*
* @brief Checks whether two globe rectangles are exactly equal.
*
* @param left The first rectangle.
Expand Down Expand Up @@ -271,4 +284,5 @@ class CESIUMGEOSPATIAL_API GlobeRectangle final {
double _east;
double _north;
};

} // namespace CesiumGeospatial
16 changes: 16 additions & 0 deletions CesiumGeospatial/src/GlobeRectangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ GlobeRectangle::computeUnion(const GlobeRectangle& other) const noexcept {
glm::max(this->_north, other._north));
}

std::pair<GlobeRectangle, std::optional<GlobeRectangle>>
GlobeRectangle::splitAtAntiMeridian() const noexcept {
if (this->_west <= this->_east) {
return {*this, std::nullopt};
}

GlobeRectangle a(this->_west, this->_south, Math::OnePi, this->_north);
GlobeRectangle b(-Math::OnePi, this->_south, this->_east, this->_north);

if (a.computeWidth() > b.computeWidth()) {
return {a, b};
} else {
return {b, a};
}
}

bool GlobeRectangle::equals(
const GlobeRectangle& left,
const GlobeRectangle& right) noexcept {
Expand Down
65 changes: 65 additions & 0 deletions CesiumGeospatial/test/TestGlobeRectangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,71 @@ TEST_CASE("GlobeRectangle::equals") {
GlobeRectangle unequalNorth(0.1, 0.2, 0.3, 0.5);
CHECK(!GlobeRectangle::equals(simple, unequalNorth));
}

SECTION("splitAtAntiMeridian") {
// Cross Prime meridian, do not cross Antimeridian
GlobeRectangle nonCrossing =
GlobeRectangle::fromDegrees(-10.0, -20.0, 30.0, 40.0);
std::pair<GlobeRectangle, std::optional<GlobeRectangle>> split =
nonCrossing.splitAtAntiMeridian();
CHECK(!split.second);
CHECK(split.first.getWest() == nonCrossing.getWest());
CHECK(split.first.getEast() == nonCrossing.getEast());
CHECK(split.first.getSouth() == nonCrossing.getSouth());
CHECK(split.first.getNorth() == nonCrossing.getNorth());

// Do not cross Prime or Antimeridian
GlobeRectangle nonCrossing2 =
GlobeRectangle::fromDegrees(10.0, -20.0, 30.0, 40.0);
split = nonCrossing2.splitAtAntiMeridian();
CHECK(!split.second);
CHECK(split.first.getWest() == nonCrossing2.getWest());
CHECK(split.first.getEast() == nonCrossing2.getEast());
CHECK(split.first.getSouth() == nonCrossing2.getSouth());
CHECK(split.first.getNorth() == nonCrossing2.getNorth());

// Cross Antimeridian
GlobeRectangle crossing1 =
GlobeRectangle::fromDegrees(160.0, -20.0, -170.0, 40.0);
split = crossing1.splitAtAntiMeridian();
CHECK(split.first.getWest() == crossing1.getWest());
CHECK(split.first.getEast() == Math::OnePi);
CHECK(split.first.getSouth() == crossing1.getSouth());
CHECK(split.first.getNorth() == crossing1.getNorth());
REQUIRE(split.second);
CHECK(split.second->getWest() == -Math::OnePi);
CHECK(split.second->getEast() == crossing1.getEast());
CHECK(split.second->getSouth() == crossing1.getSouth());
CHECK(split.second->getNorth() == crossing1.getNorth());

// Same test, offset, with different returned order
GlobeRectangle crossing2 =
GlobeRectangle::fromDegrees(170.0, -20.0, -160.0, 40.0);
split = crossing2.splitAtAntiMeridian();
CHECK(split.first.getWest() == -Math::OnePi);
CHECK(split.first.getEast() == crossing2.getEast());
CHECK(split.first.getSouth() == crossing2.getSouth());
CHECK(split.first.getNorth() == crossing2.getNorth());
REQUIRE(split.second);
CHECK(split.second->getWest() == crossing2.getWest());
CHECK(split.second->getEast() == Math::OnePi);
CHECK(split.second->getSouth() == crossing2.getSouth());
CHECK(split.second->getNorth() == crossing2.getNorth());

// Cross Prime and Antimeridian
GlobeRectangle crossing3 =
GlobeRectangle::fromDegrees(-10.0, -20.0, -160.0, 40.0);
split = crossing3.splitAtAntiMeridian();
CHECK(split.first.getWest() == crossing3.getWest());
CHECK(split.first.getEast() == Math::OnePi);
CHECK(split.first.getSouth() == crossing3.getSouth());
CHECK(split.first.getNorth() == crossing3.getNorth());
REQUIRE(split.second);
CHECK(split.second->getWest() == -Math::OnePi);
CHECK(split.second->getEast() == crossing3.getEast());
CHECK(split.second->getSouth() == crossing3.getSouth());
CHECK(split.second->getNorth() == crossing3.getNorth());
}
}

TEST_CASE("GlobeRectangle::equalsEpsilon") {
Expand Down
9 changes: 9 additions & 0 deletions CesiumRasterOverlays/src/RasterOverlayUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ RasterOverlayUtilities::createRasterOverlayTextureCoordinates(
: GltfUtilities::computeBoundingRegion(model, modelToEcefTransform)
.getRectangle();

// Don't let the bounding rectangle cross the anti-meridian. If it does, split
// it into two rectangles. Ideally we'd map both of them (separately) to the
// model, but in our current "only Geographic and Web Mercator are supported"
// world, crossing the anti-meridian is almost certain to simply be numerical
// noise. So we just use the larger of the two rectangles.
std::pair<GlobeRectangle, std::optional<GlobeRectangle>> splits =
bounds.splitAtAntiMeridian();
bounds = splits.first;

// Currently, a Longitude/Latitude Rectangle maps perfectly to all possible
// projection types, because the only possible projection types are
// Geographic and Web Mercator. In the future if/when we add projections
Expand Down
Loading