co-maps/libs/drape_frontend/gui/ruler_helper.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

238 lines
6.7 KiB
C++
Raw Normal View History

2025-11-22 13:58:55 +01:00
#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 <algorithm>
#include <cmath>
#include <iterator>
#include <limits>
#include <set>
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<int>::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<float>(minPxWidth);
if (higherThanMax)
m_pixelLength = static_cast<float>(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<float>(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<int>(-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<char> 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<uint32_t>(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