blob: 8d891b585e314dc0493ac507453711eee2fc1a72 [file] [log] [blame]
// Copyright 2005 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/s2cellid.h"
#include <algorithm>
#include <cfloat>
#include <cstring>
#include <iosfwd>
#include <vector>
#include <mutex>
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "s2/r1interval.h"
#include "s2/s2latlng.h"
#ifdef _MSC_VER
#pragma warning(disable : 4018) /* '<' : signed/unsigned mismatch */
#endif
using S2::internal::kSwapMask;
using S2::internal::kInvertMask;
using S2::internal::kPosToIJ;
using S2::internal::kPosToOrientation;
using std::max;
using std::min;
using std::vector;
// The following lookup tables are used to convert efficiently between an
// (i,j) cell index and the corresponding position along the Hilbert curve.
// "lookup_pos" maps 4 bits of "i", 4 bits of "j", and 2 bits representing the
// orientation of the current cell into 8 bits representing the order in which
// that subcell is visited by the Hilbert curve, plus 2 bits indicating the
// new orientation of the Hilbert curve within that subcell. (Cell
// orientations are represented as combination of kSwapMask and kInvertMask.)
//
// "lookup_ij" is an inverted table used for mapping in the opposite
// direction.
//
// We also experimented with looking up 16 bits at a time (14 bits of position
// plus 2 of orientation) but found that smaller lookup tables gave better
// performance. (2KB fits easily in the primary cache.)
// Values for these constants are *declared* in the *.h file. Even though
// the declaration specifies a value for the constant, that declaration
// is not a *definition* of storage for the value. Because the values are
// supplied in the declaration, we don't need the values here. Failing to
// define storage causes link errors for any code that tries to take the
// address of one of these values.
int const S2CellId::kFaceBits;
int const S2CellId::kNumFaces;
int const S2CellId::kMaxLevel;
int const S2CellId::kPosBits;
int const S2CellId::kMaxSize;
static int const kLookupBits = 4;
static uint16_t lookup_pos[1 << (2 * kLookupBits + 2)];
static uint16_t lookup_ij[1 << (2 * kLookupBits + 2)];
static void InitLookupCell(int level,
int i,
int j,
int orig_orientation,
int pos,
int orientation) {
if (level == kLookupBits) {
int ij = (i << kLookupBits) + j;
lookup_pos[(ij << 2) + orig_orientation] = (pos << 2) + orientation;
lookup_ij[(pos << 2) + orig_orientation] = (ij << 2) + orientation;
} else {
level++;
i <<= 1;
j <<= 1;
pos <<= 2;
int const* r = kPosToIJ[orientation];
InitLookupCell(level, i + (r[0] >> 1), j + (r[0] & 1), orig_orientation,
pos, orientation ^ kPosToOrientation[0]);
InitLookupCell(level, i + (r[1] >> 1), j + (r[1] & 1), orig_orientation,
pos + 1, orientation ^ kPosToOrientation[1]);
InitLookupCell(level, i + (r[2] >> 1), j + (r[2] & 1), orig_orientation,
pos + 2, orientation ^ kPosToOrientation[2]);
InitLookupCell(level, i + (r[3] >> 1), j + (r[3] & 1), orig_orientation,
pos + 3, orientation ^ kPosToOrientation[3]);
}
}
static std::once_flag flag;
inline static void MaybeInit() {
std::call_once(flag, [] {
InitLookupCell(0, 0, 0, 0, 0, 0);
InitLookupCell(0, 0, 0, kSwapMask, 0, kSwapMask);
InitLookupCell(0, 0, 0, kInvertMask, 0, kInvertMask);
InitLookupCell(0, 0, 0, kSwapMask | kInvertMask, 0,
kSwapMask | kInvertMask);
});
}
S2CellId S2CellId::advance(int64_t steps) const {
if (steps == 0)
return *this;
// We clamp the number of steps if necessary to ensure that we do not
// advance past the End() or before the Begin() of this level. Note that
// min_steps and max_steps always fit in a signed 64-bit integer.
int step_shift = 2 * (kMaxLevel - level()) + 1;
if (steps < 0) {
int64_t min_steps = -static_cast<int64_t>(id_ >> step_shift);
if (steps < min_steps)
steps = min_steps;
} else {
int64_t max_steps = (kWrapOffset + lsb() - id_) >> step_shift;
if (steps > max_steps)
steps = max_steps;
}
// If steps is negative, then shifting it left has undefined behavior.
// Cast to uint64_t for a 2's complement answer.
return S2CellId(id_ + (static_cast<uint64_t>(steps) << step_shift));
}
int64_t S2CellId::distance_from_begin() const {
const int step_shift = 2 * (kMaxLevel - level()) + 1;
return id_ >> step_shift;
}
S2CellId S2CellId::advance_wrap(int64_t steps) const {
DCHECK(is_valid());
if (steps == 0)
return *this;
int step_shift = 2 * (kMaxLevel - level()) + 1;
if (steps < 0) {
int64_t min_steps = -static_cast<int64_t>(id_ >> step_shift);
if (steps < min_steps) {
int64_t step_wrap = kWrapOffset >> step_shift;
steps %= step_wrap;
if (steps < min_steps)
steps += step_wrap;
}
} else {
// Unlike advance(), we don't want to return End(level).
int64_t max_steps = (kWrapOffset - id_) >> step_shift;
if (steps > max_steps) {
int64_t step_wrap = kWrapOffset >> step_shift;
steps %= step_wrap;
if (steps > max_steps)
steps -= step_wrap;
}
}
return S2CellId(id_ + (static_cast<uint64_t>(steps) << step_shift));
}
S2CellId S2CellId::maximum_tile(S2CellId const limit) const {
S2CellId id = *this;
S2CellId start = id.range_min();
if (start >= limit.range_min())
return limit;
if (id.range_max() >= limit) {
// The cell is too large. Shrink it. Note that when generating coverings
// of S2CellId ranges, this loop usually executes only once. Also because
// id.range_min() < limit.range_min(), we will always exit the loop by the
// time we reach a leaf cell.
do {
id = id.child(0);
} while (id.range_max() >= limit);
return id;
}
// The cell may be too small. Grow it if necessary. Note that generally
// this loop only iterates once.
while (!id.is_face()) {
S2CellId parent = id.parent();
if (parent.range_min() != start || parent.range_max() >= limit)
break;
id = parent;
}
return id;
}
int S2CellId::GetCommonAncestorLevel(S2CellId other) const {
// Basically we find the first bit position at which the two S2CellIds
// differ and convert that to a level. The max() below is necessary for the
// case where one S2CellId is a descendant of the other.
uint64_t bits = max(id() ^ other.id(), max(lsb(), other.lsb()));
// Compute the position of the most significant bit, and then map
// {0} -> 30, {1,2} -> 29, {3,4} -> 28, ... , {59,60} -> 0, {61,62,63} -> -1.
return max(60 - Bits::FindMSBSetNonZero64(bits), -1) >> 1;
}
// Print the num_digits low order hex digits.
static std::string HexFormatString(uint64_t val, size_t num_digits) {
std::string result(num_digits, ' ');
for (; num_digits--; val >>= 4)
result[num_digits] = "0123456789abcdef"[val & 0xF];
return result;
}
std::string S2CellId::ToToken() const {
// Simple implementation: print the id in hex without trailing zeros.
// Using hex has the advantage that the tokens are case-insensitive, all
// characters are alphanumeric, no characters require any special escaping
// in queries for most indexing systems, and it's easy to compare cell
// tokens against the feature ids of the corresponding features.
//
// Using base 64 would produce slightly shorter tokens, but for typical cell
// sizes used during indexing (up to level 15 or so) the average savings
// would be less than 2 bytes per cell which doesn't seem worth it.
// "0" with trailing 0s stripped is the empty std::string, which is not a
// reasonable token. Encode as "X".
if (id_ == 0)
return "X";
size_t const num_zero_digits = Bits::FindLSBSetNonZero64(id_) / 4;
return HexFormatString(id_ >> (4 * num_zero_digits), 16 - num_zero_digits);
}
S2CellId S2CellId::FromToken(const char* token, size_t length) {
if (length > 16)
return S2CellId::None();
uint64_t id = 0;
for (int i = 0, pos = 60; i < length; ++i, pos -= 4) {
uint64_t d;
if ('0' <= token[i] && token[i] <= '9') {
d = token[i] - '0';
} else if ('a' <= token[i] && token[i] <= 'f') {
d = token[i] - 'a' + 10;
} else if ('A' <= token[i] && token[i] <= 'F') {
d = token[i] - 'A' + 10;
} else {
return S2CellId::None();
}
id |= d << pos;
}
return S2CellId(id);
}
S2CellId S2CellId::FromToken(std::string const& token) {
return FromToken(token.data(), token.size());
}
S2CellId S2CellId::FromFaceIJ(int face, int i, int j) {
// Initialization if not done yet
MaybeInit();
// Optimization notes:
// - Non-overlapping bit fields can be combined with either "+" or "|".
// Generally "+" seems to produce better code, but not always.
// Note that this value gets shifted one bit to the left at the end
// of the function.
uint64_t n = static_cast<uint64_t>(face) << (kPosBits - 1);
// Alternating faces have opposite Hilbert curve orientations; this
// is necessary in order for all faces to have a right-handed
// coordinate system.
uint64_t bits = (face & kSwapMask);
// Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert
// curve position. The lookup table transforms a 10-bit key of the form
// "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the
// letters [ijpo] denote bits of "i", "j", Hilbert curve position, and
// Hilbert curve orientation respectively.
#define GET_BITS(k) \
do { \
int const mask = (1 << kLookupBits) - 1; \
bits += ((i >> (k * kLookupBits)) & mask) << (kLookupBits + 2); \
bits += ((j >> (k * kLookupBits)) & mask) << 2; \
bits = lookup_pos[bits]; \
n |= (bits >> 2) << (k * 2 * kLookupBits); \
bits &= (kSwapMask | kInvertMask); \
} while (0)
GET_BITS(7);
GET_BITS(6);
GET_BITS(5);
GET_BITS(4);
GET_BITS(3);
GET_BITS(2);
GET_BITS(1);
GET_BITS(0);
#undef GET_BITS
return S2CellId(n * 2 + 1);
}
S2CellId::S2CellId(S2Point const& p) {
double u, v;
int face = S2::XYZtoFaceUV(p, &u, &v);
int i = S2::STtoIJ(S2::UVtoST(u));
int j = S2::STtoIJ(S2::UVtoST(v));
id_ = FromFaceIJ(face, i, j).id();
}
S2CellId::S2CellId(S2LatLng const& ll) : S2CellId(ll.ToPoint()) {}
int S2CellId::ToFaceIJOrientation(int* pi, int* pj, int* orientation) const {
// Initialization if not done yet
MaybeInit();
int i = 0, j = 0;
int face = this->face();
int bits = (face & kSwapMask);
// Each iteration maps 8 bits of the Hilbert curve position into
// 4 bits of "i" and "j". The lookup table transforms a key of the
// form "ppppppppoo" to a value of the form "iiiijjjjoo", where the
// letters [ijpo] represents bits of "i", "j", the Hilbert curve
// position, and the Hilbert curve orientation respectively.
//
// On the first iteration we need to be careful to clear out the bits
// representing the cube face.
#define GET_BITS(k) \
do { \
int const nbits = (k == 7) ? (kMaxLevel - 7 * kLookupBits) : kLookupBits; \
bits += (static_cast<int>(id_ >> (k * 2 * kLookupBits + 1)) & \
((1 << (2 * nbits)) - 1)) \
<< 2; \
bits = lookup_ij[bits]; \
i += (bits >> (kLookupBits + 2)) << (k * kLookupBits); \
j += ((bits >> 2) & ((1 << kLookupBits) - 1)) << (k * kLookupBits); \
bits &= (kSwapMask | kInvertMask); \
} while (0)
GET_BITS(7);
GET_BITS(6);
GET_BITS(5);
GET_BITS(4);
GET_BITS(3);
GET_BITS(2);
GET_BITS(1);
GET_BITS(0);
#undef GET_BITS
*pi = i;
*pj = j;
if (orientation != nullptr) {
// The position of a non-leaf cell at level "n" consists of a prefix of
// 2*n bits that identifies the cell, followed by a suffix of
// 2*(kMaxLevel-n)+1 bits of the form 10*. If n==kMaxLevel, the suffix is
// just "1" and has no effect. Otherwise, it consists of "10", followed
// by (kMaxLevel-n-1) repetitions of "00", followed by "0". The "10" has
// no effect, while each occurrence of "00" has the effect of reversing
// the kSwapMask bit.
DCHECK_EQ(0, kPosToOrientation[2]);
DCHECK_EQ(kSwapMask, kPosToOrientation[0]);
if (lsb() & static_cast<uint64_t>(0x1111111111111110)) {
bits ^= kSwapMask;
}
*orientation = bits;
}
return face;
}
S2Point S2CellId::ToPointRaw() const {
int si, ti;
int face = GetCenterSiTi(&si, &ti);
return S2::FaceSiTitoXYZ(face, si, ti);
}
S2LatLng S2CellId::ToLatLng() const {
return S2LatLng(ToPointRaw());
}
R2Point S2CellId::GetCenterST() const {
int si, ti;
GetCenterSiTi(&si, &ti);
return R2Point(S2::SiTitoST(si), S2::SiTitoST(ti));
}
R2Point S2CellId::GetCenterUV() const {
int si, ti;
GetCenterSiTi(&si, &ti);
return R2Point(S2::STtoUV(S2::SiTitoST(si)), S2::STtoUV(S2::SiTitoST(ti)));
}
R2Rect S2CellId::IJLevelToBoundUV(int ij[2], int level) {
R2Rect bound;
int cell_size = GetSizeIJ(level);
for (int d = 0; d < 2; ++d) {
int ij_lo = ij[d] & -cell_size;
int ij_hi = ij_lo + cell_size;
bound[d][0] = S2::STtoUV(S2::IJtoSTMin(ij_lo));
bound[d][1] = S2::STtoUV(S2::IJtoSTMin(ij_hi));
}
return bound;
}
R2Rect S2CellId::GetBoundST() const {
double size = GetSizeST();
return R2Rect::FromCenterSize(GetCenterST(), R2Point(size, size));
}
R2Rect S2CellId::GetBoundUV() const {
int ij[2];
ToFaceIJOrientation(&ij[0], &ij[1], nullptr);
return IJLevelToBoundUV(ij, level());
}
// This is a helper function for ExpandedByDistanceUV().
//
// Given an edge of the form (u,v0)-(u,v1), let max_v = max(abs(v0), abs(v1)).
// This method returns a new u-coordinate u' such that the distance from the
// line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance
// (which is specified as the sine of the angle corresponding to the distance).
static double ExpandEndpoint(double u, double max_v, double sin_dist) {
// This is based on solving a spherical right triangle, similar to the
// calculation in S2Cap::GetRectBound.
double sin_u_shift =
sin_dist * sqrt((1 + u * u + max_v * max_v) / (1 + u * u));
double cos_u_shift = sqrt(1 - sin_u_shift * sin_u_shift);
// The following is an expansion of tan(atan(u) + asin(sin_u_shift)).
return (cos_u_shift * u + sin_u_shift) / (cos_u_shift - sin_u_shift * u);
}
/* static */
R2Rect S2CellId::ExpandedByDistanceUV(R2Rect const& uv, S1Angle distance) {
// Expand each of the four sides of the rectangle just enough to include all
// points within the given distance of that side. (The rectangle may be
// expanded by a different amount in (u,v)-space on each side.)
double u0 = uv[0][0], u1 = uv[0][1], v0 = uv[1][0], v1 = uv[1][1];
double max_u = std::max(std::abs(u0), std::abs(u1));
double max_v = std::max(std::abs(v0), std::abs(v1));
double sin_dist = sin(distance);
return R2Rect(R1Interval(ExpandEndpoint(u0, max_v, -sin_dist),
ExpandEndpoint(u1, max_v, sin_dist)),
R1Interval(ExpandEndpoint(v0, max_u, -sin_dist),
ExpandEndpoint(v1, max_u, sin_dist)));
}
S2CellId S2CellId::FromFaceIJWrap(int face, int i, int j) {
// Convert i and j to the coordinates of a leaf cell just beyond the
// boundary of this face. This prevents 32-bit overflow in the case
// of finding the neighbors of a face cell.
i = max(-1, min(kMaxSize, i));
j = max(-1, min(kMaxSize, j));
// We want to wrap these coordinates onto the appropriate adjacent face.
// The easiest way to do this is to convert the (i,j) coordinates to (x,y,z)
// (which yields a point outside the normal face boundary), and then call
// S2::XYZtoFaceUV() to project back onto the correct face.
//
// The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using
// the linear projection (u=2*s-1 and v=2*t-1). (The code further below
// converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1).
// Any projection would work here, so we use the simplest.) We also clamp
// the (u,v) coordinates so that the point is barely outside the
// [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step
// (which divides by the new z coordinate) might change the other
// coordinates enough so that we end up in the wrong leaf cell.
static const double kScale = 1.0 / kMaxSize;
static const double kLimit = 1.0 + DBL_EPSILON;
// The arithmetic below is designed to avoid 32-bit integer overflows.
DCHECK_EQ(0, kMaxSize % 2);
double u = max(-kLimit, min(kLimit, kScale * (2 * (i - kMaxSize / 2) + 1)));
double v = max(-kLimit, min(kLimit, kScale * (2 * (j - kMaxSize / 2) + 1)));
// Find the leaf cell coordinates on the adjacent face, and convert
// them to a cell id at the appropriate level.
face = S2::XYZtoFaceUV(S2::FaceUVtoXYZ(face, u, v), &u, &v);
return FromFaceIJ(face, S2::STtoIJ(0.5 * (u + 1)), S2::STtoIJ(0.5 * (v + 1)));
}
inline S2CellId S2CellId::FromFaceIJSame(int face,
int i,
int j,
bool same_face) {
if (same_face)
return S2CellId::FromFaceIJ(face, i, j);
else
return S2CellId::FromFaceIJWrap(face, i, j);
}
void S2CellId::GetEdgeNeighbors(S2CellId neighbors[4]) const {
int i, j;
int level = this->level();
int size = GetSizeIJ(level);
int face = ToFaceIJOrientation(&i, &j, nullptr);
// Edges 0, 1, 2, 3 are in the down, right, up, left directions.
neighbors[0] = FromFaceIJSame(face, i, j - size, j - size >= 0).parent(level);
neighbors[1] =
FromFaceIJSame(face, i + size, j, i + size < kMaxSize).parent(level);
neighbors[2] =
FromFaceIJSame(face, i, j + size, j + size < kMaxSize).parent(level);
neighbors[3] = FromFaceIJSame(face, i - size, j, i - size >= 0).parent(level);
}
void S2CellId::AppendVertexNeighbors(int level,
vector<S2CellId>* output) const {
// "level" must be strictly less than this cell's level so that we can
// determine which vertex this cell is closest to.
DCHECK_LT(level, this->level());
int i, j;
int face = ToFaceIJOrientation(&i, &j, nullptr);
// Determine the i- and j-offsets to the closest neighboring cell in each
// direction. This involves looking at the next bit of "i" and "j" to
// determine which quadrant of this->parent(level) this cell lies in.
int halfsize = GetSizeIJ(level + 1);
int size = halfsize << 1;
bool isame, jsame;
int ioffset, joffset;
if (i & halfsize) {
ioffset = size;
isame = (i + size) < kMaxSize;
} else {
ioffset = -size;
isame = (i - size) >= 0;
}
if (j & halfsize) {
joffset = size;
jsame = (j + size) < kMaxSize;
} else {
joffset = -size;
jsame = (j - size) >= 0;
}
output->push_back(parent(level));
output->push_back(FromFaceIJSame(face, i + ioffset, j, isame).parent(level));
output->push_back(FromFaceIJSame(face, i, j + joffset, jsame).parent(level));
// If i- and j- edge neighbors are *both* on a different face, then this
// vertex only has three neighbors (it is one of the 8 cube vertices).
if (isame || jsame) {
output->push_back(
FromFaceIJSame(face, i + ioffset, j + joffset, isame && jsame)
.parent(level));
}
}
void S2CellId::AppendAllNeighbors(int nbr_level,
vector<S2CellId>* output) const {
DCHECK_GE(nbr_level, level());
int i, j;
int face = ToFaceIJOrientation(&i, &j, nullptr);
// Find the coordinates of the lower left-hand leaf cell. We need to
// normalize (i,j) to a known position within the cell because nbr_level
// may be larger than this cell's level.
int size = GetSizeIJ();
i &= -size;
j &= -size;
int nbr_size = GetSizeIJ(nbr_level);
DCHECK_LE(nbr_size, size);
// We compute the top-bottom, left-right, and diagonal neighbors in one
// pass. The loop test is at the end of the loop to avoid 32-bit overflow.
for (int k = -nbr_size;; k += nbr_size) {
bool same_face;
if (k < 0) {
same_face = (j + k >= 0);
} else if (k >= size) {
same_face = (j + k < kMaxSize);
} else {
same_face = true;
// Top and bottom neighbors.
output->push_back(FromFaceIJSame(face, i + k, j - nbr_size, j - size >= 0)
.parent(nbr_level));
output->push_back(
FromFaceIJSame(face, i + k, j + size, j + size < kMaxSize)
.parent(nbr_level));
}
// Left, right, and diagonal neighbors.
output->push_back(
FromFaceIJSame(face, i - nbr_size, j + k, same_face && i - size >= 0)
.parent(nbr_level));
output->push_back(
FromFaceIJSame(face, i + size, j + k, same_face && i + size < kMaxSize)
.parent(nbr_level));
if (k >= size)
break;
}
}
std::string S2CellId::ToString() const {
if (!is_valid()) {
return base::StringPrintf("Invalid: %016" PRIu64, id());
}
std::string out = base::StringPrintf("%d/", face());
for (int current_level = 1; current_level <= level(); ++current_level) {
// Avoid dependencies of SimpleItoA, and slowness of StringAppendF &
// std::to_string.
out += "0123"[child_position(current_level)];
}
return out;
}
std::ostream& operator<<(std::ostream& os, S2CellId id) {
return os << id.ToString();
}