diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f3cdb4c8e..8473923779c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ Known issues: ## iOS master +- Fixed screen coordinates for LatLng coordinates accross the antimeridian. ([#4215](https://github.com/mapbox/mapbox-gl-native/issues/4155)) +- Fixed a bounce-back effect when panning the map. ([#4214](https://github.com/mapbox/mapbox-gl-native/pull/4214)) - An icon laid out along a line no longer appears if it would extend past the end of the line. Some one-way arrows no longer point the wrong way. ([#3839](https://github.com/mapbox/mapbox-gl-native/pull/3839)) - Reduce slanted segments in dashed lines near corners. ([#3914](https://github.com/mapbox/mapbox-gl-native/pull/3914)) - Telemetry location gathering now only occurs when the device is in motion. ([#4115](https://github.com/mapbox/mapbox-gl-native/pull/4115)) diff --git a/include/mbgl/annotation/point_annotation.hpp b/include/mbgl/annotation/point_annotation.hpp index e226673997f..3dea9a3e3e5 100644 --- a/include/mbgl/annotation/point_annotation.hpp +++ b/include/mbgl/annotation/point_annotation.hpp @@ -9,9 +9,8 @@ namespace mbgl { class PointAnnotation { public: - inline PointAnnotation(const LatLng& position_, const std::string& icon_ = "") - : position(position_), icon(icon_) { - } + PointAnnotation(const LatLng& position_, const std::string& icon_ = "") + : position(position_.wrapped()), icon(icon_) {} const LatLng position; const std::string icon; diff --git a/include/mbgl/annotation/shape_annotation.hpp b/include/mbgl/annotation/shape_annotation.hpp index fffa7dab6e6..121cb7f1d7d 100644 --- a/include/mbgl/annotation/shape_annotation.hpp +++ b/include/mbgl/annotation/shape_annotation.hpp @@ -33,11 +33,24 @@ class ShapeAnnotation { std::string>; // creates an annotation whose type and properties are sourced from a style layer ShapeAnnotation(const AnnotationSegments& segments_, const Properties& properties_) - : segments(segments_), properties(properties_) { - } + : segments(wrapCoordinates(segments_)), properties(properties_) {} const AnnotationSegments segments; const Properties properties; + +private: + AnnotationSegments wrapCoordinates(const AnnotationSegments& segments_) { + AnnotationSegments wrappedSegments; + // Wrap all segments coordinates. + for (const auto& segment_ : segments_) { + AnnotationSegment wrappedSegment; + for (const auto& latLng_ : segment_) { + wrappedSegment.push_back(latLng_.wrapped()); + } + wrappedSegments.push_back(wrappedSegment); + } + return wrappedSegments; + } }; } // namespace mbgl diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp index 56d575ceaac..94fbdc3d09a 100644 --- a/include/mbgl/util/geo.hpp +++ b/include/mbgl/util/geo.hpp @@ -25,19 +25,17 @@ class LatLng { LatLng wrapped() const { return { latitude, longitude, Wrapped }; } void wrap() { - if (longitude < -util::LONGITUDE_MAX) longitude = std::fmod(longitude, util::LONGITUDE_MAX * 2); - if (longitude > util::LONGITUDE_MAX) longitude = -util::LONGITUDE_MAX + std::fmod(longitude, util::LONGITUDE_MAX); - } - - /** If a path crossing the antemeridian would be shorter, extend the final - coordinate so that interpolating between the two endpoints will cross it. */ - void unwrapForShortestPath(const LatLng& start) { - if (std::abs(start.longitude) + std::abs(longitude) > util::LONGITUDE_MAX) { - if (start.longitude > 0 && longitude < 0) { - longitude += util::DEGREES_MAX; - } else if (start.longitude < 0 && longitude > 0) { - longitude -= util::DEGREES_MAX; - } + if (longitude < -util::LONGITUDE_MAX) longitude = util::LONGITUDE_MAX + std::fmod(longitude + util::LONGITUDE_MAX, util::DEGREES_MAX); + if (longitude > util::LONGITUDE_MAX) longitude = -util::LONGITUDE_MAX + std::fmod(longitude + util::LONGITUDE_MAX, util::DEGREES_MAX); + } + + // If we pass through the antimeridian, we update the start coordinate to make sure + // the end coordinate is always wrapped. + void unwrapForShortestPath(const LatLng& end) { + if (end.longitude < -util::LONGITUDE_MAX) { + longitude += util::DEGREES_MAX; + } else if (end.longitude > util::LONGITUDE_MAX) { + longitude -= util::DEGREES_MAX; } } @@ -201,9 +199,10 @@ class EdgeInsets { : top(t), left(l), bottom(b), right(r) {} explicit operator bool() const { - return top || left || bottom || right; + return !(std::isnan(top) || std::isnan(left) || std::isnan(bottom) || std::isnan(right)) + && (top || left || bottom || right); } - + void operator+=(const EdgeInsets& o) { top += o.top; left += o.left; diff --git a/include/mbgl/util/vec.hpp b/include/mbgl/util/vec.hpp index 83530a1798c..a8bbd14b0f2 100644 --- a/include/mbgl/util/vec.hpp +++ b/include/mbgl/util/vec.hpp @@ -105,6 +105,18 @@ struct vec3 { inline bool operator==(const vec3& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; } + + template::has_quiet_NaN, int>::type = 0> + inline operator bool() const { + return !std::isnan(x) && !std::isnan(y) && !std::isnan(z); + } + + template::has_quiet_NaN, int>::type = 0> + inline operator bool() const { + return x != std::numeric_limits::min() + && y != std::numeric_limits::min() + && z != std::numeric_limits::min(); + } }; template @@ -117,6 +129,19 @@ struct vec4 { inline bool operator==(const vec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } + + template::has_quiet_NaN, int>::type = 0> + inline operator bool() const { + return !std::isnan(x) && !std::isnan(y) && !std::isnan(z) && !std::isnan(w); + } + + template::has_quiet_NaN, int>::type = 0> + inline operator bool() const { + return x != std::numeric_limits::min() + && y != std::numeric_limits::min() + && z != std::numeric_limits::min() + && w != std::numeric_limits::min(); + } }; diff --git a/src/mbgl/annotation/point_annotation_impl.cpp b/src/mbgl/annotation/point_annotation_impl.cpp index b54995bfdd4..c02b60c748d 100644 --- a/src/mbgl/annotation/point_annotation_impl.cpp +++ b/src/mbgl/annotation/point_annotation_impl.cpp @@ -14,8 +14,8 @@ void PointAnnotationImpl::updateLayer(const TileID& tileID, AnnotationTileLayer& mbgl::ScreenCoordinate projected = point.position.project(); projected *= 1 << tileID.z; - projected.x -= int16_t(projected.x); - projected.y -= int16_t(projected.y); + projected.x = std::fmod(projected.x, 1); + projected.y = std::fmod(projected.y, 1); projected *= GeometryTileFeature::defaultExtent; layer.features.emplace_back( diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 74e43a62fe0..d3cd30a6275 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -35,10 +35,6 @@ static double _normalizeAngle(double angle, double anchorAngle) return angle; } -inline bool _validPoint(const ScreenCoordinate& point) { - return !std::isnan(point.x) && !std::isnan(point.y); -} - Transform::Transform(View &view_, ConstrainMode constrainMode) : view(view_) , state(constrainMode) @@ -95,17 +91,22 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim if (camera.padding) { padding = *camera.padding; } - const LatLng startLatLng = getLatLng(padding); + + LatLng startLatLng = getLatLng(padding); + startLatLng.unwrapForShortestPath(latLng); + + // Make sure the end coordinate always remains valid. + latLng.wrap(); + const ScreenCoordinate startPoint = { state.lngX(startLatLng.longitude), state.latY(startLatLng.latitude), }; - latLng.unwrapForShortestPath(getLatLng()); const ScreenCoordinate endPoint = { state.lngX(latLng.longitude), state.latY(latLng.latitude), }; - ScreenCoordinate center = padding.getCenter(state.width, state.height); + ScreenCoordinate center = getScreenCoordinate(padding); center.y = state.height - center.y; // Constrain camera options. @@ -136,7 +137,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim ScreenCoordinate framePoint = util::interpolate(startPoint, endPoint, t); LatLng frameLatLng = { state.yLat(framePoint.y, startWorldSize), - state.xLng(framePoint.x, startWorldSize), + state.xLng(framePoint.x, startWorldSize) }; double frameScale = util::interpolate(startScale, scale, t); state.setLatLngZoom(frameLatLng, state.scaleZoom(frameScale)); @@ -178,17 +179,22 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima if (camera.padding) { padding = *camera.padding; } - const LatLng startLatLng = getLatLng(padding); + + LatLng startLatLng = getLatLng(padding); + startLatLng.unwrapForShortestPath(latLng); + + // Make sure the end coordinate always remains valid. + latLng.wrap(); + const ScreenCoordinate startPoint = { state.lngX(startLatLng.longitude), state.latY(startLatLng.latitude), }; - latLng.unwrapForShortestPath(getLatLng()); const ScreenCoordinate endPoint = { state.lngX(latLng.longitude), state.latY(latLng.latitude), }; - ScreenCoordinate center = padding.getCenter(state.width, state.height); + ScreenCoordinate center = getScreenCoordinate(padding); center.y = state.height - center.y; // Constrain camera options. @@ -205,8 +211,9 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima /// w₀: Initial visible span, measured in pixels at the initial scale. /// Known henceforth as a screenful. - double w0 = std::max(state.width - padding.left - padding.right, - state.height - padding.top - padding.bottom); + double w0 = padding ? std::max(state.width, state.height) + : std::max(state.width - padding.left - padding.right, + state.height - padding.top - padding.bottom); /// w₁: Final visible span, measured in pixels with respect to the initial /// scale. double w1 = w0 / state.zoomScale(zoom - startZoom); @@ -331,15 +338,13 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima #pragma mark - Position void Transform::moveBy(const ScreenCoordinate& offset, const Duration& duration) { - if (!_validPoint(offset)) { - return; - } + if (!offset) return; ScreenCoordinate centerOffset = { offset.x, -offset.y, }; - ScreenCoordinate centerPoint = state.latLngToScreenCoordinate(state.getLatLng()) - centerOffset; + ScreenCoordinate centerPoint = getScreenCoordinate() - centerOffset; CameraOptions camera; camera.center = state.screenCoordinateToLatLng(centerPoint); @@ -376,7 +381,7 @@ void Transform::setLatLng(const LatLng& latLng, const ScreenCoordinate& point, c padding.left = point.x; padding.bottom = state.height - point.y; padding.right = state.width - point.x; - camera.padding = padding; + if (padding) camera.padding = padding; easeTo(camera, duration); } @@ -406,11 +411,19 @@ LatLng Transform::getLatLng(const EdgeInsets& padding) const { } } +ScreenCoordinate Transform::getScreenCoordinate(const EdgeInsets& padding) const { + if (padding) { + return padding.getCenter(state.width, state.height); + } else { + return { state.width / 2., state.height / 2. }; + } +} + #pragma mark - Zoom void Transform::scaleBy(double ds, const ScreenCoordinate& center, const Duration& duration) { - if (std::isnan(ds)) { + if (std::isnan(ds) || !center) { return; } @@ -423,8 +436,7 @@ void Transform::setZoom(double zoom, const ScreenCoordinate& anchor, const Durat } void Transform::setZoom(double zoom, const EdgeInsets& padding, const Duration& duration) { - const ScreenCoordinate center = padding.getCenter(state.width, state.height); - setZoom(zoom, center, duration); + setScale(state.zoomScale(zoom), padding, duration); } double Transform::getZoom() const { @@ -442,20 +454,21 @@ void Transform::setScale(double scale, const ScreenCoordinate& anchor, const Dur CameraOptions camera; camera.zoom = state.scaleZoom(scale); - camera.anchor = anchor; + if (anchor) camera.anchor = anchor; easeTo(camera, duration); } void Transform::setScale(double scale, const EdgeInsets& padding, const Duration& duration) { - const ScreenCoordinate center = padding.getCenter(state.width, state.height); - setScale(scale, center, duration); + setScale(scale, getScreenCoordinate(padding), duration); } void Transform::setMinZoom(const double minZoom) { + if (std::isnan(minZoom)) return; state.setMinZoom(minZoom); } void Transform::setMaxZoom(const double maxZoom) { + if (std::isnan(maxZoom)) return; state.setMaxZoom(maxZoom); } @@ -466,9 +479,7 @@ void Transform::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& return; } - ScreenCoordinate center(state.width, state.height); - center /= 2; - + ScreenCoordinate center = getScreenCoordinate(); const ScreenCoordinate offset = first - center; const double distance = std::sqrt(std::pow(2, offset.x) + std::pow(2, offset.y)); @@ -491,7 +502,7 @@ void Transform::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& } void Transform::setAngle(double angle, const Duration& duration) { - setAngle(angle, {NAN, NAN}, duration); + setAngle(angle, ScreenCoordinate {}, duration); } void Transform::setAngle(double angle, const ScreenCoordinate& anchor, const Duration& duration) { @@ -501,13 +512,12 @@ void Transform::setAngle(double angle, const ScreenCoordinate& anchor, const Dur CameraOptions camera; camera.angle = angle; - camera.anchor = anchor; + if (anchor) camera.anchor = anchor; easeTo(camera, duration); } void Transform::setAngle(double angle, const EdgeInsets& padding, const Duration& duration) { - const ScreenCoordinate center = padding.getCenter(state.width, state.height); - setAngle(angle, center, duration); + setAngle(angle, getScreenCoordinate(padding), duration); } double Transform::getAngle() const { @@ -517,7 +527,7 @@ double Transform::getAngle() const { #pragma mark - Pitch void Transform::setPitch(double pitch, const Duration& duration) { - setPitch(pitch, {NAN, NAN}, duration); + setPitch(pitch, ScreenCoordinate {}, duration); } void Transform::setPitch(double pitch, const ScreenCoordinate& anchor, const Duration& duration) { @@ -527,7 +537,7 @@ void Transform::setPitch(double pitch, const ScreenCoordinate& anchor, const Dur CameraOptions camera; camera.pitch = pitch; - camera.anchor = anchor; + if (anchor) camera.anchor = anchor; easeTo(camera, duration); } @@ -571,11 +581,11 @@ void Transform::startTransition(const CameraOptions& camera, view.notifyMapChange(isAnimated ? MapChangeRegionWillChangeAnimated : MapChangeRegionWillChange); // Associate the anchor, if given, with a coordinate. - ScreenCoordinate anchor = camera.anchor ? *camera.anchor : ScreenCoordinate(NAN, NAN); + optional anchor = camera.anchor; LatLng anchorLatLng; - if (_validPoint(anchor)) { - anchor.y = state.getHeight() - anchor.y; - anchorLatLng = state.screenCoordinateToLatLng(anchor); + if (anchor && *anchor) { + anchor->y = state.getHeight() - anchor->y; + anchorLatLng = state.screenCoordinateToLatLng(*anchor); } transitionStart = Clock::now(); @@ -591,9 +601,7 @@ void Transform::startTransition(const CameraOptions& camera, result = frame(ease.solve(t, 0.001)); } - if (_validPoint(anchor)) { - state.moveLatLng(anchorLatLng, anchor); - } + if (anchor && *anchor) state.moveLatLng(anchorLatLng, *anchor); // At t = 1.0, a DidChangeAnimated notification should be sent from finish(). if (t < 1.0) { @@ -651,12 +659,28 @@ void Transform::setGestureInProgress(bool inProgress) { #pragma mark Conversion and projection ScreenCoordinate Transform::latLngToScreenCoordinate(const LatLng& latLng) const { - ScreenCoordinate point = state.latLngToScreenCoordinate(latLng); + if (!latLng) return {}; + + // If the center and point coordinates are not in the same side of the + // antimeridian, we need to unwrap the point longitude to make sure it can + // still be seen from the visible side of the antimeridian that is opposite + // to the center side. + double longitude = latLng.longitude; + const double centerLng = getLatLng().longitude; + if (centerLng - latLng.longitude > util::LONGITUDE_MAX) { + if (centerLng > 0 && latLng.longitude < 0) { + longitude += util::DEGREES_MAX; + } else if (centerLng < 0 && latLng.longitude > 0) { + longitude -= util::DEGREES_MAX; + } + } + ScreenCoordinate point = state.latLngToScreenCoordinate({ latLng.latitude, longitude }); point.y = state.height - point.y; return point; } LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point) const { + if (!point) return {}; ScreenCoordinate flippedPoint = point; flippedPoint.y = state.height - flippedPoint.y; return state.screenCoordinateToLatLng(flippedPoint); diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 638eead5f1b..e904b3956a2 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -48,6 +48,7 @@ class Transform : private util::noncopyable { void setLatLngZoom(const LatLng&, double zoom, const Duration& = Duration::zero()); void setLatLngZoom(const LatLng&, double zoom, const EdgeInsets&, const Duration& = Duration::zero()); LatLng getLatLng(const EdgeInsets& = {}) const; + ScreenCoordinate getScreenCoordinate(const EdgeInsets& = {}) const; // Zoom @@ -55,12 +56,12 @@ class Transform : private util::noncopyable { @param ds The difference in scale factors to scale the map by. @param anchor A point relative to the top-left corner of the view. If unspecified, the center point is fixed within the view. */ - void scaleBy(double ds, const ScreenCoordinate& anchor = {NAN, NAN}, const Duration& = Duration::zero()); + void scaleBy(double ds, const ScreenCoordinate& anchor = {}, const Duration& = Duration::zero()); /** Sets the scale factor, keeping the given point fixed within the view. @param scale The new scale factor. @param anchor A point relative to the top-left corner of the view. If unspecified, the center point is fixed within the view. */ - void setScale(double scale, const ScreenCoordinate& anchor = {NAN, NAN}, const Duration& = Duration::zero()); + void setScale(double scale, const ScreenCoordinate& anchor = {}, const Duration& = Duration::zero()); /** Sets the scale factor, keeping the center point fixed within the inset view. @param scale The new scale factor. @param padding The viewport padding that affects the fixed center point. */ @@ -69,7 +70,7 @@ class Transform : private util::noncopyable { @param zoom The new zoom level. @param anchor A point relative to the top-left corner of the view. If unspecified, the center point is fixed within the view. */ - void setZoom(double zoom, const ScreenCoordinate& anchor = {NAN, NAN}, const Duration& = Duration::zero()); + void setZoom(double zoom, const ScreenCoordinate& anchor = {}, const Duration& = Duration::zero()); /** Sets the zoom level, keeping the center point fixed within the inset view. @param zoom The new zoom level. @param padding The viewport padding that affects the fixed center point. */ diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 006a8d5d3a5..a23ee71f991 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -89,38 +89,11 @@ ConstrainMode TransformState::getConstrainMode() const { #pragma mark - Position LatLng TransformState::getLatLng(LatLng::WrapMode wrapMode) const { - LatLng ll { + return { util::RAD2DEG * (2 * std::atan(std::exp(y / Cc)) - 0.5 * M_PI), -x / Bc, wrapMode }; - - if (wrapMode == LatLng::Unwrapped) return ll; - - // adjust for date line - double w = worldSize() / 2; - double x_ = x; - if (x_ > w) { - while (x_ > w) { - x_ -= w; - if (ll.longitude < 0) { - ll.longitude += util::LONGITUDE_MAX; - } else if (ll.longitude > 0) { - ll.longitude -= util::LONGITUDE_MAX; - } - } - } else if (x_ < -w) { - while (x_ < -w) { - x_ += w; - if (ll.longitude < 0) { - ll.longitude -= util::LONGITUDE_MAX; - } else if (ll.longitude > 0) { - ll.longitude -= util::LONGITUDE_MAX; - } - } - } - - return ll; } double TransformState::pixel_x() const { @@ -350,7 +323,7 @@ void TransformState::moveLatLng(const LatLng& latLng, const ScreenCoordinate& an auto centerCoord = latLngToTileCoord(getLatLng(LatLng::Unwrapped)); auto latLngCoord = latLngToTileCoord(latLng); - auto anchorCoord = latLngToTileCoord(screenCoordinateToLatLng(anchor, LatLng::Unwrapped)); + auto anchorCoord = latLngToTileCoord(screenCoordinateToLatLng(anchor)); setLatLngZoom(tileCoordToLatLng(centerCoord + latLngCoord - anchorCoord), getZoom()); } diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 797a28692e3..ef3a5dfefd8 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -37,7 +37,7 @@ class TransformState { ConstrainMode getConstrainMode() const; // Position - LatLng getLatLng(LatLng::WrapMode = LatLng::Wrapped) const; + LatLng getLatLng(LatLng::WrapMode = LatLng::Unwrapped) const; double pixel_x() const; double pixel_y() const; @@ -65,7 +65,7 @@ class TransformState { // Conversion and projection ScreenCoordinate latLngToScreenCoordinate(const LatLng&) const; - LatLng screenCoordinateToLatLng(const ScreenCoordinate&, LatLng::WrapMode = LatLng::Wrapped) const; + LatLng screenCoordinateToLatLng(const ScreenCoordinate&, LatLng::WrapMode = LatLng::Unwrapped) const; double xLng(double x, double worldSize) const; double yLat(double y, double worldSize) const; diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 0fae940271e..6efff4bb573 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -23,7 +23,7 @@ class TileCoordinate { } static TileCoordinate fromScreenCoordinate(const TransformState& state, double zoom, const ScreenCoordinate& point) { - return fromLatLng(state, zoom, state.screenCoordinateToLatLng(point, LatLng::Unwrapped)); + return fromLatLng(state, zoom, state.screenCoordinateToLatLng(point)); } }; diff --git a/test/map/transform.cpp b/test/map/transform.cpp index 98fc1223e54..6b9e960fe6b 100644 --- a/test/map/transform.cpp +++ b/test/map/transform.cpp @@ -158,13 +158,17 @@ TEST(Transform, UnwrappedLatLng) { ASSERT_NEAR(fromScreenCoordinate.latitude, 37.999999999999829, 0.0001); // 1.71E-13 ASSERT_NEAR(fromScreenCoordinate.longitude, -76.999999999999773, 0.0001); // 2.27E-13 - LatLng wrappedForwards = state.screenCoordinateToLatLng(state.latLngToScreenCoordinate({ 38, 283, LatLng::Wrapped })); + LatLng wrappedForwards = state.screenCoordinateToLatLng(state.latLngToScreenCoordinate({ 38, 283 })); ASSERT_NEAR(wrappedForwards.latitude, 37.999999999999716, 0.0001); // 2.84E-13 - ASSERT_DOUBLE_EQ(wrappedForwards.longitude, fromScreenCoordinate.longitude); - - LatLng wrappedBackwards = state.screenCoordinateToLatLng(state.latLngToScreenCoordinate({ 38, -437, LatLng::Wrapped })); - ASSERT_DOUBLE_EQ(wrappedBackwards.latitude, wrappedForwards.latitude); - ASSERT_DOUBLE_EQ(wrappedBackwards.longitude, fromScreenCoordinate.longitude); + ASSERT_NEAR(wrappedForwards.longitude, 282.99999999988751, 0.0001); // 1.1249E-11 + wrappedForwards.wrap(); + ASSERT_NEAR(wrappedForwards.longitude, -77.000000000112493, 0.001); // 1.1249E-11 + + LatLng wrappedBackwards = state.screenCoordinateToLatLng(state.latLngToScreenCoordinate({ 38, -437 })); + ASSERT_NEAR(wrappedBackwards.latitude, wrappedForwards.latitude, 0.001); + ASSERT_NEAR(wrappedBackwards.longitude, -436.99999999988728, 0.001); // 1.1272E-11 + wrappedBackwards.wrap(); + ASSERT_NEAR(wrappedBackwards.longitude, -76.99999999988728, 0.001); // 1.1272E-11 } TEST(Transform, ConstrainHeightOnly) { @@ -178,7 +182,7 @@ TEST(Transform, ConstrainHeightOnly) { transform.setLatLng(LatLngBounds::world().southwest()); loc = transform.getLatLng(); ASSERT_NEAR(-util::LATITUDE_MAX, loc.latitude, 0.001); - ASSERT_NEAR(-util::LONGITUDE_MAX, loc.longitude, 0.001); + ASSERT_NEAR(util::LONGITUDE_MAX, std::abs(loc.longitude), 0.001); transform.setLatLng(LatLngBounds::world().northeast()); loc = transform.getLatLng(); @@ -249,8 +253,14 @@ TEST(Transform, Padding) { }); EdgeInsets padding; + + padding.top = 0; + ASSERT_FALSE(bool(padding)); + + padding.top = NAN; + ASSERT_FALSE(bool(padding)); + padding.top = 1000.0 / 2.0; - ASSERT_GT(padding.top, 0); ASSERT_TRUE(bool(padding)); const LatLng shiftedCenter = transform.getLatLng(padding); @@ -259,3 +269,53 @@ TEST(Transform, Padding) { ASSERT_DOUBLE_EQ(manualShiftedCenter.latitude, shiftedCenter.latitude); ASSERT_DOUBLE_EQ(manualShiftedCenter.longitude, shiftedCenter.longitude); } + +TEST(Transform, MoveBy) { + MockView view; + Transform transform(view, ConstrainMode::HeightOnly); + transform.resize({{ 1000, 1000 }}); + transform.setLatLngZoom({ 0, 0 }, 10); + + LatLng trueCenter = transform.getLatLng(); + ASSERT_DOUBLE_EQ(0, trueCenter.latitude); + ASSERT_DOUBLE_EQ(0, trueCenter.longitude); + ASSERT_DOUBLE_EQ(10, transform.getZoom()); + + for (uint8_t x = 0; x < 20; ++x) { + bool odd = x % 2; + bool forward = x % 10; + + LatLng coordinate = transform.screenCoordinateToLatLng({ odd ? 400. : 600., forward ? 400. : 600 }); + transform.moveBy({ odd ? 100. : -100., forward ? 100. : -100 }); + + trueCenter = transform.getLatLng(); + ASSERT_NEAR(coordinate.latitude, trueCenter.latitude, 0.0001); + ASSERT_NEAR(coordinate.longitude, trueCenter.longitude, 0.0001); + } + + // We have ~1.1 precision loss for each coordinate for 20 rounds of moveBy. + ASSERT_NEAR(0, trueCenter.latitude, 1.1); + ASSERT_NEAR(0, trueCenter.longitude, 1.1); +} + +TEST(Transform, Antimeridian) { + MockView view; + Transform transform(view, ConstrainMode::HeightOnly); + transform.resize({{ 1000, 1000 }}); + transform.setLatLngZoom({ 0, 0 }, 1); + + const LatLng coordinateSanFrancisco { 37.7833, -122.4167 }; + ScreenCoordinate pixelSF = transform.latLngToScreenCoordinate(coordinateSanFrancisco); + ASSERT_DOUBLE_EQ(151.79409149185352, pixelSF.x); + ASSERT_DOUBLE_EQ(383.76774094913071, pixelSF.y); + + transform.setLatLng({ 0, -181 }); + ScreenCoordinate pixelSFBackwards = transform.latLngToScreenCoordinate(coordinateSanFrancisco); + ASSERT_DOUBLE_EQ(666.63617954008976, pixelSFBackwards.x); + ASSERT_DOUBLE_EQ(pixelSF.y, pixelSFBackwards.y); + + transform.setLatLng({ 0, 179 }); + ScreenCoordinate pixelSFForwards = transform.latLngToScreenCoordinate(coordinateSanFrancisco); + ASSERT_DOUBLE_EQ(pixelSFBackwards.x, pixelSFForwards.x); + ASSERT_DOUBLE_EQ(pixelSFBackwards.y, pixelSFForwards.y); +} diff --git a/test/util/geo.cpp b/test/util/geo.cpp index 86f448ebbc7..bdf335dc47a 100644 --- a/test/util/geo.cpp +++ b/test/util/geo.cpp @@ -105,6 +105,34 @@ TEST(LatLng, FromTileID) { } } +TEST(LatLng, Boundaries) { + LatLng coordinate; + ASSERT_DOUBLE_EQ(0, coordinate.latitude); + ASSERT_DOUBLE_EQ(0, coordinate.longitude); + + coordinate.longitude = -180.1; + ASSERT_DOUBLE_EQ(-180.1, coordinate.longitude); + + coordinate.wrap(); + ASSERT_DOUBLE_EQ(179.90000000000001, coordinate.longitude); // 1E-14 + + coordinate.longitude = 180.9; + coordinate.wrap(); + ASSERT_DOUBLE_EQ(-179.09999999999999, coordinate.longitude); + + coordinate.longitude = -360.5; + coordinate.wrap(); + ASSERT_DOUBLE_EQ(-0.5, coordinate.longitude); + + coordinate.longitude = 360.5; + coordinate.wrap(); + ASSERT_DOUBLE_EQ(0.5, coordinate.longitude); + + coordinate.longitude = 360000.5; + coordinate.wrap(); + ASSERT_DOUBLE_EQ(0.5, coordinate.longitude); +} + TEST(LatLngBounds, FromTileID) { { const LatLngBounds bounds{ TileID(0, 0, 0, 0) };