// Orders Bounds by their midpoint. This is really only useful if the bounds // are arranged in a grid and are of equal size (like during a MetaQuery). inline bool operator<(const Bounds& lhs, const Bounds& rhs) { const auto& lhsMid(lhs.mid()); const auto& rhsMid(rhs.mid()); return (lhsMid.x < rhsMid.x) || (lhsMid.x == rhsMid.x && lhsMid.y < rhsMid.y) || (lhsMid.x == rhsMid.x && lhsMid.y == rhsMid.y && lhsMid.z < rhsMid.z); }
TEST(Infer, Reprojection) { const std::string path(test::dataPath() + "ellipsoid-single-nyc"); const std::string badPath(path + "-wrong-srs"); const Bounds utmBounds( Point(580621.19214, 4504618.31537, -50), Point(580850.55166, 4504772.01557, 50)); { Inference inference(path); inference.go(); ASSERT_TRUE(inference.done()); checkBoundsNear(inference.bounds(), nycBounds); const Delta* d(inference.delta()); ASSERT_TRUE(d); EXPECT_EQ(d->scale(), Scale(.01)); checkPointNear(d->offset(), nycCenter, 20.0); } { const Reprojection r("", "EPSG:26918"); Inference inference(path, &r); inference.go(); ASSERT_TRUE(inference.done()); checkBoundsNear(inference.bounds(), utmBounds); const Delta* d(inference.delta()); ASSERT_TRUE(d); EXPECT_EQ(d->scale(), Scale(.01)); checkPointNear(d->offset(), utmBounds.mid(), 20.0); } { const Reprojection r("EPSG:3857", "EPSG:26918", true); Inference inference(badPath, &r); inference.go(); ASSERT_TRUE(inference.done()); const Delta* d(inference.delta()); ASSERT_TRUE(d); EXPECT_EQ(d->scale(), Scale(.01)); checkPointNear(d->offset(), utmBounds.mid(), 20.0); } }
// Returns true if these Bounds share any area in common with another. bool overlaps(const Bounds& other, bool force2d = false) const { Point otherMid(other.mid()); return std::abs(m_mid.x - otherMid.x) <= width() / 2.0 + other.width() / 2.0 && std::abs(m_mid.y - otherMid.y) <= depth() / 2.0 + other.depth() / 2.0 && (force2d || std::abs(m_mid.z - otherMid.z) <= height() / 2.0 + other.height() / 2.0); }
Transformation Inference::calcTransformation() { // We have our full bounds and info for all files in EPSG:4978. Now: // 1) determine a transformation matrix so outward is up // 2) transform our file info and bounds accordingly // We're going to use our Point class to represent Vectors in this function. using Vector = Point; // Let O = (0,0,0) be the origin (center of the earth). This is our native // projection system with unit vectors i=(1,0,0), j=(0,1,0), and k=(0,0,1). // Let P = bounds.mid(), our transformed origin point. // Let S be the sphere centered at O with radius ||P||. // Let T = the plane tangent to S at P. // Now, we can define our desired coordinate system: // // k' = "up" = normalized vector O->P // // j' = "north" = the normalized projected vector onto tangent plane T, of // the north pole vector (0,0,1) from the non-transformed coordinate system. // // i' = "east" = j' cross k' // Determine normalized vector k'. const Point p(bounds().mid()); const Vector up(Vector::normalize(p)); // Project the north pole vector onto k'. const Vector northPole(0, 0, 1); const double dot(Point::dot(up, northPole)); const Vector proj(up * dot); // Subtract that projection from the north pole vector to project it onto // tangent plane T - then normalize to determine vector j'. const Vector north(Vector::normalize(northPole - proj)); // Finally, calculate j' cross k' to determine i', which should turn out to // be normalized since the inputs are orthogonal and normalized. const Vector east(Vector::cross(north, up)); // First, rotate so up is outward from the center of the earth. const std::vector<double> rotation { east.x, east.y, east.z, 0, north.x, north.y, north.z, 0, up.x, up.y, up.z, 0, 0, 0, 0, 1 }; // Then, translate around our current best guess at a center point. This // should be close enough to the origin for reasonable precision. const Bounds tentativeCenter( m_executor.transform(bounds(), rotation)); const std::vector<double> translation { 1, 0, 0, -tentativeCenter.mid().x, 0, 1, 0, -tentativeCenter.mid().y, 0, 0, 1, -tentativeCenter.mid().z, 0, 0, 0, 1 }; return matrix::multiply(translation, rotation); }