From 82ebcfa14e2eb8d13c3eca9c2eb5adaa229f4182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 8 Mar 2016 13:49:54 +0100 Subject: [PATCH] [core] introduce three distinct TileID types * CanonicalTileID has z, x, and y, with x/y being within the bounds z defines. z can be anything from 0-32. * OverscaledTileID is composed of a z value, and a canonical tile. The z value indicates the zoom level the tile is intended for. It is primarily used for indexing overscaled data tiles. * UnwrappedTileID is composed of a wrap value, and a canonical tile. The wrap value is used for representing tiles to the left and right of the main (0/0/0 based) tile pyramid. It is primarily used for indicating the position a tile should be rendered at. --- src/mbgl/tile/tile_id.hpp | 270 ++++++++++++++++++++++++++++++++ src/mbgl/tile/tile_id_io.cpp | 38 +++++ test/test.gypi | 2 + test/tile/tile_id.cpp | 295 +++++++++++++++++++++++++++++++++++ 4 files changed, 605 insertions(+) create mode 100644 src/mbgl/tile/tile_id.hpp create mode 100644 src/mbgl/tile/tile_id_io.cpp create mode 100644 test/tile/tile_id.cpp diff --git a/src/mbgl/tile/tile_id.hpp b/src/mbgl/tile/tile_id.hpp new file mode 100644 index 000000000..8c86c0d09 --- /dev/null +++ b/src/mbgl/tile/tile_id.hpp @@ -0,0 +1,270 @@ +#ifndef MBGL_TILE_TILE_ID +#define MBGL_TILE_TILE_ID + +#include + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class OverscaledTileID; +class CanonicalTileID; +class UnwrappedTileID; + +// Has integer z/x/y coordinates +// All tiles must be derived from 0/0/0 (=no tiles outside of the main tile pyramid) +// Used for requesting data; represents data tiles that exist out there. +// z is never larger than the source's maxzoom +class CanonicalTileID { +public: + CanonicalTileID(uint8_t z, uint32_t x, uint32_t y); + bool operator==(const CanonicalTileID&) const; + bool operator!=(const CanonicalTileID&) const; + bool operator<(const CanonicalTileID&) const; + bool isChildOf(const CanonicalTileID&) const; + CanonicalTileID scaledTo(uint8_t z) const; + std::array children() const; + + const uint8_t z; + const uint32_t x; + const uint32_t y; +}; + +::std::ostream& operator<<(::std::ostream& os, const CanonicalTileID& rhs); +namespace util { +std::string toString(const CanonicalTileID&); +} // namespace util + +// Has integer z/x/y coordinates +// overscaledZ describes the zoom level this tile is intented to represent, e.g. when parsing data +// z is never larger than the source's maxzoom +// z/x/y describe the +class OverscaledTileID { +public: + OverscaledTileID(uint8_t overscaledZ, const CanonicalTileID&); + OverscaledTileID(uint8_t overscaledZ, CanonicalTileID&&); + OverscaledTileID(uint8_t overscaledZ, uint8_t z, uint32_t x, uint32_t y); + OverscaledTileID(uint8_t z, uint32_t x, uint32_t y); + explicit OverscaledTileID(const CanonicalTileID&); + explicit OverscaledTileID(CanonicalTileID&&); + bool operator==(const OverscaledTileID&) const; + bool operator!=(const OverscaledTileID&) const; + bool operator<(const OverscaledTileID&) const; + bool isChildOf(const OverscaledTileID&) const; + uint32_t overscaleFactor() const; + OverscaledTileID scaledTo(uint8_t z) const; + UnwrappedTileID unwrapTo(int16_t wrap) const; + + const uint8_t overscaledZ; + const CanonicalTileID canonical; +}; + +::std::ostream& operator<<(::std::ostream& os, const OverscaledTileID& rhs); +namespace util { +std::string toString(const OverscaledTileID&); +} // namespace util + +// Has integer z/x/y coordinates +// wrap describes tiles that are left/right of the main tile pyramid, e.g. when wrapping the world +// Used for describing what position tiles are getting rendered at (= calc the matrix) +// z is never larger than the source's maxzoom +class UnwrappedTileID { +public: + UnwrappedTileID(uint8_t z, int64_t x, int64_t y); + UnwrappedTileID(int16_t wrap, const CanonicalTileID&); + UnwrappedTileID(int16_t wrap, CanonicalTileID&&); + bool operator==(const UnwrappedTileID&) const; + bool operator!=(const UnwrappedTileID&) const; + bool operator<(const UnwrappedTileID&) const; + bool isChildOf(const UnwrappedTileID&) const; + std::array children() const; + OverscaledTileID overscaleTo(uint8_t z) const; + float pixelsToTileUnits(float pixelValue, float zoom) const; + + const int16_t wrap; + const CanonicalTileID canonical; +}; + +::std::ostream& operator<<(::std::ostream& os, const UnwrappedTileID& rhs); +namespace util { +std::string toString(const UnwrappedTileID&); +} // namespace util + +inline CanonicalTileID::CanonicalTileID(uint8_t z_, uint32_t x_, uint32_t y_) : z(z_), x(x_), y(y_) { + assert(z <= 32); + assert(x < (1ull << z)); + assert(y < (1ull << z)); +} + +inline bool CanonicalTileID::operator==(const CanonicalTileID& rhs) const { + return z == rhs.z && x == rhs.x && y == rhs.y; +} + +inline bool CanonicalTileID::operator!=(const CanonicalTileID& rhs) const { + return z != rhs.z || x != rhs.x || y != rhs.y; +} + +inline bool CanonicalTileID::operator<(const CanonicalTileID& rhs) const { + if (z != rhs.z) { + return z < rhs.z; + } else if (x != rhs.x) { + return x < rhs.x; + } + return y < rhs.y; +} + +inline bool CanonicalTileID::isChildOf(const CanonicalTileID& parent) const { + // We're first testing for z == 0, to avoid a 32 bit shift, which is undefined. + return parent.z == 0 || + (parent.z < z && parent.x == (x >> (z - parent.z)) && parent.y == (y >> (z - parent.z))); +} + +inline CanonicalTileID CanonicalTileID::scaledTo(uint8_t targetZ) const { + if (targetZ <= z) { + return { targetZ, x >> (z - targetZ), y >> (z - targetZ) }; // parent or same + } else { + return { targetZ, x << (targetZ - z), y << (targetZ - z) }; // child + } +} + +inline std::array CanonicalTileID::children() const { + const uint8_t childZ = z + 1; + const uint32_t childX = x * 2; + const uint32_t childY = y * 2; + return { { + { childZ, childX, childY }, + { childZ, childX, childY + 1 }, + { childZ, childX + 1, childY }, + { childZ, childX + 1, childY + 1 }, + } }; +} + +inline OverscaledTileID::OverscaledTileID(uint8_t overscaledZ_, const CanonicalTileID& canonical_) + : overscaledZ(overscaledZ_), canonical(canonical_) { + assert(overscaledZ >= canonical.z); +} + +inline OverscaledTileID::OverscaledTileID(uint8_t overscaledZ_, CanonicalTileID&& canonical_) + : overscaledZ(overscaledZ_), canonical(std::forward(canonical_)) { + assert(overscaledZ >= canonical.z); +} + +inline OverscaledTileID::OverscaledTileID(uint8_t overscaledZ_, uint8_t z, uint32_t x, uint32_t y) + : overscaledZ(overscaledZ_), canonical(z, x, y) { + assert(overscaledZ >= canonical.z); +} + +inline OverscaledTileID::OverscaledTileID(uint8_t z, uint32_t x, uint32_t y) + : overscaledZ(z), canonical(z, x, y) { +} + +inline OverscaledTileID::OverscaledTileID(const CanonicalTileID& canonical_) + : overscaledZ(canonical_.z), canonical(canonical_) { + assert(overscaledZ >= canonical.z); +} + +inline OverscaledTileID::OverscaledTileID(CanonicalTileID&& canonical_) + : overscaledZ(canonical_.z), canonical(std::forward(canonical_)) { + assert(overscaledZ >= canonical.z); +} + +inline bool OverscaledTileID::operator==(const OverscaledTileID& rhs) const { + return overscaledZ == rhs.overscaledZ && canonical == rhs.canonical; +} + +inline bool OverscaledTileID::operator!=(const OverscaledTileID& rhs) const { + return overscaledZ != rhs.overscaledZ || canonical != rhs.canonical; +} + +inline bool OverscaledTileID::operator<(const OverscaledTileID& rhs) const { + if (overscaledZ != rhs.overscaledZ) { + return overscaledZ < rhs.overscaledZ; + } + return canonical < rhs.canonical; +} + +inline uint32_t OverscaledTileID::overscaleFactor() const { + return 1u << (overscaledZ - canonical.z); +} + +inline bool OverscaledTileID::isChildOf(const OverscaledTileID& rhs) const { + return overscaledZ > rhs.overscaledZ && + (canonical == rhs.canonical || canonical.isChildOf(rhs.canonical)); +} + +inline OverscaledTileID OverscaledTileID::scaledTo(uint8_t z) const { + if (z >= canonical.z) { + return { z, canonical }; + } else { + return { z, canonical.scaledTo(z) }; + } +} + +inline UnwrappedTileID OverscaledTileID::unwrapTo(int16_t wrap) const { + return { wrap, canonical }; +} + +inline UnwrappedTileID::UnwrappedTileID(uint8_t z_, int64_t x_, int64_t y_) + : wrap((x_ < 0 ? x_ - (1ll << z_) + 1 : x_) / (1ll << z_)), + canonical( + z_, + static_cast(x_ - wrap * (1ll << z_)), + y_ < 0 ? 0 : std::min(static_cast(y_), static_cast(1ull << z_) - 1)) { +} + +inline UnwrappedTileID::UnwrappedTileID(int16_t wrap_, const CanonicalTileID& canonical_) + : wrap(wrap_), canonical(canonical_) { +} + +inline UnwrappedTileID::UnwrappedTileID(int16_t wrap_, CanonicalTileID&& canonical_) + : wrap(wrap_), canonical(std::forward(canonical_)) { +} + +inline bool UnwrappedTileID::operator==(const UnwrappedTileID& rhs) const { + return wrap == rhs.wrap && canonical == rhs.canonical; +} + +inline bool UnwrappedTileID::operator!=(const UnwrappedTileID& rhs) const { + return wrap != rhs.wrap || canonical != rhs.canonical; +} + +inline bool UnwrappedTileID::operator<(const UnwrappedTileID& rhs) const { + if (wrap != rhs.wrap) { + return wrap < rhs.wrap; + } + return canonical < rhs.canonical; +} + +inline bool UnwrappedTileID::isChildOf(const UnwrappedTileID& parent) const { + return wrap == parent.wrap && canonical.isChildOf(parent.canonical); +} + +inline std::array UnwrappedTileID::children() const { + const uint8_t childZ = canonical.z + 1; + const uint32_t childX = canonical.x * 2; + const uint32_t childY = canonical.y * 2; + return { { + { wrap, { childZ, childX, childY } }, + { wrap, { childZ, childX, childY + 1 } }, + { wrap, { childZ, childX + 1, childY } }, + { wrap, { childZ, childX + 1, childY + 1 } }, + } }; +} + +inline OverscaledTileID UnwrappedTileID::overscaleTo(const uint8_t overscaledZ) const { + assert(overscaledZ >= canonical.z); + return { overscaledZ, canonical }; +} + +inline float UnwrappedTileID::pixelsToTileUnits(const float pixelValue, const float zoom) const { + return pixelValue * (util::EXTENT / (util::tileSize * std::pow(2, zoom - canonical.z))); +} + +} // namespace mbgl + +#endif diff --git a/src/mbgl/tile/tile_id_io.cpp b/src/mbgl/tile/tile_id_io.cpp new file mode 100644 index 000000000..f6adbf183 --- /dev/null +++ b/src/mbgl/tile/tile_id_io.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include + +namespace mbgl { + +::std::ostream& operator<<(::std::ostream& os, const CanonicalTileID& rhs) { + return os << uint32_t(rhs.z) << "/" << rhs.x << "/" << rhs.y; +} + +namespace util { +std::string toString(const CanonicalTileID& rhs) { + return util::toString(rhs.z) + "/" + util::toString(rhs.x) + "/" + util::toString(rhs.y); +} +} // namespace util + +::std::ostream& operator<<(::std::ostream& os, const OverscaledTileID& rhs) { + return os << rhs.canonical << "=>" << uint32_t(rhs.overscaledZ); +} + +namespace util { +std::string toString(const OverscaledTileID& rhs) { + return util::toString(rhs.canonical) + "=>" + util::toString(rhs.overscaledZ); +} +} // namespace util + +::std::ostream& operator<<(::std::ostream& os, const UnwrappedTileID& rhs) { + return os << rhs.canonical << (rhs.wrap >= 0 ? "+" : "") << rhs.wrap; +} + +namespace util { +std::string toString(const UnwrappedTileID& rhs) { + return util::toString(rhs.canonical) + (rhs.wrap >= 0 ? "+" : "") + util::toString(rhs.wrap); +} +} // namespace util + +} // namespace mbgl diff --git a/test/test.gypi b/test/test.gypi index 1e136389e..7c1d48f2f 100644 --- a/test/test.gypi +++ b/test/test.gypi @@ -50,6 +50,8 @@ 'math/minmax.cpp', 'math/clamp.cpp', + 'tile/tile_id.cpp', + 'storage/offline.cpp', 'storage/offline_database.cpp', 'storage/offline_download.cpp', diff --git a/test/tile/tile_id.cpp b/test/tile/tile_id.cpp new file mode 100644 index 000000000..145ce93f4 --- /dev/null +++ b/test/tile/tile_id.cpp @@ -0,0 +1,295 @@ +#include "../fixtures/util.hpp" + +#include + +using namespace mbgl; + +TEST(TileID, Canonical) { + CanonicalTileID id_0_0_0(0, 0, 0); + EXPECT_EQ(0, id_0_0_0.z); + EXPECT_EQ(0, id_0_0_0.x); + EXPECT_EQ(0, id_0_0_0.y); + + EXPECT_TRUE(CanonicalTileID(4, 2, 3) == CanonicalTileID(4, 2, 3)); + EXPECT_FALSE(CanonicalTileID(4, 2, 3) != CanonicalTileID(4, 2, 3)); + EXPECT_TRUE(CanonicalTileID(4, 2, 3) != CanonicalTileID(5, 2, 3)); + EXPECT_FALSE(CanonicalTileID(4, 2, 3) == CanonicalTileID(5, 2, 3)); + EXPECT_TRUE(CanonicalTileID(4, 2, 3) != CanonicalTileID(4, 3, 3)); + EXPECT_FALSE(CanonicalTileID(4, 2, 3) == CanonicalTileID(4, 3, 3)); + EXPECT_TRUE(CanonicalTileID(4, 2, 3) != CanonicalTileID(4, 2, 4)); + EXPECT_FALSE(CanonicalTileID(4, 2, 3) == CanonicalTileID(4, 2, 4)); + EXPECT_TRUE(CanonicalTileID(5, 2, 3) != CanonicalTileID(4, 2, 3)); + EXPECT_FALSE(CanonicalTileID(5, 2, 3) == CanonicalTileID(4, 2, 3)); + EXPECT_TRUE(CanonicalTileID(4, 3, 3) != CanonicalTileID(4, 2, 3)); + EXPECT_FALSE(CanonicalTileID(4, 3, 3) == CanonicalTileID(4, 2, 3)); + EXPECT_TRUE(CanonicalTileID(4, 2, 4) != CanonicalTileID(4, 2, 3)); + EXPECT_FALSE(CanonicalTileID(4, 2, 4) == CanonicalTileID(4, 2, 3)); + + EXPECT_TRUE(CanonicalTileID(0, 0, 0) < CanonicalTileID(1, 0, 0)); + EXPECT_FALSE(CanonicalTileID(1, 0, 0) < CanonicalTileID(0, 0, 0)); + EXPECT_TRUE(CanonicalTileID(4, 2, 3) < CanonicalTileID(4, 3, 2)); + EXPECT_FALSE(CanonicalTileID(4, 3, 2) < CanonicalTileID(4, 2, 3)); + EXPECT_FALSE(CanonicalTileID(5, 3, 6) < CanonicalTileID(5, 3, 6)); + + const CanonicalTileID child(4, 2, 3); + EXPECT_TRUE(child.isChildOf({ 3, 1, 1 })); + EXPECT_TRUE(child.isChildOf({ 2, 0, 0 })); + EXPECT_TRUE(child.isChildOf({ 1, 0, 0 })); + EXPECT_TRUE(child.isChildOf({ 0, 0, 0 })); + EXPECT_FALSE(child.isChildOf({ 5, 4, 6 })); + EXPECT_FALSE(child.isChildOf({ 4, 2, 4 })); + EXPECT_FALSE(child.isChildOf({ 3, 2, 2 })); + EXPECT_FALSE(child.isChildOf({ 2, 1, 0 })); + EXPECT_FALSE(child.isChildOf({ 1, 0, 1 })); + + EXPECT_EQ(0, 4294967295 >> 32); + + const CanonicalTileID maxChild(32, 4294967295, 4294967295); + EXPECT_FALSE(maxChild.isChildOf({ 32, 4294967295, 4294967295 })); + EXPECT_TRUE(maxChild.isChildOf({ 31, 2147483647, 2147483647 })); + EXPECT_TRUE(maxChild.isChildOf({ 30, 1073741823, 1073741823 })); + EXPECT_TRUE(maxChild.isChildOf({ 29, 536870911, 536870911 })); + EXPECT_TRUE(maxChild.isChildOf({ 28, 268435455, 268435455 })); + EXPECT_TRUE(maxChild.isChildOf({ 27, 134217727, 134217727 })); + EXPECT_TRUE(maxChild.isChildOf({ 26, 67108863, 67108863 })); + EXPECT_TRUE(maxChild.isChildOf({ 25, 33554431, 33554431 })); + EXPECT_TRUE(maxChild.isChildOf({ 24, 16777215, 16777215 })); + EXPECT_TRUE(maxChild.isChildOf({ 23, 8388607, 8388607 })); + EXPECT_TRUE(maxChild.isChildOf({ 22, 4194303, 4194303 })); + EXPECT_TRUE(maxChild.isChildOf({ 21, 2097151, 2097151 })); + EXPECT_TRUE(maxChild.isChildOf({ 20, 1048575, 1048575 })); + EXPECT_TRUE(maxChild.isChildOf({ 19, 524287, 524287 })); + EXPECT_TRUE(maxChild.isChildOf({ 18, 262143, 262143 })); + EXPECT_TRUE(maxChild.isChildOf({ 17, 131071, 131071 })); + EXPECT_TRUE(maxChild.isChildOf({ 16, 65535, 65535 })); + EXPECT_TRUE(maxChild.isChildOf({ 15, 32767, 32767 })); + EXPECT_TRUE(maxChild.isChildOf({ 14, 16383, 16383 })); + EXPECT_TRUE(maxChild.isChildOf({ 13, 8191, 8191 })); + EXPECT_TRUE(maxChild.isChildOf({ 12, 4095, 4095 })); + EXPECT_TRUE(maxChild.isChildOf({ 11, 2047, 2047 })); + EXPECT_TRUE(maxChild.isChildOf({ 10, 1023, 1023 })); + EXPECT_TRUE(maxChild.isChildOf({ 9, 511, 511 })); + EXPECT_TRUE(maxChild.isChildOf({ 8, 255, 255 })); + EXPECT_TRUE(maxChild.isChildOf({ 7, 127, 127 })); + EXPECT_TRUE(maxChild.isChildOf({ 6, 63, 63 })); + EXPECT_TRUE(maxChild.isChildOf({ 5, 31, 31 })); + EXPECT_TRUE(maxChild.isChildOf({ 4, 15, 15 })); + EXPECT_TRUE(maxChild.isChildOf({ 3, 7, 7 })); + EXPECT_TRUE(maxChild.isChildOf({ 2, 3, 3 })); + EXPECT_TRUE(maxChild.isChildOf({ 1, 1, 1 })); + EXPECT_TRUE(maxChild.isChildOf({ 0, 0, 0 })); + + const CanonicalTileID scaleBase(4, 5, 13); + EXPECT_EQ(CanonicalTileID(0, 0, 0), scaleBase.scaledTo(0)); + EXPECT_EQ(CanonicalTileID(1, 0, 1), scaleBase.scaledTo(1)); + EXPECT_EQ(CanonicalTileID(2, 1, 3), scaleBase.scaledTo(2)); + EXPECT_EQ(CanonicalTileID(3, 2, 6), scaleBase.scaledTo(3)); + EXPECT_EQ(CanonicalTileID(4, 5, 13), scaleBase.scaledTo(4)); + EXPECT_EQ(CanonicalTileID(5, 10, 26), scaleBase.scaledTo(5)); + EXPECT_EQ(CanonicalTileID(6, 20, 52), scaleBase.scaledTo(6)); + EXPECT_EQ(CanonicalTileID(7, 40, 104), scaleBase.scaledTo(7)); + EXPECT_EQ(CanonicalTileID(8, 80, 208), scaleBase.scaledTo(8)); + EXPECT_EQ(CanonicalTileID(9, 160, 416), scaleBase.scaledTo(9)); + EXPECT_EQ(CanonicalTileID(10, 320, 832), scaleBase.scaledTo(10)); + EXPECT_EQ(CanonicalTileID(11, 640, 1664), scaleBase.scaledTo(11)); + EXPECT_EQ(CanonicalTileID(12, 1280, 3328), scaleBase.scaledTo(12)); + EXPECT_EQ(CanonicalTileID(13, 2560, 6656), scaleBase.scaledTo(13)); + EXPECT_EQ(CanonicalTileID(14, 5120, 13312), scaleBase.scaledTo(14)); + EXPECT_EQ(CanonicalTileID(15, 10240, 26624), scaleBase.scaledTo(15)); + EXPECT_EQ(CanonicalTileID(16, 20480, 53248), scaleBase.scaledTo(16)); + EXPECT_EQ(CanonicalTileID(17, 40960, 106496), scaleBase.scaledTo(17)); + EXPECT_EQ(CanonicalTileID(18, 81920, 212992), scaleBase.scaledTo(18)); + EXPECT_EQ(CanonicalTileID(19, 163840, 425984), scaleBase.scaledTo(19)); + EXPECT_EQ(CanonicalTileID(20, 327680, 851968), scaleBase.scaledTo(20)); + EXPECT_EQ(CanonicalTileID(21, 655360, 1703936), scaleBase.scaledTo(21)); + EXPECT_EQ(CanonicalTileID(22, 1310720, 3407872), scaleBase.scaledTo(22)); + EXPECT_EQ(CanonicalTileID(23, 2621440, 6815744), scaleBase.scaledTo(23)); + EXPECT_EQ(CanonicalTileID(24, 5242880, 13631488), scaleBase.scaledTo(24)); + EXPECT_EQ(CanonicalTileID(25, 10485760, 27262976), scaleBase.scaledTo(25)); + EXPECT_EQ(CanonicalTileID(26, 20971520, 54525952), scaleBase.scaledTo(26)); + EXPECT_EQ(CanonicalTileID(27, 41943040, 109051904), scaleBase.scaledTo(27)); + EXPECT_EQ(CanonicalTileID(28, 83886080, 218103808), scaleBase.scaledTo(28)); + EXPECT_EQ(CanonicalTileID(29, 167772160, 436207616), scaleBase.scaledTo(29)); + EXPECT_EQ(CanonicalTileID(30, 335544320, 872415232), scaleBase.scaledTo(30)); + EXPECT_EQ(CanonicalTileID(31, 671088640, 1744830464), scaleBase.scaledTo(31)); + EXPECT_EQ(CanonicalTileID(32, 1342177280, 3489660928), scaleBase.scaledTo(32)); + + EXPECT_EQ((std::array{ { + { 5, 4, 6 }, { 5, 4, 7 }, { 5, 5, 6 }, { 5, 5, 7 }, + } }), + CanonicalTileID(4, 2, 3).children()); + EXPECT_EQ((std::array{ { + { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 }, + } }), + CanonicalTileID(0, 0, 0).children()); +} + +TEST(TileID, Overscaled) { + EXPECT_TRUE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 2, 3)); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, { 4, 2, 3 }) == OverscaledTileID(4, 2, 3)); + EXPECT_FALSE(OverscaledTileID(4, { 4, 2, 3 }) != OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, { 4, 2, 3 })); + + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(5, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(5, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(6, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(6, { 4, 2, 3 })); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(7, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(7, { 4, 2, 3 })); + + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 2, 4)); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 2, 4)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(4, 3, 3)); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(4, 3, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) != OverscaledTileID(5, 2, 3)); + EXPECT_FALSE(OverscaledTileID(4, 2, 3) == OverscaledTileID(5, 2, 3)); + + EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }) == OverscaledTileID(7, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }) != OverscaledTileID(7, { 4, 2, 3 })); + + EXPECT_FALSE(OverscaledTileID(4, 2, 3) < OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(5, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(6, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + EXPECT_TRUE(OverscaledTileID(4, 2, 3) < OverscaledTileID(7, { 4, 2, 3 })); + EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }) < OverscaledTileID(4, 2, 3)); + + EXPECT_EQ(8, OverscaledTileID(7, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(4, OverscaledTileID(6, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(2, OverscaledTileID(5, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(1, OverscaledTileID(4, { 4, 2, 3 }).overscaleFactor()); + EXPECT_EQ(2147483648, OverscaledTileID(31, { 0, 0, 0 }).overscaleFactor()); + + EXPECT_EQ(OverscaledTileID(0, { 0, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(0)); + EXPECT_EQ(OverscaledTileID(1, { 1, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(1)); + EXPECT_EQ(OverscaledTileID(2, { 2, 0, 0 }), OverscaledTileID(4, 2, 3).scaledTo(2)); + EXPECT_EQ(OverscaledTileID(3, { 3, 1, 1 }), OverscaledTileID(4, 2, 3).scaledTo(3)); + EXPECT_EQ(OverscaledTileID(4, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(4)); + EXPECT_EQ(OverscaledTileID(5, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(5)); + EXPECT_EQ(OverscaledTileID(6, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(6)); + EXPECT_EQ(OverscaledTileID(7, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(7)); + EXPECT_EQ(OverscaledTileID(8, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(8)); + EXPECT_EQ(OverscaledTileID(32, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).scaledTo(32)); + + EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(0)); + EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(-1)); + EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(4, 2, 3).unwrapTo(1)); + EXPECT_EQ(UnwrappedTileID(0, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(0)); + EXPECT_EQ(UnwrappedTileID(-1, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(-1)); + EXPECT_EQ(UnwrappedTileID(1, { 4, 2, 3 }), OverscaledTileID(5, { 4, 2, 3 }).unwrapTo(1)); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(3, 1, 1))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 2, 3 }))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + EXPECT_TRUE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(6, { 4, 2, 3 }))); + + EXPECT_FALSE(OverscaledTileID(2, 0, 0).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(3, 1, 1).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(4, 2, 3).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(5, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(6, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + EXPECT_FALSE(OverscaledTileID(7, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 4, 0, 0 }))); + + EXPECT_FALSE(OverscaledTileID(4, { 4, 2, 3 }).isChildOf(OverscaledTileID(5, { 3, 1, 1 }))); +} + +TEST(TileID, Unwrapped) { + UnwrappedTileID id_0_0_0(0, 0, 0); + EXPECT_EQ(0, id_0_0_0.canonical.z); + EXPECT_EQ(0, id_0_0_0.canonical.x); + EXPECT_EQ(0, id_0_0_0.canonical.y); + EXPECT_EQ(0, id_0_0_0.wrap); + + UnwrappedTileID id_0_1_0(0, 1, 0); + EXPECT_EQ(0, id_0_1_0.canonical.z); + EXPECT_EQ(0, id_0_1_0.canonical.x); + EXPECT_EQ(0, id_0_1_0.canonical.y); + EXPECT_EQ(1, id_0_1_0.wrap); + + UnwrappedTileID id_0_4_0(0, 4, 0); + EXPECT_EQ(0, id_0_4_0.canonical.z); + EXPECT_EQ(0, id_0_4_0.canonical.x); + EXPECT_EQ(0, id_0_4_0.canonical.y); + EXPECT_EQ(4, id_0_4_0.wrap); + + UnwrappedTileID id_0_n1_0(0, -1, 0); + EXPECT_EQ(0, id_0_n1_0.canonical.z); + EXPECT_EQ(0, id_0_n1_0.canonical.x); + EXPECT_EQ(0, id_0_n1_0.canonical.y); + EXPECT_EQ(-1, id_0_n1_0.wrap); + + UnwrappedTileID id_0_0_1(0, 0, 1); + EXPECT_EQ(0, id_0_0_1.canonical.z); + EXPECT_EQ(0, id_0_0_1.canonical.x); + EXPECT_EQ(0, id_0_0_1.canonical.y); + EXPECT_EQ(0, id_0_0_1.wrap); + + UnwrappedTileID id_0_0_n1(0, 0, -1); + EXPECT_EQ(0, id_0_0_n1.canonical.z); + EXPECT_EQ(0, id_0_0_n1.canonical.x); + EXPECT_EQ(0, id_0_0_n1.canonical.y); + EXPECT_EQ(0, id_0_0_n1.wrap); + + UnwrappedTileID id_18_262143_0(18, 262143, 0); + EXPECT_EQ(18, id_18_262143_0.canonical.z); + EXPECT_EQ(262143, id_18_262143_0.canonical.x); + EXPECT_EQ(0, id_18_262143_0.canonical.y); + EXPECT_EQ(0, id_18_262143_0.wrap); + + UnwrappedTileID id_18_262144_0(18, 262144, 0); + EXPECT_EQ(18, id_18_262144_0.canonical.z); + EXPECT_EQ(0, id_18_262144_0.canonical.x); + EXPECT_EQ(0, id_18_262144_0.canonical.y); + EXPECT_EQ(1, id_18_262144_0.wrap); + + UnwrappedTileID a(2, 4, 3); + UnwrappedTileID b(a); + EXPECT_EQ(2, b.canonical.z); + EXPECT_EQ(0, b.canonical.x); + EXPECT_EQ(3, b.canonical.y); + EXPECT_EQ(1, b.wrap); + + EXPECT_TRUE(UnwrappedTileID(0, 0, 0) < UnwrappedTileID(1, 0, 0)); + EXPECT_TRUE(UnwrappedTileID(1, 0, 0) < UnwrappedTileID(0, 1, 0)); + EXPECT_FALSE(UnwrappedTileID(0, 1, 0) < UnwrappedTileID(1, 0, 0)); + EXPECT_FALSE(UnwrappedTileID(5, 3, 6) < UnwrappedTileID(5, 3, 6)); + + EXPECT_EQ(OverscaledTileID(4, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(4)); + EXPECT_EQ(OverscaledTileID(5, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(5)); + EXPECT_EQ(OverscaledTileID(6, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(6)); + EXPECT_EQ(OverscaledTileID(32, { 4, 2, 3 }), UnwrappedTileID(4, 2, 3).overscaleTo(32)); + + EXPECT_EQ(UnwrappedTileID(-1, { 1, 0, 0 }), UnwrappedTileID(1, -2, 0)); + EXPECT_EQ(UnwrappedTileID(-1, { 1, 0, 1 }), UnwrappedTileID(1, -2, 1)); + EXPECT_EQ(UnwrappedTileID(-1, { 1, 1, 0 }), UnwrappedTileID(1, -1, 0)); + EXPECT_EQ(UnwrappedTileID(-1, { 1, 1, 1 }), UnwrappedTileID(1, -1, 1)); + EXPECT_EQ(UnwrappedTileID(-1, { 0, 0, 0 }), UnwrappedTileID(0, -1, 0)); + + EXPECT_EQ((std::array{ { + { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 }, + } }), + UnwrappedTileID(0, 0, 0).children()); + EXPECT_EQ((std::array{ { + { 1, -2, 0 }, { 1, -2, 1 }, { 1, -1, 0 }, { 1, -1, 1 }, + } }), + UnwrappedTileID(0, -1, 0).children()); +}