#include "drape_frontend/gui/ruler_helper.hpp" #include "drape_frontend/gui/drape_gui.hpp" #include "drape_frontend/visual_params.hpp" #include "platform/measurement_utils.hpp" #include "geometry/mercator.hpp" #include "geometry/screenbase.hpp" #include "base/macros.hpp" #include #include #include #include #include namespace gui { namespace { float constexpr kMinPixelWidth = 60.f; int constexpr kMinMetersWidth = 5; int constexpr kMaxMetersWidth = 1000000; int constexpr kMinUnitValue = -1; int constexpr kMaxUnitValue = std::numeric_limits::max() - 1; int constexpr kInvalidUnitValue = kMaxUnitValue + 1; int constexpr kVisibleRulerBottomScale = 5; struct UnitValue { char const * m_s; int m_i; }; // TODO: Localize these strings. UnitValue constexpr g_arrFeets[] = { {"10 ft", 10}, {"20 ft", 20}, {"50 ft", 50}, {"100 ft", 100}, {"200 ft", 200}, {"0.1 mi", 528}, {"0.2 mi", 528 * 2}, {"0.5 mi", 528 * 5}, {"1 mi", 5280}, {"2 mi", 2 * 5280}, {"5 mi", 5 * 5280}, {"10 mi", 10 * 5280}, {"20 mi", 20 * 5280}, {"50 mi", 50 * 5280}, {"100 mi", 100 * 5280}, {"200 mi", 200 * 5280}, {"500 mi", 500 * 5280}}; UnitValue constexpr g_arrYards[] = { {"50 yd", 50}, {"100 yd", 100}, {"200 yd", 200}, {"500 yd", 500}, {"0.5 mi", 1760 / 2}, {"1 mi", 1760}, {"2 mi", 2 * 1760}, {"5 mi", 5 * 1760}, {"10 mi", 10 * 1760}, {"20 mi", 20 * 1760}, {"50 mi", 50 * 1760}, {"100 mi", 100 * 1760}, {"200 mi", 200 * 1760}, {"500 mi", 500 * 1760}}; // TODO: fix ruler text to the current zoom level, i.e. make it 100m for z16 always // (ruler length will vary still). It'll make debugging and user support easier as // we'll be able to tell zoom level of a screenshot by looking at the ruler. UnitValue constexpr g_arrMeters[] = {{"1 m", 1}, {"2 m", 2}, {"5 m", 5}, {"10 m", 10}, {"20 m", 20}, {"50 m", 50}, {"100 m", 100}, {"200 m", 200}, {"500 m", 500}, {"1 km", 1000}, {"2 km", 2000}, {"5 km", 5000}, {"10 km", 10000}, {"20 km", 20000}, {"50 km", 50000}, {"100 km", 100000}, {"200 km", 200000}, {"500 km", 500000}, {"1000 km", 1000000}}; double Identity(double val) { return val; } } // namespace RulerHelper::RulerHelper() : m_pixelLength(0.0f) , m_rangeIndex(kInvalidUnitValue) , m_isTextDirty(false) , m_dirtyTextRequested(false) {} void RulerHelper::Update(ScreenBase const & screen) { m2::PointD pivot = screen.PixelRect().Center(); long const minPxWidth = std::lround(kMinPixelWidth * df::VisualParams::Instance().GetVisualScale()); m2::PointD pt1 = screen.PtoG(pivot); m2::PointD pt0 = screen.PtoG(pivot - m2::PointD(minPxWidth, 0)); double const distanceInMeters = mercator::DistanceOnEarth(pt0, pt1); // convert metres to units for calculating m_metresDiff. double metersDiff = CalcMetersDiff(distanceInMeters); bool const higherThanMax = metersDiff > kMaxMetersWidth; ASSERT_GREATER_OR_EQUAL(metersDiff, kMinMetersWidth, ()); m_pixelLength = static_cast(minPxWidth); if (higherThanMax) m_pixelLength = static_cast(minPxWidth) * 3.0f / 2.0f; else { double const a = ang::AngleTo(pt1, pt0); pt0 = mercator::GetSmPoint(pt1, cos(a) * metersDiff, sin(a) * metersDiff); m_pixelLength = std::round(pivot.Length(screen.GtoP(pt0))); } int drawScale = df::GetDrawTileScale(screen); if (m_currentDrawScale < kVisibleRulerBottomScale && drawScale >= kVisibleRulerBottomScale) SetTextDirty(); m_currentDrawScale = drawScale; } // static bool RulerHelper::IsVisible(ScreenBase const & screen) { DrapeGui & gui = DrapeGui::Instance(); return !gui.IsCopyrightActive() && df::GetDrawTileScale(screen) >= kVisibleRulerBottomScale; } void RulerHelper::Invalidate() { SetTextDirty(); } // static float RulerHelper::GetRulerHalfHeight() { float constexpr kRulerHalfHeight = 1.0f; return kRulerHalfHeight * static_cast(df::VisualParams::Instance().GetVisualScale()); } float RulerHelper::GetRulerPixelLength() const { return m_pixelLength; } // static float RulerHelper::GetMaxRulerPixelLength() { return kMinPixelWidth * 3.0f / 2.0f; } // static int RulerHelper::GetVerticalTextOffset() { return static_cast(-5 * df::VisualParams::Instance().GetVisualScale()); } bool RulerHelper::IsTextDirty() const { return m_isTextDirty; } std::string const & RulerHelper::GetRulerText() const { m_dirtyTextRequested = true; return m_rulerText; } void RulerHelper::ResetTextDirtyFlag() { if (m_dirtyTextRequested) m_isTextDirty = false; } // static void RulerHelper::GetTextInitInfo(std::string & alphabet, uint32_t & size) { std::set symbols; size_t result = 0; auto const functor = [&result, &symbols](UnitValue const & v) { size_t stringSize = strlen(v.m_s); result = std::max(result, stringSize); for (size_t i = 0; i < stringSize; ++i) symbols.insert(v.m_s[i]); }; std::for_each(std::begin(g_arrFeets), std::end(g_arrFeets), functor); std::for_each(std::begin(g_arrMeters), std::end(g_arrMeters), functor); std::for_each(std::begin(g_arrYards), std::end(g_arrYards), functor); std::for_each(begin(symbols), end(symbols), [&alphabet](char c) { alphabet.push_back(c); }); alphabet.append("<>"); size = static_cast(result) + 2; // add 2 char for symbols "< " and "> ". } double RulerHelper::CalcMetersDiff(double value) { UnitValue const * arrU = g_arrMeters; int count = ARRAY_SIZE(g_arrMeters); auto conversionFn = &Identity; using namespace measurement_utils; if (GetMeasurementUnits() == Units::Imperial) { arrU = g_arrFeets; count = ARRAY_SIZE(g_arrFeets); conversionFn = &MetersToFeet; } int prevUnitRange = m_rangeIndex; double result = 0.0; double v = conversionFn(value); if (arrU[0].m_i > v) { m_rangeIndex = kMinUnitValue; // TODO: "< X" ruler text seems to be never used. m_rulerText = std::string("< ") + arrU[0].m_s; result = kMinMetersWidth - 1.0; } else if (arrU[count - 1].m_i <= v) { m_rangeIndex = kMaxUnitValue; m_rulerText = std::string("> ") + arrU[count - 1].m_s; result = kMaxMetersWidth + 1.0; } else { for (int i = 0; i < count; ++i) { if (arrU[i].m_i > v) { m_rangeIndex = i; result = arrU[i].m_i / conversionFn(1.0); m_rulerText = arrU[i].m_s; break; } } } if (m_rangeIndex != prevUnitRange) SetTextDirty(); return result; } void RulerHelper::SetTextDirty() { m_dirtyTextRequested = false; m_isTextDirty = true; } } // namespace gui