Skip to content

Commit 37e36fd

Browse files
oxidasedaniel-j-h
authored andcommitted
Fix possible division by zero by clamping latitude to 85.05°
Resolves #3530
1 parent 341109a commit 37e36fd

File tree

3 files changed

+29
-31
lines changed

3 files changed

+29
-31
lines changed

include/util/web_mercator.hpp

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const constexpr double EARTH_RADIUS_WGS84 = 6378137.0;
2020
// earth circumference devided by 2
2121
const constexpr double MAXEXTENT = EARTH_RADIUS_WGS84 * boost::math::constants::pi<double>();
2222
// ^ math functions are not constexpr since they have side-effects (setting errno) :(
23-
const constexpr double MAX_LATITUDE = 85.;
23+
const constexpr double EPSG3857_MAX_LATITUDE = 85.051128779806592378; // 90(4*atan(exp(pi))/pi-1)
2424
const constexpr double MAX_LONGITUDE = 180.0;
2525
}
2626

@@ -29,6 +29,18 @@ const constexpr double DEGREE_TO_PX = detail::MAXEXTENT / 180.0;
2929
// This is the global default tile size for all Mapbox Vector Tiles
3030
const constexpr double TILE_SIZE = 256.0;
3131

32+
inline FloatLatitude clamp(const FloatLatitude lat)
33+
{
34+
return std::max(std::min(lat, FloatLatitude{detail::EPSG3857_MAX_LATITUDE}),
35+
FloatLatitude{-detail::EPSG3857_MAX_LATITUDE});
36+
}
37+
38+
inline FloatLongitude clamp(const FloatLongitude lon)
39+
{
40+
return std::max(std::min(lon, FloatLongitude{detail::MAX_LONGITUDE}),
41+
FloatLongitude{-detail::MAX_LONGITUDE});
42+
}
43+
3244
inline FloatLatitude yToLat(const double y)
3345
{
3446
const auto clamped_y = std::max(-180., std::min(180., y));
@@ -41,10 +53,9 @@ inline FloatLatitude yToLat(const double y)
4153
inline double latToY(const FloatLatitude latitude)
4254
{
4355
// apparently this is the (faster) version of the canonical log(tan()) version
44-
const double f = std::sin(detail::DEGREE_TO_RAD * static_cast<double>(latitude));
45-
const double y = detail::RAD_TO_DEGREE * 0.5 * std::log((1 + f) / (1 - f));
46-
const auto clamped_y = std::max(-180., std::min(180., y));
47-
return clamped_y;
56+
const auto clamped_latitude = clamp(latitude);
57+
const double f = std::sin(detail::DEGREE_TO_RAD * static_cast<double>(clamped_latitude));
58+
return detail::RAD_TO_DEGREE * 0.5 * std::log((1 + f) / (1 - f));
4859
}
4960

5061
template <typename T> constexpr double horner(double, T an) { return an; }
@@ -91,18 +102,6 @@ inline double latToYapprox(const FloatLatitude latitude)
91102
-3.23083224835967391884404730e-28);
92103
}
93104

94-
inline FloatLatitude clamp(const FloatLatitude lat)
95-
{
96-
return std::max(std::min(lat, FloatLatitude{detail::MAX_LATITUDE}),
97-
FloatLatitude{-detail::MAX_LATITUDE});
98-
}
99-
100-
inline FloatLongitude clamp(const FloatLongitude lon)
101-
{
102-
return std::max(std::min(lon, FloatLongitude{detail::MAX_LONGITUDE}),
103-
FloatLongitude{-detail::MAX_LONGITUDE});
104-
}
105-
106105
inline void pixelToDegree(const double shift, double &x, double &y)
107106
{
108107
const double b = shift / 2.0;
@@ -166,9 +165,9 @@ inline void xyzToMercator(
166165
xyzToWGS84(x, y, z, minx, miny, maxx, maxy);
167166

168167
minx = static_cast<double>(clamp(util::FloatLongitude{minx})) * DEGREE_TO_PX;
169-
miny = latToY(clamp(util::FloatLatitude{miny})) * DEGREE_TO_PX;
168+
miny = latToY(util::FloatLatitude{miny}) * DEGREE_TO_PX;
170169
maxx = static_cast<double>(clamp(util::FloatLongitude{maxx})) * DEGREE_TO_PX;
171-
maxy = latToY(clamp(util::FloatLatitude{maxy})) * DEGREE_TO_PX;
170+
maxy = latToY(util::FloatLatitude{maxy}) * DEGREE_TO_PX;
172171
}
173172
}
174173
}

unit_tests/util/coordinate_calculation.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,20 @@ BOOST_AUTO_TEST_CASE(compute_angle)
117117
BOOST_CHECK_EQUAL(angle, 180);
118118

119119
// Tiny changes below our calculation resolution
120-
// This should be equivalent to having two points on the same
121-
// spot.
120+
// This should be equivalent to having two points on the same spot.
122121
first = Coordinate{FloatLongitude{0}, FloatLatitude{0}};
123122
middle = Coordinate{FloatLongitude{1}, FloatLatitude{0}};
124123
end = Coordinate{FloatLongitude{1 + std::numeric_limits<double>::epsilon()}, FloatLatitude{0}};
125124
angle = coordinate_calculation::computeAngle(first, middle, end);
126125
BOOST_CHECK_EQUAL(angle, 180);
127126

128127
// Invalid values
129-
/* TODO: Enable this when I figure out how to use BOOST_CHECK_THROW
130-
* and not have the whole test case fail...
131-
first = Coordinate(FloatLongitude{0}, FloatLatitude{0});
132-
middle = Coordinate(FloatLongitude{1}, FloatLatitude{0});
133-
end = Coordinate(FloatLongitude(std::numeric_limits<double>::max()), FloatLatitude{0});
134-
BOOST_CHECK_THROW( coordinate_calculation::computeAngle(first,middle,end),
135-
boost::numeric::positive_overflow);
136-
*/
128+
BOOST_CHECK_THROW(
129+
coordinate_calculation::computeAngle(
130+
Coordinate(FloatLongitude{0}, FloatLatitude{0}),
131+
Coordinate(FloatLongitude{1}, FloatLatitude{0}),
132+
Coordinate(FloatLongitude{std::numeric_limits<double>::max()}, FloatLatitude{0})),
133+
boost::numeric::positive_overflow);
137134
}
138135

139136
// Regression test for bug captured in #1347

unit_tests/util/web_mercator.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@ BOOST_AUTO_TEST_CASE(xyz_to_mercator)
7373
double miny;
7474
double maxx;
7575
double maxy;
76+
77+
// http://tools.geofabrik.de/map/#13/85.0500/-175.5876&type=Geofabrik_Standard&grid=1
7678
web_mercator::xyzToMercator(100, 0, 13, minx, miny, maxx, maxy);
7779

7880
BOOST_CHECK_CLOSE(minx, -19548311.361764118075, 0.0001);
79-
BOOST_CHECK_CLOSE(miny, 19971868.8804085782, 0.0001);
81+
BOOST_CHECK_CLOSE(miny, 20032616.372979045, 0.0001);
8082
BOOST_CHECK_CLOSE(maxx, -19543419.391953866929, 0.0001);
81-
BOOST_CHECK_CLOSE(maxy, 19971868.880408578, 0.0001);
83+
BOOST_CHECK_CLOSE(maxy, 20037508.342789277, 0.0001); // Mercator 6378137*pi, WGS 85.0511
8284
}
8385

8486
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)