290 lines
10 KiB
C++
290 lines
10 KiB
C++
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4100 4127 4309 4996)
|
|
|
|
#define _CHAR_UNSIGNED
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#define _USE_MATH_DEFINES
|
|
#endif
|
|
|
|
// #include <s2/s2cell.h>
|
|
#include <s2/r2rect.h>
|
|
#include <s2/s1interval.h>
|
|
#include <s2/s2cell_id.h>
|
|
#include <s2/s2coords.h>
|
|
#include <s2/s2latlng.h>
|
|
#include <s2/s2point.h>
|
|
// #include <s2/s2latlng_rect.h>
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
#include "HilbertOrder.h"
|
|
|
|
#include <CesiumGeometry/QuadtreeTileID.h>
|
|
#include <CesiumGeospatial/Cartographic.h>
|
|
#include <CesiumGeospatial/GlobeRectangle.h>
|
|
#include <CesiumGeospatial/S2CellID.h>
|
|
#include <CesiumUtility/Assert.h>
|
|
#include <CesiumUtility/Math.h>
|
|
|
|
#include <array>
|
|
#include <cfloat>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
using namespace CesiumGeometry;
|
|
using namespace CesiumGeospatial;
|
|
|
|
using GoogleS2CellID = S2CellId;
|
|
|
|
/*static*/ S2CellID S2CellID::fromToken(const std::string_view& token) {
|
|
return S2CellID(
|
|
GoogleS2CellID::FromToken(absl::string_view(token.data(), token.size()))
|
|
.id());
|
|
}
|
|
|
|
/*static*/ S2CellID S2CellID::fromFaceLevelPosition(
|
|
uint8_t face,
|
|
uint32_t level,
|
|
uint64_t position) {
|
|
// Convert the position-within-level to an absolute, level 30 position as
|
|
// expected by the Google implementation.
|
|
position <<= (GoogleS2CellID::kMaxLevel - level) * 2 + 1;
|
|
return S2CellID(
|
|
GoogleS2CellID::FromFacePosLevel(int(face), position, int(level)).id());
|
|
}
|
|
|
|
/*static*/ S2CellID S2CellID::fromQuadtreeTileID(
|
|
uint8_t face,
|
|
const QuadtreeTileID& quadtreeTileID) {
|
|
|
|
uint64_t position = (face & 1) == 0 ? HilbertOrder::encode2D(
|
|
quadtreeTileID.level,
|
|
quadtreeTileID.x,
|
|
quadtreeTileID.y)
|
|
: HilbertOrder::encode2D(
|
|
quadtreeTileID.level,
|
|
quadtreeTileID.y,
|
|
quadtreeTileID.x);
|
|
|
|
return S2CellID::fromFaceLevelPosition(face, quadtreeTileID.level, position);
|
|
}
|
|
|
|
S2CellID::S2CellID(uint64_t id) : _id(id) {}
|
|
|
|
bool S2CellID::isValid() const { return GoogleS2CellID(this->_id).is_valid(); }
|
|
|
|
std::string S2CellID::toToken() const {
|
|
return GoogleS2CellID(this->_id).ToToken();
|
|
}
|
|
|
|
int32_t S2CellID::getLevel() const { return GoogleS2CellID(this->_id).level(); }
|
|
|
|
uint8_t S2CellID::getFace() const {
|
|
return static_cast<uint8_t>(GoogleS2CellID(this->_id).face());
|
|
}
|
|
|
|
Cartographic S2CellID::getCenter() const {
|
|
S2LatLng ll = GoogleS2CellID(this->_id).ToLatLng();
|
|
return Cartographic(ll.lng().radians(), ll.lat().radians(), 0.0);
|
|
}
|
|
|
|
namespace {
|
|
Cartographic toCartographic(const S2Point& p) {
|
|
S2LatLng ll(p);
|
|
return Cartographic(ll.lng().radians(), ll.lat().radians(), 0.0);
|
|
}
|
|
} // namespace
|
|
|
|
std::array<Cartographic, 4> S2CellID::getVertices() const {
|
|
GoogleS2CellID cell(this->_id);
|
|
R2Rect rect = cell.GetBoundUV();
|
|
int face = cell.face();
|
|
return {
|
|
toCartographic(S2::FaceUVtoXYZ(face, rect.GetVertex(0, 0))),
|
|
toCartographic(S2::FaceUVtoXYZ(face, rect.GetVertex(1, 0))),
|
|
toCartographic(S2::FaceUVtoXYZ(face, rect.GetVertex(1, 1))),
|
|
toCartographic(S2::FaceUVtoXYZ(face, rect.GetVertex(0, 1)))};
|
|
}
|
|
|
|
S2CellID S2CellID::getParent() const {
|
|
return S2CellID(GoogleS2CellID(this->_id).parent().id());
|
|
}
|
|
|
|
S2CellID S2CellID::getChild(size_t index) const {
|
|
CESIUM_ASSERT(index <= 3);
|
|
return S2CellID(GoogleS2CellID(this->_id).child(int(index)).id());
|
|
}
|
|
|
|
namespace {
|
|
|
|
// These anonymous-namespace functions are adaptations from s2geometry.
|
|
double GetLatitude(const R2Rect& uv_, int face_, int i, int j) {
|
|
S2Point p = S2::FaceUVtoXYZ(face_, uv_[0][i], uv_[1][j]);
|
|
return S2LatLng::Latitude(p).radians();
|
|
}
|
|
|
|
double GetLongitude(const R2Rect& uv_, int face_, int i, int j) {
|
|
S2Point p = S2::FaceUVtoXYZ(face_, uv_[0][i], uv_[1][j]);
|
|
return S2LatLng::Longitude(p).radians();
|
|
}
|
|
|
|
GlobeRectangle
|
|
GlobeRectangleFromLatLng(const R1Interval& lat_, const S1Interval& lng_) {
|
|
return GlobeRectangle(lng_.lo(), lat_.lo(), lng_.hi(), lat_.hi());
|
|
}
|
|
|
|
R1Interval FullLat() {
|
|
return R1Interval(
|
|
-CesiumUtility::Math::PiOverTwo,
|
|
CesiumUtility::Math::PiOverTwo);
|
|
}
|
|
|
|
GlobeRectangle Expanded(
|
|
const R1Interval& lat_,
|
|
const S1Interval& lng_,
|
|
const S2LatLng& margin) {
|
|
R1Interval lat = lat_.Expanded(margin.lat().radians());
|
|
S1Interval lng = lng_.Expanded(margin.lng().radians());
|
|
if (lat.is_empty() || lng.is_empty())
|
|
return GlobeRectangle(0.0, 0.0, 0.0, 0.0);
|
|
return GlobeRectangleFromLatLng(lat.Intersection(FullLat()), lng);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
GlobeRectangle S2CellID::computeBoundingRectangle() const {
|
|
GoogleS2CellID google(this->_id);
|
|
R2Rect uv_ = google.GetBoundUV();
|
|
int level_ = google.level();
|
|
int face_ = google.face();
|
|
|
|
// The below code is copied from s2geometry's s2cell.cc.
|
|
// We copy it rather than just call it like we do with other code in that
|
|
// library because s2cell.cc has some steep dependencies that eventually lead
|
|
// to requiring OpenSSL.
|
|
|
|
// However, we've changed the implementation slightly from s2geometry. In
|
|
// s2geometry, a cell that contains the North or South pole is deemed to
|
|
// include all longitudes. While this is technically true, because any
|
|
// longitude at +/-PI/2 radians latitude is at the pole, it is still
|
|
// "losing information" to present it that way. We'd rather return the
|
|
// actual longitude bounds and let client code account for the singularity
|
|
// when necessary. So we don't do the "PolarClosure" operation performed by
|
|
// the original implementation.
|
|
|
|
if (level_ > 0) {
|
|
// Except for cells at level 0, the latitude and longitude extremes are
|
|
// attained at the vertices. Furthermore, the latitude range is
|
|
// determined by one pair of diagonally opposite vertices and the
|
|
// longitude range is determined by the other pair.
|
|
//
|
|
// We first determine which corner (i,j) of the cell has the largest
|
|
// absolute latitude. To maximize latitude, we want to find the point in
|
|
// the cell that has the largest absolute z-coordinate and the smallest
|
|
// absolute x- and y-coordinates. To do this we look at each coordinate
|
|
// (u and v), and determine whether we want to minimize or maximize that
|
|
// coordinate based on the axis direction and the cell's (u,v) quadrant.
|
|
double u = uv_[0][0] + uv_[0][1];
|
|
double v = uv_[1][0] + uv_[1][1];
|
|
int i = S2::GetUAxis(face_)[2] == 0 ? (u < 0) : (u > 0);
|
|
int j = S2::GetVAxis(face_)[2] == 0 ? (v < 0) : (v > 0);
|
|
R1Interval lat = R1Interval::FromPointPair(
|
|
GetLatitude(uv_, face_, i, j),
|
|
GetLatitude(uv_, face_, 1 - i, 1 - j));
|
|
S1Interval lng = S1Interval::FromPointPair(
|
|
GetLongitude(uv_, face_, i, 1 - j),
|
|
GetLongitude(uv_, face_, 1 - i, j));
|
|
|
|
// We grow the bounds slightly to make sure that the bounding rectangle
|
|
// contains S2LatLng(P) for any point P inside the loop L defined by the
|
|
// four *normalized* vertices. Note that normalization of a vector can
|
|
// change its direction by up to 0.5 * DBL_EPSILON radians, and it is not
|
|
// enough just to add Normalize() calls to the code above because the
|
|
// latitude/longitude ranges are not necessarily determined by diagonally
|
|
// opposite vertex pairs after normalization.
|
|
//
|
|
// We would like to bound the amount by which the latitude/longitude of a
|
|
// contained point P can exceed the bounds computed above. In the case of
|
|
// longitude, the normalization error can change the direction of rounding
|
|
// leading to a maximum difference in longitude of 2 * DBL_EPSILON. In
|
|
// the case of latitude, the normalization error can shift the latitude by
|
|
// up to 0.5 * DBL_EPSILON and the other sources of error can cause the
|
|
// two latitudes to differ by up to another 1.5 * DBL_EPSILON, which also
|
|
// leads to a maximum difference of 2 * DBL_EPSILON.
|
|
return Expanded(
|
|
lat,
|
|
lng,
|
|
S2LatLng::FromRadians(2 * DBL_EPSILON, 2 * DBL_EPSILON));
|
|
}
|
|
|
|
// The 4 cells around the equator extend to +/-45 degrees latitude at the
|
|
// midpoints of their top and bottom edges. The two cells covering the
|
|
// poles extend down to +/-35.26 degrees at their vertices. The maximum
|
|
// error in this calculation is 0.5 * DBL_EPSILON.
|
|
static const double kPoleMinLat = asin(sqrt(1. / 3)) - 0.5 * DBL_EPSILON;
|
|
|
|
// The face centers are the +X, +Y, +Z, -X, -Y, -Z axes in that order.
|
|
CESIUM_ASSERT(((face_ < 3) ? 1 : -1) == S2::GetNorm(face_)[face_ % 3]);
|
|
|
|
R1Interval lat;
|
|
S1Interval lng;
|
|
switch (face_) {
|
|
case 0:
|
|
lat = R1Interval(
|
|
-CesiumUtility::Math::PiOverFour,
|
|
CesiumUtility::Math::PiOverFour);
|
|
lng = S1Interval(
|
|
-CesiumUtility::Math::PiOverFour,
|
|
CesiumUtility::Math::PiOverFour);
|
|
break;
|
|
case 1:
|
|
lat = R1Interval(
|
|
-CesiumUtility::Math::PiOverFour,
|
|
CesiumUtility::Math::PiOverFour);
|
|
lng = S1Interval(
|
|
CesiumUtility::Math::PiOverFour,
|
|
3 * CesiumUtility::Math::PiOverFour);
|
|
break;
|
|
case 2:
|
|
lat = R1Interval(kPoleMinLat, CesiumUtility::Math::PiOverTwo);
|
|
lng = S1Interval::Full();
|
|
break;
|
|
case 3:
|
|
lat = R1Interval(
|
|
-CesiumUtility::Math::PiOverFour,
|
|
CesiumUtility::Math::PiOverFour);
|
|
lng = S1Interval(
|
|
3 * CesiumUtility::Math::PiOverFour,
|
|
-3 * CesiumUtility::Math::PiOverFour);
|
|
break;
|
|
case 4:
|
|
lat = R1Interval(
|
|
-CesiumUtility::Math::PiOverFour,
|
|
CesiumUtility::Math::PiOverFour);
|
|
lng = S1Interval(
|
|
-3 * CesiumUtility::Math::PiOverFour,
|
|
-CesiumUtility::Math::PiOverFour);
|
|
break;
|
|
default:
|
|
lat = R1Interval(-CesiumUtility::Math::PiOverTwo, -kPoleMinLat);
|
|
lng = S1Interval::Full();
|
|
break;
|
|
}
|
|
// Finally, we expand the bound to account for the error when a point P is
|
|
// converted to an S2LatLng to test for containment. (The bound should be
|
|
// large enough so that it contains the computed S2LatLng of any contained
|
|
// point, not just the infinite-precision version.) We don't need to expand
|
|
// longitude because longitude is calculated via a single call to atan2(),
|
|
// which is guaranteed to be semi-monotonic. (In fact the Gnu implementation
|
|
// is also correctly rounded, but we don't even need that here.)
|
|
return Expanded(lat, lng, S2LatLng::FromRadians(DBL_EPSILON, 0));
|
|
}
|