Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
109
libs/map/CMakeLists.txt
Normal file
109
libs/map/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
project(map)
|
||||
|
||||
set(SRC
|
||||
api_mark_point.cpp
|
||||
api_mark_point.hpp
|
||||
benchmark_tools.hpp
|
||||
benchmark_tools.cpp
|
||||
bookmark.cpp
|
||||
bookmark.hpp
|
||||
bookmark_helpers.cpp
|
||||
bookmark_helpers.hpp
|
||||
bookmark_manager.cpp
|
||||
bookmark_manager.hpp
|
||||
bookmarks_search_params.hpp
|
||||
chart_generator.cpp
|
||||
chart_generator.hpp
|
||||
elevation_info.cpp
|
||||
elevation_info.hpp
|
||||
everywhere_search_callback.cpp
|
||||
everywhere_search_callback.hpp
|
||||
everywhere_search_params.hpp
|
||||
extrapolation/extrapolator.cpp
|
||||
extrapolation/extrapolator.hpp
|
||||
features_fetcher.cpp
|
||||
features_fetcher.hpp
|
||||
framework.cpp
|
||||
framework.hpp
|
||||
gps_track_collection.cpp
|
||||
gps_track_collection.hpp
|
||||
gps_track_filter.cpp
|
||||
gps_track_filter.hpp
|
||||
gps_track_storage.cpp
|
||||
gps_track_storage.hpp
|
||||
gps_track.cpp
|
||||
gps_track.hpp
|
||||
gps_tracker.cpp
|
||||
gps_tracker.hpp
|
||||
isolines_manager.cpp
|
||||
isolines_manager.hpp
|
||||
mwm_url.cpp
|
||||
mwm_url.hpp
|
||||
place_page_info.cpp
|
||||
place_page_info.hpp
|
||||
position_provider.hpp
|
||||
power_management/power_manager.cpp
|
||||
power_management/power_manager.hpp
|
||||
power_management/power_management_schemas.cpp
|
||||
power_management/power_management_schemas.hpp
|
||||
routing_manager.cpp
|
||||
routing_manager.hpp
|
||||
routing_mark.cpp
|
||||
routing_mark.hpp
|
||||
search_api.cpp
|
||||
search_api.hpp
|
||||
search_mark.cpp
|
||||
search_mark.hpp
|
||||
search_product_info.hpp
|
||||
track.cpp
|
||||
track.hpp
|
||||
track_statistics.cpp
|
||||
track_statistics.hpp
|
||||
track_mark.cpp
|
||||
track_mark.hpp
|
||||
traffic_manager.cpp
|
||||
traffic_manager.hpp
|
||||
transit/transit_display.cpp
|
||||
transit/transit_display.hpp
|
||||
transit/transit_reader.cpp
|
||||
transit/transit_reader.hpp
|
||||
user_mark_id_storage.cpp
|
||||
user_mark_id_storage.hpp
|
||||
user_mark_layer.cpp
|
||||
user_mark_layer.hpp
|
||||
user_mark.cpp
|
||||
user_mark.hpp
|
||||
viewport_search_params.hpp
|
||||
viewport_search_callback.cpp
|
||||
viewport_search_callback.hpp
|
||||
)
|
||||
|
||||
if (PLATFORM_DESKTOP)
|
||||
append(SRC
|
||||
framework_visualize.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
routing
|
||||
indexer
|
||||
search
|
||||
descriptions
|
||||
transit
|
||||
kml
|
||||
ge0
|
||||
drape_frontend
|
||||
agg
|
||||
)
|
||||
|
||||
omim_add_test_subdirectory(map_integration_tests)
|
||||
omim_add_test_subdirectory(map_tests)
|
||||
omim_add_test_subdirectory(mwm_tests)
|
||||
omim_add_test_subdirectory(style_tests)
|
||||
|
||||
if (PLATFORM_DESKTOP)
|
||||
omim_add_tool_subdirectory(benchmark_tool)
|
||||
omim_add_tool_subdirectory(extrapolation_benchmark)
|
||||
endif()
|
||||
74
libs/map/api_mark_point.cpp
Normal file
74
libs/map/api_mark_point.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include "map/api_mark_point.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace style
|
||||
{
|
||||
std::map<std::string, std::string> kStyleToColor = {{"placemark-red", "BookmarkRed"},
|
||||
{"placemark-blue", "BookmarkBlue"},
|
||||
{"placemark-purple", "BookmarkPurple"},
|
||||
{"placemark-yellow", "BookmarkYellow"},
|
||||
{"placemark-pink", "BookmarkPink"},
|
||||
{"placemark-brown", "BookmarkBrown"},
|
||||
{"placemark-green", "BookmarkGreen"},
|
||||
{"placemark-orange", "BookmarkOrange"},
|
||||
{"placemark-deeppurple", "BookmarkDeepPurple"},
|
||||
{"placemark-lightblue", "BookmarkLightBlue"},
|
||||
{"placemark-cyan", "BookmarkCyan"},
|
||||
{"placemark-teal", "BookmarkTeal"},
|
||||
{"placemark-lime", "BookmarkLime"},
|
||||
{"placemark-deeporange", "BookmarkDeepOrange"},
|
||||
{"placemark-gray", "BookmarkGray"},
|
||||
{"placemark-bluegray", "BookmarkBlueGray"}};
|
||||
|
||||
std::string GetSupportedStyle(std::string const & style)
|
||||
{
|
||||
auto const it = kStyleToColor.find(style);
|
||||
if (it == kStyleToColor.cend())
|
||||
return "BookmarkGreen";
|
||||
return it->second;
|
||||
}
|
||||
} // namespace style
|
||||
|
||||
ApiMarkPoint::ApiMarkPoint(m2::PointD const & ptOrg) : UserMark(ptOrg, UserMark::Type::API) {}
|
||||
|
||||
ApiMarkPoint::ApiMarkPoint(std::string const & name, std::string const & id, std::string const & style,
|
||||
m2::PointD const & ptOrg)
|
||||
: UserMark(ptOrg, UserMark::Type::API)
|
||||
, m_name(name)
|
||||
, m_id(id)
|
||||
, m_style(style)
|
||||
{}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> ApiMarkPoint::GetSymbolNames() const
|
||||
{
|
||||
// TODO: use its own icon.
|
||||
auto symbol = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbol->insert(std::make_pair(1 /* zoomLevel */, "coloredmark-default-s"));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
df::ColorConstant ApiMarkPoint::GetColorConstant() const
|
||||
{
|
||||
return m_style;
|
||||
}
|
||||
|
||||
void ApiMarkPoint::SetName(std::string const & name)
|
||||
{
|
||||
SetDirty();
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
void ApiMarkPoint::SetApiID(std::string const & id)
|
||||
{
|
||||
SetDirty();
|
||||
m_id = id;
|
||||
}
|
||||
|
||||
void ApiMarkPoint::SetStyle(std::string const & style)
|
||||
{
|
||||
SetDirty();
|
||||
m_style = style;
|
||||
}
|
||||
39
libs/map/api_mark_point.hpp
Normal file
39
libs/map/api_mark_point.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/user_mark.hpp"
|
||||
#include "map/user_mark_layer.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace style
|
||||
{
|
||||
// Fixes icons which are not supported by CoMaps.
|
||||
std::string GetSupportedStyle(std::string const & style);
|
||||
} // namespace style
|
||||
|
||||
class ApiMarkPoint : public UserMark
|
||||
{
|
||||
public:
|
||||
ApiMarkPoint(m2::PointD const & ptOrg);
|
||||
|
||||
ApiMarkPoint(std::string const & name, std::string const & id, std::string const & style, m2::PointD const & ptOrg);
|
||||
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
df::ColorConstant GetColorConstant() const override;
|
||||
|
||||
std::string const & GetName() const { return m_name; }
|
||||
void SetName(std::string const & name);
|
||||
|
||||
std::string const & GetApiID() const { return m_id; }
|
||||
void SetApiID(std::string const & id);
|
||||
|
||||
void SetStyle(std::string const & style);
|
||||
std::string const & GetStyle() const { return m_style; }
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_id;
|
||||
std::string m_style;
|
||||
};
|
||||
15
libs/map/benchmark_tool/CMakeLists.txt
Normal file
15
libs/map/benchmark_tool/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
project(benchmark_tool)
|
||||
|
||||
set(SRC
|
||||
api.cpp
|
||||
api.hpp
|
||||
features_loading.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
map
|
||||
gflags::gflags
|
||||
)
|
||||
54
libs/map/benchmark_tool/api.cpp
Normal file
54
libs/map/benchmark_tool/api.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "map/benchmark_tool/api.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace bench
|
||||
{
|
||||
void Result::PrintAllTimes()
|
||||
{
|
||||
sort(m_time.begin(), m_time.end());
|
||||
copy(m_time.begin(), m_time.end(), std::ostream_iterator<double>(cout, ", "));
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
void Result::CalcMetrics()
|
||||
{
|
||||
if (!m_time.empty())
|
||||
{
|
||||
sort(m_time.begin(), m_time.end());
|
||||
|
||||
m_max = m_time.back();
|
||||
m_med = m_time[m_time.size() / 2];
|
||||
m_all = accumulate(m_time.begin(), m_time.end(), 0.0);
|
||||
m_avg = m_all / m_time.size();
|
||||
|
||||
m_time.clear();
|
||||
}
|
||||
else
|
||||
m_all = -1.0;
|
||||
}
|
||||
|
||||
void AllResult::Print()
|
||||
{
|
||||
// m_reading.PrintAllTimes();
|
||||
m_reading.CalcMetrics();
|
||||
|
||||
if (m_all < 0.0)
|
||||
cout << "No frames" << endl;
|
||||
else
|
||||
{
|
||||
cout << fixed << setprecision(10);
|
||||
size_t const count = 1000;
|
||||
cout << "FRAME*1000[ median:" << m_reading.m_med * count << " avg:" << m_reading.m_avg * count
|
||||
<< " max:" << m_reading.m_max * count << " ] ";
|
||||
cout << "TOTAL[ idx:" << m_all - m_reading.m_all << " decoding:" << m_reading.m_all << " summ:" << m_all << " ]"
|
||||
<< endl;
|
||||
}
|
||||
}
|
||||
} // namespace bench
|
||||
42
libs/map/benchmark_tool/api.hpp
Normal file
42
libs/map/benchmark_tool/api.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace bench
|
||||
{
|
||||
class Result
|
||||
{
|
||||
public:
|
||||
void Add(double t) { m_time.push_back(t); }
|
||||
|
||||
void Add(Result const & r) { m_time.insert(m_time.end(), r.m_time.begin(), r.m_time.end()); }
|
||||
|
||||
void PrintAllTimes();
|
||||
void CalcMetrics();
|
||||
|
||||
double m_all = 0.0;
|
||||
double m_max = 0.0;
|
||||
double m_avg = 0.0;
|
||||
double m_med = 0.0;
|
||||
|
||||
private:
|
||||
std::vector<double> m_time;
|
||||
};
|
||||
|
||||
class AllResult
|
||||
{
|
||||
public:
|
||||
AllResult() = default;
|
||||
|
||||
void Add(double t) { m_all += t; }
|
||||
void Print();
|
||||
|
||||
Result m_reading;
|
||||
double m_all = 0.0;
|
||||
};
|
||||
|
||||
/// @param[in] count number of times to run benchmark
|
||||
void RunFeaturesLoadingBenchmark(std::string filePath, std::pair<int, int> scaleR, AllResult & res);
|
||||
} // namespace bench
|
||||
124
libs/map/benchmark_tool/features_loading.cpp
Normal file
124
libs/map/benchmark_tool/features_loading.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "map/benchmark_tool/api.hpp"
|
||||
|
||||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "indexer/feature_visibility.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/macros.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace bench
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class Accumulator
|
||||
{
|
||||
public:
|
||||
explicit Accumulator(Result & res) : m_res(res) {}
|
||||
|
||||
void Reset(int scale)
|
||||
{
|
||||
m_scale = scale;
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return m_count == 0; }
|
||||
|
||||
void operator()(FeatureType & ft)
|
||||
{
|
||||
++m_count;
|
||||
|
||||
m_timer.Reset();
|
||||
|
||||
drule::KeysT keys;
|
||||
UNUSED_VALUE(feature::GetDrawRule(feature::TypesHolder(ft), m_scale, keys));
|
||||
|
||||
if (!keys.empty())
|
||||
{
|
||||
// Call this function to load feature's inner data and geometry.
|
||||
UNUSED_VALUE(ft.IsEmptyGeometry(m_scale));
|
||||
}
|
||||
|
||||
m_res.Add(m_timer.ElapsedSeconds());
|
||||
}
|
||||
|
||||
private:
|
||||
base::Timer m_timer;
|
||||
size_t m_count = 0;
|
||||
|
||||
Result & m_res;
|
||||
|
||||
int m_scale = 0;
|
||||
};
|
||||
|
||||
void RunBenchmark(FeaturesFetcher const & src, m2::RectD const & rect, pair<int, int> const & scaleRange,
|
||||
AllResult & res)
|
||||
{
|
||||
ASSERT_LESS_OR_EQUAL(scaleRange.first, scaleRange.second, ());
|
||||
|
||||
vector<m2::RectD> rects;
|
||||
rects.push_back(rect);
|
||||
|
||||
Accumulator acc(res.m_reading);
|
||||
|
||||
while (!rects.empty())
|
||||
{
|
||||
m2::RectD const r = rects.back();
|
||||
rects.pop_back();
|
||||
|
||||
bool doDivide = true;
|
||||
int const scale = scales::GetScaleLevel(r);
|
||||
if (scale >= scaleRange.first)
|
||||
{
|
||||
acc.Reset(scale);
|
||||
|
||||
base::Timer timer;
|
||||
src.ForEachFeature(r, acc, scale);
|
||||
res.Add(timer.ElapsedSeconds());
|
||||
|
||||
doDivide = !acc.IsEmpty();
|
||||
}
|
||||
|
||||
if (doDivide && scale < scaleRange.second)
|
||||
{
|
||||
m2::RectD r1, r2;
|
||||
r.DivideByGreaterSize(r1, r2);
|
||||
rects.push_back(r1);
|
||||
rects.push_back(r2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RunFeaturesLoadingBenchmark(string fileName, pair<int, int> scaleRange, AllResult & res)
|
||||
{
|
||||
base::GetNameFromFullPath(fileName);
|
||||
base::GetNameWithoutExt(fileName);
|
||||
|
||||
FeaturesFetcher src;
|
||||
auto const r = src.RegisterMap(platform::LocalCountryFile::MakeForTesting(std::move(fileName)));
|
||||
if (r.second != MwmSet::RegResult::Success)
|
||||
return;
|
||||
|
||||
uint8_t const minScale = r.first.GetInfo()->m_minScale;
|
||||
uint8_t const maxScale = r.first.GetInfo()->m_maxScale;
|
||||
if (minScale > scaleRange.first)
|
||||
scaleRange.first = minScale;
|
||||
if (maxScale < scaleRange.second)
|
||||
scaleRange.second = maxScale;
|
||||
|
||||
if (scaleRange.first > scaleRange.second)
|
||||
return;
|
||||
|
||||
RunBenchmark(src, r.first.GetInfo()->m_bordersRect, scaleRange, res);
|
||||
}
|
||||
} // namespace bench
|
||||
51
libs/map/benchmark_tool/main.cpp
Normal file
51
libs/map/benchmark_tool/main.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "map/benchmark_tool/api.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_header.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
DEFINE_string(input, "", "MWM file name in the data directory");
|
||||
DEFINE_int32(lowS, 10, "Low processing scale");
|
||||
DEFINE_int32(highS, 17, "High processing scale");
|
||||
DEFINE_bool(print_scales, false, "Print geometry scales for MWM and exit");
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
gflags::SetUsageMessage("MWM benchmarking tool");
|
||||
if (argc < 2)
|
||||
{
|
||||
gflags::ShowUsageWithFlagsRestrict(argv[0], "main");
|
||||
return 0;
|
||||
}
|
||||
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, false);
|
||||
|
||||
if (FLAGS_print_scales)
|
||||
{
|
||||
feature::DataHeader h(FLAGS_input);
|
||||
cout << "Scales with geometry: ";
|
||||
for (size_t i = 0; i < h.GetScalesCount(); ++i)
|
||||
cout << h.GetScale(i) << " ";
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!FLAGS_input.empty())
|
||||
{
|
||||
using namespace bench;
|
||||
|
||||
AllResult res;
|
||||
RunFeaturesLoadingBenchmark(FLAGS_input, make_pair(FLAGS_lowS, FLAGS_highS), res);
|
||||
|
||||
res.Print();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
205
libs/map/benchmark_tools.cpp
Normal file
205
libs/map/benchmark_tools.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#include "map/benchmark_tools.hpp"
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "drape_frontend/drape_measurer.hpp"
|
||||
#include "drape_frontend/scenario_manager.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
#include "platform/http_client.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cppjansson/cppjansson.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct BenchmarkHandle
|
||||
{
|
||||
std::vector<df::ScenarioManager::ScenarioData> m_scenariosToRun;
|
||||
size_t m_currentScenario = 0;
|
||||
std::vector<storage::CountryId> m_regionsToDownload;
|
||||
size_t m_regionsToDownloadCounter = 0;
|
||||
|
||||
#ifdef DRAPE_MEASURER_BENCHMARK
|
||||
std::vector<std::pair<std::string, df::DrapeMeasurer::DrapeStatistic>> m_drapeStatistic;
|
||||
#endif
|
||||
};
|
||||
|
||||
void RunScenario(Framework * framework, std::shared_ptr<BenchmarkHandle> handle)
|
||||
{
|
||||
if (handle->m_currentScenario >= handle->m_scenariosToRun.size())
|
||||
{
|
||||
#ifdef DRAPE_MEASURER_BENCHMARK
|
||||
for (auto const & it : handle->m_drapeStatistic)
|
||||
{
|
||||
LOG(LINFO, ("\n ***** Report for scenario", it.first, "*****\n", it.second.ToString(),
|
||||
"\n ***** Report for scenario", it.first, "*****\n"));
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
auto & scenarioData = handle->m_scenariosToRun[handle->m_currentScenario];
|
||||
|
||||
framework->GetDrapeEngine()->RunScenario(std::move(scenarioData),
|
||||
[handle](std::string const & name)
|
||||
{
|
||||
#ifdef DRAPE_MEASURER_BENCHMARK
|
||||
df::DrapeMeasurer::Instance().Start();
|
||||
#endif
|
||||
}, [framework, handle](std::string const & name)
|
||||
{
|
||||
#ifdef DRAPE_MEASURER_BENCHMARK
|
||||
df::DrapeMeasurer::Instance().Stop();
|
||||
auto const drapeStatistic = df::DrapeMeasurer::Instance().GetDrapeStatistic();
|
||||
handle->m_drapeStatistic.push_back(make_pair(name, drapeStatistic));
|
||||
#endif
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [framework, handle]()
|
||||
{
|
||||
handle->m_currentScenario++;
|
||||
RunScenario(framework, handle);
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace benchmark
|
||||
{
|
||||
void RunGraphicsBenchmark(Framework * framework)
|
||||
{
|
||||
#ifdef SCENARIO_ENABLE
|
||||
using namespace df;
|
||||
|
||||
// Load scenario from file.
|
||||
auto const fn = base::JoinPath(GetPlatform().SettingsDir(), "graphics_benchmark.json");
|
||||
if (!GetPlatform().IsFileExistsByFullPath(fn))
|
||||
return;
|
||||
|
||||
std::string benchmarkData;
|
||||
try
|
||||
{
|
||||
ReaderPtr<Reader>(GetPlatform().GetReader(fn)).ReadAsString(benchmarkData);
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LCRITICAL, ("Error reading benchmark file: ", e.what()));
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<BenchmarkHandle> handle = std::make_shared<BenchmarkHandle>();
|
||||
|
||||
// Parse scenarios.
|
||||
std::vector<m2::PointD> points;
|
||||
try
|
||||
{
|
||||
base::Json root(benchmarkData.c_str());
|
||||
json_t * scenariosNode = json_object_get(root.get(), "scenarios");
|
||||
if (scenariosNode == nullptr || !json_is_array(scenariosNode))
|
||||
return;
|
||||
size_t const sz = json_array_size(scenariosNode);
|
||||
handle->m_scenariosToRun.resize(sz);
|
||||
for (size_t i = 0; i < sz; ++i)
|
||||
{
|
||||
auto scenarioElem = json_array_get(scenariosNode, i);
|
||||
if (scenarioElem == nullptr)
|
||||
return;
|
||||
FromJSONObject(scenarioElem, "name", handle->m_scenariosToRun[i].m_name);
|
||||
json_t * stepsNode = json_object_get(scenarioElem, "steps");
|
||||
if (stepsNode != nullptr && json_is_array(stepsNode))
|
||||
{
|
||||
size_t const stepsCount = json_array_size(stepsNode);
|
||||
auto & scenario = handle->m_scenariosToRun[i].m_scenario;
|
||||
scenario.reserve(stepsCount);
|
||||
for (size_t j = 0; j < stepsCount; ++j)
|
||||
{
|
||||
auto stepElem = json_array_get(stepsNode, j);
|
||||
if (stepElem == nullptr)
|
||||
return;
|
||||
std::string actionType;
|
||||
FromJSONObject(stepElem, "actionType", actionType);
|
||||
if (actionType == "waitForTime")
|
||||
{
|
||||
json_int_t timeInSeconds = 0;
|
||||
FromJSONObject(stepElem, "time", timeInSeconds);
|
||||
scenario.push_back(std::unique_ptr<ScenarioManager::Action>(
|
||||
new ScenarioManager::WaitForTimeAction(std::chrono::seconds(timeInSeconds))));
|
||||
}
|
||||
else if (actionType == "centerViewport")
|
||||
{
|
||||
json_t * centerNode = json_object_get(stepElem, "center");
|
||||
if (centerNode == nullptr)
|
||||
return;
|
||||
double lat = 0.0, lon = 0.0;
|
||||
FromJSONObject(centerNode, "lat", lat);
|
||||
FromJSONObject(centerNode, "lon", lon);
|
||||
json_int_t zoomLevel = -1;
|
||||
FromJSONObject(stepElem, "zoomLevel", zoomLevel);
|
||||
m2::PointD const pt = mercator::FromLatLon(lat, lon);
|
||||
points.push_back(pt);
|
||||
scenario.push_back(std::unique_ptr<ScenarioManager::Action>(
|
||||
new ScenarioManager::CenterViewportAction(pt, static_cast<int>(zoomLevel))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (base::Json::Exception const & e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (handle->m_scenariosToRun.empty())
|
||||
return;
|
||||
|
||||
// Find out regions to download.
|
||||
std::set<storage::CountryId> regions;
|
||||
for (m2::PointD const & pt : points)
|
||||
regions.insert(framework->GetCountryInfoGetter().GetRegionCountryId(pt));
|
||||
|
||||
for (auto const & countryId : regions)
|
||||
{
|
||||
storage::NodeStatuses statuses;
|
||||
framework->GetStorage().GetNodeStatuses(countryId, statuses);
|
||||
if (statuses.m_status != storage::NodeStatus::OnDisk)
|
||||
handle->m_regionsToDownload.push_back(countryId);
|
||||
}
|
||||
|
||||
// Download regions and run scenarios after downloading.
|
||||
if (!handle->m_regionsToDownload.empty())
|
||||
{
|
||||
framework->GetStorage().Subscribe([framework, handle](storage::CountryId const & countryId)
|
||||
{
|
||||
if (base::IsExist(handle->m_regionsToDownload, countryId))
|
||||
{
|
||||
handle->m_regionsToDownloadCounter++;
|
||||
if (handle->m_regionsToDownloadCounter == handle->m_regionsToDownload.size())
|
||||
{
|
||||
handle->m_regionsToDownload.clear();
|
||||
RunScenario(framework, handle);
|
||||
}
|
||||
}
|
||||
}, [](storage::CountryId const &, downloader::Progress const &) {});
|
||||
|
||||
for (auto const & countryId : handle->m_regionsToDownload)
|
||||
framework->GetStorage().DownloadNode(countryId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run scenarios without downloading.
|
||||
RunScenario(framework, handle);
|
||||
#endif
|
||||
}
|
||||
} // namespace benchmark
|
||||
8
libs/map/benchmark_tools.hpp
Normal file
8
libs/map/benchmark_tools.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
class Framework;
|
||||
|
||||
namespace benchmark
|
||||
{
|
||||
void RunGraphicsBenchmark(Framework * framework);
|
||||
} // namespace benchmark
|
||||
403
libs/map/bookmark.cpp
Normal file
403
libs/map/bookmark.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
#include "map/bookmark.hpp"
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string GetBookmarkIconType(kml::BookmarkIcon const & icon)
|
||||
{
|
||||
switch (icon)
|
||||
{
|
||||
case kml::BookmarkIcon::None: return "default";
|
||||
case kml::BookmarkIcon::Hotel: return "hotel";
|
||||
case kml::BookmarkIcon::Animals: return "animals";
|
||||
case kml::BookmarkIcon::Buddhism: return "buddhism";
|
||||
case kml::BookmarkIcon::Building: return "building";
|
||||
case kml::BookmarkIcon::Christianity: return "christianity";
|
||||
case kml::BookmarkIcon::Entertainment: return "entertainment";
|
||||
case kml::BookmarkIcon::Exchange: return "exchange";
|
||||
case kml::BookmarkIcon::Food: return "restaurant";
|
||||
case kml::BookmarkIcon::Gas: return "gas";
|
||||
case kml::BookmarkIcon::Judaism: return "judaism";
|
||||
case kml::BookmarkIcon::Medicine: return "medicine";
|
||||
case kml::BookmarkIcon::Mountain: return "mountain";
|
||||
case kml::BookmarkIcon::Museum: return "museum";
|
||||
case kml::BookmarkIcon::Islam: return "islam";
|
||||
case kml::BookmarkIcon::Park: return "park";
|
||||
case kml::BookmarkIcon::Parking: return "parking";
|
||||
case kml::BookmarkIcon::Shop: return "shop";
|
||||
case kml::BookmarkIcon::Sights: return "sights";
|
||||
case kml::BookmarkIcon::Swim: return "swim";
|
||||
case kml::BookmarkIcon::Water: return "water";
|
||||
case kml::BookmarkIcon::Bar: return "bar";
|
||||
case kml::BookmarkIcon::Transport: return "transport";
|
||||
case kml::BookmarkIcon::Viewpoint: return "viewpoint";
|
||||
case kml::BookmarkIcon::Sport: return "sport";
|
||||
case kml::BookmarkIcon::Pub: return "pub";
|
||||
case kml::BookmarkIcon::Art: return "art";
|
||||
case kml::BookmarkIcon::Bank: return "bank";
|
||||
case kml::BookmarkIcon::Cafe: return "cafe";
|
||||
case kml::BookmarkIcon::Pharmacy: return "pharmacy";
|
||||
case kml::BookmarkIcon::Stadium: return "stadium";
|
||||
case kml::BookmarkIcon::Theatre: return "theatre";
|
||||
case kml::BookmarkIcon::Information: return "information";
|
||||
case kml::BookmarkIcon::ChargingStation: return "charging_station";
|
||||
case kml::BookmarkIcon::BicycleParking: return "bicycle_parking";
|
||||
case kml::BookmarkIcon::BicycleParkingCovered: return "bicycle_parking_covered";
|
||||
case kml::BookmarkIcon::BicycleRental: return "bicycle_rental";
|
||||
case kml::BookmarkIcon::FastFood: return "fast_food";
|
||||
case kml::BookmarkIcon::Count: ASSERT(false, ("Invalid bookmark icon type")); return {};
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string const kCustomImageProperty = "CustomImage";
|
||||
std::string const kHasElevationProfileProperty = "has_elevation_profile";
|
||||
int constexpr kInvalidColor = 0;
|
||||
} // namespace
|
||||
|
||||
Bookmark::Bookmark(m2::PointD const & ptOrg) : Base(ptOrg, UserMark::BOOKMARK), m_groupId(kml::kInvalidMarkGroupId)
|
||||
{
|
||||
m_data.m_point = ptOrg;
|
||||
m_data.m_id = GetId();
|
||||
}
|
||||
|
||||
Bookmark::Bookmark(kml::BookmarkData && data)
|
||||
: Base(data.m_id, data.m_point, UserMark::BOOKMARK)
|
||||
, m_data(std::move(data))
|
||||
, m_groupId(kml::kInvalidMarkGroupId)
|
||||
{
|
||||
m_data.m_id = GetId();
|
||||
}
|
||||
|
||||
void Bookmark::SetData(kml::BookmarkData const & data)
|
||||
{
|
||||
SetDirty();
|
||||
m_data = data;
|
||||
}
|
||||
|
||||
kml::BookmarkData const & Bookmark::GetData() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
search::ReverseGeocoder::RegionAddress const & Bookmark::GetAddress() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
void Bookmark::SetAddress(search::ReverseGeocoder::RegionAddress const & address)
|
||||
{
|
||||
SetDirty();
|
||||
m_address = address;
|
||||
}
|
||||
|
||||
void Bookmark::SetIsVisible(bool isVisible)
|
||||
{
|
||||
SetDirty();
|
||||
m_isVisible = isVisible;
|
||||
}
|
||||
|
||||
dp::Anchor Bookmark::GetAnchor() const
|
||||
{
|
||||
return dp::Bottom;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> Bookmark::GetSymbolNames() const
|
||||
{
|
||||
auto symbolNames = GetCustomSymbolNames();
|
||||
if (symbolNames != nullptr)
|
||||
return symbolNames;
|
||||
|
||||
symbolNames = make_unique_dp<SymbolNameZoomInfo>();
|
||||
|
||||
symbolNames->insert(std::make_pair(1 /* zoomLevel */, "bookmark-default-xs"));
|
||||
symbolNames->insert(std::make_pair(8 /* zoomLevel */, "bookmark-default-s"));
|
||||
auto const iconType = GetBookmarkIconType(m_data.m_icon);
|
||||
symbolNames->insert(std::make_pair(14 /* zoomLevel */, "bookmark-" + iconType + "-m"));
|
||||
return symbolNames;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> Bookmark::GetCustomSymbolNames() const
|
||||
{
|
||||
auto const it = m_data.m_properties.find(kCustomImageProperty);
|
||||
if (it == m_data.m_properties.end())
|
||||
return nullptr;
|
||||
|
||||
auto symbolNames = make_unique_dp<SymbolNameZoomInfo>();
|
||||
strings::Tokenize(it->second, ";", [&](std::string_view token)
|
||||
{
|
||||
uint8_t zoomLevel = 1;
|
||||
auto pos = token.find(',');
|
||||
if (pos != std::string::npos && strings::to_uint(token.substr(0, pos), zoomLevel))
|
||||
token = token.substr(pos + 1);
|
||||
if (!token.empty() && zoomLevel >= 1 && zoomLevel <= scales::GetUpperStyleScale())
|
||||
symbolNames->emplace(zoomLevel, std::string(token));
|
||||
});
|
||||
|
||||
if (symbolNames->empty())
|
||||
return nullptr;
|
||||
|
||||
return symbolNames;
|
||||
}
|
||||
|
||||
df::ColorConstant Bookmark::GetColorConstant() const
|
||||
{
|
||||
switch (m_data.m_color.m_predefinedColor)
|
||||
{
|
||||
case kml::PredefinedColor::Red: return "BookmarkRed";
|
||||
case kml::PredefinedColor::Blue: return "BookmarkBlue";
|
||||
case kml::PredefinedColor::Purple: return "BookmarkPurple";
|
||||
case kml::PredefinedColor::Yellow: return "BookmarkYellow";
|
||||
case kml::PredefinedColor::Pink: return "BookmarkPink";
|
||||
case kml::PredefinedColor::Brown: return "BookmarkBrown";
|
||||
case kml::PredefinedColor::Green: return "BookmarkGreen";
|
||||
case kml::PredefinedColor::Orange: return "BookmarkOrange";
|
||||
case kml::PredefinedColor::DeepPurple: return "BookmarkDeepPurple";
|
||||
case kml::PredefinedColor::LightBlue: return "BookmarkLightBlue";
|
||||
case kml::PredefinedColor::Cyan: return "BookmarkCyan";
|
||||
case kml::PredefinedColor::Teal: return "BookmarkTeal";
|
||||
case kml::PredefinedColor::Lime: return "BookmarkLime";
|
||||
case kml::PredefinedColor::DeepOrange: return "BookmarkDeepOrange";
|
||||
case kml::PredefinedColor::Gray: return "BookmarkGray";
|
||||
case kml::PredefinedColor::BlueGray: return "BookmarkBlueGray";
|
||||
case kml::PredefinedColor::None:
|
||||
case kml::PredefinedColor::Count: return "BookmarkRed";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool Bookmark::HasCreationAnimation() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
kml::PredefinedColor Bookmark::GetColor() const
|
||||
{
|
||||
return m_data.m_color.m_predefinedColor;
|
||||
}
|
||||
|
||||
void Bookmark::InvalidateRGBAColor()
|
||||
{
|
||||
m_data.m_color.m_rgba = kInvalidColor;
|
||||
}
|
||||
|
||||
void Bookmark::SetColor(kml::PredefinedColor color)
|
||||
{
|
||||
SetDirty();
|
||||
m_data.m_color.m_predefinedColor = color;
|
||||
InvalidateRGBAColor();
|
||||
}
|
||||
|
||||
std::string Bookmark::GetPreferredName() const
|
||||
{
|
||||
return GetPreferredBookmarkName(m_data);
|
||||
}
|
||||
|
||||
kml::LocalizableString Bookmark::GetName() const
|
||||
{
|
||||
return m_data.m_name;
|
||||
}
|
||||
|
||||
void Bookmark::SetName(kml::LocalizableString const & name)
|
||||
{
|
||||
SetDirty();
|
||||
m_data.m_name = name;
|
||||
}
|
||||
|
||||
void Bookmark::SetName(std::string const & name, int8_t langCode)
|
||||
{
|
||||
SetDirty();
|
||||
m_data.m_name[langCode] = name;
|
||||
}
|
||||
|
||||
std::string Bookmark::GetCustomName() const
|
||||
{
|
||||
return GetPreferredBookmarkStr(m_data.m_customName);
|
||||
}
|
||||
|
||||
void Bookmark::SetCustomName(std::string const & customName)
|
||||
{
|
||||
SetDirty();
|
||||
kml::SetDefaultStr(m_data.m_customName, customName);
|
||||
}
|
||||
|
||||
m2::RectD Bookmark::GetViewport() const
|
||||
{
|
||||
return m2::RectD(GetPivot(), GetPivot());
|
||||
}
|
||||
|
||||
std::string Bookmark::GetDescription() const
|
||||
{
|
||||
return GetPreferredBookmarkStr(m_data.m_description);
|
||||
}
|
||||
|
||||
void Bookmark::SetDescription(std::string const & description)
|
||||
{
|
||||
SetDirty();
|
||||
kml::SetDefaultStr(m_data.m_description, description);
|
||||
}
|
||||
|
||||
kml::Timestamp Bookmark::GetTimeStamp() const
|
||||
{
|
||||
return m_data.m_timestamp;
|
||||
}
|
||||
|
||||
void Bookmark::SetTimeStamp(kml::Timestamp timeStamp)
|
||||
{
|
||||
SetDirty();
|
||||
m_data.m_timestamp = timeStamp;
|
||||
}
|
||||
|
||||
uint8_t Bookmark::GetScale() const
|
||||
{
|
||||
return m_data.m_viewportScale;
|
||||
}
|
||||
|
||||
void Bookmark::SetScale(uint8_t scale)
|
||||
{
|
||||
SetDirty();
|
||||
m_data.m_viewportScale = scale;
|
||||
}
|
||||
|
||||
kml::MarkGroupId Bookmark::GetGroupId() const
|
||||
{
|
||||
return m_groupId;
|
||||
}
|
||||
|
||||
bool Bookmark::CanFillPlacePageMetadata() const
|
||||
{
|
||||
auto const & p = m_data.m_properties;
|
||||
if (auto const hours = p.find("hours"); hours != p.end() && !hours->second.empty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bookmark::Attach(kml::MarkGroupId groupId)
|
||||
{
|
||||
ASSERT_NOT_EQUAL(groupId, kml::kInvalidMarkGroupId, ());
|
||||
ASSERT_EQUAL(m_groupId, kml::kInvalidMarkGroupId, ());
|
||||
m_groupId = groupId;
|
||||
}
|
||||
|
||||
void Bookmark::AttachCompilation(kml::MarkGroupId groupId)
|
||||
{
|
||||
ASSERT(groupId != kml::kInvalidMarkGroupId, ());
|
||||
m_compilationIds.push_back(groupId);
|
||||
}
|
||||
|
||||
void Bookmark::Detach()
|
||||
{
|
||||
m_groupId = kml::kInvalidMarkGroupId;
|
||||
m_compilationIds.clear();
|
||||
}
|
||||
|
||||
BookmarkCategory::BookmarkCategory(std::string const & name, kml::MarkGroupId groupId, bool autoSave)
|
||||
: Base(UserMark::Type::BOOKMARK)
|
||||
, m_autoSave(autoSave)
|
||||
{
|
||||
m_data.m_id = groupId;
|
||||
SetName(name);
|
||||
}
|
||||
|
||||
BookmarkCategory::BookmarkCategory(kml::CategoryData && data, bool autoSave)
|
||||
: Base(UserMark::Type::BOOKMARK)
|
||||
, m_autoSave(autoSave)
|
||||
, m_data(std::move(data))
|
||||
{
|
||||
Base::SetIsVisible(m_data.m_visible);
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetIsVisible(bool isVisible)
|
||||
{
|
||||
Base::SetIsVisible(isVisible);
|
||||
m_data.m_visible = isVisible;
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetName(std::string const & name)
|
||||
{
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
kml::SetDefaultStr(m_data.m_name, name);
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetDescription(std::string const & desc)
|
||||
{
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
kml::SetDefaultStr(m_data.m_description, desc);
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetServerId(std::string const & serverId)
|
||||
{
|
||||
if (m_serverId == serverId)
|
||||
return;
|
||||
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
m_serverId = serverId;
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetTags(std::vector<std::string> const & tags)
|
||||
{
|
||||
if (m_data.m_tags == tags)
|
||||
return;
|
||||
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
m_data.m_tags = tags;
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetCustomProperty(std::string const & key, std::string const & value)
|
||||
{
|
||||
auto it = m_data.m_properties.find(key);
|
||||
if (it != m_data.m_properties.end() && it->second == value)
|
||||
return;
|
||||
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
m_data.m_properties[key] = value;
|
||||
}
|
||||
|
||||
std::string BookmarkCategory::GetName() const
|
||||
{
|
||||
return GetPreferredBookmarkStr(m_data.m_name);
|
||||
}
|
||||
|
||||
bool BookmarkCategory::HasElevationProfile() const
|
||||
{
|
||||
auto const it = m_data.m_properties.find(kHasElevationProfileProperty);
|
||||
return (it != m_data.m_properties.end()) && (it->second != "0");
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetAuthor(std::string const & name, std::string const & id)
|
||||
{
|
||||
if (m_data.m_authorName == name && m_data.m_authorId == id)
|
||||
return;
|
||||
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
m_data.m_authorName = name;
|
||||
m_data.m_authorId = id;
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetAccessRules(kml::AccessRules accessRules)
|
||||
{
|
||||
if (m_data.m_accessRules == accessRules)
|
||||
return;
|
||||
|
||||
SetDirty(true /* updateModificationTime */);
|
||||
m_data.m_accessRules = accessRules;
|
||||
}
|
||||
|
||||
// static
|
||||
kml::PredefinedColor BookmarkCategory::GetDefaultColor()
|
||||
{
|
||||
return kml::PredefinedColor::Red;
|
||||
}
|
||||
|
||||
void BookmarkCategory::SetDirty(bool updateModificationDate)
|
||||
{
|
||||
Base::SetDirty(updateModificationDate);
|
||||
if (updateModificationDate)
|
||||
m_data.m_lastModified = kml::TimestampClock::now();
|
||||
}
|
||||
135
libs/map/bookmark.hpp
Normal file
135
libs/map/bookmark.hpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/user_mark.hpp"
|
||||
#include "map/user_mark_layer.hpp"
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
#include "search/reverse_geocoder.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Bookmark : public UserMark
|
||||
{
|
||||
using Base = UserMark;
|
||||
|
||||
public:
|
||||
explicit Bookmark(m2::PointD const & ptOrg);
|
||||
|
||||
explicit Bookmark(kml::BookmarkData && data);
|
||||
|
||||
void SetData(kml::BookmarkData const & data);
|
||||
kml::BookmarkData const & GetData() const;
|
||||
|
||||
search::ReverseGeocoder::RegionAddress const & GetAddress() const;
|
||||
void SetAddress(search::ReverseGeocoder::RegionAddress const & address);
|
||||
|
||||
bool IsVisible() const override { return m_isVisible; }
|
||||
void SetIsVisible(bool isVisible);
|
||||
|
||||
bool HasCreationAnimation() const override;
|
||||
|
||||
std::string GetPreferredName() const;
|
||||
|
||||
kml::LocalizableString GetName() const;
|
||||
void SetName(kml::LocalizableString const & name);
|
||||
void SetName(std::string const & name, int8_t langCode);
|
||||
|
||||
std::string GetCustomName() const;
|
||||
void SetCustomName(std::string const & customName);
|
||||
|
||||
kml::PredefinedColor GetColor() const;
|
||||
void InvalidateRGBAColor();
|
||||
void SetColor(kml::PredefinedColor color);
|
||||
|
||||
m2::RectD GetViewport() const;
|
||||
|
||||
std::string GetDescription() const;
|
||||
void SetDescription(std::string const & description);
|
||||
|
||||
kml::Timestamp GetTimeStamp() const;
|
||||
void SetTimeStamp(kml::Timestamp timeStamp);
|
||||
|
||||
uint8_t GetScale() const;
|
||||
void SetScale(uint8_t scale);
|
||||
|
||||
dp::Anchor GetAnchor() const override;
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
|
||||
df::ColorConstant GetColorConstant() const override;
|
||||
|
||||
kml::MarkGroupId GetGroupId() const override;
|
||||
|
||||
int GetMinZoom() const override { return m_data.m_minZoom; }
|
||||
|
||||
// Whether m_data.m_properties suitable to fill "Key info" part of placepage.
|
||||
bool CanFillPlacePageMetadata() const;
|
||||
|
||||
void Attach(kml::MarkGroupId groupId);
|
||||
void AttachCompilation(kml::MarkGroupId groupId);
|
||||
void Detach();
|
||||
|
||||
kml::GroupIdCollection const & GetCompilations() const { return m_compilationIds; }
|
||||
|
||||
private:
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> GetCustomSymbolNames() const;
|
||||
|
||||
kml::BookmarkData m_data;
|
||||
kml::MarkGroupId m_groupId;
|
||||
kml::GroupIdCollection m_compilationIds;
|
||||
bool m_isVisible = true;
|
||||
search::ReverseGeocoder::RegionAddress m_address;
|
||||
};
|
||||
|
||||
class BookmarkCategory : public UserMarkLayer
|
||||
{
|
||||
using Base = UserMarkLayer;
|
||||
|
||||
public:
|
||||
BookmarkCategory(std::string const & name, kml::MarkGroupId groupId, bool autoSave);
|
||||
BookmarkCategory(kml::CategoryData && data, bool autoSave);
|
||||
|
||||
static kml::PredefinedColor GetDefaultColor();
|
||||
|
||||
kml::MarkGroupId GetID() const { return m_data.m_id; }
|
||||
kml::MarkGroupId GetParentID() const { return m_parentId; }
|
||||
void SetParentId(kml::MarkGroupId parentId) { m_parentId = parentId; }
|
||||
|
||||
void SetIsVisible(bool isVisible) override;
|
||||
void SetName(std::string const & name);
|
||||
void SetDescription(std::string const & desc);
|
||||
void SetFileName(std::string const & fileName) { m_file = fileName; }
|
||||
std::string GetName() const;
|
||||
std::string const & GetFileName() const { return m_file; }
|
||||
|
||||
void EnableAutoSave(bool enable) { m_autoSave = enable; }
|
||||
bool IsAutoSaveEnabled() const { return m_autoSave; }
|
||||
|
||||
kml::CategoryData const & GetCategoryData() const { return m_data; }
|
||||
|
||||
void SetServerId(std::string const & serverId);
|
||||
std::string const & GetServerId() const { return m_serverId; }
|
||||
|
||||
bool HasElevationProfile() const;
|
||||
|
||||
void SetAuthor(std::string const & name, std::string const & id);
|
||||
void SetAccessRules(kml::AccessRules accessRules);
|
||||
void SetTags(std::vector<std::string> const & tags);
|
||||
void SetCustomProperty(std::string const & key, std::string const & value);
|
||||
|
||||
void SetDirty(bool updateModificationDate) override;
|
||||
|
||||
kml::Timestamp GetLastModifiedTime() const { return m_data.m_lastModified; }
|
||||
|
||||
// For serdes to access protected UserMarkLayer sets.
|
||||
friend class BookmarkManager;
|
||||
|
||||
private:
|
||||
// Stores file name from which bookmarks were loaded.
|
||||
std::string m_file;
|
||||
bool m_autoSave = true;
|
||||
kml::CategoryData m_data;
|
||||
std::string m_serverId;
|
||||
kml::MarkGroupId m_parentId = kml::kInvalidMarkGroupId;
|
||||
};
|
||||
763
libs/map/bookmark_helpers.cpp
Normal file
763
libs/map/bookmark_helpers.cpp
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
#include "map/bookmark_helpers.hpp"
|
||||
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "kml/serdes.hpp"
|
||||
#include "kml/serdes_binary.hpp"
|
||||
#include "kml/serdes_gpx.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
|
||||
#include "platform/localization.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/preferred_languages.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/internal/file_data.hpp"
|
||||
#include "coding/zip_reader.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct BookmarkMatchInfo
|
||||
{
|
||||
BookmarkMatchInfo(kml::BookmarkIcon icon, BookmarkBaseType type) : m_icon(icon), m_type(type) {}
|
||||
|
||||
kml::BookmarkIcon m_icon;
|
||||
BookmarkBaseType m_type;
|
||||
};
|
||||
|
||||
std::map<std::string, BookmarkMatchInfo> const kFeatureTypeToBookmarkMatchInfo = {
|
||||
{"amenity-veterinary", {kml::BookmarkIcon::Animals, BookmarkBaseType::Animals}},
|
||||
{"leisure-dog_park", {kml::BookmarkIcon::Animals, BookmarkBaseType::Animals}},
|
||||
{"tourism-zoo", {kml::BookmarkIcon::Animals, BookmarkBaseType::Animals}},
|
||||
|
||||
{"amenity-bar", {kml::BookmarkIcon::Bar, BookmarkBaseType::Food}},
|
||||
{"amenity-biergarten", {kml::BookmarkIcon::Pub, BookmarkBaseType::Food}},
|
||||
{"amenity-pub", {kml::BookmarkIcon::Pub, BookmarkBaseType::Food}},
|
||||
{"amenity-cafe", {kml::BookmarkIcon::Cafe, BookmarkBaseType::Food}},
|
||||
|
||||
{"amenity-bbq", {kml::BookmarkIcon::Food, BookmarkBaseType::Food}},
|
||||
{"amenity-food_court", {kml::BookmarkIcon::Food, BookmarkBaseType::Food}},
|
||||
{"amenity-restaurant", {kml::BookmarkIcon::Food, BookmarkBaseType::Food}},
|
||||
{"leisure-picnic_table", {kml::BookmarkIcon::Food, BookmarkBaseType::Food}},
|
||||
{"tourism-picnic_site", {kml::BookmarkIcon::Food, BookmarkBaseType::Food}},
|
||||
|
||||
{"amenity-fast_food", {kml::BookmarkIcon::FastFood, BookmarkBaseType::Food}},
|
||||
|
||||
{"amenity-place_of_worship-buddhist", {kml::BookmarkIcon::Buddhism, BookmarkBaseType::ReligiousPlace}},
|
||||
|
||||
{"amenity-college", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-courthouse", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-kindergarten", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-library", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-police", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-prison", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"amenity-school", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"building-university", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"office", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"office-diplomatic", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
{"office-lawyer", {kml::BookmarkIcon::Building, BookmarkBaseType::Building}},
|
||||
|
||||
{"amenity-grave_yard-christian", {kml::BookmarkIcon::Christianity, BookmarkBaseType::ReligiousPlace}},
|
||||
{"amenity-place_of_worship-christian", {kml::BookmarkIcon::Christianity, BookmarkBaseType::ReligiousPlace}},
|
||||
{"landuse-cemetery-christian", {kml::BookmarkIcon::Christianity, BookmarkBaseType::ReligiousPlace}},
|
||||
|
||||
{"amenity-casino", {kml::BookmarkIcon::Entertainment, BookmarkBaseType::Entertainment}},
|
||||
{"amenity-cinema", {kml::BookmarkIcon::Entertainment, BookmarkBaseType::Entertainment}},
|
||||
{"amenity-nightclub", {kml::BookmarkIcon::Entertainment, BookmarkBaseType::Entertainment}},
|
||||
{"shop-bookmaker", {kml::BookmarkIcon::Entertainment, BookmarkBaseType::Entertainment}},
|
||||
{"tourism-theme_park", {kml::BookmarkIcon::Entertainment, BookmarkBaseType::Entertainment}},
|
||||
|
||||
{"amenity-theatre", {kml::BookmarkIcon::Theatre, BookmarkBaseType::Entertainment}},
|
||||
|
||||
{"amenity-atm", {kml::BookmarkIcon::Bank, BookmarkBaseType::Exchange}},
|
||||
{"amenity-bank", {kml::BookmarkIcon::Bank, BookmarkBaseType::Exchange}},
|
||||
{"shop-money_lender", {kml::BookmarkIcon::Bank, BookmarkBaseType::Exchange}},
|
||||
|
||||
{"amenity-bureau_de_change", {kml::BookmarkIcon::Exchange, BookmarkBaseType::Exchange}},
|
||||
|
||||
{"amenity-charging_station", {kml::BookmarkIcon::ChargingStation, BookmarkBaseType::Gas}},
|
||||
{"amenity-charging_station-bicycle", {kml::BookmarkIcon::ChargingStation, BookmarkBaseType::Gas}},
|
||||
{"amenity-charging_station-motorcar", {kml::BookmarkIcon::ChargingStation, BookmarkBaseType::Gas}},
|
||||
{"amenity-fuel", {kml::BookmarkIcon::Gas, BookmarkBaseType::Gas}},
|
||||
|
||||
{"tourism-alpine_hut", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-camp_site", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-chalet", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-guest_house", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-hostel", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-hotel", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-motel", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-resort", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-wilderness_hut", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
{"tourism-apartment", {kml::BookmarkIcon::Hotel, BookmarkBaseType::Hotel}},
|
||||
|
||||
{"amenity-place_of_worship-muslim", {kml::BookmarkIcon::Islam, BookmarkBaseType::ReligiousPlace}},
|
||||
|
||||
{"amenity-place_of_worship-jewish", {kml::BookmarkIcon::Judaism, BookmarkBaseType::ReligiousPlace}},
|
||||
|
||||
{"amenity-childcare", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
{"amenity-clinic", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
{"amenity-dentist", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
{"amenity-doctors", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
{"amenity-hospital", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
{"emergency-defibrillator", {kml::BookmarkIcon::Medicine, BookmarkBaseType::Medicine}},
|
||||
|
||||
{"amenity-pharmacy", {kml::BookmarkIcon::Pharmacy, BookmarkBaseType::Medicine}},
|
||||
|
||||
{"natural-bare_rock", {kml::BookmarkIcon::Mountain, BookmarkBaseType::Mountain}},
|
||||
{"natural-cave_entrance", {kml::BookmarkIcon::Mountain, BookmarkBaseType::Mountain}},
|
||||
{"natural-peak", {kml::BookmarkIcon::Mountain, BookmarkBaseType::Mountain}},
|
||||
{"natural-rock", {kml::BookmarkIcon::Mountain, BookmarkBaseType::Mountain}},
|
||||
{"natural-volcano", {kml::BookmarkIcon::Mountain, BookmarkBaseType::Mountain}},
|
||||
|
||||
{"amenity-arts_centre", {kml::BookmarkIcon::Art, BookmarkBaseType::Museum}},
|
||||
{"tourism-gallery", {kml::BookmarkIcon::Art, BookmarkBaseType::Museum}},
|
||||
|
||||
{"tourism-museum", {kml::BookmarkIcon::Museum, BookmarkBaseType::Museum}},
|
||||
|
||||
{"boundary-national_park", {kml::BookmarkIcon::Park, BookmarkBaseType::Park}},
|
||||
{"landuse-forest", {kml::BookmarkIcon::Park, BookmarkBaseType::Park}},
|
||||
{"leisure-garden", {kml::BookmarkIcon::Park, BookmarkBaseType::Park}},
|
||||
{"leisure-nature_reserve", {kml::BookmarkIcon::Park, BookmarkBaseType::Park}},
|
||||
{"leisure-park", {kml::BookmarkIcon::Park, BookmarkBaseType::Park}},
|
||||
|
||||
{"amenity-bicycle_parking", {kml::BookmarkIcon::BicycleParking, BookmarkBaseType::Parking}},
|
||||
{"amenity-bicycle_parking-covered", {kml::BookmarkIcon::BicycleParkingCovered, BookmarkBaseType::Parking}},
|
||||
{"amenity-bicycle_rental", {kml::BookmarkIcon::BicycleRental, BookmarkBaseType::Parking}},
|
||||
|
||||
{"amenity-motorcycle_parking", {kml::BookmarkIcon::Parking, BookmarkBaseType::Parking}},
|
||||
{"amenity-parking", {kml::BookmarkIcon::Parking, BookmarkBaseType::Parking}},
|
||||
{"highway-services", {kml::BookmarkIcon::Parking, BookmarkBaseType::Parking}},
|
||||
{"tourism-caravan_site", {kml::BookmarkIcon::Parking, BookmarkBaseType::Parking}},
|
||||
{"amenity-vending_machine-parking_tickets", {kml::BookmarkIcon::Parking, BookmarkBaseType::Parking}},
|
||||
|
||||
{"amenity-ice_cream", {kml::BookmarkIcon::Shop, BookmarkBaseType::Shop}},
|
||||
{"amenity-marketplace", {kml::BookmarkIcon::Shop, BookmarkBaseType::Shop}},
|
||||
{"amenity-vending_machine", {kml::BookmarkIcon::Shop, BookmarkBaseType::Shop}},
|
||||
{"shop", {kml::BookmarkIcon::Shop, BookmarkBaseType::Shop}},
|
||||
|
||||
{"amenity-place_of_worship", {kml::BookmarkIcon::Sights, BookmarkBaseType::ReligiousPlace}},
|
||||
{"historic-archaeological_site", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-boundary_stone", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-castle", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-fort", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-memorial", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-monument", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-ruins", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-ship", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-tomb", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-wayside_cross", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"historic-wayside_shrine", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"tourism-artwork", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"tourism-attraction", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
{"waterway-waterfall", {kml::BookmarkIcon::Sights, BookmarkBaseType::Sights}},
|
||||
|
||||
{"tourism-information", {kml::BookmarkIcon::Information, BookmarkBaseType::Sights}},
|
||||
{"tourism-information-office", {kml::BookmarkIcon::Information, BookmarkBaseType::Sights}},
|
||||
{"tourism-information-visitor_centre", {kml::BookmarkIcon::Information, BookmarkBaseType::Sights}},
|
||||
|
||||
{"leisure-fitness_centre", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
{"leisure-skiing", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
{"leisure-sports_centre-climbing", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
{"leisure-sports_centre-shooting", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
{"leisure-sports_centre-yoga", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
{"sport", {kml::BookmarkIcon::Sport, BookmarkBaseType::Entertainment}},
|
||||
|
||||
{"leisure-stadium", {kml::BookmarkIcon::Stadium, BookmarkBaseType::Entertainment}},
|
||||
|
||||
{"leisure-sports_centre-swimming", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"leisure-swimming_pool", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"leisure-water_park", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"natural-beach", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"sport-diving", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"sport-scuba_diving", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
{"sport-swimming", {kml::BookmarkIcon::Swim, BookmarkBaseType::Swim}},
|
||||
|
||||
{"aeroway-aerodrome", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"aeroway-aerodrome-international", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"amenity-bus_station", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"amenity-car_sharing", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"amenity-ferry_terminal", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"amenity-taxi", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"building-train_station", {kml::BookmarkIcon::Transport, BookmarkBaseType::Building}},
|
||||
{"highway-bus_stop", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"public_transport-platform", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"railway-halt", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"railway-station", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
{"railway-tram_stop", {kml::BookmarkIcon::Transport, BookmarkBaseType::None}},
|
||||
|
||||
{"tourism-viewpoint", {kml::BookmarkIcon::Viewpoint, BookmarkBaseType::Sights}},
|
||||
|
||||
{"amenity-drinking_water", {kml::BookmarkIcon::Water, BookmarkBaseType::Water}},
|
||||
{"amenity-fountain", {kml::BookmarkIcon::Water, BookmarkBaseType::Water}},
|
||||
{"amenity-water_point", {kml::BookmarkIcon::Water, BookmarkBaseType::Water}},
|
||||
{"man_made-water_tap", {kml::BookmarkIcon::Water, BookmarkBaseType::Water}},
|
||||
{"natural-spring", {kml::BookmarkIcon::Water, BookmarkBaseType::Water}},
|
||||
|
||||
{"shop-funeral_directors", {kml::BookmarkIcon::None, BookmarkBaseType::None}}};
|
||||
|
||||
void ValidateKmlData(std::unique_ptr<kml::FileData> & data)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (auto & t : data->m_tracksData)
|
||||
if (t.m_layers.empty())
|
||||
t.m_layers.emplace_back(kml::KmlParser::GetDefaultTrackLayer());
|
||||
}
|
||||
|
||||
/// @todo(KK): This code is a temporary solution for the filtering the duplicated points in KMLs.
|
||||
/// When the deserealizer reads the data from the KML that uses <gx:Track>
|
||||
/// as a first step will be parsed the list of the timestamps <when> and than the list of the coordinates <gx:coord>.
|
||||
/// So the filtering can be done only when all the data is parsed.
|
||||
void RemoveDuplicatedTrackPoints(std::unique_ptr<kml::FileData> & data)
|
||||
{
|
||||
for (auto & trackData : data->m_tracksData)
|
||||
{
|
||||
auto const & geometry = trackData.m_geometry;
|
||||
kml::MultiGeometry validGeometry;
|
||||
|
||||
for (size_t lineIndex = 0; lineIndex < geometry.m_lines.size(); ++lineIndex)
|
||||
{
|
||||
auto const & line = geometry.m_lines[lineIndex];
|
||||
auto const & timestamps = geometry.m_timestamps[lineIndex];
|
||||
|
||||
if (line.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Empty line in track:", trackData.m_name[kml::kDefaultLang]));
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const hasTimestamps = geometry.HasTimestampsFor(lineIndex);
|
||||
if (hasTimestamps && timestamps.size() != line.size())
|
||||
MYTHROW(kml::DeserializerKml::DeserializeException,
|
||||
("Timestamps count", timestamps.size(), "doesn't match points count", line.size()));
|
||||
|
||||
validGeometry.m_lines.emplace_back();
|
||||
validGeometry.m_timestamps.emplace_back();
|
||||
|
||||
auto & validLine = validGeometry.m_lines.back();
|
||||
auto & validTimestamps = validGeometry.m_timestamps.back();
|
||||
|
||||
for (size_t pointIndex = 0; pointIndex < line.size(); ++pointIndex)
|
||||
{
|
||||
auto const & currPoint = line[pointIndex];
|
||||
|
||||
// We don't expect vertical surfaces, so do not compare heights here.
|
||||
// Will get a lot of duplicating points otherwise after import some user KMLs.
|
||||
// https://github.com/organicmaps/organicmaps/issues/3895
|
||||
if (validLine.empty() || !AlmostEqualAbs(validLine.back().GetPoint(), currPoint.GetPoint(), kMwmPointAccuracy))
|
||||
{
|
||||
validLine.push_back(currPoint);
|
||||
if (hasTimestamps)
|
||||
validTimestamps.push_back(timestamps[pointIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackData.m_geometry = std::move(validGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsBadCharForPath(strings::UniChar c)
|
||||
{
|
||||
if (c < ' ')
|
||||
return true;
|
||||
for (strings::UniChar const illegalChar : {':', '/', '\\', '<', '>', '\"', '|', '?', '*'})
|
||||
if (illegalChar == c)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string GetBookmarksDirectory()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().SettingsDir(), "bookmarks");
|
||||
}
|
||||
|
||||
std::string GetTrashDirectory()
|
||||
{
|
||||
std::string const trashDir = base::JoinPath(GetPlatform().SettingsDir(), std::string{kTrashDirectoryName});
|
||||
if (!Platform::IsFileExistsByFullPath(trashDir) && !Platform::MkDirChecked(trashDir))
|
||||
CHECK(false, ("Failed to create .Trash directory."));
|
||||
return trashDir;
|
||||
}
|
||||
|
||||
std::string RemoveInvalidSymbols(std::string const & name)
|
||||
{
|
||||
strings::UniString filtered;
|
||||
filtered.reserve(name.size());
|
||||
auto it = name.begin();
|
||||
while (it != name.end())
|
||||
{
|
||||
auto const c = ::utf8::unchecked::next(it);
|
||||
if (!IsBadCharForPath(c))
|
||||
filtered.push_back(c);
|
||||
}
|
||||
return strings::ToUtf8(filtered);
|
||||
}
|
||||
|
||||
// Returns extension with a dot in a lower case.
|
||||
std::string GetLowercaseFileExt(std::string const & filePath)
|
||||
{
|
||||
return strings::MakeLowerCase(base::GetFileExtension(filePath));
|
||||
}
|
||||
|
||||
std::string GenerateUniqueFileName(std::string const & path, std::string name, std::string_view ext)
|
||||
{
|
||||
// Remove extension, if file name already contains it.
|
||||
if (name.ends_with(ext))
|
||||
name.resize(name.size() - ext.size());
|
||||
|
||||
size_t counter = 1;
|
||||
std::string suffix, res;
|
||||
do
|
||||
{
|
||||
res = name;
|
||||
res = base::JoinPath(path, res.append(suffix).append(ext));
|
||||
if (!Platform::IsFileExistsByFullPath(res))
|
||||
break;
|
||||
suffix = strings::to_string(counter++);
|
||||
}
|
||||
while (true);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string GenerateValidAndUniqueFilePathForKML(std::string const & fileName)
|
||||
{
|
||||
std::string filePath = RemoveInvalidSymbols(fileName);
|
||||
if (filePath.empty())
|
||||
filePath = kDefaultBookmarksFileName;
|
||||
|
||||
return GenerateUniqueFileName(GetBookmarksDirectory(), std::move(filePath), kKmlExtension);
|
||||
}
|
||||
|
||||
std::string GenerateValidAndUniqueFilePathForGPX(std::string const & fileName)
|
||||
{
|
||||
std::string filePath = RemoveInvalidSymbols(fileName);
|
||||
if (filePath.empty())
|
||||
filePath = kDefaultBookmarksFileName;
|
||||
|
||||
return GenerateUniqueFileName(GetBookmarksDirectory(), std::move(filePath), kGpxExtension);
|
||||
}
|
||||
|
||||
std::string GenerateValidAndUniqueTrashedFilePath(std::string const & fileName)
|
||||
{
|
||||
std::string extension = base::GetFileExtension(fileName);
|
||||
std::string filePath = RemoveInvalidSymbols(fileName);
|
||||
if (filePath.empty())
|
||||
filePath = kDefaultBookmarksFileName;
|
||||
return GenerateUniqueFileName(GetTrashDirectory(), std::move(filePath), extension);
|
||||
}
|
||||
|
||||
std::string const kDefaultBookmarksFileName = "Bookmarks";
|
||||
|
||||
// Populate empty category & track names based on file name: assign file name to category name,
|
||||
// if there is only one unnamed track - assign file name to it, otherwise add numbers 1, 2, 3...
|
||||
// to file name to build names for all unnamed tracks
|
||||
void FillEmptyNames(std::unique_ptr<kml::FileData> & kmlData, std::string const & file)
|
||||
{
|
||||
auto start = file.find_last_of('/') + 1;
|
||||
auto end = file.find_last_of('.');
|
||||
if (end == std::string::npos)
|
||||
end = file.size();
|
||||
auto const name = file.substr(start, end - start);
|
||||
|
||||
if (kmlData->m_categoryData.m_name.empty())
|
||||
kmlData->m_categoryData.m_name[kml::kDefaultLang] = name;
|
||||
|
||||
if (kmlData->m_tracksData.empty())
|
||||
return;
|
||||
|
||||
auto const emptyNames = std::count_if(kmlData->m_tracksData.begin(), kmlData->m_tracksData.end(),
|
||||
[](kml::TrackData const & t) { return t.m_name.empty(); });
|
||||
if (emptyNames == 0)
|
||||
return;
|
||||
|
||||
auto emptyTrackNum = 1;
|
||||
for (auto & track : kmlData->m_tracksData)
|
||||
{
|
||||
if (track.m_name.empty())
|
||||
{
|
||||
if (emptyNames == 1)
|
||||
{
|
||||
track.m_name[kml::kDefaultLang] = name;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
track.m_name[kml::kDefaultLang] = name + " " + std::to_string(emptyTrackNum);
|
||||
emptyTrackNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<kml::FileData> LoadKmlFile(std::string const & file, KmlFileType fileType)
|
||||
{
|
||||
std::unique_ptr<kml::FileData> kmlData;
|
||||
try
|
||||
{
|
||||
kmlData = LoadKmlData(FileReader(file), fileType);
|
||||
if (kmlData != nullptr)
|
||||
FillEmptyNames(kmlData, file);
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "loading failure:", e.what()));
|
||||
kmlData.reset();
|
||||
}
|
||||
if (kmlData == nullptr)
|
||||
LOG(LWARNING, ("Loading bookmarks failed, file", file));
|
||||
return kmlData;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetKMLOrGPXFilesPathsToLoad(std::string const & filePath)
|
||||
{
|
||||
std::string const fileExt = GetLowercaseFileExt(filePath);
|
||||
if (fileExt == kKmlExtension)
|
||||
{
|
||||
return GetFilePathsToLoadFromKml(filePath);
|
||||
}
|
||||
else if (fileExt == kGpxExtension)
|
||||
{
|
||||
return GetFilePathsToLoadFromGpx(filePath);
|
||||
}
|
||||
else if (fileExt == kKmbExtension)
|
||||
{
|
||||
return GetFilePathsToLoadFromKmb(filePath);
|
||||
}
|
||||
else if (fileExt == kKmzExtension)
|
||||
{
|
||||
return GetFilePathsToLoadFromKmz(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Unknown file type", filePath));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFilePathsToLoadFromKmz(std::string const & filePath)
|
||||
{ // Extract KML files from KMZ archive and save to temp KMLs with unique name.
|
||||
std::vector<std::string> kmlFilePaths;
|
||||
try
|
||||
{
|
||||
ZipFileReader::FileList files;
|
||||
ZipFileReader::FilesList(filePath, files);
|
||||
files.erase(std::remove_if(files.begin(), files.end(),
|
||||
[](auto const & file) { return GetLowercaseFileExt(file.first) != kKmlExtension; }),
|
||||
files.end());
|
||||
for (auto const & [kmlFileInZip, size] : files)
|
||||
{
|
||||
auto const name = base::FileNameFromFullPath(kmlFileInZip);
|
||||
auto fileSavePath = GenerateValidAndUniqueFilePathForKML(kmlFileInZip);
|
||||
ZipFileReader::UnzipFile(filePath, kmlFileInZip, fileSavePath);
|
||||
kmlFilePaths.push_back(std::move(fileSavePath));
|
||||
}
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error unzipping file", filePath, e.Msg()));
|
||||
}
|
||||
return kmlFilePaths;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFilePathsToLoadFromKmb(std::string const & filePath)
|
||||
{ // Convert input file and save to temp KML with unique name.
|
||||
auto kmlData = LoadKmlFile(filePath, KmlFileType::Binary);
|
||||
if (kmlData == nullptr)
|
||||
return {};
|
||||
|
||||
auto fileSavePath = GenerateValidAndUniqueFilePathForKML(base::FileNameFromFullPath(filePath));
|
||||
if (!SaveKmlFileByExt(*kmlData, fileSavePath))
|
||||
return {};
|
||||
return {std::move(fileSavePath)};
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFilePathsToLoadFromGpx(std::string const & filePath)
|
||||
{ // Copy input file to temp GPX with unique name.
|
||||
auto fileSavePath = GenerateValidAndUniqueFilePathForGPX(base::FileNameFromFullPath(filePath));
|
||||
if (!base::CopyFileX(filePath, fileSavePath))
|
||||
return {};
|
||||
return {std::move(fileSavePath)};
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFilePathsToLoadFromKml(std::string const & filePath)
|
||||
{ // Copy input file to temp output KML with unique name.
|
||||
auto fileSavePath = GenerateValidAndUniqueFilePathForKML(base::FileNameFromFullPath(filePath));
|
||||
if (!base::CopyFileX(filePath, fileSavePath))
|
||||
return {};
|
||||
return {std::move(fileSavePath)};
|
||||
}
|
||||
|
||||
std::unique_ptr<kml::FileData> LoadKmlData(Reader const & reader, KmlFileType fileType)
|
||||
{
|
||||
auto data = std::make_unique<kml::FileData>();
|
||||
try
|
||||
{
|
||||
if (fileType == KmlFileType::Binary)
|
||||
{
|
||||
kml::binary::DeserializerKml des(*data);
|
||||
des.Deserialize(reader);
|
||||
}
|
||||
else if (fileType == KmlFileType::Text)
|
||||
{
|
||||
kml::DeserializerKml des(*data);
|
||||
des.Deserialize(reader);
|
||||
}
|
||||
else if (fileType == KmlFileType::Gpx)
|
||||
{
|
||||
kml::DeserializerGpx des(*data);
|
||||
des.Deserialize(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(false, ("Not supported KmlFileType"));
|
||||
}
|
||||
ValidateKmlData(data);
|
||||
RemoveDuplicatedTrackPoints(data);
|
||||
}
|
||||
catch (Reader::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "reading failure:", e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
catch (kml::binary::DeserializerKml::DeserializeException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "deserialization failure:", e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
catch (kml::DeserializerKml::DeserializeException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "deserialization failure:", e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "loading failure:", e.what()));
|
||||
return nullptr;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool SaveGpxData(kml::FileData & kmlData, Writer & writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
kml::gpx::SerializerGpx ser(kmlData);
|
||||
ser.Serialize(writer);
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("GPX writing failure:", e.what()));
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("GPX serialization failure:", e.what()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveKmlFile(kml::FileData & kmlData, std::string const & file, KmlFileType fileType)
|
||||
{
|
||||
FileWriter writer(file);
|
||||
switch (fileType)
|
||||
{
|
||||
case KmlFileType::Text: // fallthrough
|
||||
case KmlFileType::Binary: return SaveKmlData(kmlData, writer, fileType);
|
||||
case KmlFileType::Gpx: return SaveGpxData(kmlData, writer);
|
||||
default:
|
||||
{
|
||||
LOG(LWARNING, ("Unexpected KmlFileType", fileType));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SaveKmlFileSafe(kml::FileData & kmlData, std::string const & file, KmlFileType fileType)
|
||||
{
|
||||
LOG(LINFO, ("Save kml file of type", fileType, "to", file));
|
||||
return base::WriteToTempAndRenameToFile(
|
||||
file, [&kmlData, fileType](std::string const & fileName) { return SaveKmlFile(kmlData, fileName, fileType); });
|
||||
}
|
||||
|
||||
bool SaveKmlFileByExt(kml::FileData & kmlData, std::string const & file)
|
||||
{
|
||||
auto const ext = base::GetFileExtension(file);
|
||||
return SaveKmlFileSafe(kmlData, file, ext == kKmbExtension ? KmlFileType::Binary : KmlFileType::Text);
|
||||
}
|
||||
|
||||
bool SaveKmlData(kml::FileData & kmlData, Writer & writer, KmlFileType fileType)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (fileType == KmlFileType::Binary)
|
||||
{
|
||||
kml::binary::SerializerKml ser(kmlData);
|
||||
ser.Serialize(writer);
|
||||
}
|
||||
else if (fileType == KmlFileType::Text)
|
||||
{
|
||||
kml::SerializerKml ser(kmlData);
|
||||
ser.Serialize(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(false, ("Not supported KmlFileType"));
|
||||
}
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "writing failure:", e.what()));
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("KML", fileType, "serialization failure:", e.what()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResetIds(kml::FileData & kmlData)
|
||||
{
|
||||
kmlData.m_categoryData.m_id = kml::kInvalidMarkGroupId;
|
||||
for (auto & bmData : kmlData.m_bookmarksData)
|
||||
bmData.m_id = kml::kInvalidMarkId;
|
||||
for (auto & trackData : kmlData.m_tracksData)
|
||||
trackData.m_id = kml::kInvalidTrackId;
|
||||
for (auto & compilationData : kmlData.m_compilationsData)
|
||||
compilationData.m_id = kml::kInvalidMarkGroupId;
|
||||
}
|
||||
|
||||
bool TruncType(std::string & type)
|
||||
{
|
||||
auto const pos = type.rfind('-');
|
||||
if (pos == std::string::npos)
|
||||
return false;
|
||||
type.resize(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
BookmarkBaseType GetBookmarkBaseType(std::vector<uint32_t> const & featureTypes)
|
||||
{
|
||||
auto const & c = classif();
|
||||
for (auto typeIndex : featureTypes)
|
||||
{
|
||||
auto const type = c.GetTypeForIndex(typeIndex);
|
||||
auto typeStr = c.GetReadableObjectName(type);
|
||||
|
||||
do
|
||||
{
|
||||
auto const itType = kFeatureTypeToBookmarkMatchInfo.find(typeStr);
|
||||
if (itType != kFeatureTypeToBookmarkMatchInfo.cend())
|
||||
return itType->second.m_type;
|
||||
}
|
||||
while (TruncType(typeStr));
|
||||
}
|
||||
return BookmarkBaseType::None;
|
||||
}
|
||||
|
||||
kml::BookmarkIcon GetBookmarkIconByFeatureType(uint32_t type)
|
||||
{
|
||||
auto typeStr = classif().GetReadableObjectName(type);
|
||||
|
||||
do
|
||||
{
|
||||
auto const itIcon = kFeatureTypeToBookmarkMatchInfo.find(typeStr);
|
||||
if (itIcon != kFeatureTypeToBookmarkMatchInfo.cend())
|
||||
return itIcon->second.m_icon;
|
||||
}
|
||||
while (TruncType(typeStr));
|
||||
|
||||
return kml::BookmarkIcon::None;
|
||||
}
|
||||
|
||||
void SaveFeatureTypes(feature::TypesHolder const & types, kml::BookmarkData & bmData)
|
||||
{
|
||||
auto const & c = classif();
|
||||
feature::TypesHolder copy(types);
|
||||
copy.SortBySpec();
|
||||
bmData.m_featureTypes.reserve(copy.Size());
|
||||
for (auto it = copy.begin(); it != copy.end(); ++it)
|
||||
{
|
||||
if (c.IsTypeValid(*it))
|
||||
{
|
||||
bmData.m_featureTypes.push_back(c.GetIndexForType(*it));
|
||||
if (bmData.m_icon == kml::BookmarkIcon::None)
|
||||
bmData.m_icon = GetBookmarkIconByFeatureType(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetPreferredBookmarkStr(kml::LocalizableString const & name)
|
||||
{
|
||||
auto const mapLanguageNorm = languages::Normalize(languages::GetCurrentMapLanguage());
|
||||
return kml::GetPreferredBookmarkStr(name, mapLanguageNorm);
|
||||
}
|
||||
|
||||
std::string GetPreferredBookmarkStr(kml::LocalizableString const & name, feature::RegionData const & regionData)
|
||||
{
|
||||
auto const mapLanguageNorm = languages::Normalize(languages::GetCurrentMapLanguage());
|
||||
return kml::GetPreferredBookmarkStr(name, regionData, mapLanguageNorm);
|
||||
}
|
||||
|
||||
std::string GetLocalizedFeatureType(std::vector<uint32_t> const & types)
|
||||
{
|
||||
return kml::GetLocalizedFeatureType(types);
|
||||
}
|
||||
|
||||
std::string GetLocalizedBookmarkBaseType(BookmarkBaseType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BookmarkBaseType::None: return {};
|
||||
case BookmarkBaseType::Hotel: return platform::GetLocalizedString("hotels");
|
||||
case BookmarkBaseType::Animals: return platform::GetLocalizedString("animals");
|
||||
case BookmarkBaseType::Building: return platform::GetLocalizedString("buildings");
|
||||
case BookmarkBaseType::Entertainment: return platform::GetLocalizedString("entertainment");
|
||||
case BookmarkBaseType::Exchange: return platform::GetLocalizedString("money");
|
||||
case BookmarkBaseType::Food: return platform::GetLocalizedString("food_places");
|
||||
case BookmarkBaseType::Gas: return platform::GetLocalizedString("fuel_places");
|
||||
case BookmarkBaseType::Medicine: return platform::GetLocalizedString("medicine");
|
||||
case BookmarkBaseType::Mountain: return platform::GetLocalizedString("mountains");
|
||||
case BookmarkBaseType::Museum: return platform::GetLocalizedString("museums");
|
||||
case BookmarkBaseType::Park: return platform::GetLocalizedString("parks");
|
||||
case BookmarkBaseType::Parking: return platform::GetLocalizedString("parkings");
|
||||
case BookmarkBaseType::ReligiousPlace: return platform::GetLocalizedString("religious_places");
|
||||
case BookmarkBaseType::Shop: return platform::GetLocalizedString("shops");
|
||||
case BookmarkBaseType::Sights: return platform::GetLocalizedString("tourist_places");
|
||||
case BookmarkBaseType::Swim: return platform::GetLocalizedString("swim_places");
|
||||
case BookmarkBaseType::Water: return platform::GetLocalizedString("water");
|
||||
case BookmarkBaseType::Count: CHECK(false, ("Invalid bookmark base type")); return {};
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string GetPreferredBookmarkName(kml::BookmarkData const & bmData)
|
||||
{
|
||||
return kml::GetPreferredBookmarkName(bmData, languages::GetCurrentMapLanguage());
|
||||
}
|
||||
|
||||
void ExpandRectForPreview(m2::RectD & rect)
|
||||
{
|
||||
if (!rect.IsValid())
|
||||
return;
|
||||
|
||||
rect.Scale(df::kBoundingBoxScale);
|
||||
}
|
||||
138
libs/map/bookmark_helpers.hpp
Normal file
138
libs/map/bookmark_helpers.hpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/bookmark.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
struct BookmarkInfo
|
||||
{
|
||||
BookmarkInfo() = default;
|
||||
|
||||
BookmarkInfo(kml::MarkId id, kml::BookmarkData const & data) : m_bookmarkId(id), m_bookmarkData(data) {}
|
||||
|
||||
BookmarkInfo(kml::MarkId id, kml::BookmarkData const & data, search::ReverseGeocoder::RegionAddress const & address)
|
||||
: m_bookmarkId(id)
|
||||
, m_bookmarkData(data)
|
||||
, m_address(address)
|
||||
{}
|
||||
|
||||
kml::MarkId m_bookmarkId;
|
||||
kml::BookmarkData m_bookmarkData;
|
||||
search::ReverseGeocoder::RegionAddress m_address;
|
||||
};
|
||||
|
||||
struct BookmarkGroupInfo
|
||||
{
|
||||
BookmarkGroupInfo() = default;
|
||||
BookmarkGroupInfo(kml::MarkGroupId id, kml::MarkIdCollection && marks)
|
||||
: m_groupId(id)
|
||||
, m_bookmarkIds(std::move(marks))
|
||||
{}
|
||||
|
||||
kml::MarkGroupId m_groupId;
|
||||
kml::MarkIdCollection m_bookmarkIds;
|
||||
};
|
||||
|
||||
// Do not change the order.
|
||||
enum class BookmarkBaseType : uint16_t
|
||||
{
|
||||
None = 0,
|
||||
Hotel,
|
||||
Animals,
|
||||
Building,
|
||||
Entertainment,
|
||||
Exchange,
|
||||
Food,
|
||||
Gas,
|
||||
Medicine,
|
||||
Mountain,
|
||||
Museum,
|
||||
Park,
|
||||
Parking,
|
||||
ReligiousPlace,
|
||||
Shop,
|
||||
Sights,
|
||||
Swim,
|
||||
Water,
|
||||
Count
|
||||
};
|
||||
|
||||
std::string_view constexpr kKmzExtension = ".kmz";
|
||||
std::string_view constexpr kKmlExtension = ".kml";
|
||||
std::string_view constexpr kKmbExtension = ".kmb";
|
||||
std::string_view constexpr kGpxExtension = ".gpx";
|
||||
|
||||
std::string_view constexpr kTrashDirectoryName = ".Trash";
|
||||
|
||||
extern std::string const kDefaultBookmarksFileName;
|
||||
|
||||
enum class KmlFileType
|
||||
{
|
||||
Text,
|
||||
Binary,
|
||||
Gpx
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(KmlFileType fileType)
|
||||
{
|
||||
switch (fileType)
|
||||
{
|
||||
case KmlFileType::Text: return "Text";
|
||||
case KmlFileType::Binary: return "Binary";
|
||||
case KmlFileType::Gpx: return "GPX";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
/// @name File name/path helpers.
|
||||
/// @{
|
||||
std::string GetBookmarksDirectory();
|
||||
std::string GetTrashDirectory();
|
||||
std::string RemoveInvalidSymbols(std::string const & name);
|
||||
std::string GenerateUniqueFileName(std::string const & path, std::string name, std::string_view ext = kKmlExtension);
|
||||
std::string GenerateValidAndUniqueFilePathForKML(std::string const & fileName);
|
||||
std::string GenerateValidAndUniqueFilePathForGPX(std::string const & fileName);
|
||||
std::string GenerateValidAndUniqueTrashedFilePath(std::string const & fileName);
|
||||
/// @}
|
||||
|
||||
/// @name SerDes helpers.
|
||||
/// @{
|
||||
std::unique_ptr<kml::FileData> LoadKmlFile(std::string const & file, KmlFileType fileType);
|
||||
std::unique_ptr<kml::FileData> LoadKmlData(Reader const & reader, KmlFileType fileType);
|
||||
|
||||
std::vector<std::string> GetKMLOrGPXFilesPathsToLoad(std::string const & filePath);
|
||||
std::vector<std::string> GetFilePathsToLoadFromKml(std::string const & filePath);
|
||||
std::vector<std::string> GetFilePathsToLoadFromGpx(std::string const & filePath);
|
||||
std::vector<std::string> GetFilePathsToLoadFromKmb(std::string const & filePath);
|
||||
std::vector<std::string> GetFilePathsToLoadFromKmz(std::string const & filePath);
|
||||
std::string GetLowercaseFileExt(std::string const & filePath);
|
||||
|
||||
bool SaveKmlFileSafe(kml::FileData & kmlData, std::string const & file, KmlFileType fileType);
|
||||
bool SaveKmlData(kml::FileData & kmlData, Writer & writer, KmlFileType fileType);
|
||||
bool SaveKmlFileByExt(kml::FileData & kmlData, std::string const & file);
|
||||
/// @}
|
||||
|
||||
void ResetIds(kml::FileData & kmlData);
|
||||
|
||||
namespace feature
|
||||
{
|
||||
class TypesHolder;
|
||||
}
|
||||
void SaveFeatureTypes(feature::TypesHolder const & types, kml::BookmarkData & bmData);
|
||||
|
||||
std::string GetPreferredBookmarkName(kml::BookmarkData const & bmData);
|
||||
|
||||
std::string GetPreferredBookmarkStr(kml::LocalizableString const & name);
|
||||
std::string GetPreferredBookmarkStr(kml::LocalizableString const & name, feature::RegionData const & regionData);
|
||||
std::string GetLocalizedFeatureType(std::vector<uint32_t> const & types);
|
||||
std::string GetLocalizedBookmarkBaseType(BookmarkBaseType type);
|
||||
|
||||
kml::BookmarkIcon GetBookmarkIconByFeatureType(uint32_t type);
|
||||
BookmarkBaseType GetBookmarkBaseType(std::vector<uint32_t> const & featureTypes);
|
||||
|
||||
void ExpandRectForPreview(m2::RectD & rect);
|
||||
3623
libs/map/bookmark_manager.cpp
Normal file
3623
libs/map/bookmark_manager.cpp
Normal file
File diff suppressed because it is too large
Load diff
831
libs/map/bookmark_manager.hpp
Normal file
831
libs/map/bookmark_manager.hpp
Normal file
|
|
@ -0,0 +1,831 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/bookmark.hpp"
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
#include "map/elevation_info.hpp"
|
||||
#include "map/track.hpp"
|
||||
#include "map/user_mark_layer.hpp"
|
||||
|
||||
#include "search/region_address_getter.hpp"
|
||||
|
||||
#include "drape_frontend/drape_engine_safe_ptr.hpp"
|
||||
|
||||
#include "platform/safe_callback.hpp"
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/strings_bundle.hpp"
|
||||
#include "base/thread_checker.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class CountryInfoGetter;
|
||||
} // namespace storage
|
||||
|
||||
class DataSource;
|
||||
class SearchAPI;
|
||||
|
||||
class BookmarkManager final
|
||||
{
|
||||
using UserMarkLayers = std::vector<std::unique_ptr<UserMarkLayer>>;
|
||||
using CategoriesCollection = std::map<kml::MarkGroupId, std::unique_ptr<BookmarkCategory>>;
|
||||
using MarksCollection = std::map<kml::MarkId, std::unique_ptr<UserMark>>;
|
||||
using BookmarksCollection = std::map<kml::MarkId, std::unique_ptr<Bookmark>>;
|
||||
using TracksCollection = std::map<kml::TrackId, std::unique_ptr<Track>>;
|
||||
|
||||
public:
|
||||
using KMLDataCollection = std::vector<std::pair<std::string, std::unique_ptr<kml::FileData>>>;
|
||||
using KMLDataCollectionPtr = std::shared_ptr<KMLDataCollection>;
|
||||
|
||||
using BookmarksChangedCallback = std::function<void()>;
|
||||
using CategoriesChangedCallback = std::function<void()>;
|
||||
using ElevationActivePointChangedCallback = std::function<void()>;
|
||||
using ElevationMyPositionChangedCallback = std::function<void()>;
|
||||
|
||||
using OnSymbolSizesAcquiredCallback = std::function<void()>;
|
||||
|
||||
using AsyncLoadingStartedCallback = std::function<void()>;
|
||||
using AsyncLoadingFinishedCallback = std::function<void()>;
|
||||
using AsyncLoadingFileCallback = std::function<void(std::string const &, bool)>;
|
||||
|
||||
struct AsyncLoadingCallbacks
|
||||
{
|
||||
AsyncLoadingStartedCallback m_onStarted;
|
||||
AsyncLoadingFinishedCallback m_onFinished;
|
||||
AsyncLoadingFileCallback m_onFileError;
|
||||
AsyncLoadingFileCallback m_onFileSuccess;
|
||||
};
|
||||
|
||||
struct Callbacks
|
||||
{
|
||||
using GetStringsBundleFn = std::function<StringsBundle const &()>;
|
||||
using GetSeacrhAPIFn = std::function<SearchAPI &()>;
|
||||
using CreatedBookmarksCallback = std::function<void(std::vector<BookmarkInfo> const &)>;
|
||||
using UpdatedBookmarksCallback = std::function<void(std::vector<BookmarkInfo> const &)>;
|
||||
using DeletedBookmarksCallback = std::function<void(std::vector<kml::MarkId> const &)>;
|
||||
using AttachedBookmarksCallback = std::function<void(std::vector<BookmarkGroupInfo> const &)>;
|
||||
using DetachedBookmarksCallback = std::function<void(std::vector<BookmarkGroupInfo> const &)>;
|
||||
|
||||
template <typename StringsBundleProvider, typename SearchAPIProvider, typename CreateListener,
|
||||
typename UpdateListener, typename DeleteListener, typename AttachListener, typename DetachListener>
|
||||
Callbacks(StringsBundleProvider && stringsBundleProvider, SearchAPIProvider && searchAPIProvider,
|
||||
CreateListener && createListener, UpdateListener && updateListener, DeleteListener && deleteListener,
|
||||
AttachListener && attachListener, DetachListener && detachListener)
|
||||
: m_getStringsBundle(std::forward<StringsBundleProvider>(stringsBundleProvider))
|
||||
, m_getSearchAPI(std::forward<SearchAPIProvider>(searchAPIProvider))
|
||||
, m_createdBookmarksCallback(std::forward<CreateListener>(createListener))
|
||||
, m_updatedBookmarksCallback(std::forward<UpdateListener>(updateListener))
|
||||
, m_deletedBookmarksCallback(std::forward<DeleteListener>(deleteListener))
|
||||
, m_attachedBookmarksCallback(std::forward<AttachListener>(attachListener))
|
||||
, m_detachedBookmarksCallback(std::forward<DetachListener>(detachListener))
|
||||
{}
|
||||
|
||||
GetStringsBundleFn m_getStringsBundle;
|
||||
GetSeacrhAPIFn m_getSearchAPI;
|
||||
CreatedBookmarksCallback m_createdBookmarksCallback;
|
||||
UpdatedBookmarksCallback m_updatedBookmarksCallback;
|
||||
DeletedBookmarksCallback m_deletedBookmarksCallback;
|
||||
AttachedBookmarksCallback m_attachedBookmarksCallback;
|
||||
DetachedBookmarksCallback m_detachedBookmarksCallback;
|
||||
};
|
||||
|
||||
class EditSession
|
||||
{
|
||||
public:
|
||||
explicit EditSession(BookmarkManager & bmManager);
|
||||
~EditSession();
|
||||
|
||||
template <typename UserMarkT>
|
||||
UserMarkT * CreateUserMark(m2::PointD const & ptOrg)
|
||||
{
|
||||
return m_bmManager.CreateUserMark<UserMarkT>(ptOrg);
|
||||
}
|
||||
|
||||
Bookmark * CreateBookmark(kml::BookmarkData && bmData);
|
||||
Bookmark * CreateBookmark(kml::BookmarkData && bmData, kml::MarkGroupId groupId);
|
||||
Track * CreateTrack(kml::TrackData && trackData);
|
||||
|
||||
template <typename UserMarkT>
|
||||
UserMarkT * GetMarkForEdit(kml::MarkId markId)
|
||||
{
|
||||
return m_bmManager.GetMarkForEdit<UserMarkT>(markId);
|
||||
}
|
||||
|
||||
Bookmark * GetBookmarkForEdit(kml::MarkId bmId);
|
||||
|
||||
template <typename UserMarkT, typename F>
|
||||
void DeleteUserMarks(UserMark::Type type, F && deletePredicate)
|
||||
{
|
||||
return m_bmManager.DeleteUserMarks<UserMarkT>(type, std::forward<F>(deletePredicate));
|
||||
}
|
||||
|
||||
void DeleteUserMark(kml::MarkId markId);
|
||||
void DeleteBookmark(kml::MarkId bmId);
|
||||
void DeleteTrack(kml::TrackId trackId);
|
||||
|
||||
void ClearGroup(kml::MarkGroupId groupId);
|
||||
|
||||
void SetIsVisible(kml::MarkGroupId groupId, bool visible);
|
||||
|
||||
void MoveBookmark(kml::MarkId bmID, kml::MarkGroupId curGroupID, kml::MarkGroupId newGroupID);
|
||||
void UpdateBookmark(kml::MarkId bmId, kml::BookmarkData const & bm);
|
||||
|
||||
void AttachBookmark(kml::MarkId bmId, kml::MarkGroupId groupId);
|
||||
void DetachBookmark(kml::MarkId bmId, kml::MarkGroupId groupId);
|
||||
|
||||
Track * GetTrackForEdit(kml::TrackId trackId);
|
||||
|
||||
void MoveTrack(kml::TrackId trackID, kml::MarkGroupId curGroupID, kml::MarkGroupId newGroupID);
|
||||
void AttachTrack(kml::TrackId trackId, kml::MarkGroupId groupId);
|
||||
void DetachTrack(kml::TrackId trackId, kml::MarkGroupId groupId);
|
||||
void ChangeTrackColor(kml::TrackId trackId, dp::Color color);
|
||||
void UpdateTrack(kml::TrackId trackId, kml::TrackData const & trackData);
|
||||
|
||||
void SetCategoryName(kml::MarkGroupId categoryId, std::string const & name);
|
||||
void SetCategoryDescription(kml::MarkGroupId categoryId, std::string const & desc);
|
||||
void SetCategoryTags(kml::MarkGroupId categoryId, std::vector<std::string> const & tags);
|
||||
void SetCategoryAccessRules(kml::MarkGroupId categoryId, kml::AccessRules accessRules);
|
||||
void SetCategoryCustomProperty(kml::MarkGroupId categoryId, std::string const & key, std::string const & value);
|
||||
|
||||
/// Removes the category from the list of categories and deletes the related file.
|
||||
/// @param permanently If true, the file will be removed from the disk. If false, the file will be marked as deleted
|
||||
/// and moved into a trash.
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId, bool permanently);
|
||||
void NotifyChanges();
|
||||
|
||||
private:
|
||||
BookmarkManager & m_bmManager;
|
||||
};
|
||||
|
||||
explicit BookmarkManager(Callbacks && callbacks);
|
||||
|
||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
||||
|
||||
void InitRegionAddressGetter(DataSource const & dataSource, storage::CountryInfoGetter const & infoGetter);
|
||||
|
||||
void SetBookmarksChangedCallback(BookmarksChangedCallback && callback);
|
||||
void SetCategoriesChangedCallback(CategoriesChangedCallback && callback);
|
||||
void SetAsyncLoadingCallbacks(AsyncLoadingCallbacks && callbacks);
|
||||
bool IsAsyncLoadingInProgress() const { return m_asyncLoadingInProgress; }
|
||||
|
||||
bool AreSymbolSizesAcquired(OnSymbolSizesAcquiredCallback && callback);
|
||||
|
||||
EditSession GetEditSession();
|
||||
|
||||
void UpdateViewport(ScreenBase const & screen);
|
||||
void Teardown();
|
||||
|
||||
static bool IsBookmarkCategory(kml::MarkGroupId groupId)
|
||||
{
|
||||
return groupId != kml::kInvalidMarkGroupId && groupId >= UserMark::USER_MARK_TYPES_COUNT_MAX;
|
||||
}
|
||||
static bool IsBookmark(kml::MarkId markId)
|
||||
{
|
||||
return markId != kml::kInvalidMarkId && UserMark::GetMarkType(markId) == UserMark::BOOKMARK;
|
||||
}
|
||||
static UserMark::Type GetGroupType(kml::MarkGroupId groupId)
|
||||
{
|
||||
return IsBookmarkCategory(groupId) ? UserMark::BOOKMARK : static_cast<UserMark::Type>(groupId);
|
||||
}
|
||||
|
||||
template <typename UserMarkT>
|
||||
UserMarkT const * GetMark(kml::MarkId markId) const
|
||||
{
|
||||
auto * mark = GetUserMark(markId);
|
||||
ASSERT(dynamic_cast<UserMarkT const *>(mark) != nullptr, ());
|
||||
return static_cast<UserMarkT const *>(mark);
|
||||
}
|
||||
|
||||
UserMark const * GetUserMark(kml::MarkId markId) const;
|
||||
Bookmark const * GetBookmark(kml::MarkId markId) const;
|
||||
Track const * GetTrack(kml::TrackId trackId) const;
|
||||
|
||||
kml::MarkIdSet const & GetUserMarkIds(kml::MarkGroupId groupId) const;
|
||||
kml::TrackIdSet const & GetTrackIds(kml::MarkGroupId groupId) const;
|
||||
|
||||
// Do not change the order.
|
||||
enum class SortingType
|
||||
{
|
||||
ByType,
|
||||
ByDistance,
|
||||
ByTime,
|
||||
ByName
|
||||
};
|
||||
|
||||
struct SortedBlock
|
||||
{
|
||||
bool operator==(SortedBlock const & other) const;
|
||||
|
||||
std::string m_blockName;
|
||||
kml::MarkIdCollection m_markIds;
|
||||
kml::MarkIdCollection m_trackIds;
|
||||
};
|
||||
using SortedBlocksCollection = std::vector<SortedBlock>;
|
||||
|
||||
struct SortParams
|
||||
{
|
||||
enum class Status
|
||||
{
|
||||
Completed,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
using OnResults = std::function<void(SortedBlocksCollection && sortedBlocks, Status status)>;
|
||||
|
||||
kml::MarkGroupId m_groupId = kml::kInvalidMarkGroupId;
|
||||
SortingType m_sortingType = SortingType::ByType;
|
||||
bool m_hasMyPosition = false;
|
||||
m2::PointD m_myPosition = {0.0, 0.0};
|
||||
OnResults m_onResults;
|
||||
};
|
||||
|
||||
std::vector<SortingType> GetAvailableSortingTypes(kml::MarkGroupId groupId, bool hasMyPosition) const;
|
||||
void GetSortedCategory(SortParams const & params);
|
||||
|
||||
bool GetLastSortingType(kml::MarkGroupId groupId, SortingType & sortingType) const;
|
||||
void SetLastSortingType(kml::MarkGroupId groupId, SortingType sortingType);
|
||||
void ResetLastSortingType(kml::MarkGroupId groupId);
|
||||
|
||||
void PrepareForSearch(kml::MarkGroupId groupId);
|
||||
|
||||
bool IsVisible(kml::MarkGroupId groupId) const;
|
||||
|
||||
kml::MarkGroupId CreateBookmarkCategory(kml::CategoryData && data, bool autoSave = true);
|
||||
kml::MarkGroupId CreateBookmarkCategory(std::string const & name, bool autoSave = true);
|
||||
void UpdateBookmarkCategory(kml::MarkGroupId & groupId, kml::CategoryData && data, bool autoSave);
|
||||
|
||||
BookmarkCategory * CreateBookmarkCompilation(kml::CategoryData && data);
|
||||
|
||||
std::string GetCategoryName(kml::MarkGroupId categoryId) const;
|
||||
std::string GetCategoryFileName(kml::MarkGroupId categoryId) const;
|
||||
kml::MarkGroupId GetCategoryByFileName(std::string const & fileName) const;
|
||||
m2::RectD GetCategoryRect(kml::MarkGroupId categoryId, bool addIconsSize) const;
|
||||
kml::CategoryData const & GetCategoryData(kml::MarkGroupId categoryId) const;
|
||||
|
||||
kml::MarkGroupId GetCategoryId(std::string const & name) const;
|
||||
|
||||
kml::GroupIdCollection const & GetUnsortedBmGroupsIdList() const { return m_unsortedBmGroupsIdList; }
|
||||
kml::GroupIdCollection GetSortedBmGroupIdList() const;
|
||||
size_t GetBmGroupsCount() const { return m_unsortedBmGroupsIdList.size(); }
|
||||
bool HasBmCategory(kml::MarkGroupId groupId) const;
|
||||
bool HasBookmark(kml::MarkId markId) const;
|
||||
bool HasTrack(kml::TrackId trackId) const;
|
||||
kml::MarkGroupId LastEditedBMCategory();
|
||||
kml::PredefinedColor LastEditedBMColor() const;
|
||||
|
||||
void SetLastEditedBmCategory(kml::MarkGroupId groupId);
|
||||
void SetLastEditedBmColor(kml::PredefinedColor color);
|
||||
|
||||
using TTouchRectHolder = std::function<m2::AnyRectD(UserMark::Type)>;
|
||||
using TFindOnlyVisibleChecker = std::function<bool(UserMark::Type)>;
|
||||
UserMark const * FindNearestUserMark(TTouchRectHolder const & holder,
|
||||
TFindOnlyVisibleChecker const & findOnlyVisible) const;
|
||||
UserMark const * FindNearestUserMark(m2::AnyRectD const & rect) const;
|
||||
UserMark const * FindMarkInRect(kml::MarkGroupId groupId, m2::AnyRectD const & rect, bool findOnlyVisible,
|
||||
double & d) const;
|
||||
|
||||
/// Scans and loads all kml files with bookmarks.
|
||||
void LoadBookmarks();
|
||||
void LoadBookmark(std::string const & filePath, bool isTemporaryFile);
|
||||
void ReloadBookmark(std::string const & filePath);
|
||||
|
||||
/// Uses the same file name from which was loaded, or
|
||||
/// creates unique file name on first save and uses it every time.
|
||||
void SaveBookmarks(kml::GroupIdCollection const & groupIdCollection);
|
||||
|
||||
StaticMarkPoint & SelectionMark() { return *m_selectionMark; }
|
||||
StaticMarkPoint const & SelectionMark() const { return *m_selectionMark; }
|
||||
|
||||
MyPositionMarkPoint & MyPositionMark() { return *m_myPositionMark; }
|
||||
MyPositionMarkPoint const & MyPositionMark() const { return *m_myPositionMark; }
|
||||
|
||||
struct SharingResult
|
||||
{
|
||||
enum class Code
|
||||
{
|
||||
Success = 0,
|
||||
EmptyCategory,
|
||||
ArchiveError,
|
||||
FileError
|
||||
};
|
||||
|
||||
SharingResult(kml::GroupIdCollection && categoriesIds, std::string && sharingPath, std::string const & mimeType)
|
||||
: m_categoriesIds(categoriesIds)
|
||||
, m_code(Code::Success)
|
||||
, m_sharingPath(std::move(sharingPath))
|
||||
, m_mimeType(mimeType)
|
||||
{}
|
||||
|
||||
SharingResult(kml::GroupIdCollection && categoriesIds, Code code)
|
||||
: m_categoriesIds(std::move(categoriesIds))
|
||||
, m_code(code)
|
||||
{}
|
||||
|
||||
SharingResult(kml::GroupIdCollection && categoriesIds, Code code, std::string && errorString)
|
||||
: m_categoriesIds(std::move(categoriesIds))
|
||||
, m_code(code)
|
||||
, m_errorString(std::move(errorString))
|
||||
{}
|
||||
|
||||
kml::MarkIdCollection m_categoriesIds;
|
||||
Code m_code;
|
||||
std::string m_sharingPath;
|
||||
std::string m_mimeType;
|
||||
std::string m_errorString;
|
||||
};
|
||||
|
||||
using SharingHandler = platform::SafeCallback<void(SharingResult const & result)>;
|
||||
void PrepareFileForSharing(kml::GroupIdCollection && categoriesIds, SharingHandler && handler,
|
||||
KmlFileType kmlFileType);
|
||||
void PrepareTrackFileForSharing(kml::TrackId trackId, SharingHandler && handler, KmlFileType kmlFileType);
|
||||
void PrepareAllFilesForSharing(SharingHandler && handler);
|
||||
|
||||
bool AreAllCategoriesEmpty() const;
|
||||
bool IsCategoryEmpty(kml::MarkGroupId categoryId) const;
|
||||
|
||||
bool IsUsedCategoryName(std::string const & name) const;
|
||||
bool AreAllCategoriesVisible() const;
|
||||
bool AreAllCategoriesInvisible() const;
|
||||
void SetAllCategoriesVisibility(bool visible);
|
||||
void SetChildCategoriesVisibility(kml::MarkGroupId categoryId, kml::CompilationType compilationType, bool visible);
|
||||
|
||||
void SetNotificationsEnabled(bool enabled);
|
||||
bool AreNotificationsEnabled() const;
|
||||
|
||||
void FilterInvalidBookmarks(kml::MarkIdCollection & bookmarks) const;
|
||||
void FilterInvalidTracks(kml::TrackIdCollection & tracks) const;
|
||||
|
||||
void EnableTestMode(bool enable);
|
||||
bool SaveBookmarkCategory(kml::MarkGroupId groupId);
|
||||
bool SaveBookmarkCategory(kml::MarkGroupId groupId, Writer & writer, KmlFileType fileType) const;
|
||||
|
||||
bool HasRecentlyDeletedBookmark() const { return m_recentlyDeletedBookmark.operator bool(); }
|
||||
void ResetRecentlyDeletedBookmark();
|
||||
|
||||
size_t GetRecentlyDeletedCategoriesCount() const;
|
||||
BookmarkManager::KMLDataCollectionPtr GetRecentlyDeletedCategories();
|
||||
bool IsRecentlyDeletedCategory(std::string const & filePath) const;
|
||||
|
||||
void RecoverRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & filePaths);
|
||||
void DeleteRecentlyDeletedCategoriesAtPaths(std::vector<std::string> const & filePaths);
|
||||
|
||||
// Used for LoadBookmarks() and unit tests only. Does *not* update last modified time.
|
||||
void CreateCategories(KMLDataCollection && dataCollection, bool autoSave = false);
|
||||
|
||||
static std::string GetTracksSortedBlockName();
|
||||
static std::string GetBookmarksSortedBlockName();
|
||||
static std::string GetOthersSortedBlockName();
|
||||
static std::string GetNearMeSortedBlockName();
|
||||
enum class SortedByTimeBlockType : uint32_t
|
||||
{
|
||||
WeekAgo,
|
||||
MonthAgo,
|
||||
MoreThanMonthAgo,
|
||||
MoreThanYearAgo,
|
||||
Others
|
||||
};
|
||||
static std::string GetSortedByTimeBlockName(SortedByTimeBlockType blockType);
|
||||
std::string GetLocalizedRegionAddress(m2::PointD const & pt);
|
||||
|
||||
void SetElevationActivePoint(kml::TrackId const & trackId, m2::PointD pt, double distanceInMeters);
|
||||
// Returns distance from the start of the track to active point in meters.
|
||||
double GetElevationActivePoint(kml::TrackId const & trackId) const;
|
||||
|
||||
void UpdateElevationMyPosition(kml::TrackId const & trackId);
|
||||
// Returns distance from the start of the track to my position in meters.
|
||||
// Returns negative value if my position is not on the track.
|
||||
double GetElevationMyPosition(kml::TrackId const & trackId) const;
|
||||
|
||||
void SetElevationActivePointChangedCallback(ElevationActivePointChangedCallback const & cb);
|
||||
void SetElevationMyPositionChangedCallback(ElevationMyPositionChangedCallback const & cb);
|
||||
|
||||
using TracksFilter = std::function<bool(Track const * track)>;
|
||||
Track::TrackSelectionInfo FindNearestTrack(m2::RectD const & touchRect,
|
||||
TracksFilter const & tracksFilter = nullptr) const;
|
||||
Track::TrackSelectionInfo GetTrackSelectionInfo(kml::TrackId const & trackId) const;
|
||||
|
||||
void SetTrackSelectionInfo(Track::TrackSelectionInfo const & trackSelectionInfo, bool notifyListeners);
|
||||
void OnTrackSelected(kml::TrackId trackId);
|
||||
void OnTrackDeselected();
|
||||
|
||||
kml::GroupIdCollection GetChildrenCategories(kml::MarkGroupId parentCategoryId) const;
|
||||
kml::GroupIdCollection GetChildrenCollections(kml::MarkGroupId parentCategoryId) const;
|
||||
|
||||
bool IsCompilation(kml::MarkGroupId id) const;
|
||||
kml::CompilationType GetCompilationType(kml::MarkGroupId id) const;
|
||||
|
||||
kml::TrackId SaveTrackRecording(std::string trackName);
|
||||
std::string GenerateTrackRecordingName() const;
|
||||
dp::Color GenerateTrackRecordingColor() const;
|
||||
|
||||
kml::TrackId SaveRoute(std::vector<geometry::PointWithAltitude> points, std::string const & from,
|
||||
std::string const & to);
|
||||
|
||||
private:
|
||||
class MarksChangesTracker : public df::UserMarksProvider
|
||||
{
|
||||
public:
|
||||
explicit MarksChangesTracker(BookmarkManager * bmManager) : m_bmManager(bmManager)
|
||||
{
|
||||
CHECK(m_bmManager != nullptr, ());
|
||||
}
|
||||
|
||||
void OnAddMark(kml::MarkId markId);
|
||||
void OnDeleteMark(kml::MarkId markId);
|
||||
void OnUpdateMark(kml::MarkId markId);
|
||||
|
||||
void OnAddLine(kml::TrackId lineId);
|
||||
void OnDeleteLine(kml::TrackId lineId);
|
||||
void OnUpdateLine(kml::TrackId lineId);
|
||||
|
||||
void OnAddGroup(kml::MarkGroupId groupId);
|
||||
void OnDeleteGroup(kml::MarkGroupId groupId);
|
||||
|
||||
void OnAttachBookmark(kml::MarkId markId, kml::MarkGroupId catId);
|
||||
void OnDetachBookmark(kml::MarkId markId, kml::MarkGroupId catId);
|
||||
|
||||
void AcceptDirtyItems();
|
||||
bool HasChanges() const;
|
||||
bool HasBookmarksChanges() const;
|
||||
bool HasCategoriesChanges() const;
|
||||
void ResetChanges();
|
||||
void AddChanges(MarksChangesTracker const & changes);
|
||||
|
||||
using GroupMarkIdSet = std::map<kml::MarkGroupId, kml::MarkIdSet>;
|
||||
GroupMarkIdSet const & GetAttachedBookmarks() const { return m_attachedBookmarks; }
|
||||
GroupMarkIdSet const & GetDetachedBookmarks() const { return m_detachedBookmarks; }
|
||||
|
||||
// UserMarksProvider
|
||||
kml::GroupIdSet GetAllGroupIds() const override;
|
||||
kml::GroupIdSet const & GetUpdatedGroupIds() const override { return m_updatedGroups; }
|
||||
kml::GroupIdSet const & GetRemovedGroupIds() const override { return m_removedGroups; }
|
||||
kml::MarkIdSet const & GetCreatedMarkIds() const override { return m_createdMarks; }
|
||||
kml::MarkIdSet const & GetRemovedMarkIds() const override { return m_removedMarks; }
|
||||
kml::MarkIdSet const & GetUpdatedMarkIds() const override { return m_updatedMarks; }
|
||||
kml::TrackIdSet const & GetCreatedLineIds() const override { return m_createdLines; }
|
||||
kml::TrackIdSet const & GetRemovedLineIds() const override { return m_removedLines; }
|
||||
kml::TrackIdSet const & GetUpdatedLineIds() const override { return m_updatedLines; }
|
||||
kml::GroupIdSet const & GetBecameVisibleGroupIds() const override { return m_becameVisibleGroups; }
|
||||
kml::GroupIdSet const & GetBecameInvisibleGroupIds() const override { return m_becameInvisibleGroups; }
|
||||
bool IsGroupVisible(kml::MarkGroupId groupId) const override;
|
||||
kml::MarkIdSet const & GetGroupPointIds(kml::MarkGroupId groupId) const override;
|
||||
kml::TrackIdSet const & GetGroupLineIds(kml::MarkGroupId groupId) const override;
|
||||
df::UserPointMark const * GetUserPointMark(kml::MarkId markId) const override;
|
||||
df::UserLineMark const * GetUserLineMark(kml::TrackId lineId) const override;
|
||||
|
||||
private:
|
||||
void OnUpdateGroup(kml::MarkGroupId groupId);
|
||||
void OnBecomeVisibleGroup(kml::MarkGroupId groupId);
|
||||
void OnBecomeInvisibleGroup(kml::MarkGroupId groupId);
|
||||
|
||||
static void InsertBookmark(kml::MarkId markId, kml::MarkGroupId catId, GroupMarkIdSet & setToInsert,
|
||||
GroupMarkIdSet & setToErase);
|
||||
static bool HasBookmarkCategories(kml::GroupIdSet const & groupIds);
|
||||
|
||||
void InferVisibility(BookmarkCategory * const group);
|
||||
|
||||
BookmarkManager * m_bmManager;
|
||||
|
||||
kml::MarkIdSet m_createdMarks;
|
||||
kml::MarkIdSet m_removedMarks;
|
||||
kml::MarkIdSet m_updatedMarks;
|
||||
|
||||
kml::TrackIdSet m_createdLines;
|
||||
kml::TrackIdSet m_removedLines;
|
||||
kml::TrackIdSet m_updatedLines;
|
||||
|
||||
kml::GroupIdSet m_createdGroups;
|
||||
kml::GroupIdSet m_removedGroups;
|
||||
|
||||
kml::GroupIdSet m_updatedGroups;
|
||||
kml::GroupIdSet m_becameVisibleGroups;
|
||||
kml::GroupIdSet m_becameInvisibleGroups;
|
||||
|
||||
GroupMarkIdSet m_attachedBookmarks;
|
||||
GroupMarkIdSet m_detachedBookmarks;
|
||||
};
|
||||
|
||||
template <typename UserMarkT>
|
||||
UserMarkT * CreateUserMark(m2::PointD const & ptOrg)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
auto mark = std::make_unique<UserMarkT>(ptOrg);
|
||||
auto * m = mark.get();
|
||||
auto const markId = m->GetId();
|
||||
auto const groupId = static_cast<kml::MarkGroupId>(m->GetMarkType());
|
||||
CHECK_EQUAL(m_userMarks.count(markId), 0, ());
|
||||
ASSERT_GREATER(groupId, 0, ());
|
||||
ASSERT_LESS(groupId - 1, m_userMarkLayers.size(), ());
|
||||
m_userMarks.emplace(markId, std::move(mark));
|
||||
m_changesTracker.OnAddMark(markId);
|
||||
m_userMarkLayers[static_cast<size_t>(groupId - 1)]->AttachUserMark(markId);
|
||||
return m;
|
||||
}
|
||||
|
||||
template <typename UserMarkT>
|
||||
UserMarkT * GetMarkForEdit(kml::MarkId markId)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
auto * mark = GetUserMarkForEdit(markId);
|
||||
ASSERT(dynamic_cast<UserMarkT *>(mark) != nullptr, ());
|
||||
return static_cast<UserMarkT *>(mark);
|
||||
}
|
||||
|
||||
template <typename UserMarkT, typename F>
|
||||
void DeleteUserMarks(UserMark::Type type, F && deletePredicate)
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
std::list<kml::MarkId> marksToDelete;
|
||||
for (auto markId : GetUserMarkIds(type))
|
||||
if (deletePredicate(GetMark<UserMarkT>(markId)))
|
||||
marksToDelete.push_back(markId);
|
||||
// Delete after iterating to avoid iterators invalidation issues.
|
||||
for (auto markId : marksToDelete)
|
||||
DeleteUserMark(markId);
|
||||
}
|
||||
|
||||
UserMark * GetUserMarkForEdit(kml::MarkId markId);
|
||||
void DeleteUserMark(kml::MarkId markId);
|
||||
|
||||
Bookmark * CreateBookmark(kml::BookmarkData && bmData);
|
||||
Bookmark * CreateBookmark(kml::BookmarkData && bmData, kml::MarkGroupId groupId);
|
||||
|
||||
Bookmark * GetBookmarkForEdit(kml::MarkId markId);
|
||||
void AttachBookmark(kml::MarkId bmId, kml::MarkGroupId groupId);
|
||||
void DetachBookmark(kml::MarkId bmId, kml::MarkGroupId groupId);
|
||||
void DeleteBookmark(kml::MarkId bmId);
|
||||
void DetachUserMark(kml::MarkId bmId, kml::MarkGroupId catId);
|
||||
void DeleteCompilations(kml::GroupIdCollection const & compilations);
|
||||
|
||||
Track * CreateTrack(kml::TrackData && trackData);
|
||||
|
||||
Track * GetTrackForEdit(kml::TrackId trackId);
|
||||
void AttachTrack(kml::TrackId trackId, kml::MarkGroupId groupId);
|
||||
void DetachTrack(kml::TrackId trackId, kml::MarkGroupId groupId);
|
||||
void DeleteTrack(kml::TrackId trackId);
|
||||
void MoveTrack(kml::TrackId trackID, kml::MarkGroupId curGroupID, kml::MarkGroupId newGroupID);
|
||||
void ChangeTrackColor(kml::TrackId trackId, dp::Color color);
|
||||
void UpdateTrack(kml::TrackId trackId, kml::TrackData const & trackData);
|
||||
|
||||
void ClearGroup(kml::MarkGroupId groupId);
|
||||
void SetIsVisible(kml::MarkGroupId groupId, bool visible);
|
||||
|
||||
void SetCategoryName(kml::MarkGroupId categoryId, std::string const & name);
|
||||
void SetCategoryDescription(kml::MarkGroupId categoryId, std::string const & desc);
|
||||
void SetCategoryTags(kml::MarkGroupId categoryId, std::vector<std::string> const & tags);
|
||||
void SetCategoryAccessRules(kml::MarkGroupId categoryId, kml::AccessRules accessRules);
|
||||
void SetCategoryCustomProperty(kml::MarkGroupId categoryId, std::string const & key, std::string const & value);
|
||||
bool DeleteBmCategory(kml::MarkGroupId groupId, bool permanently);
|
||||
|
||||
void MoveBookmark(kml::MarkId bmID, kml::MarkGroupId curGroupID, kml::MarkGroupId newGroupID);
|
||||
void UpdateBookmark(kml::MarkId bmId, kml::BookmarkData const & bm);
|
||||
|
||||
UserMark const * GetMark(kml::MarkId markId) const;
|
||||
|
||||
UserMarkLayer * GetGroup(kml::MarkGroupId groupId) const;
|
||||
BookmarkCategory * GetBmCategorySafe(kml::MarkGroupId categoryId) const;
|
||||
BookmarkCategory * GetBmCategory(kml::MarkGroupId categoryId) const
|
||||
{
|
||||
auto * res = GetBmCategorySafe(categoryId);
|
||||
CHECK(res, (categoryId));
|
||||
return res;
|
||||
}
|
||||
|
||||
Bookmark * AddBookmark(std::unique_ptr<Bookmark> && bookmark);
|
||||
Track * AddTrack(std::unique_ptr<Track> && track);
|
||||
|
||||
void OnEditSessionOpened();
|
||||
void OnEditSessionClosed();
|
||||
void NotifyChanges(bool saveChangesOnDisk);
|
||||
|
||||
void SaveState() const;
|
||||
void LoadState();
|
||||
|
||||
void SaveMetadata();
|
||||
void LoadMetadata();
|
||||
void CleanupInvalidMetadata();
|
||||
std::string GetMetadataEntryName(kml::MarkGroupId groupId) const;
|
||||
|
||||
std::string GenerateSavedRouteName(std::string const & from, std::string const & to);
|
||||
void NotifyAboutStartAsyncLoading();
|
||||
void NotifyAboutFinishAsyncLoading(KMLDataCollectionPtr && collection);
|
||||
void NotifyAboutFile(bool success, std::string const & filePath, bool isTemporaryFile);
|
||||
void LoadBookmarkRoutine(std::string const & filePath, bool isTemporaryFile);
|
||||
void ReloadBookmarkRoutine(std::string const & filePath);
|
||||
|
||||
using BookmarksChecker = std::function<bool(kml::FileData const &)>;
|
||||
KMLDataCollectionPtr LoadBookmarks(std::string const & dir, std::string_view ext, KmlFileType fileType,
|
||||
BookmarksChecker const & checker);
|
||||
|
||||
void GetDirtyGroups(kml::GroupIdSet & dirtyGroups) const;
|
||||
void UpdateBmGroupIdList();
|
||||
|
||||
void NotifyBookmarksChanged();
|
||||
void NotifyCategoriesChanged();
|
||||
|
||||
void SendBookmarksChanges(MarksChangesTracker const & changesTracker);
|
||||
void GetBookmarksInfo(kml::MarkIdSet const & marks, std::vector<BookmarkInfo> & bookmarks) const;
|
||||
static void GetBookmarkGroupsInfo(MarksChangesTracker::GroupMarkIdSet const & groups,
|
||||
std::vector<BookmarkGroupInfo> & groupsInfo);
|
||||
|
||||
kml::MarkGroupId CheckAndCreateDefaultCategory();
|
||||
void CheckAndResetLastIds();
|
||||
|
||||
std::unique_ptr<kml::FileData> CollectBmGroupKMLData(BookmarkCategory const * group) const;
|
||||
KMLDataCollectionPtr PrepareToSaveBookmarks(kml::GroupIdCollection const & groupIdCollection);
|
||||
KMLDataCollectionPtr PrepareToSaveBookmarksForTrack(kml::TrackId trackId);
|
||||
|
||||
bool HasDuplicatedIds(kml::FileData const & fileData) const;
|
||||
template <typename UniquityChecker>
|
||||
void SetUniqueName(kml::CategoryData & data, UniquityChecker checker);
|
||||
bool CheckVisibility(bool isVisible) const;
|
||||
|
||||
struct SortBookmarkData
|
||||
{
|
||||
SortBookmarkData(kml::BookmarkData const & bmData, search::ReverseGeocoder::RegionAddress const & address)
|
||||
: m_id(bmData.m_id)
|
||||
, m_name(GetPreferredBookmarkName(bmData))
|
||||
, m_point(bmData.m_point)
|
||||
, m_type(GetBookmarkBaseType(bmData.m_featureTypes))
|
||||
, m_timestamp(bmData.m_timestamp)
|
||||
, m_address(address)
|
||||
{}
|
||||
|
||||
kml::MarkId m_id;
|
||||
std::string m_name;
|
||||
m2::PointD m_point;
|
||||
BookmarkBaseType m_type;
|
||||
kml::Timestamp m_timestamp;
|
||||
search::ReverseGeocoder::RegionAddress m_address;
|
||||
};
|
||||
|
||||
struct SortTrackData
|
||||
{
|
||||
explicit SortTrackData(kml::TrackData const & trackData)
|
||||
: m_id(trackData.m_id)
|
||||
, m_name(GetPreferredBookmarkStr(trackData.m_name))
|
||||
, m_timestamp(trackData.m_timestamp)
|
||||
{}
|
||||
|
||||
kml::TrackId m_id;
|
||||
std::string m_name;
|
||||
kml::Timestamp m_timestamp;
|
||||
};
|
||||
|
||||
void GetSortedCategoryImpl(SortParams const & params, std::vector<SortBookmarkData> const & bookmarksForSort,
|
||||
std::vector<SortTrackData> const & tracksForSort, SortedBlocksCollection & sortedBlocks);
|
||||
|
||||
void SortByDistance(std::vector<SortBookmarkData> const & bookmarksForSort,
|
||||
std::vector<SortTrackData> const & tracksForSort, m2::PointD const & myPosition,
|
||||
SortedBlocksCollection & sortedBlocks);
|
||||
static void SortByTime(std::vector<SortBookmarkData> const & bookmarksForSort,
|
||||
std::vector<SortTrackData> const & tracksForSort, SortedBlocksCollection & sortedBlocks);
|
||||
static void SortByType(std::vector<SortBookmarkData> const & bookmarksForSort,
|
||||
std::vector<SortTrackData> const & tracksForSort, SortedBlocksCollection & sortedBlocks);
|
||||
static void SortByName(std::vector<SortBookmarkData> const & bookmarksForSort,
|
||||
std::vector<SortTrackData> const & tracksForSort, SortedBlocksCollection & sortedBlocks);
|
||||
|
||||
using AddressesCollection = std::vector<std::pair<kml::MarkId, search::ReverseGeocoder::RegionAddress>>;
|
||||
void PrepareBookmarksAddresses(std::vector<SortBookmarkData> & bookmarksForSort, AddressesCollection & newAddresses);
|
||||
void FilterInvalidData(SortedBlocksCollection & sortedBlocks, AddressesCollection & newAddresses) const;
|
||||
void SetBookmarksAddresses(AddressesCollection const & addresses);
|
||||
static void AddTracksSortedBlock(std::vector<SortTrackData> const & sortedTracks,
|
||||
SortedBlocksCollection & sortedBlocks);
|
||||
static void SortTracksByTime(std::vector<SortTrackData> & tracks);
|
||||
static void SortTracksByName(std::vector<SortTrackData> & tracks);
|
||||
|
||||
kml::MarkId GetTrackSelectionMarkId(kml::TrackId trackId) const;
|
||||
int GetTrackSelectionMarkMinZoom(kml::TrackId trackId) const;
|
||||
void SetTrackSelectionMark(kml::TrackId trackId, m2::PointD const & pt, double distance);
|
||||
void DeleteTrackSelectionMark(kml::TrackId trackId);
|
||||
void ResetTrackInfoMark(kml::TrackId trackId);
|
||||
|
||||
void UpdateTrackMarksMinZoom();
|
||||
void UpdateTrackMarksVisibility(kml::MarkGroupId groupId);
|
||||
void RequestSymbolSizes();
|
||||
|
||||
kml::GroupIdCollection GetCompilationOfType(kml::MarkGroupId parentId, kml::CompilationType type) const;
|
||||
|
||||
ThreadChecker m_threadChecker;
|
||||
|
||||
Callbacks m_callbacks;
|
||||
MarksChangesTracker m_changesTracker;
|
||||
MarksChangesTracker m_bookmarksChangesTracker;
|
||||
MarksChangesTracker m_drapeChangesTracker;
|
||||
df::DrapeEngineSafePtr m_drapeEngine;
|
||||
|
||||
std::unique_ptr<search::RegionAddressGetter> m_regionAddressGetter;
|
||||
std::mutex m_regionAddressMutex;
|
||||
|
||||
BookmarksChangedCallback m_bookmarksChangedCallback;
|
||||
CategoriesChangedCallback m_categoriesChangedCallback;
|
||||
ElevationActivePointChangedCallback m_elevationActivePointChanged;
|
||||
ElevationMyPositionChangedCallback m_elevationMyPositionChanged;
|
||||
m2::PointD m_lastElevationMyPosition = m2::PointD::Zero();
|
||||
|
||||
OnSymbolSizesAcquiredCallback m_onSymbolSizesAcquiredFn;
|
||||
bool m_symbolSizesAcquired = false;
|
||||
|
||||
AsyncLoadingCallbacks m_asyncLoadingCallbacks;
|
||||
std::atomic<bool> m_needTeardown;
|
||||
size_t m_openedEditSessionsCount = 0;
|
||||
bool m_loadBookmarksCalled = false;
|
||||
bool m_loadBookmarksFinished = false;
|
||||
bool m_firstDrapeNotification = false;
|
||||
bool m_notificationsEnabled = true;
|
||||
|
||||
ScreenBase m_viewport;
|
||||
|
||||
CategoriesCollection m_categories;
|
||||
kml::GroupIdCollection m_unsortedBmGroupsIdList;
|
||||
|
||||
std::string m_lastCategoryUrl;
|
||||
kml::MarkGroupId m_lastEditedGroupId = kml::kInvalidMarkGroupId;
|
||||
kml::PredefinedColor m_lastColor = kml::PredefinedColor::Red;
|
||||
UserMarkLayers m_userMarkLayers;
|
||||
|
||||
MarksCollection m_userMarks;
|
||||
BookmarksCollection m_bookmarks;
|
||||
TracksCollection m_tracks;
|
||||
|
||||
StaticMarkPoint * m_selectionMark = nullptr;
|
||||
MyPositionMarkPoint * m_myPositionMark = nullptr;
|
||||
|
||||
kml::MarkId m_trackInfoMarkId = kml::kInvalidMarkId;
|
||||
kml::TrackId m_selectedTrackId = kml::kInvalidTrackId;
|
||||
m2::PointF m_maxBookmarkSymbolSize;
|
||||
|
||||
std::unique_ptr<Bookmark> m_recentlyDeletedBookmark;
|
||||
|
||||
bool m_asyncLoadingInProgress = false;
|
||||
struct BookmarkLoaderInfo
|
||||
{
|
||||
BookmarkLoaderInfo(std::string const & filename, bool isTemporaryFile, bool isReloading)
|
||||
: m_filename(filename)
|
||||
, m_isTemporaryFile(isTemporaryFile)
|
||||
, m_isReloading(isReloading)
|
||||
{}
|
||||
|
||||
std::string m_filename;
|
||||
bool m_isTemporaryFile = false;
|
||||
bool m_isReloading = false;
|
||||
};
|
||||
std::list<BookmarkLoaderInfo> m_bookmarkLoadingQueue;
|
||||
|
||||
struct RestoringCache
|
||||
{
|
||||
std::string m_serverId;
|
||||
kml::AccessRules m_accessRules;
|
||||
};
|
||||
std::map<std::string, RestoringCache> m_restoringCache;
|
||||
|
||||
struct ExpiredCategory
|
||||
{
|
||||
ExpiredCategory(kml::MarkGroupId id, std::string const & serverId) : m_id(id), m_serverId(serverId) {}
|
||||
|
||||
kml::MarkGroupId m_id;
|
||||
std::string m_serverId;
|
||||
};
|
||||
std::vector<ExpiredCategory> m_expiredCategories;
|
||||
|
||||
struct Properties
|
||||
{
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Properties, visitor(m_values, "values"))
|
||||
|
||||
bool GetProperty(std::string const & propertyName, std::string & value) const;
|
||||
|
||||
std::map<std::string, std::string> m_values;
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Metadata, visitor(m_entriesProperties, "entriesProperties"),
|
||||
visitor(m_commonProperties, "commonProperties"))
|
||||
|
||||
bool GetEntryProperty(std::string const & entryName, std::string const & propertyName, std::string & value) const;
|
||||
|
||||
std::map<std::string, Properties> m_entriesProperties;
|
||||
Properties m_commonProperties;
|
||||
};
|
||||
|
||||
Metadata m_metadata;
|
||||
|
||||
// Switch some operations in bookmark manager to synchronous mode to simplify unit-testing.
|
||||
bool m_testModeEnabled = false;
|
||||
|
||||
CategoriesCollection m_compilations;
|
||||
|
||||
DISALLOW_COPY_AND_MOVE(BookmarkManager);
|
||||
};
|
||||
43
libs/map/bookmarks_search_params.hpp
Normal file
43
libs/map/bookmarks_search_params.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "kml/type_utils.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace search
|
||||
{
|
||||
struct BookmarksSearchParams
|
||||
{
|
||||
using Results = std::vector<kml::MarkId>;
|
||||
|
||||
enum class Status
|
||||
{
|
||||
InProgress,
|
||||
Completed,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
std::string m_query;
|
||||
kml::MarkGroupId m_groupId = kml::kInvalidMarkGroupId;
|
||||
|
||||
using OnResults = std::function<void(Results results, Status status)>;
|
||||
OnResults m_onResults;
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(BookmarksSearchParams::Status status)
|
||||
{
|
||||
using Status = BookmarksSearchParams::Status;
|
||||
switch (status)
|
||||
{
|
||||
case Status::InProgress: return "InProgress";
|
||||
case Status::Completed: return "Completed";
|
||||
case Status::Cancelled: return "Cancelled";
|
||||
}
|
||||
ASSERT(false, ("Unknown status"));
|
||||
return "Unknown";
|
||||
}
|
||||
} // namespace search
|
||||
302
libs/map/chart_generator.cpp
Normal file
302
libs/map/chart_generator.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#include "map/chart_generator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "3party/agg/agg_conv_curve.h"
|
||||
#include "3party/agg/agg_conv_stroke.h"
|
||||
#include "3party/agg/agg_path_storage.h"
|
||||
#include "3party/agg/agg_pixfmt_rgba.h"
|
||||
#include "3party/agg/agg_rasterizer_scanline_aa.h"
|
||||
#include "3party/agg/agg_renderer_scanline.h"
|
||||
#include "3party/agg/agg_scanline_p.h"
|
||||
|
||||
namespace maps
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <class Color, class Order>
|
||||
struct BlendAdaptor
|
||||
{
|
||||
using order_type = Order;
|
||||
using color_type = Color;
|
||||
using TValueType = typename color_type::value_type;
|
||||
using TCalcType = typename color_type::calc_type;
|
||||
|
||||
enum
|
||||
{
|
||||
ScaleShift = color_type::base_shift,
|
||||
ScaleMask = color_type::base_mask,
|
||||
};
|
||||
|
||||
static AGG_INLINE void blend_pix(unsigned op, TValueType * p, unsigned cr, unsigned cg, unsigned cb, unsigned ca,
|
||||
unsigned cover)
|
||||
{
|
||||
using TBlendTable = agg::comp_op_table_rgba<Color, Order>;
|
||||
if (p[Order::A])
|
||||
{
|
||||
TBlendTable::g_comp_op_func[op](p, (cr * ca + ScaleMask) >> ScaleShift, (cg * ca + ScaleMask) >> ScaleShift,
|
||||
(cb * ca + ScaleMask) >> ScaleShift, ca, cover);
|
||||
}
|
||||
else
|
||||
{
|
||||
TBlendTable::g_comp_op_func[op](p, cr, cg, cb, ca, cover);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
agg::rgba8 GetLineColor(MapStyle mapStyle)
|
||||
{
|
||||
switch (mapStyle)
|
||||
{
|
||||
case MapStyleCount: LOG(LERROR, ("Wrong map style param.")); // fallthrough
|
||||
case MapStyleDefaultDark:
|
||||
case MapStyleVehicleDark:
|
||||
case MapStyleOutdoorsDark: return agg::rgba8(255, 230, 140, 255);
|
||||
case MapStyleDefaultLight:
|
||||
case MapStyleVehicleLight:
|
||||
case MapStyleOutdoorsLight:
|
||||
case MapStyleMerged: return agg::rgba8(30, 150, 240, 255);
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
agg::rgba8 GetCurveColor(MapStyle mapStyle)
|
||||
{
|
||||
switch (mapStyle)
|
||||
{
|
||||
case MapStyleCount:
|
||||
LOG(LERROR, ("Wrong map style param."));
|
||||
[[fallthrough]];
|
||||
// No need break or return here.
|
||||
case MapStyleDefaultDark:
|
||||
case MapStyleVehicleDark:
|
||||
case MapStyleOutdoorsDark: return agg::rgba8(255, 230, 140, 20);
|
||||
case MapStyleDefaultLight:
|
||||
case MapStyleVehicleLight:
|
||||
case MapStyleOutdoorsLight:
|
||||
case MapStyleMerged: return agg::rgba8(30, 150, 240, 20);
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ScaleChartData(vector<double> & chartData, double scale)
|
||||
{
|
||||
for (size_t i = 0; i < chartData.size(); ++i)
|
||||
chartData[i] *= scale;
|
||||
}
|
||||
|
||||
void ShiftChartData(vector<double> & chartData, double shift)
|
||||
{
|
||||
for (size_t i = 0; i < chartData.size(); ++i)
|
||||
chartData[i] += shift;
|
||||
}
|
||||
|
||||
void ReflectChartData(vector<double> & chartData)
|
||||
{
|
||||
for (size_t i = 0; i < chartData.size(); ++i)
|
||||
chartData[i] = -chartData[i];
|
||||
}
|
||||
|
||||
bool NormalizeChartData(vector<double> const & distanceDataM, geometry::Altitudes const & altitudeDataM,
|
||||
size_t resultPointCount, vector<double> & uniformAltitudeDataM)
|
||||
{
|
||||
double constexpr kEpsilon = 1e-6;
|
||||
|
||||
if (distanceDataM.size() != altitudeDataM.size())
|
||||
{
|
||||
LOG(LERROR, ("Altitude and distance data have different size."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_sorted(distanceDataM.cbegin(), distanceDataM.cend()))
|
||||
{
|
||||
LOG(LERROR, ("Route segment distances are not sorted."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (distanceDataM.empty() || resultPointCount == 0)
|
||||
{
|
||||
uniformAltitudeDataM.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const calculateAltitude = [&](double distFormStartM)
|
||||
{
|
||||
if (distFormStartM <= distanceDataM.front())
|
||||
return static_cast<double>(altitudeDataM.front());
|
||||
if (distFormStartM >= distanceDataM.back())
|
||||
return static_cast<double>(altitudeDataM.back());
|
||||
|
||||
auto const lowerIt = lower_bound(distanceDataM.cbegin(), distanceDataM.cend(), distFormStartM);
|
||||
size_t const nextPointIdx = distance(distanceDataM.cbegin(), lowerIt);
|
||||
ASSERT_LESS(0, nextPointIdx, ("distFormStartM is greater than 0 but nextPointIdx == 0."));
|
||||
size_t const prevPointIdx = nextPointIdx - 1;
|
||||
|
||||
if (AlmostEqualAbs(distanceDataM[prevPointIdx], distanceDataM[nextPointIdx], kEpsilon))
|
||||
return static_cast<double>(altitudeDataM[prevPointIdx]);
|
||||
|
||||
double const k = (altitudeDataM[nextPointIdx] - altitudeDataM[prevPointIdx]) /
|
||||
(distanceDataM[nextPointIdx] - distanceDataM[prevPointIdx]);
|
||||
return static_cast<double>(altitudeDataM[prevPointIdx]) + k * (distFormStartM - distanceDataM[prevPointIdx]);
|
||||
};
|
||||
|
||||
double const routeLenM = distanceDataM.back();
|
||||
uniformAltitudeDataM.resize(resultPointCount);
|
||||
double const stepLenM = resultPointCount <= 1 ? 0.0 : routeLenM / (resultPointCount - 1);
|
||||
|
||||
for (size_t i = 0; i < resultPointCount; ++i)
|
||||
uniformAltitudeDataM[i] = calculateAltitude(static_cast<double>(i) * stepLenM);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl, vector<double> const & altitudeDataM,
|
||||
vector<double> & yAxisDataPxl)
|
||||
{
|
||||
if (altitudeDataM.empty())
|
||||
{
|
||||
yAxisDataPxl.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t constexpr kHeightIndentPxl = 2;
|
||||
uint32_t heightIndentPxl = kHeightIndentPxl;
|
||||
if (height <= 2 * kHeightIndentPxl)
|
||||
{
|
||||
LOG(LERROR, ("Chart height is less or equal than 2 * kHeightIndentPxl (", 2 * kHeightIndentPxl, ")"));
|
||||
heightIndentPxl = 0;
|
||||
}
|
||||
|
||||
auto const minMaxAltitudeIt = minmax_element(altitudeDataM.begin(), altitudeDataM.end());
|
||||
double const minAltM = *minMaxAltitudeIt.first;
|
||||
double const maxAltM = *minMaxAltitudeIt.second;
|
||||
double const deltaAltM = maxAltM - minAltM;
|
||||
uint32_t const drawHeightPxl = height - 2 * heightIndentPxl;
|
||||
double const metersPerPxl = max(minMetersPerPxl, deltaAltM / static_cast<double>(drawHeightPxl));
|
||||
if (metersPerPxl == 0.0)
|
||||
{
|
||||
LOG(LERROR, ("metersPerPxl == 0.0"));
|
||||
return false;
|
||||
}
|
||||
// int avoids double errors which make freeHeightSpacePxl slightly less than zero.
|
||||
int const deltaAltPxl = deltaAltM / metersPerPxl;
|
||||
double const freeHeightSpacePxl = drawHeightPxl - deltaAltPxl;
|
||||
if (freeHeightSpacePxl < 0 || freeHeightSpacePxl > drawHeightPxl)
|
||||
{
|
||||
LOG(LERROR, ("Number of pixels free of chart points (", freeHeightSpacePxl,
|
||||
") is below zero or greater than the number of pixels for the chart (", drawHeightPxl, ")."));
|
||||
return false;
|
||||
}
|
||||
|
||||
double const maxAltPxl = maxAltM / metersPerPxl;
|
||||
yAxisDataPxl.assign(altitudeDataM.cbegin(), altitudeDataM.cend());
|
||||
ScaleChartData(yAxisDataPxl, 1.0 / metersPerPxl);
|
||||
ReflectChartData(yAxisDataPxl);
|
||||
ShiftChartData(yAxisDataPxl, maxAltPxl + heightIndentPxl + freeHeightSpacePxl / 2.0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GenerateChartByPoints(uint32_t width, uint32_t height, vector<m2::PointD> const & geometry, MapStyle mapStyle,
|
||||
vector<uint8_t> & frameBuffer)
|
||||
{
|
||||
frameBuffer.clear();
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
|
||||
agg::rgba8 const kBackgroundColor = agg::rgba8(255, 255, 255, 0);
|
||||
agg::rgba8 const kLineColor = GetLineColor(mapStyle);
|
||||
agg::rgba8 const kCurveColor = GetCurveColor(mapStyle);
|
||||
double constexpr kLineWidthPxl = 2.0;
|
||||
|
||||
using TBlender = BlendAdaptor<agg::rgba8, agg::order_rgba>;
|
||||
using TPixelFormat = agg::pixfmt_custom_blend_rgba<TBlender, agg::rendering_buffer>;
|
||||
using TBaseRenderer = agg::renderer_base<TPixelFormat>;
|
||||
using TPath = agg::poly_container_adaptor<vector<m2::PointD>>;
|
||||
using TStroke = agg::conv_stroke<TPath>;
|
||||
|
||||
agg::rendering_buffer renderBuffer;
|
||||
TPixelFormat pixelFormat(renderBuffer, agg::comp_op_src_over);
|
||||
TBaseRenderer baseRenderer(pixelFormat);
|
||||
|
||||
frameBuffer.assign(width * kAltitudeChartBPP * height, 0);
|
||||
renderBuffer.attach(&frameBuffer[0], static_cast<unsigned>(width), static_cast<unsigned>(height),
|
||||
static_cast<int>(width * kAltitudeChartBPP));
|
||||
|
||||
// Background.
|
||||
baseRenderer.reset_clipping(true);
|
||||
unsigned const op = pixelFormat.comp_op();
|
||||
pixelFormat.comp_op(agg::comp_op_src);
|
||||
baseRenderer.clear(kBackgroundColor);
|
||||
pixelFormat.comp_op(op);
|
||||
|
||||
agg::rasterizer_scanline_aa<> rasterizer;
|
||||
rasterizer.clip_box(0, 0, width, height);
|
||||
|
||||
if (geometry.empty())
|
||||
return true; /* No chart line to draw. */
|
||||
|
||||
// Polygon under chart line.
|
||||
agg::path_storage underChartGeometryPath;
|
||||
underChartGeometryPath.move_to(geometry.front().x, static_cast<double>(height));
|
||||
for (auto const & p : geometry)
|
||||
underChartGeometryPath.line_to(p.x, p.y);
|
||||
underChartGeometryPath.line_to(geometry.back().x, static_cast<double>(height));
|
||||
underChartGeometryPath.close_polygon();
|
||||
|
||||
agg::conv_curve<agg::path_storage> curve(underChartGeometryPath);
|
||||
rasterizer.add_path(curve);
|
||||
agg::scanline32_p8 scanline;
|
||||
agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, kCurveColor);
|
||||
|
||||
// Chart line.
|
||||
TPath path_adaptor(geometry, false);
|
||||
TStroke stroke(path_adaptor);
|
||||
stroke.width(kLineWidthPxl);
|
||||
stroke.line_cap(agg::round_cap);
|
||||
stroke.line_join(agg::round_join);
|
||||
|
||||
rasterizer.add_path(stroke);
|
||||
agg::render_scanlines_aa_solid(rasterizer, scanline, baseRenderer, kLineColor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GenerateChart(uint32_t width, uint32_t height, vector<double> const & distanceDataM,
|
||||
geometry::Altitudes const & altitudeDataM, MapStyle mapStyle, vector<uint8_t> & frameBuffer)
|
||||
{
|
||||
if (distanceDataM.size() != altitudeDataM.size())
|
||||
{
|
||||
LOG(LERROR, ("The route is in inconsistent state. Size of altitudes is", altitudeDataM.size(),
|
||||
". Number of segment is", distanceDataM.size()));
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
if (!NormalizeChartData(distanceDataM, altitudeDataM, width, uniformAltitudeDataM))
|
||||
return false;
|
||||
|
||||
vector<double> yAxisDataPxl;
|
||||
if (!GenerateYAxisChartData(height, 1.0 /* minMetersPerPxl */, uniformAltitudeDataM, yAxisDataPxl))
|
||||
return false;
|
||||
|
||||
size_t const uniformAltitudeDataSize = yAxisDataPxl.size();
|
||||
vector<m2::PointD> geometry(uniformAltitudeDataSize);
|
||||
|
||||
if (uniformAltitudeDataSize != 0)
|
||||
{
|
||||
double const oneSegLenPix =
|
||||
static_cast<double>(width) / (uniformAltitudeDataSize == 1 ? 1 : (uniformAltitudeDataSize - 1));
|
||||
for (size_t i = 0; i < uniformAltitudeDataSize; ++i)
|
||||
geometry[i] = m2::PointD(i * oneSegLenPix, yAxisDataPxl[i]);
|
||||
}
|
||||
|
||||
return GenerateChartByPoints(width, height, geometry, mapStyle, frameBuffer);
|
||||
}
|
||||
} // namespace maps
|
||||
47
libs/map/chart_generator.hpp
Normal file
47
libs/map/chart_generator.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/map_style.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace maps
|
||||
{
|
||||
uint32_t constexpr kAltitudeChartBPP = 4;
|
||||
|
||||
void ScaleChartData(std::vector<double> & chartData, double scale);
|
||||
void ShiftChartData(std::vector<double> & chartData, double shift);
|
||||
void ReflectChartData(std::vector<double> & chartData);
|
||||
|
||||
/// \brief fills uniformAltitudeDataM with altitude data which evenly distributed by
|
||||
/// |resultPointCount| points. |distanceDataM| and |altitudeDataM| form a curve of route altitude.
|
||||
/// This method is used to generalize and evenly distribute points of the chart.
|
||||
bool NormalizeChartData(std::vector<double> const & distanceDataM, geometry::Altitudes const & altitudeDataM,
|
||||
size_t resultPointCount, std::vector<double> & uniformAltitudeDataM);
|
||||
|
||||
/// \brief fills |yAxisDataPxl|. |yAxisDataPxl| is formed to pevent displaying
|
||||
/// big waves on the chart in case of small deviation in absolute values in |yAxisData|.
|
||||
/// \param height image chart height in pixels.
|
||||
/// \param minMetersInPixel minimum meter number per height pixel.
|
||||
/// \param altitudeDataM altitude data vector in meters.
|
||||
/// \param yAxisDataPxl Y-axis data of altitude chart in pixels.
|
||||
bool GenerateYAxisChartData(uint32_t height, double minMetersPerPxl, std::vector<double> const & altitudeDataM,
|
||||
std::vector<double> & yAxisDataPxl);
|
||||
|
||||
/// \brief generates chart image on a canvas with size |width|, |height| with |geometry|.
|
||||
/// (0, 0) is a left-top corner. X-axis goes down and Y-axis goes right.
|
||||
/// \param width is result image width in pixels.
|
||||
/// \param height is result image height in pixels.
|
||||
/// \param geometry is points which is used to draw a curve of the chart.
|
||||
/// \param mapStyle is a current map style.
|
||||
/// \param frameBuffer is a vector for a result image. It's resized in this method.
|
||||
/// It's filled with RGBA(8888) image date.
|
||||
bool GenerateChartByPoints(uint32_t width, uint32_t height, std::vector<m2::PointD> const & geometry, MapStyle mapStyle,
|
||||
std::vector<uint8_t> & frameBuffer);
|
||||
|
||||
bool GenerateChart(uint32_t width, uint32_t height, std::vector<double> const & distanceDataM,
|
||||
geometry::Altitudes const & altitudeDataM, MapStyle mapStyle, std::vector<uint8_t> & frameBuffer);
|
||||
} // namespace maps
|
||||
63
libs/map/elevation_info.cpp
Normal file
63
libs/map/elevation_info.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#include "map/elevation_info.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
using namespace geometry;
|
||||
using namespace mercator;
|
||||
|
||||
ElevationInfo::ElevationInfo(std::vector<GeometryLine> const & lines)
|
||||
{
|
||||
// Concatenate all segments.
|
||||
for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex)
|
||||
{
|
||||
auto const & line = lines[lineIndex];
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
if (lineIndex > 0)
|
||||
m_segmentsDistances.emplace_back(m_points.back().m_distance);
|
||||
|
||||
AddPoints(line, true /* new segment */);
|
||||
}
|
||||
/// @todo(KK) Implement difficulty calculation.
|
||||
m_difficulty = Difficulty::Unknown;
|
||||
}
|
||||
|
||||
void ElevationInfo::AddGpsPoints(GpsPoints const & points)
|
||||
{
|
||||
GeometryLine line;
|
||||
line.reserve(points.size());
|
||||
for (auto const & point : points)
|
||||
line.emplace_back(FromLatLon(point.m_latitude, point.m_longitude), point.m_altitude);
|
||||
AddPoints(line);
|
||||
}
|
||||
|
||||
void ElevationInfo::AddPoints(GeometryLine const & line, bool isNewSegment)
|
||||
{
|
||||
if (line.empty())
|
||||
return;
|
||||
|
||||
double distance = m_points.empty() ? 0 : m_points.back().m_distance;
|
||||
for (size_t pointIndex = 0; pointIndex < line.size(); ++pointIndex)
|
||||
{
|
||||
auto const & point = line[pointIndex];
|
||||
|
||||
if (m_points.empty())
|
||||
{
|
||||
m_points.emplace_back(point, distance);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNewSegment && pointIndex == 0)
|
||||
{
|
||||
m_points.emplace_back(point, distance);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const & previousPoint = m_points.back().m_point;
|
||||
distance += mercator::DistanceOnEarth(previousPoint.GetPoint(), point.GetPoint());
|
||||
m_points.emplace_back(point, distance);
|
||||
}
|
||||
}
|
||||
57
libs/map/elevation_info.hpp
Normal file
57
libs/map/elevation_info.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ElevationInfo
|
||||
{
|
||||
public:
|
||||
struct Point
|
||||
{
|
||||
Point(geometry::PointWithAltitude point, double distance) : m_point(point), m_distance(distance) {}
|
||||
geometry::PointWithAltitude const m_point;
|
||||
double const m_distance;
|
||||
};
|
||||
|
||||
using Points = std::vector<Point>;
|
||||
using GpsPoints = std::vector<location::GpsInfo>;
|
||||
using GeometryLine = kml::MultiGeometry::LineT;
|
||||
using SegmentsDistances = std::vector<double>;
|
||||
|
||||
enum Difficulty : uint8_t
|
||||
{
|
||||
Unknown,
|
||||
Easy,
|
||||
Medium,
|
||||
Hard
|
||||
};
|
||||
|
||||
ElevationInfo() = default;
|
||||
explicit ElevationInfo(std::vector<GeometryLine> const & lines);
|
||||
|
||||
void AddGpsPoints(GpsPoints const & points);
|
||||
|
||||
size_t GetSize() const { return m_points.size(); }
|
||||
Points const & GetPoints() const { return m_points; }
|
||||
uint8_t GetDifficulty() const { return m_difficulty; }
|
||||
SegmentsDistances const & GetSegmentsDistances() const { return m_segmentsDistances; }
|
||||
|
||||
private:
|
||||
// Points with distance from start of the track and altitude.
|
||||
Points m_points;
|
||||
// Some digital difficulty level with value in range [0-kMaxDifficulty]
|
||||
// or kInvalidDifficulty when difficulty is not found or incorrect.
|
||||
Difficulty m_difficulty = Difficulty::Unknown;
|
||||
// Distances to the start of each segment.
|
||||
SegmentsDistances m_segmentsDistances;
|
||||
|
||||
void AddPoints(GeometryLine const & line, bool isNewSegment = false);
|
||||
};
|
||||
24
libs/map/everywhere_search_callback.cpp
Normal file
24
libs/map/everywhere_search_callback.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "map/everywhere_search_callback.hpp"
|
||||
|
||||
namespace search
|
||||
{
|
||||
EverywhereSearchCallback::EverywhereSearchCallback(Delegate & delegate, OnResults onResults)
|
||||
: m_delegate(delegate)
|
||||
, m_onResults(std::move(onResults))
|
||||
{
|
||||
CHECK(m_onResults, ());
|
||||
}
|
||||
|
||||
void EverywhereSearchCallback::operator()(Results const & results)
|
||||
{
|
||||
size_t const prevSize = m_productInfo.size();
|
||||
size_t const currSize = results.GetCount();
|
||||
ASSERT_LESS_OR_EQUAL(prevSize, currSize, ());
|
||||
|
||||
for (size_t i = prevSize; i < currSize; ++i)
|
||||
m_productInfo.push_back(m_delegate.GetProductInfo(results[i]));
|
||||
|
||||
m_delegate.RunUITask([onResults = m_onResults, results, productInfo = m_productInfo]() mutable
|
||||
{ onResults(std::move(results), std::move(productInfo)); });
|
||||
}
|
||||
} // namespace search
|
||||
37
libs/map/everywhere_search_callback.hpp
Normal file
37
libs/map/everywhere_search_callback.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/everywhere_search_params.hpp"
|
||||
#include "map/search_product_info.hpp"
|
||||
|
||||
#include "search/result.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace search
|
||||
{
|
||||
/// @brief An on-results-callback that should be used for search over all maps.
|
||||
/// @note NOT thread safe.
|
||||
class EverywhereSearchCallback
|
||||
{
|
||||
public:
|
||||
class Delegate
|
||||
{
|
||||
public:
|
||||
virtual ~Delegate() = default;
|
||||
|
||||
virtual void RunUITask(std::function<void()> fn) = 0;
|
||||
virtual ProductInfo GetProductInfo(Result const & result) const = 0;
|
||||
};
|
||||
|
||||
using OnResults = EverywhereSearchParams::OnResults;
|
||||
EverywhereSearchCallback(Delegate & delegate, OnResults onResults);
|
||||
|
||||
void operator()(Results const & results);
|
||||
|
||||
private:
|
||||
Delegate & m_delegate;
|
||||
OnResults m_onResults;
|
||||
std::vector<ProductInfo> m_productInfo;
|
||||
};
|
||||
} // namespace search
|
||||
26
libs/map/everywhere_search_params.hpp
Normal file
26
libs/map/everywhere_search_params.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/search_product_info.hpp"
|
||||
|
||||
#include "search/result.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace search
|
||||
{
|
||||
struct EverywhereSearchParams
|
||||
{
|
||||
using OnResults = std::function<void(Results results, std::vector<ProductInfo> productInfo)>;
|
||||
|
||||
std::string m_query;
|
||||
std::string m_inputLocale;
|
||||
std::optional<std::chrono::steady_clock::duration> m_timeout;
|
||||
bool m_isCategory = false;
|
||||
|
||||
OnResults m_onResults;
|
||||
};
|
||||
} // namespace search
|
||||
208
libs/map/extrapolation/extrapolator.cpp
Normal file
208
libs/map/extrapolation/extrapolator.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#include "map/extrapolation/extrapolator.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace
|
||||
{
|
||||
// If the difference of values between two instances of GpsInfo is greater
|
||||
// than the appropriate constant below the extrapolator will be switched off for
|
||||
// these two instances of GpsInfo.
|
||||
double constexpr kMaxExtrapolationSpeedMPS = 75.0;
|
||||
double constexpr kMaxExtrapolationDistMeters = 100.0;
|
||||
double constexpr kMaxExtrapolationTimeSeconds = 2.1;
|
||||
|
||||
class LinearExtrapolator
|
||||
{
|
||||
public:
|
||||
LinearExtrapolator(uint64_t timeBetweenMs, uint64_t timeAfterMs)
|
||||
: m_timeBetweenMs(timeBetweenMs)
|
||||
, m_timeAfterMs(timeAfterMs)
|
||||
{
|
||||
CHECK_NOT_EQUAL(m_timeBetweenMs, 0, ());
|
||||
}
|
||||
|
||||
double Extrapolate(double x1, double x2) const { return x2 + ((x2 - x1) / m_timeBetweenMs) * m_timeAfterMs; }
|
||||
|
||||
private:
|
||||
uint64_t m_timeBetweenMs;
|
||||
uint64_t m_timeAfterMs;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace extrapolation
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
location::GpsInfo LinearExtrapolation(location::GpsInfo const & gpsInfo1, location::GpsInfo const & gpsInfo2,
|
||||
uint64_t timeAfterPoint2Ms)
|
||||
{
|
||||
if (gpsInfo2.m_timestamp <= gpsInfo1.m_timestamp)
|
||||
{
|
||||
ASSERT(false, ("Incorrect gps data"));
|
||||
return gpsInfo2;
|
||||
}
|
||||
|
||||
auto const timeBetweenPointsMs = static_cast<uint64_t>((gpsInfo2.m_timestamp - gpsInfo1.m_timestamp) * 1000);
|
||||
if (timeBetweenPointsMs == 0)
|
||||
return gpsInfo2;
|
||||
|
||||
location::GpsInfo result = gpsInfo2;
|
||||
LinearExtrapolator const e(timeBetweenPointsMs, timeAfterPoint2Ms);
|
||||
|
||||
result.m_timestamp += static_cast<double>(timeAfterPoint2Ms) / 1000.0;
|
||||
result.m_longitude = math::Clamp(e.Extrapolate(gpsInfo1.m_longitude, gpsInfo2.m_longitude), -180.0, 180.0);
|
||||
result.m_latitude = math::Clamp(e.Extrapolate(gpsInfo1.m_latitude, gpsInfo2.m_latitude), -90.0, 90.0);
|
||||
result.m_altitude = e.Extrapolate(gpsInfo1.m_altitude, gpsInfo2.m_altitude);
|
||||
|
||||
// @TODO(bykoianko) Now |result.m_bearing| == |gpsInfo2.m_bearing|.
|
||||
// In case of |gpsInfo1.HasBearing() && gpsInfo2.HasBearing() == true|
|
||||
// consider finding an average value between |gpsInfo1.m_bearing| and |gpsInfo2.m_bearing|
|
||||
// taking into account that they are periodic. It's important to implement it
|
||||
// because current implementation leads to changing course by steps. It doesn't
|
||||
// look nice when the road changes its direction.
|
||||
|
||||
if (gpsInfo1.HasSpeed() && gpsInfo2.HasSpeed())
|
||||
result.m_speed = e.Extrapolate(gpsInfo1.m_speed, gpsInfo2.m_speed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AreCoordsGoodForExtrapolation(location::GpsInfo const & info1, location::GpsInfo const & info2)
|
||||
{
|
||||
if (!info1.IsValid() || !info2.IsValid())
|
||||
return false;
|
||||
|
||||
double const distM = ms::DistanceOnEarth(info1.m_latitude, info1.m_longitude, info2.m_latitude, info2.m_longitude);
|
||||
|
||||
double const timeS = info2.m_timestamp - info1.m_timestamp;
|
||||
if (timeS <= 0.0)
|
||||
return false;
|
||||
|
||||
// |maxDistAfterExtrapolationM| is maximum possible distance from |info2| to
|
||||
// the furthest extrapolated point.
|
||||
double const maxDistAfterExtrapolationM = distM * (Extrapolator::kMaxExtrapolationTimeMs / 1000.0) / timeS;
|
||||
// |maxDistForAllExtrapolationsM| is maximum possible distance from |info2| to
|
||||
// all extrapolated points in any cases.
|
||||
double const maxDistForAllExtrapolationsM = kMaxExtrapolationSpeedMPS * kMaxExtrapolationTimeSeconds;
|
||||
double const distLastGpsInfoToMeridian180 =
|
||||
ms::DistanceOnEarth(info2.m_latitude, info2.m_longitude, info2.m_latitude, 180.0 /* lon2Deg */);
|
||||
// Switching off extrapolation if |info2| are so close to meridian 180 that extrapolated
|
||||
// points may cross meridian 180 or if |info1| and |info2| are located on
|
||||
// different sides of meridian 180.
|
||||
if (distLastGpsInfoToMeridian180 < maxDistAfterExtrapolationM ||
|
||||
(distLastGpsInfoToMeridian180 < maxDistForAllExtrapolationsM && info2.m_longitude * info1.m_longitude < 0.0) ||
|
||||
ms::DistanceOnEarth(info2.m_latitude, info2.m_longitude, 90.0 /* lat2Deg */, info2.m_longitude) <
|
||||
maxDistAfterExtrapolationM ||
|
||||
ms::DistanceOnEarth(info2.m_latitude, info2.m_longitude, -90.0 /* lat2Deg */, info2.m_longitude) <
|
||||
maxDistAfterExtrapolationM)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note. |timeS| may be less than zero. (info1.m_timestampS >=
|
||||
// info2.m_timestampS) It may happen in rare cases because GpsInfo::m_timestampS is not
|
||||
// monotonic generally. Please see comment in declaration of class GpsInfo for details.
|
||||
|
||||
// @TODO(bykoianko) Switching off extrapolation based on acceleration should be implemented.
|
||||
// Switching off extrapolation based on speed, distance and time.
|
||||
return distM / timeS <= kMaxExtrapolationSpeedMPS && distM <= kMaxExtrapolationDistMeters &&
|
||||
timeS <= kMaxExtrapolationTimeSeconds;
|
||||
}
|
||||
|
||||
// Extrapolator ------------------------------------------------------------------------------------
|
||||
Extrapolator::Extrapolator(ExtrapolatedLocationUpdateFn const & update)
|
||||
: m_isEnabled(false)
|
||||
, m_extrapolatedLocationUpdate(update)
|
||||
{
|
||||
RunTaskOnBackgroundThread(false /* delayed */);
|
||||
}
|
||||
|
||||
void Extrapolator::OnLocationUpdate(location::GpsInfo const & gpsInfo)
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> guard(m_mutex);
|
||||
m_beforeLastGpsInfo = m_lastGpsInfo;
|
||||
m_lastGpsInfo = gpsInfo;
|
||||
m_consecutiveRuns = 0;
|
||||
// Canceling all background tasks which are put to the queue before the task run in this method.
|
||||
++m_locationUpdateCounter;
|
||||
m_locationUpdateMinValid = m_locationUpdateCounter;
|
||||
}
|
||||
RunTaskOnBackgroundThread(false /* delayed */);
|
||||
}
|
||||
|
||||
void Extrapolator::Enable(bool enabled)
|
||||
{
|
||||
lock_guard<mutex> guard(m_mutex);
|
||||
m_isEnabled = enabled;
|
||||
}
|
||||
|
||||
void Extrapolator::ExtrapolatedLocationUpdate(uint64_t locationUpdateCounter)
|
||||
{
|
||||
location::GpsInfo gpsInfo;
|
||||
{
|
||||
lock_guard<mutex> guard(m_mutex);
|
||||
// Canceling all calls of the method which were activated before |m_locationUpdateMinValid|.
|
||||
if (locationUpdateCounter < m_locationUpdateMinValid)
|
||||
return;
|
||||
|
||||
uint64_t const extrapolationTimeMs = kExtrapolationPeriodMs * m_consecutiveRuns;
|
||||
if (extrapolationTimeMs < kMaxExtrapolationTimeMs && m_lastGpsInfo.IsValid())
|
||||
{
|
||||
if (DoesExtrapolationWork())
|
||||
gpsInfo = LinearExtrapolation(m_beforeLastGpsInfo, m_lastGpsInfo, extrapolationTimeMs);
|
||||
else
|
||||
gpsInfo = m_lastGpsInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpsInfo.IsValid())
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, gpsInfo]() { m_extrapolatedLocationUpdate(gpsInfo); });
|
||||
|
||||
{
|
||||
lock_guard<mutex> guard(m_mutex);
|
||||
if (m_consecutiveRuns != kExtrapolationCounterUndefined)
|
||||
++m_consecutiveRuns;
|
||||
}
|
||||
|
||||
// Calling ExtrapolatedLocationUpdate() in |kExtrapolationPeriodMs| milliseconds.
|
||||
RunTaskOnBackgroundThread(true /* delayed */);
|
||||
}
|
||||
|
||||
void Extrapolator::RunTaskOnBackgroundThread(bool delayed)
|
||||
{
|
||||
uint64_t locationUpdateCounter = 0;
|
||||
{
|
||||
lock_guard<mutex> guard(m_mutex);
|
||||
locationUpdateCounter = m_locationUpdateCounter;
|
||||
}
|
||||
|
||||
if (delayed)
|
||||
{
|
||||
auto constexpr period = std::chrono::milliseconds(kExtrapolationPeriodMs);
|
||||
GetPlatform().RunDelayedTask(Platform::Thread::Background, period,
|
||||
[this, locationUpdateCounter] { ExtrapolatedLocationUpdate(locationUpdateCounter); });
|
||||
}
|
||||
else
|
||||
{
|
||||
GetPlatform().RunTask(Platform::Thread::Background,
|
||||
[this, locationUpdateCounter] { ExtrapolatedLocationUpdate(locationUpdateCounter); });
|
||||
}
|
||||
}
|
||||
|
||||
bool Extrapolator::DoesExtrapolationWork() const
|
||||
{
|
||||
if (!m_isEnabled || m_consecutiveRuns == kExtrapolationCounterUndefined)
|
||||
return false;
|
||||
|
||||
return AreCoordsGoodForExtrapolation(m_beforeLastGpsInfo, m_lastGpsInfo);
|
||||
}
|
||||
} // namespace extrapolation
|
||||
79
libs/map/extrapolation/extrapolator.hpp
Normal file
79
libs/map/extrapolation/extrapolator.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
namespace extrapolation
|
||||
{
|
||||
/// \brief Returns extrapolated position after |point2| in |timeAfterPoint2Ms|.
|
||||
/// \note |timeAfterPoint2Ms| should be reasonably small (several seconds maximum).
|
||||
location::GpsInfo LinearExtrapolation(location::GpsInfo const & gpsInfo1, location::GpsInfo const & gpsInfo2,
|
||||
uint64_t timeAfterPoint2Ms);
|
||||
|
||||
/// \returns true if linear extrapolation based on |info1| and |info2| should be done.
|
||||
/// \param info1 location information about point gotten before last one.
|
||||
/// \param info2 the latest location information.
|
||||
bool AreCoordsGoodForExtrapolation(location::GpsInfo const & info1, location::GpsInfo const & info2);
|
||||
|
||||
/// \brief This class implements linear extrapolation based on methods LinearExtrapolation()
|
||||
/// and AreCoordsGoodForExtrapolation(). The idea implemented in this class is
|
||||
/// - OnLocationUpdate() should be called from gui thread when new data from gps is available.
|
||||
/// - When OnLocationUpdate() was called twice so that AreCoordsGoodForExtrapolation()
|
||||
/// returns true, extrapolation for this two location will be launched.
|
||||
/// - That means all obsolete extrapolation calls in background thread will be canceled by
|
||||
/// incrementing |m_locationUpdateMinValid|.
|
||||
/// - Several new extrapolations based on two previous locations will be generated.
|
||||
class Extrapolator
|
||||
{
|
||||
static uint64_t constexpr kExtrapolationCounterUndefined = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
public:
|
||||
using ExtrapolatedLocationUpdateFn = std::function<void(location::GpsInfo const &)>;
|
||||
|
||||
// |kMaxExtrapolationTimeMs| is time in milliseconds showing how long location will be
|
||||
// extrapolated after last location gotten from GPS.
|
||||
static uint64_t constexpr kMaxExtrapolationTimeMs = 2000;
|
||||
// |kExtrapolationPeriodMs| is time in milliseconds showing how often location will be
|
||||
// extrapolated. So if the last location was obtained from GPS at time X the next location
|
||||
// will be emulated by Extrapolator at time X + kExtrapolationPeriodMs.
|
||||
// Then X + 2 * kExtrapolationPeriodMs and so on till
|
||||
// X + n * kExtrapolationPeriodMs <= kMaxExtrapolationTimeMs.
|
||||
static uint64_t constexpr kExtrapolationPeriodMs = 200;
|
||||
|
||||
/// \param update is a function which is called with params according to extrapolated position.
|
||||
/// |update| will be called on gui thread.
|
||||
explicit Extrapolator(ExtrapolatedLocationUpdateFn const & update);
|
||||
void OnLocationUpdate(location::GpsInfo const & gpsInfo);
|
||||
// @TODO(bykoianko) Gyroscope information should be taken into account as well for calculation
|
||||
// extrapolated position.
|
||||
|
||||
void Enable(bool enabled);
|
||||
|
||||
private:
|
||||
/// \returns true if there's enough information for extrapolation and extrapolation is enabled.
|
||||
/// \note This method should be called only when |m_mutex| is locked.
|
||||
bool DoesExtrapolationWork() const;
|
||||
void ExtrapolatedLocationUpdate(uint64_t locationUpdateCounter);
|
||||
void RunTaskOnBackgroundThread(bool delayed);
|
||||
|
||||
bool m_isEnabled;
|
||||
|
||||
std::mutex m_mutex;
|
||||
ExtrapolatedLocationUpdateFn m_extrapolatedLocationUpdate;
|
||||
location::GpsInfo m_lastGpsInfo;
|
||||
location::GpsInfo m_beforeLastGpsInfo;
|
||||
uint64_t m_consecutiveRuns = kExtrapolationCounterUndefined;
|
||||
// Number of calls Extrapolator::OnLocationUpdate() method. This way |m_locationUpdateCounter|
|
||||
// reflects generation of extrapolations. That mean the next gps location is
|
||||
// the next generation.
|
||||
uint64_t m_locationUpdateCounter = 0;
|
||||
// If |m_locationUpdateCounter| < |m_locationUpdateMinValid| when
|
||||
// ExtrapolatedLocationUpdate() is called (on background thread)
|
||||
// ExtrapolatedLocationUpdate() cancels its execution.
|
||||
uint64_t m_locationUpdateMinValid = 0;
|
||||
};
|
||||
} // namespace extrapolation
|
||||
11
libs/map/extrapolation_benchmark/CMakeLists.txt
Normal file
11
libs/map/extrapolation_benchmark/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
project(extrapolation_benchmark)
|
||||
|
||||
set(SRC extrapolation_benchmark.cpp
|
||||
)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
map
|
||||
gflags::gflags
|
||||
)
|
||||
315
libs/map/extrapolation_benchmark/extrapolation_benchmark.cpp
Normal file
315
libs/map/extrapolation_benchmark/extrapolation_benchmark.cpp
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#include "map/extrapolation/extrapolator.hpp"
|
||||
|
||||
#include "routing/base/followed_polyline.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/polyline2d.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "gflags/gflags_declare.h"
|
||||
|
||||
// This tool is written to estimate quality of location extrapolation. To launch the benchmark
|
||||
// you need tracks in csv file with the format described below. To generate the csv file
|
||||
// you need to run track_analyzer tool with unmatched_tracks command.
|
||||
// For example: extrapolation_benchmark -csv_path=trafin_log.20180517-0000.track.csv
|
||||
|
||||
DEFINE_string(csv_path, "",
|
||||
"Path to csv file with user in following format: mwm id (string), aloha id (string), "
|
||||
"latitude of the first coord (double), longitude of the first coord (double), "
|
||||
"timestamp in seconds (int), latitude of the second coord (double) and so on.");
|
||||
|
||||
using namespace extrapolation;
|
||||
using namespace location;
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct GpsPoint
|
||||
{
|
||||
GpsPoint(double timestampS, double lat, double lon) : m_timestampS(timestampS), m_lat(lat), m_lon(lon) {}
|
||||
|
||||
double m_timestampS;
|
||||
// @TODO(bykoianko) Using LatLog type instead of two double should be considered.
|
||||
double m_lat;
|
||||
double m_lon;
|
||||
};
|
||||
|
||||
class MovingAverage
|
||||
{
|
||||
public:
|
||||
void Add(double value)
|
||||
{
|
||||
++m_counter;
|
||||
m_movingAverage += (value - m_movingAverage) / m_counter;
|
||||
}
|
||||
|
||||
double Get() const { return m_movingAverage; }
|
||||
size_t GetCounter() const { return m_counter; }
|
||||
|
||||
private:
|
||||
double m_movingAverage = 0.0;
|
||||
size_t m_counter = 0;
|
||||
};
|
||||
|
||||
class MovingAverageVec
|
||||
{
|
||||
public:
|
||||
explicit MovingAverageVec(size_t size) { m_mes.resize(size); }
|
||||
|
||||
void Add(vector<double> const & values)
|
||||
{
|
||||
CHECK_EQUAL(values.size(), m_mes.size(), ());
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
m_mes[i].Add(values[i]);
|
||||
}
|
||||
|
||||
vector<MovingAverage> const & Get() const { return m_mes; }
|
||||
|
||||
private:
|
||||
vector<MovingAverage> m_mes;
|
||||
};
|
||||
|
||||
using Track = vector<GpsPoint>;
|
||||
using Tracks = vector<Track>;
|
||||
|
||||
bool GetString(istringstream & lineStream, string & result)
|
||||
{
|
||||
if (!lineStream.good())
|
||||
return false;
|
||||
getline(lineStream, result, ',');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetDouble(istringstream & lineStream, double & result)
|
||||
{
|
||||
string strResult;
|
||||
if (!GetString(lineStream, strResult))
|
||||
return false;
|
||||
return strings::to_double(strResult, result);
|
||||
}
|
||||
|
||||
bool GetUint64(istringstream & lineStream, uint64_t & result)
|
||||
{
|
||||
string strResult;
|
||||
if (!GetString(lineStream, strResult))
|
||||
return false;
|
||||
return strings::to_uint64(strResult, result);
|
||||
}
|
||||
|
||||
bool GetGpsPoint(istringstream & lineStream, uint64_t & timestampS, double & lat, double & lon)
|
||||
{
|
||||
if (!GetUint64(lineStream, timestampS))
|
||||
return false;
|
||||
if (!GetDouble(lineStream, lat))
|
||||
return false;
|
||||
|
||||
return GetDouble(lineStream, lon);
|
||||
}
|
||||
|
||||
/// \brief Fills |tracks| based on file |pathToCsv| content. File |pathToCsv| should be
|
||||
/// a text file with following lines:
|
||||
/// <Mwm name (country id)>, <Aloha id>,
|
||||
/// <Latitude of the first point>, <Longitude of the first point>, <Timestamp in seconds of the
|
||||
/// first point>, <Latitude of the second point> and so on.
|
||||
bool Parse(string const & pathToCsv, Tracks & tracks)
|
||||
{
|
||||
tracks.clear();
|
||||
|
||||
std::ifstream csvStream(pathToCsv);
|
||||
if (!csvStream.is_open())
|
||||
return false;
|
||||
|
||||
string line;
|
||||
while (getline(csvStream, line))
|
||||
{
|
||||
istringstream lineStream(line);
|
||||
string dummy;
|
||||
GetString(lineStream, dummy); // mwm id
|
||||
GetString(lineStream, dummy); // aloha id
|
||||
|
||||
Track track;
|
||||
while (!lineStream.eof())
|
||||
{
|
||||
double lat;
|
||||
double lon;
|
||||
uint64_t timestamp;
|
||||
if (!GetGpsPoint(lineStream, timestamp, lat, lon))
|
||||
return false;
|
||||
track.emplace_back(static_cast<double>(timestamp), lat, lon);
|
||||
}
|
||||
tracks.push_back(std::move(track));
|
||||
}
|
||||
csvStream.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpsPointToGpsInfo(GpsPoint const gpsPoint, GpsInfo & gpsInfo)
|
||||
{
|
||||
gpsInfo = {};
|
||||
gpsInfo.m_source = TLocationSource::EAppleNative;
|
||||
gpsInfo.m_timestamp = gpsPoint.m_timestampS;
|
||||
gpsInfo.m_latitude = gpsPoint.m_lat;
|
||||
gpsInfo.m_longitude = gpsPoint.m_lon;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/// \brief This benchmark is written to estimate how LinearExtrapolation() extrapolates real users
|
||||
/// tracks. The idea behind the test is to measure the distance between extrapolated location and
|
||||
/// real track.
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
gflags::SetUsageMessage(
|
||||
"Location extrapolation benchmark. Cumulative moving average, variance and standard "
|
||||
"deviation for all extrapolation deviations from tracks passed in comma separated csv with "
|
||||
"csv_path.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_csv_path.empty())
|
||||
{
|
||||
LOG(LERROR, ("Path to csv file with user tracks should be set."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
Tracks tracks;
|
||||
if (!Parse(FLAGS_csv_path, tracks))
|
||||
{
|
||||
LOG(LERROR, ("An error while parsing", FLAGS_csv_path, "file. Please check if it has a correct format."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Printing some statistics about tracks.
|
||||
size_t trackPointNum = 0;
|
||||
for (auto const & t : tracks)
|
||||
trackPointNum += t.size();
|
||||
|
||||
double trackLengths = 0;
|
||||
for (auto const & t : tracks)
|
||||
{
|
||||
double trackLenM = 0;
|
||||
for (size_t j = 1; j < t.size(); ++j)
|
||||
trackLenM += ms::DistanceOnEarth(ms::LatLon(t[j - 1].m_lat, t[j - 1].m_lon), ms::LatLon(t[j].m_lat, t[j].m_lon));
|
||||
trackLengths += trackLenM;
|
||||
}
|
||||
|
||||
LOG(LINFO, ("General tracks statistics."
|
||||
"\n Number of tracks:",
|
||||
tracks.size(), "\n Number of track points:", trackPointNum, "\n Average points per track:",
|
||||
trackPointNum / tracks.size(), "\n Average track length:", trackLengths / tracks.size(), "meters"));
|
||||
|
||||
// For all points of each track in |tracks| some extrapolations will be calculated.
|
||||
// The number of extrapolations depends on |Extrapolator::kExtrapolationPeriodMs|
|
||||
// and |Extrapolator::kMaxExtrapolationTimeMs| and equal for all points.
|
||||
// Then cumulative moving average and variance of each extrapolation will be printed.
|
||||
auto const extrapolationNumber =
|
||||
static_cast<size_t>(Extrapolator::kMaxExtrapolationTimeMs / Extrapolator::kExtrapolationPeriodMs);
|
||||
MovingAverageVec mes(extrapolationNumber);
|
||||
MovingAverageVec squareMes(extrapolationNumber);
|
||||
// Number of extrapolations for which projections are calculated successfully.
|
||||
size_t projectionCounter = 0;
|
||||
for (auto const & t : tracks)
|
||||
{
|
||||
if (t.size() <= 1)
|
||||
continue;
|
||||
|
||||
m2::PolylineD poly;
|
||||
for (auto const & p : t)
|
||||
poly.Add(mercator::FromLatLon(p.m_lat, p.m_lon));
|
||||
CHECK_EQUAL(poly.GetSize(), t.size(), ());
|
||||
FollowedPolyline followedPoly(poly.Begin(), poly.End());
|
||||
CHECK(followedPoly.IsValid(), ());
|
||||
|
||||
// For each track point except for the first one some extrapolations will be calculated.
|
||||
for (size_t i = 1; i < t.size(); ++i)
|
||||
{
|
||||
GpsInfo info1;
|
||||
GpsPointToGpsInfo(t[i - 1], info1);
|
||||
GpsInfo info2;
|
||||
GpsPointToGpsInfo(t[i], info2);
|
||||
|
||||
if (!AreCoordsGoodForExtrapolation(info1, info2))
|
||||
break;
|
||||
|
||||
vector<double> onePointDeviations;
|
||||
vector<double> onePointDeviationsSquared;
|
||||
bool projFound = true;
|
||||
for (size_t timeMs = Extrapolator::kExtrapolationPeriodMs; timeMs <= Extrapolator::kMaxExtrapolationTimeMs;
|
||||
timeMs += Extrapolator::kExtrapolationPeriodMs)
|
||||
{
|
||||
GpsInfo const extrapolated = LinearExtrapolation(info1, info2, timeMs);
|
||||
m2::PointD const extrapolatedMerc = mercator::FromLatLon(extrapolated.m_latitude, extrapolated.m_longitude);
|
||||
|
||||
double constexpr kHalfSquareSide = 100.0;
|
||||
// |kHalfSquareSide| is chosen based on maximum value of GpsInfo::m_horizontalAccuracy
|
||||
// which is used calculation of projection in production code.
|
||||
m2::RectD const posSquare =
|
||||
mercator::MetersToXY(extrapolated.m_longitude, extrapolated.m_latitude, kHalfSquareSide);
|
||||
// One is deducted from polyline size because in GetClosestProjectionInInterval()
|
||||
// is used segment indices but not point indices.
|
||||
auto const & iter = followedPoly.GetClosestProjectionInInterval(
|
||||
posSquare, [&extrapolatedMerc](FollowedPolyline::Iter const & it)
|
||||
{
|
||||
ASSERT(it.IsValid(), ());
|
||||
return mercator::DistanceOnEarth(it.m_pt, extrapolatedMerc);
|
||||
}, 0 /* start segment index */, followedPoly.GetPolyline().GetSize() - 1);
|
||||
|
||||
if (iter.IsValid())
|
||||
{
|
||||
++projectionCounter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This situation is possible if |posRect| param of GetClosestProjectionInInterval()
|
||||
// method is too small and there is no segment in |followedPoly|
|
||||
// which is covered by this rect. It's a rare situation.
|
||||
projFound = false;
|
||||
break;
|
||||
}
|
||||
|
||||
double const distFromPoly = mercator::DistanceOnEarth(iter.m_pt, extrapolatedMerc);
|
||||
onePointDeviations.push_back(distFromPoly);
|
||||
onePointDeviationsSquared.push_back(distFromPoly * distFromPoly);
|
||||
}
|
||||
|
||||
if (projFound)
|
||||
{
|
||||
CHECK_EQUAL(onePointDeviations.size(), extrapolationNumber, ());
|
||||
mes.Add(onePointDeviations);
|
||||
squareMes.Add(onePointDeviationsSquared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_GREATER(extrapolationNumber, 0, ());
|
||||
LOG(LINFO,
|
||||
("\n Processed", mes.Get()[0].GetCounter(), "points.\n", " ", mes.Get()[0].GetCounter() * extrapolationNumber,
|
||||
"extrapolations is calculated.\n", " Projection is calculated for", projectionCounter, "extrapolations."));
|
||||
|
||||
LOG(LINFO, ("Cumulative moving average, variance and standard deviation for each extrapolation:"));
|
||||
for (size_t i = 0; i < extrapolationNumber; ++i)
|
||||
{
|
||||
double const variance = squareMes.Get()[i].Get() - math::Pow2(mes.Get()[i].Get());
|
||||
LOG(LINFO, ("Extrapolation", i + 1, ",", Extrapolator::kExtrapolationPeriodMs * (i + 1),
|
||||
"seconds after point two. Cumulative moving average =", mes.Get()[i].Get(), "meters.",
|
||||
"Variance =", max(0.0, variance), ". Standard deviation =", sqrt(max(0.0, variance))));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
94
libs/map/features_fetcher.cpp
Normal file
94
libs/map/features_fetcher.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
using platform::CountryFile;
|
||||
using platform::LocalCountryFile;
|
||||
|
||||
FeaturesFetcher::FeaturesFetcher()
|
||||
{
|
||||
m_dataSource.AddObserver(*this);
|
||||
}
|
||||
|
||||
FeaturesFetcher::~FeaturesFetcher()
|
||||
{
|
||||
m_dataSource.RemoveObserver(*this);
|
||||
}
|
||||
|
||||
// While reading any files (classificator or mwm), there are 2 types of possible exceptions:
|
||||
// Reader::Exception, FileAbsentException.
|
||||
// Let's process RootException everywhere, to supress such errors.
|
||||
|
||||
void FeaturesFetcher::InitClassificator()
|
||||
{
|
||||
try
|
||||
{
|
||||
classificator::Load();
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LERROR, ("Classificator read error: ", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<MwmSet::MwmId, MwmSet::RegResult> FeaturesFetcher::RegisterMap(LocalCountryFile const & localFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = m_dataSource.RegisterMap(localFile);
|
||||
if (result.second != MwmSet::RegResult::Success)
|
||||
{
|
||||
LOG(LWARNING, ("Can't add map", localFile.GetCountryName(), "(", result.second, ").",
|
||||
"Probably it's already added or has newer data version."));
|
||||
}
|
||||
else
|
||||
{
|
||||
MwmSet::MwmId const & id = result.first;
|
||||
ASSERT(id.IsAlive(), ());
|
||||
m_rect.Add(id.GetInfo()->m_bordersRect);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (RootException const & ex)
|
||||
{
|
||||
LOG(LERROR, ("IO error while adding", localFile.GetCountryName(), "map.", ex.Msg()));
|
||||
return std::make_pair(MwmSet::MwmId(), MwmSet::RegResult::BadFile);
|
||||
}
|
||||
}
|
||||
|
||||
bool FeaturesFetcher::DeregisterMap(CountryFile const & countryFile)
|
||||
{
|
||||
return m_dataSource.Deregister(countryFile);
|
||||
}
|
||||
|
||||
void FeaturesFetcher::Clear()
|
||||
{
|
||||
m_dataSource.Clear();
|
||||
}
|
||||
|
||||
void FeaturesFetcher::ClearCaches()
|
||||
{
|
||||
m_dataSource.ClearCache();
|
||||
}
|
||||
|
||||
m2::RectD FeaturesFetcher::GetWorldRect() const
|
||||
{
|
||||
if (m_rect == m2::RectD())
|
||||
{
|
||||
// Rect is empty when no countries are loaded, return max global rect.
|
||||
return mercator::Bounds::FullRect();
|
||||
}
|
||||
return m_rect;
|
||||
}
|
||||
|
||||
void FeaturesFetcher::OnMapDeregistered(platform::LocalCountryFile const & localFile)
|
||||
{
|
||||
if (m_onMapDeregistered)
|
||||
m_onMapDeregistered(localFile);
|
||||
}
|
||||
76
libs/map/features_fetcher.hpp
Normal file
76
libs/map/features_fetcher.hpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "editor/editable_data_source.hpp"
|
||||
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class FeaturesFetcher : public MwmSet::Observer
|
||||
{
|
||||
public:
|
||||
using Reader = ModelReaderPtr;
|
||||
|
||||
using MapDeregisteredCallback = std::function<void(platform::LocalCountryFile const &)>;
|
||||
|
||||
FeaturesFetcher();
|
||||
|
||||
virtual ~FeaturesFetcher();
|
||||
|
||||
void InitClassificator();
|
||||
|
||||
void SetOnMapDeregisteredCallback(MapDeregisteredCallback const & callback) { m_onMapDeregistered = callback; }
|
||||
|
||||
// Registers a new map.
|
||||
std::pair<MwmSet::MwmId, MwmSet::RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
||||
|
||||
// Deregisters a map denoted by file from internal records.
|
||||
bool DeregisterMap(platform::CountryFile const & countryFile);
|
||||
|
||||
void Clear();
|
||||
|
||||
void ClearCaches();
|
||||
|
||||
bool IsLoaded(std::string_view countryFileName) const
|
||||
{
|
||||
return m_dataSource.IsLoaded(platform::CountryFile(std::string(countryFileName)));
|
||||
}
|
||||
|
||||
void ForEachFeature(m2::RectD const & rect, std::function<void(FeatureType &)> const & fn, int scale) const
|
||||
{
|
||||
m_dataSource.ForEachInRect(fn, rect, scale);
|
||||
}
|
||||
|
||||
void ForEachFeatureID(m2::RectD const & rect, std::function<void(FeatureID const &)> const & fn, int scale) const
|
||||
{
|
||||
// Pass lightweight LowLevelsOnly mode, because rect is equal with pow 2 tiles.
|
||||
m_dataSource.ForEachFeatureIDInRect(fn, rect, scale, covering::LowLevelsOnly);
|
||||
}
|
||||
|
||||
template <class ToDo>
|
||||
void ReadFeatures(ToDo & toDo, std::vector<FeatureID> const & features) const
|
||||
{
|
||||
m_dataSource.ReadFeatures(toDo, features);
|
||||
}
|
||||
|
||||
DataSource const & GetDataSource() const { return m_dataSource; }
|
||||
DataSource & GetDataSource() { return m_dataSource; }
|
||||
m2::RectD GetWorldRect() const;
|
||||
|
||||
// MwmSet::Observer overrides:
|
||||
void OnMapDeregistered(platform::LocalCountryFile const & localFile) override;
|
||||
|
||||
private:
|
||||
m2::RectD m_rect;
|
||||
|
||||
EditableDataSource m_dataSource;
|
||||
|
||||
MapDeregisteredCallback m_onMapDeregistered;
|
||||
};
|
||||
3294
libs/map/framework.cpp
Normal file
3294
libs/map/framework.cpp
Normal file
File diff suppressed because it is too large
Load diff
772
libs/map/framework.hpp
Normal file
772
libs/map/framework.hpp
Normal file
|
|
@ -0,0 +1,772 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/api_mark_point.hpp"
|
||||
#include "map/bookmark.hpp"
|
||||
#include "map/bookmark_manager.hpp"
|
||||
#include "map/features_fetcher.hpp"
|
||||
#include "map/isolines_manager.hpp"
|
||||
#include "map/mwm_url.hpp"
|
||||
#include "map/place_page_info.hpp"
|
||||
#include "map/position_provider.hpp"
|
||||
#include "map/power_management/power_management_schemas.hpp"
|
||||
#include "map/power_management/power_manager.hpp"
|
||||
#include "map/routing_manager.hpp"
|
||||
#include "map/routing_mark.hpp"
|
||||
#include "map/search_api.hpp"
|
||||
#include "map/search_mark.hpp"
|
||||
#include "map/track.hpp"
|
||||
#include "map/track_statistics.hpp"
|
||||
#include "map/traffic_manager.hpp"
|
||||
#include "map/transit/transit_reader.hpp"
|
||||
|
||||
#include "drape_frontend/drape_api.hpp"
|
||||
#include "drape_frontend/drape_engine.hpp"
|
||||
#include "drape_frontend/gui/skin.hpp"
|
||||
#include "drape_frontend/user_event_stream.hpp"
|
||||
|
||||
#include "drape/drape_global.hpp"
|
||||
#include "drape/graphics_context_factory.hpp"
|
||||
|
||||
#include "kml/type_utils.hpp"
|
||||
|
||||
#include "editor/new_feature_categories.hpp"
|
||||
#include "editor/osm_editor.hpp"
|
||||
|
||||
#include "indexer/caching_rank_table_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/data_source_helpers.hpp"
|
||||
#include "indexer/map_object.hpp"
|
||||
#include "indexer/map_style.hpp"
|
||||
|
||||
#include "search/displayed_categories.hpp"
|
||||
#include "search/result.hpp"
|
||||
#include "search/reverse_geocoder.hpp"
|
||||
|
||||
#include "storage/downloading_policy.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
|
||||
#include "platform/distance.hpp"
|
||||
#include "platform/location.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "routing/router.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/strings_bundle.hpp"
|
||||
|
||||
#include "std/target_os.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace osm
|
||||
{
|
||||
class EditableMapObject;
|
||||
}
|
||||
|
||||
namespace search
|
||||
{
|
||||
struct EverywhereSearchParams;
|
||||
struct ViewportSearchParams;
|
||||
} // namespace search
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class CountryInfoGetter;
|
||||
struct DownloaderSearchParams;
|
||||
} // namespace storage
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace turns
|
||||
{
|
||||
class Settings;
|
||||
}
|
||||
} // namespace routing
|
||||
|
||||
namespace platform
|
||||
{
|
||||
class NetworkPolicy;
|
||||
}
|
||||
|
||||
namespace descriptions
|
||||
{
|
||||
class Loader;
|
||||
}
|
||||
|
||||
/// Uncomment line to make fixed position settings and
|
||||
/// build version for screenshots.
|
||||
// #define FIXED_LOCATION
|
||||
|
||||
struct FrameworkParams
|
||||
{
|
||||
bool m_enableDiffs = true;
|
||||
size_t m_numSearchAPIThreads = 1;
|
||||
|
||||
FrameworkParams() = default;
|
||||
FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {}
|
||||
};
|
||||
|
||||
class Framework
|
||||
: public PositionProvider
|
||||
, public SearchAPI::Delegate
|
||||
, public RoutingManager::Delegate
|
||||
, private power_management::PowerManager::Subscriber
|
||||
{
|
||||
DISALLOW_COPY(Framework);
|
||||
|
||||
#ifdef FIXED_LOCATION
|
||||
class FixedPosition
|
||||
{
|
||||
std::pair<double, double> m_latlon;
|
||||
double m_dirFromNorth;
|
||||
bool m_fixedLatLon, m_fixedDir;
|
||||
|
||||
public:
|
||||
FixedPosition();
|
||||
|
||||
void GetLat(double & l) const
|
||||
{
|
||||
if (m_fixedLatLon)
|
||||
l = m_latlon.first;
|
||||
}
|
||||
void GetLon(double & l) const
|
||||
{
|
||||
if (m_fixedLatLon)
|
||||
l = m_latlon.second;
|
||||
}
|
||||
|
||||
void GetNorth(double & n) const
|
||||
{
|
||||
if (m_fixedDir)
|
||||
n = m_dirFromNorth;
|
||||
}
|
||||
bool HasNorth() const { return m_fixedDir; }
|
||||
|
||||
} m_fixedPos;
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Must be first member in Framework and must be destroyed first in Framework destructor.
|
||||
std::unique_ptr<Platform::ThreadRunner> m_threadRunner = std::make_unique<Platform::ThreadRunner>();
|
||||
|
||||
protected:
|
||||
using TDrapeFunction = std::function<void(df::DrapeEngine *)>;
|
||||
|
||||
StringsBundle m_stringsBundle;
|
||||
|
||||
FeaturesFetcher m_featuresFetcher;
|
||||
|
||||
// The order matters here: DisplayedCategories may be used only
|
||||
// after classificator is loaded by |m_featuresFetcher|.
|
||||
std::unique_ptr<search::DisplayedCategories> m_displayedCategories;
|
||||
|
||||
// The order matters here: storage::CountryInfoGetter and
|
||||
// m_FeaturesFetcher must be initialized before
|
||||
// search::Engine and, therefore, destroyed after search::Engine.
|
||||
std::unique_ptr<storage::CountryInfoGetter> m_infoGetter;
|
||||
|
||||
std::unique_ptr<SearchAPI> m_searchAPI;
|
||||
|
||||
ScreenBase m_currentModelView;
|
||||
m2::RectD m_visibleViewport;
|
||||
|
||||
using TViewportChangedFn = df::DrapeEngine::ModelViewChangedHandler;
|
||||
TViewportChangedFn m_viewportChangedFn;
|
||||
|
||||
drape_ptr<df::DrapeEngine> m_drapeEngine;
|
||||
|
||||
StorageDownloadingPolicy m_storageDownloadingPolicy;
|
||||
storage::Storage m_storage;
|
||||
bool m_enabledDiffs;
|
||||
|
||||
location::TMyPositionModeChanged m_myPositionListener;
|
||||
|
||||
std::unique_ptr<BookmarkManager> m_bmManager;
|
||||
|
||||
SearchMarks m_searchMarks;
|
||||
|
||||
df::DrapeApi m_drapeApi;
|
||||
|
||||
bool m_isRenderingEnabled;
|
||||
|
||||
// Note. |m_powerManager| should be declared before |m_routingManager|
|
||||
power_management::PowerManager m_powerManager;
|
||||
|
||||
TransitReadManager m_transitManager;
|
||||
IsolinesManager m_isolinesManager;
|
||||
|
||||
// Note. |m_routingManager| should be declared before |m_trafficManager|
|
||||
RoutingManager m_routingManager;
|
||||
|
||||
TrafficManager m_trafficManager;
|
||||
|
||||
/// This function will be called by m_storage when latest local files
|
||||
/// is downloaded.
|
||||
void OnCountryFileDownloaded(storage::CountryId const & countryId, storage::LocalFilePtr const localFile);
|
||||
|
||||
/// This function will be called by m_storage before latest local files
|
||||
/// is deleted.
|
||||
bool OnCountryFileDelete(storage::CountryId const & countryId, storage::LocalFilePtr const localFile);
|
||||
|
||||
/// This function is called by m_featuresFetcher when the map file is deregistered.
|
||||
void OnMapDeregistered(platform::LocalCountryFile const & localFile);
|
||||
|
||||
void ClearAllCaches();
|
||||
|
||||
void OnViewportChanged(ScreenBase const & screen);
|
||||
|
||||
void InitTransliteration();
|
||||
|
||||
public:
|
||||
explicit Framework(FrameworkParams const & params = {}, bool loadMaps = true);
|
||||
virtual ~Framework() override;
|
||||
|
||||
df::DrapeApi & GetDrapeApi() { return m_drapeApi; }
|
||||
|
||||
/// \returns true if there're unsaved changes in map with |countryId| and false otherwise.
|
||||
/// \note It works for group and leaf node.
|
||||
bool HasUnsavedEdits(storage::CountryId const & countryId);
|
||||
|
||||
void LoadMapsSync();
|
||||
void LoadMapsAsync(std::function<void()> && callback);
|
||||
|
||||
/// Registers all local map files in internal indexes.
|
||||
void RegisterAllMaps();
|
||||
|
||||
/// Deregisters all registered map files.
|
||||
void DeregisterAllMaps();
|
||||
|
||||
/// Registers a local map file in internal indexes.
|
||||
std::pair<MwmSet::MwmId, MwmSet::RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
||||
|
||||
/// Shows group or leaf mwm on the map.
|
||||
void ShowNode(storage::CountryId const & countryId);
|
||||
|
||||
/// Checks, whether the country is loaded.
|
||||
bool IsCountryLoadedByName(std::string_view name) const;
|
||||
|
||||
void InvalidateRect(m2::RectD const & rect);
|
||||
|
||||
std::string GetCountryName(m2::PointD const & pt) const;
|
||||
|
||||
enum class DoAfterUpdate
|
||||
{
|
||||
Nothing,
|
||||
AutoupdateMaps,
|
||||
AskForUpdateMaps,
|
||||
Migrate
|
||||
};
|
||||
// DoAfterUpdate ToDoAfterUpdate() const;
|
||||
|
||||
storage::Storage & GetStorage() { return m_storage; }
|
||||
storage::Storage const & GetStorage() const { return m_storage; }
|
||||
search::DisplayedCategories const & GetDisplayedCategories();
|
||||
storage::CountryInfoGetter const & GetCountryInfoGetter() { return *m_infoGetter; }
|
||||
StorageDownloadingPolicy & GetDownloadingPolicy() { return m_storageDownloadingPolicy; }
|
||||
|
||||
DataSource const & GetDataSource() const { return m_featuresFetcher.GetDataSource(); }
|
||||
|
||||
SearchAPI & GetSearchAPI();
|
||||
SearchAPI const & GetSearchAPI() const;
|
||||
|
||||
/// @name Bookmarks, Tracks and other UserMarks
|
||||
/// Scans and loads all kml files with bookmarks in WritableDir.
|
||||
void LoadBookmarks();
|
||||
|
||||
/// @return Created bookmark category id.
|
||||
kml::MarkGroupId AddCategory(std::string const & categoryName);
|
||||
|
||||
kml::MarkGroupId LastEditedBMCategory() { return GetBookmarkManager().LastEditedBMCategory(); }
|
||||
kml::PredefinedColor LastEditedBMColor() const { return GetBookmarkManager().LastEditedBMColor(); }
|
||||
|
||||
void ShowBookmark(kml::MarkId id);
|
||||
void ShowBookmark(Bookmark const * bookmark);
|
||||
void ShowTrack(kml::TrackId trackId);
|
||||
void ShowFeature(FeatureID const & featureId);
|
||||
void ShowBookmarkCategory(kml::MarkGroupId categoryId, bool animation = true);
|
||||
|
||||
void AddBookmarksFile(std::string const & filePath, bool isTemporaryFile);
|
||||
|
||||
BookmarkManager & GetBookmarkManager();
|
||||
BookmarkManager const & GetBookmarkManager() const;
|
||||
|
||||
/// @name Visualize utilities, used in desktop only. Implemented in framework_visualize.cpp
|
||||
/// @{
|
||||
void VisualizeRoadsInRect(m2::RectD const & rect);
|
||||
void VisualizeCityBoundariesInRect(m2::RectD const & rect);
|
||||
void VisualizeCityRoadsInRect(m2::RectD const & rect);
|
||||
void VisualizeCrossMwmTransitionsInRect(m2::RectD const & rect);
|
||||
/// @}
|
||||
|
||||
public:
|
||||
// SearchAPI::Delegate overrides:
|
||||
void RunUITask(std::function<void()> fn) override;
|
||||
using SearchResultsIterT = SearchAPI::Delegate::ResultsIterT;
|
||||
void ShowViewportSearchResults(SearchResultsIterT begin, SearchResultsIterT end, bool clear) override;
|
||||
void ClearViewportSearchResults() override;
|
||||
// PositionProvider, SearchApi::Delegate and TipsApi::Delegate override.
|
||||
std::optional<m2::PointD> GetCurrentPosition() const override;
|
||||
bool ParseSearchQueryCommand(search::SearchParams const & params) override;
|
||||
m2::PointD GetMinDistanceBetweenResults() const override;
|
||||
|
||||
private:
|
||||
void ActivateMapSelection();
|
||||
void InvalidateUserMarks();
|
||||
|
||||
void DeactivateHotelSearchMark();
|
||||
|
||||
public:
|
||||
void DeactivateMapSelection();
|
||||
void DeactivateMapSelectionCircle(bool restoreViewport);
|
||||
void SwitchFullScreen();
|
||||
/// Used to "refresh" UI in some cases (e.g. feature editing).
|
||||
void UpdatePlacePageInfoForCurrentSelection(std::optional<place_page::BuildInfo> const & overrideInfo = {});
|
||||
|
||||
struct PlacePageEvent
|
||||
{
|
||||
/// Called to notify UI that object on a map was selected (UI should show Place Page, for example).
|
||||
using OnOpen = std::function<void()>;
|
||||
/// Called to notify UI that object on a map was deselected (UI should hide Place Page).
|
||||
/// If switchFullScreenMode is true, ui can [optionally] enter or exit full screen mode.
|
||||
using OnClose = std::function<void()>;
|
||||
using OnUpdate = std::function<void()>;
|
||||
using OnSwitchFullScreen = std::function<void()>;
|
||||
};
|
||||
|
||||
void SetPlacePageListeners(PlacePageEvent::OnOpen onOpen, PlacePageEvent::OnClose onClose,
|
||||
PlacePageEvent::OnUpdate onUpdate, PlacePageEvent::OnSwitchFullScreen onSwitchFullScreen);
|
||||
bool HasPlacePageInfo() const { return m_currentPlacePageInfo.has_value(); }
|
||||
place_page::Info const & GetCurrentPlacePageInfo() const;
|
||||
place_page::Info & GetCurrentPlacePageInfo();
|
||||
void BuildAndSetPlacePageInfo(place_page::BuildInfo const & buildInfo) { OnTapEvent(buildInfo); }
|
||||
|
||||
void InvalidateRendering();
|
||||
void EnableDebugRectRendering(bool enabled);
|
||||
|
||||
void EnableChoosePositionMode(bool enable, bool enableBounds, m2::PointD const * optionalPosition);
|
||||
void BlockTapEvents(bool block);
|
||||
|
||||
using TCurrentCountryChanged = std::function<void(storage::CountryId const &)>;
|
||||
storage::CountryId const & GetLastReportedCountry() { return m_lastReportedCountry; }
|
||||
/// Guarantees that listener is called in the main thread context.
|
||||
void SetCurrentCountryChangedListener(TCurrentCountryChanged listener);
|
||||
|
||||
std::vector<std::string> GetRegionsCountryIdByRect(m2::RectD const & rect, bool rough) const;
|
||||
std::vector<MwmSet::MwmId> GetMwmsByRect(m2::RectD const & rect, bool rough) const;
|
||||
|
||||
void ReadFeatures(std::function<void(FeatureType &)> const & reader, std::vector<FeatureID> const & features);
|
||||
|
||||
private:
|
||||
std::optional<place_page::Info> m_currentPlacePageInfo;
|
||||
|
||||
void OnTapEvent(place_page::BuildInfo const & buildInfo);
|
||||
place_page::Info BuildPlacePageInfo(place_page::BuildInfo const & buildInfo);
|
||||
void BuildTrackPlacePage(Track::TrackSelectionInfo const & trackSelectionInfo, place_page::Info & info);
|
||||
Track::TrackSelectionInfo FindTrackInTapPosition(place_page::BuildInfo const & buildInfo) const;
|
||||
UserMark const * FindUserMarkInTapPosition(place_page::BuildInfo const & buildInfo) const;
|
||||
FeatureID FindBuildingAtPoint(m2::PointD const & mercator) const;
|
||||
|
||||
void UpdateMinBuildingsTapZoom();
|
||||
|
||||
int m_minBuildingsTapZoom;
|
||||
|
||||
PlacePageEvent::OnOpen m_onPlacePageOpen;
|
||||
PlacePageEvent::OnClose m_onPlacePageClose;
|
||||
PlacePageEvent::OnUpdate m_onPlacePageUpdate;
|
||||
PlacePageEvent::OnSwitchFullScreen m_onSwitchFullScreen;
|
||||
|
||||
private:
|
||||
std::vector<m2::TriangleD> GetSelectedFeatureTriangles() const;
|
||||
|
||||
public:
|
||||
/// @name GPS location updates routine.
|
||||
void OnLocationError(location::TLocationError error);
|
||||
void OnLocationUpdate(location::GpsInfo const & info);
|
||||
void OnCompassUpdate(location::CompassInfo const & info);
|
||||
void SwitchMyPositionNextMode();
|
||||
/// Should be set before Drape initialization. Guarantees that fn is called in main thread context.
|
||||
void SetMyPositionModeListener(location::TMyPositionModeChanged && fn);
|
||||
|
||||
location::EMyPositionMode GetMyPositionMode() const;
|
||||
|
||||
private:
|
||||
void OnUserPositionChanged(m2::PointD const & position, bool hasPosition);
|
||||
|
||||
public:
|
||||
struct DrapeCreationParams
|
||||
{
|
||||
dp::ApiVersion m_apiVersion = dp::ApiVersion::OpenGLES3;
|
||||
float m_visualScale = 1.0f;
|
||||
int m_surfaceWidth = 0;
|
||||
int m_surfaceHeight = 0;
|
||||
gui::TWidgetsInitInfo m_widgetsInitInfo;
|
||||
|
||||
bool m_isChoosePositionMode = false;
|
||||
df::Hints m_hints;
|
||||
dp::RenderInjectionHandler m_renderInjectionHandler;
|
||||
};
|
||||
|
||||
void CreateDrapeEngine(ref_ptr<dp::GraphicsContextFactory> contextFactory, DrapeCreationParams && params);
|
||||
ref_ptr<df::DrapeEngine> GetDrapeEngine();
|
||||
bool IsDrapeEngineCreated() const { return m_drapeEngine != nullptr; }
|
||||
void DestroyDrapeEngine();
|
||||
/// Called when graphics engine should be temporarily paused and then resumed.
|
||||
void SetRenderingEnabled(ref_ptr<dp::GraphicsContextFactory> contextFactory = nullptr);
|
||||
void SetRenderingDisabled(bool destroySurface);
|
||||
|
||||
void SetGraphicsContextInitializationHandler(df::OnGraphicsContextInitialized && handler);
|
||||
|
||||
void OnRecoverSurface(int width, int height, bool recreateContextDependentResources);
|
||||
void OnDestroySurface();
|
||||
|
||||
void UpdateVisualScale(double vs);
|
||||
|
||||
/// Sets the distance between the bottom edge of the arrow and the bottom edge of the visible viewport
|
||||
/// in follow routing mode or resets it to the default value.
|
||||
void UpdateMyPositionRoutingOffset(bool useDefault, int offsetY);
|
||||
|
||||
private:
|
||||
/// Depends on initialized Drape engine.
|
||||
void SaveViewport();
|
||||
/// Depends on initialized Drape engine.
|
||||
void LoadViewport();
|
||||
|
||||
df::OnGraphicsContextInitialized m_onGraphicsContextInitialized;
|
||||
|
||||
public:
|
||||
void ConnectToGpsTracker();
|
||||
void DisconnectFromGpsTracker();
|
||||
|
||||
using TrackRecordingUpdateHandler = platform::SafeCallback<void(TrackStatistics const & trackStatistics)>;
|
||||
void StartTrackRecording();
|
||||
void SetTrackRecordingUpdateHandler(TrackRecordingUpdateHandler && trackRecordingDidUpdate);
|
||||
void StopTrackRecording();
|
||||
void SaveTrackRecordingWithName(std::string const & name);
|
||||
bool IsTrackRecordingEmpty() const;
|
||||
bool IsTrackRecordingEnabled() const;
|
||||
|
||||
void SaveRoute();
|
||||
/// Returns the elevation profile data of the currently recorded track.
|
||||
/// To get the data on the every track recording state update, this function should be called after receiving the
|
||||
/// callback from the `SetTrackRecordingUpdateHandler`.
|
||||
static ElevationInfo const & GetTrackRecordingElevationInfo();
|
||||
|
||||
void SetMapStyle(MapStyle mapStyle);
|
||||
void MarkMapStyle(MapStyle mapStyle);
|
||||
MapStyle GetMapStyle() const;
|
||||
|
||||
void SetupMeasurementSystem();
|
||||
|
||||
void SetWidgetLayout(gui::TWidgetsLayoutInfo && layout);
|
||||
|
||||
void PrepareToShutdown();
|
||||
|
||||
private:
|
||||
void InitCountryInfoGetter();
|
||||
void InitSearchAPI(size_t numThreads);
|
||||
|
||||
bool m_connectToGpsTrack; // need to connect to tracker when Drape is being constructed
|
||||
|
||||
void OnUpdateCurrentCountry(m2::PointD const & pt, int zoomLevel);
|
||||
|
||||
storage::CountryId m_lastReportedCountry;
|
||||
TCurrentCountryChanged m_currentCountryChanged;
|
||||
|
||||
void OnUpdateGpsTrackPointsCallback(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
|
||||
std::pair<size_t, size_t> const & toRemove,
|
||||
TrackStatistics const & trackStatistics);
|
||||
|
||||
TrackRecordingUpdateHandler m_trackRecordingUpdateHandler;
|
||||
|
||||
CachingRankTableLoader m_popularityLoader;
|
||||
|
||||
std::unique_ptr<descriptions::Loader> m_descriptionsLoader;
|
||||
|
||||
public:
|
||||
// Moves viewport to the search result and taps on it.
|
||||
void SelectSearchResult(search::Result const & res, bool animation);
|
||||
|
||||
// Cancels all searches, stops location follow and then selects
|
||||
// search result.
|
||||
void ShowSearchResult(search::Result const & res, bool animation = true);
|
||||
|
||||
void UpdateViewport(search::Results const & results);
|
||||
|
||||
void FillSearchResultsMarks(bool clear, search::Results const & results);
|
||||
void FillSearchResultsMarks(SearchResultsIterT beg, SearchResultsIterT end, bool clear);
|
||||
|
||||
/// Calculate distance and direction to POI for the given position.
|
||||
/// @param[in] point POI's position;
|
||||
/// @param[in] lat, lon, north Current position and heading from north;
|
||||
/// @param[out] distance Distance to point from (lat, lon);
|
||||
/// @param[out] azimut Azimut to point from (lat, lon);
|
||||
/// @return true If the POI is near the current position (distance < 25 km);
|
||||
bool GetDistanceAndAzimut(m2::PointD const & point, double lat, double lon, double north,
|
||||
platform::Distance & distance, double & azimut);
|
||||
|
||||
/// @name Manipulating with model view
|
||||
m2::PointD PtoG(m2::PointD const & p) const { return m_currentModelView.PtoG(p); }
|
||||
m2::PointD P3dtoG(m2::PointD const & p) const { return m_currentModelView.PtoG(m_currentModelView.P3dtoP(p)); }
|
||||
m2::PointD GtoP(m2::PointD const & p) const { return m_currentModelView.GtoP(p); }
|
||||
m2::PointD GtoP3d(m2::PointD const & p) const { return m_currentModelView.PtoP3d(m_currentModelView.GtoP(p)); }
|
||||
|
||||
/// Show all model by it's world rect.
|
||||
void ShowAll();
|
||||
|
||||
m2::PointD GetVisiblePixelCenter() const;
|
||||
|
||||
m2::PointD const & GetViewportCenter() const;
|
||||
void SetViewportCenter(m2::PointD const & pt, int zoomLevel = -1, bool isAnim = true,
|
||||
bool trackVisibleViewport = false);
|
||||
|
||||
m2::RectD GetCurrentViewport() const;
|
||||
void SetVisibleViewport(m2::RectD const & rect);
|
||||
|
||||
/// - Check minimal visible scale according to downloaded countries.
|
||||
void ShowRect(m2::RectD const & rect, int maxScale = -1, bool animation = true, bool useVisibleViewport = false);
|
||||
void ShowRect(m2::AnyRectD const & rect, bool animation = true, bool useVisibleViewport = false);
|
||||
|
||||
void GetTouchRect(m2::PointD const & center, uint32_t pxRadius, m2::AnyRectD & rect);
|
||||
|
||||
void SetViewportListener(TViewportChangedFn const & fn);
|
||||
|
||||
#if defined(OMIM_OS_DESKTOP)
|
||||
using TGraphicsReadyFn = df::DrapeEngine::GraphicsReadyHandler;
|
||||
void NotifyGraphicsReady(TGraphicsReadyFn const & fn, bool needInvalidate);
|
||||
#endif
|
||||
|
||||
void StopLocationFollow();
|
||||
|
||||
/// Resize event from window.
|
||||
void OnSize(int w, int h);
|
||||
|
||||
enum EScaleMode
|
||||
{
|
||||
SCALE_MAG,
|
||||
SCALE_MAG_LIGHT,
|
||||
SCALE_MIN,
|
||||
SCALE_MIN_LIGHT
|
||||
};
|
||||
|
||||
void Scale(EScaleMode mode, bool isAnim);
|
||||
void Scale(EScaleMode mode, m2::PointD const & pxPoint, bool isAnim);
|
||||
void Scale(double factor, bool isAnim);
|
||||
void Scale(double factor, m2::PointD const & pxPoint, bool isAnim);
|
||||
|
||||
/// Moves the viewport a distance of factorX * viewportWidth and factorY * viewportHeight.
|
||||
/// E.g. factorX == 1.0 moves the map one screen size to the right, factorX == -0.5 moves the map
|
||||
/// half screen size to the left, factorY == -2.0 moves the map two sizes down,
|
||||
/// factorY = 1.5 moves the map one and a half size up.
|
||||
void Move(double factorX, double factorY, bool isAnim);
|
||||
|
||||
void Scroll(double distanceX, double distanceY);
|
||||
|
||||
void Rotate(double azimuth, bool isAnim);
|
||||
|
||||
void TouchEvent(df::TouchEvent const & touch);
|
||||
|
||||
void MakeFrameActive();
|
||||
|
||||
int GetDrawScale() const;
|
||||
|
||||
void RunFirstLaunchAnimation();
|
||||
|
||||
/// Set correct viewport, parse API, show balloon.
|
||||
void ExecuteMapApiRequest() { m_parsedMapApi.ExecuteMapApiRequest(*this); }
|
||||
|
||||
url_scheme::ParsedMapApi::UrlType ParseAndSetApiURL(std::string const & url)
|
||||
{
|
||||
return m_parsedMapApi.SetUrlAndParse(url);
|
||||
}
|
||||
|
||||
struct ParsedRoutingData
|
||||
{
|
||||
ParsedRoutingData(std::vector<url_scheme::RoutePoint> const & points, routing::RouterType type)
|
||||
: m_points(points)
|
||||
, m_type(type)
|
||||
{}
|
||||
std::vector<url_scheme::RoutePoint> m_points;
|
||||
routing::RouterType m_type;
|
||||
};
|
||||
|
||||
ParsedRoutingData GetParsedRoutingData() const;
|
||||
url_scheme::SearchRequest GetParsedSearchRequest() const;
|
||||
std::string GetParsedOAuth2Code() const;
|
||||
std::string const & GetParsedAppName() const;
|
||||
std::string const & GetParsedBackUrl() const;
|
||||
ms::LatLon GetParsedCenterLatLon() const;
|
||||
url_scheme::InAppFeatureHighlightRequest GetInAppFeatureHighlightRequest() const;
|
||||
|
||||
using FeatureMatcher = std::function<bool(FeatureType & ft)>;
|
||||
|
||||
private:
|
||||
/// @returns true if command was handled by editor.
|
||||
bool ParseEditorDebugCommand(search::SearchParams const & params);
|
||||
|
||||
/// @returns true if command was handled by drape.
|
||||
bool ParseDrapeDebugCommand(std::string const & query);
|
||||
|
||||
/// This function can be used for enabling some experimental features for routing.
|
||||
bool ParseRoutingDebugCommand(search::SearchParams const & params);
|
||||
|
||||
bool ParseAllTypesDebugCommand(search::SearchParams const & params);
|
||||
|
||||
void FillFeatureInfo(FeatureID const & fid, place_page::Info & info) const;
|
||||
/// @param customTitle, if not empty, overrides any other calculated name.
|
||||
void FillPointInfo(place_page::Info & info, m2::PointD const & mercator, std::string const & customTitle = {},
|
||||
FeatureMatcher && matcher = nullptr) const;
|
||||
void FillNotMatchedPlaceInfo(place_page::Info & info, m2::PointD const & mercator,
|
||||
std::string const & customTitle = {}) const;
|
||||
void FillPostcodeInfo(std::string const & postcode, m2::PointD const & mercator, place_page::Info & info) const;
|
||||
|
||||
void FillUserMarkInfo(UserMark const * mark, place_page::Info & outInfo);
|
||||
|
||||
void FillInfoFromFeatureType(FeatureType & ft, place_page::Info & info) const;
|
||||
void FillApiMarkInfo(ApiMarkPoint const & api, place_page::Info & info) const;
|
||||
void FillSearchResultInfo(SearchMarkPoint const & smp, place_page::Info & info) const;
|
||||
void FillMyPositionInfo(place_page::Info & info, place_page::BuildInfo const & buildInfo) const;
|
||||
void FillRouteMarkInfo(RouteMarkPoint const & rmp, place_page::Info & info) const;
|
||||
void FillSpeedCameraMarkInfo(SpeedCameraMark const & speedCameraMark, place_page::Info & info) const;
|
||||
void FillTransitMarkInfo(TransitMark const & transitMark, place_page::Info & info) const;
|
||||
void FillRoadTypeMarkInfo(RoadWarningMark const & roadTypeMark, place_page::Info & info) const;
|
||||
void FillPointInfoForBookmark(Bookmark const & bmk, place_page::Info & info) const;
|
||||
void FillBookmarkInfo(Bookmark const & bmk, place_page::Info & info) const;
|
||||
void FillTrackInfo(Track const & track, m2::PointD const & trackPoint, place_page::Info & info) const;
|
||||
void SetPlacePageLocation(place_page::Info & info);
|
||||
void FillDescription(FeatureType & ft, place_page::Info & info) const;
|
||||
|
||||
public:
|
||||
search::ReverseGeocoder::Address GetAddressAtPoint(m2::PointD const & pt) const;
|
||||
|
||||
/// Get "best for the user" feature at given point even if it's invisible on the screen.
|
||||
/// Ignores coastlines and prefers buildings over other area features.
|
||||
/// @returns invalid FeatureID if no feature was found at the given mercator point.
|
||||
FeatureID GetFeatureAtPoint(m2::PointD const & mercator, FeatureMatcher && matcher = nullptr) const;
|
||||
template <typename TFn>
|
||||
void ForEachFeatureAtPoint(TFn && fn, m2::PointD const & mercator) const
|
||||
{
|
||||
indexer::ForEachFeatureAtPoint(m_featuresFetcher.GetDataSource(), fn, mercator, 0.0);
|
||||
}
|
||||
|
||||
osm::MapObject GetMapObjectByID(FeatureID const & fid) const;
|
||||
|
||||
void MemoryWarning();
|
||||
void EnterBackground();
|
||||
void EnterForeground();
|
||||
|
||||
/// Set the localized strings bundle
|
||||
void AddString(std::string const & name, std::string const & value) { m_stringsBundle.SetString(name, value); }
|
||||
|
||||
StringsBundle const & GetStringsBundle();
|
||||
|
||||
/// [in] lat, lon - last known location
|
||||
/// [out] lat, lon - predicted location
|
||||
static void PredictLocation(double & lat, double & lon, double accuracy, double bearing, double speed,
|
||||
double elapsedSeconds);
|
||||
|
||||
public:
|
||||
static std::string CodeGe0url(Bookmark const * bmk, bool addName);
|
||||
static std::string CodeGe0url(double lat, double lon, double zoomLevel, std::string const & name);
|
||||
|
||||
/// @name Api
|
||||
std::string GenerateApiBackUrl(ApiMarkPoint const & point) const;
|
||||
url_scheme::ParsedMapApi const & GetApiDataHolder() const { return m_parsedMapApi; }
|
||||
|
||||
private:
|
||||
url_scheme::ParsedMapApi m_parsedMapApi;
|
||||
|
||||
public:
|
||||
/// @name Data versions
|
||||
// bool IsDataVersionUpdated();
|
||||
// void UpdateSavedDataVersion();
|
||||
int64_t GetCurrentDataVersion() const;
|
||||
|
||||
public:
|
||||
void AllowTransliteration(bool allowTranslit);
|
||||
bool LoadTransliteration();
|
||||
void SaveTransliteration(bool allowTranslit);
|
||||
|
||||
void Allow3dMode(bool allow3d, bool allow3dBuildings);
|
||||
void Save3dMode(bool allow3d, bool allow3dBuildings);
|
||||
void Load3dMode(bool & allow3d, bool & allow3dBuildings);
|
||||
|
||||
private:
|
||||
void ApplyMapLanguageCode(std::string const & langCode);
|
||||
|
||||
public:
|
||||
static std::string GetMapLanguageCode();
|
||||
void SetMapLanguageCode(std::string const & langCode);
|
||||
void ResetMapLanguageCode();
|
||||
|
||||
void SetLargeFontsSize(bool isLargeSize);
|
||||
bool LoadLargeFontsSize();
|
||||
|
||||
bool LoadAutoZoom();
|
||||
void AllowAutoZoom(bool allowAutoZoom);
|
||||
void SaveAutoZoom(bool allowAutoZoom);
|
||||
|
||||
TrafficManager & GetTrafficManager();
|
||||
|
||||
TransitReadManager & GetTransitManager();
|
||||
|
||||
IsolinesManager & GetIsolinesManager();
|
||||
IsolinesManager const & GetIsolinesManager() const;
|
||||
|
||||
bool LoadTrafficEnabled();
|
||||
void SaveTrafficEnabled(bool trafficEnabled);
|
||||
|
||||
bool LoadTrafficSimplifiedColors();
|
||||
void SaveTrafficSimplifiedColors(bool simplified);
|
||||
|
||||
bool LoadTransitSchemeEnabled();
|
||||
void SaveTransitSchemeEnabled(bool enabled);
|
||||
|
||||
bool LoadIsolinesEnabled();
|
||||
void SaveIsolinesEnabled(bool enabled);
|
||||
|
||||
bool LoadOutdoorsEnabled();
|
||||
void SaveOutdoorsEnabled(bool enabled);
|
||||
|
||||
dp::ApiVersion LoadPreferredGraphicsAPI();
|
||||
void SavePreferredGraphicsAPI(dp::ApiVersion apiVersion);
|
||||
|
||||
public:
|
||||
/// Routing Manager
|
||||
RoutingManager & GetRoutingManager() { return m_routingManager; }
|
||||
RoutingManager const & GetRoutingManager() const { return m_routingManager; }
|
||||
|
||||
protected:
|
||||
/// RoutingManager::Delegate
|
||||
void OnRouteFollow(routing::RouterType type) override;
|
||||
void RegisterCountryFilesOnRoute(std::shared_ptr<routing::NumMwmIds> ptr) const override;
|
||||
|
||||
public:
|
||||
/// @returns false in case when coordinate is in the ocean or mwm is not downloaded.
|
||||
bool CanEditMapForPosition(m2::PointD const & position) const;
|
||||
|
||||
bool CreateMapObject(m2::PointD const & mercator, uint32_t const featureType, osm::EditableMapObject & emo) const;
|
||||
/// @returns false if feature is invalid or can't be edited.
|
||||
bool GetEditableMapObject(FeatureID const & fid, osm::EditableMapObject & emo) const;
|
||||
osm::Editor::SaveResult SaveEditedMapObject(osm::EditableMapObject emo);
|
||||
void DeleteFeature(FeatureID const & fid);
|
||||
void MarkPlaceAsDisused(osm::EditableMapObject emo);
|
||||
osm::NewFeatureCategories GetEditorCategories() const;
|
||||
bool RollBackChanges(FeatureID const & fid);
|
||||
void CreateNote(osm::MapObject const & mapObject, osm::Editor::NoteProblemType const type, std::string const & note);
|
||||
|
||||
private:
|
||||
settings::UsageStats m_usageStats;
|
||||
|
||||
public:
|
||||
power_management::PowerManager & GetPowerManager() { return m_powerManager; }
|
||||
|
||||
// PowerManager::Subscriber override.
|
||||
void OnPowerFacilityChanged(power_management::Facility const facility, bool enabled) override;
|
||||
void OnPowerSchemeChanged(power_management::Scheme const actualScheme) override;
|
||||
};
|
||||
185
libs/map/framework_visualize.cpp
Normal file
185
libs/map/framework_visualize.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "framework.hpp"
|
||||
|
||||
#include "routing/city_roads.hpp"
|
||||
#include "routing/cross_mwm_index_graph.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
dp::Color const colorList[] = {{255, 0, 0, 255}, {0, 255, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255},
|
||||
{0, 255, 255, 255}, {255, 0, 255, 255}, {100, 0, 0, 255}, {0, 100, 0, 255},
|
||||
{0, 0, 100, 255}, {100, 100, 0, 255}, {0, 100, 100, 255}, {100, 0, 100, 255}};
|
||||
|
||||
dp::Color const cityBoundaryBBColor{255, 0, 0, 255};
|
||||
dp::Color const cityBoundaryCBColor{0, 255, 0, 255};
|
||||
dp::Color const cityBoundaryDBColor{0, 0, 255, 255};
|
||||
|
||||
template <class Box>
|
||||
void DrawLine(Box const & box, dp::Color const & color, df::DrapeApi & drapeApi, std::string const & id)
|
||||
{
|
||||
auto points = box.Points();
|
||||
CHECK(!points.empty(), ());
|
||||
points.push_back(points.front());
|
||||
|
||||
points.erase(unique(points.begin(), points.end(),
|
||||
[](m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
m2::PointD const delta = p2 - p1;
|
||||
return delta.IsAlmostZero();
|
||||
}),
|
||||
points.end());
|
||||
|
||||
if (points.size() <= 1)
|
||||
return;
|
||||
|
||||
drapeApi.AddLine(id, df::DrapeApiLineData(points, color).Width(3.0f).ShowPoints(true).ShowId());
|
||||
}
|
||||
|
||||
void VisualizeFeatureInRect(m2::RectD const & rect, FeatureType & ft, df::DrapeApi & drapeApi)
|
||||
{
|
||||
bool allPointsOutside = true;
|
||||
std::vector<m2::PointD> points;
|
||||
ft.ForEachPoint([&](m2::PointD const & pt)
|
||||
{
|
||||
if (rect.IsPointInside(pt))
|
||||
allPointsOutside = false;
|
||||
points.push_back(pt);
|
||||
}, scales::GetUpperScale());
|
||||
|
||||
if (!allPointsOutside)
|
||||
{
|
||||
static uint64_t counter = 0;
|
||||
auto const color = colorList[counter++ % std::size(colorList)];
|
||||
|
||||
// Note. The first param at DrapeApi::AddLine() should be unique, so pass unique ft.GetID().
|
||||
// Other way last added line replaces the previous added line with the same name.
|
||||
drapeApi.AddLine(DebugPrint(ft.GetID()), df::DrapeApiLineData(points, color).Width(3.0f).ShowPoints(true).ShowId());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Framework::VisualizeRoadsInRect(m2::RectD const & rect)
|
||||
{
|
||||
m_featuresFetcher.ForEachFeature(rect, [this, &rect](FeatureType & ft)
|
||||
{
|
||||
if (routing::IsRoad(feature::TypesHolder(ft)))
|
||||
VisualizeFeatureInRect(rect, ft, m_drapeApi);
|
||||
}, scales::GetUpperScale());
|
||||
}
|
||||
|
||||
void Framework::VisualizeCityBoundariesInRect(m2::RectD const & rect)
|
||||
{
|
||||
DataSource const & dataSource = GetDataSource();
|
||||
search::CitiesBoundariesTable table(dataSource);
|
||||
table.Load();
|
||||
|
||||
std::vector<uint32_t> featureIds;
|
||||
GetCityBoundariesInRectForTesting(table, rect, featureIds);
|
||||
|
||||
FeaturesLoaderGuard loader(dataSource, dataSource.GetMwmIdByCountryFile(platform::CountryFile("World")));
|
||||
for (auto const fid : featureIds)
|
||||
{
|
||||
search::CitiesBoundariesTable::Boundaries boundaries;
|
||||
if (!table.Get(fid, boundaries))
|
||||
continue;
|
||||
|
||||
std::string id = "fid:" + strings::to_string(fid);
|
||||
auto ft = loader.GetFeatureByIndex(fid);
|
||||
if (ft)
|
||||
{
|
||||
auto name = ft->GetName(StringUtf8Multilang::kEnglishCode);
|
||||
if (name.empty())
|
||||
name = ft->GetName(StringUtf8Multilang::kDefaultCode);
|
||||
id.append(", name:").append(name);
|
||||
}
|
||||
|
||||
boundaries.ForEachBoundary([&id, this](indexer::CityBoundary const & cityBoundary, size_t i)
|
||||
{
|
||||
std::string idWithIndex = id;
|
||||
if (i > 0)
|
||||
idWithIndex = id + ", i:" + strings::to_string(i);
|
||||
|
||||
DrawLine(cityBoundary.m_bbox, cityBoundaryBBColor, m_drapeApi, idWithIndex + ", bb");
|
||||
DrawLine(cityBoundary.m_cbox, cityBoundaryCBColor, m_drapeApi, idWithIndex + ", cb");
|
||||
DrawLine(cityBoundary.m_dbox, cityBoundaryDBColor, m_drapeApi, idWithIndex + ", db");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Framework::VisualizeCityRoadsInRect(m2::RectD const & rect)
|
||||
{
|
||||
std::map<MwmSet::MwmId, std::unique_ptr<routing::CityRoads>> cityRoads;
|
||||
GetDataSource().ForEachInRect([&](FeatureType & ft)
|
||||
{
|
||||
if (ft.GetGeomType() != feature::GeomType::Line)
|
||||
return;
|
||||
|
||||
auto const & mwmId = ft.GetID().m_mwmId;
|
||||
auto const it = cityRoads.find(mwmId);
|
||||
if (it == cityRoads.cend())
|
||||
{
|
||||
MwmSet::MwmHandle handle = m_featuresFetcher.GetDataSource().GetMwmHandleById(mwmId);
|
||||
CHECK(handle.IsAlive(), ());
|
||||
|
||||
cityRoads[mwmId] = routing::LoadCityRoads(handle);
|
||||
}
|
||||
|
||||
if (cityRoads[mwmId]->IsCityRoad(ft.GetID().m_index))
|
||||
VisualizeFeatureInRect(rect, ft, m_drapeApi);
|
||||
}, rect, scales::GetUpperScale());
|
||||
}
|
||||
|
||||
void Framework::VisualizeCrossMwmTransitionsInRect(m2::RectD const & rect)
|
||||
{
|
||||
using CrossMwmID = base::GeoObjectId;
|
||||
using ConnectorT = routing::CrossMwmConnector<CrossMwmID>;
|
||||
std::map<MwmSet::MwmId, ConnectorT> connectors;
|
||||
std::map<MwmSet::MwmId, dp::Color> colors;
|
||||
|
||||
GetDataSource().ForEachInRect([&](FeatureType & ft)
|
||||
{
|
||||
if (ft.GetGeomType() != feature::GeomType::Line)
|
||||
return;
|
||||
|
||||
auto const & mwmId = ft.GetID().m_mwmId;
|
||||
auto res = connectors.try_emplace(mwmId, ConnectorT());
|
||||
ConnectorT & connector = res.first->second;
|
||||
if (res.second)
|
||||
{
|
||||
MwmSet::MwmHandle handle = m_featuresFetcher.GetDataSource().GetMwmHandleById(mwmId);
|
||||
CHECK(handle.IsAlive(), ());
|
||||
|
||||
auto reader = routing::connector::GetReader<CrossMwmID>(handle.GetValue()->m_cont);
|
||||
routing::CrossMwmConnectorBuilder<CrossMwmID> builder(connector);
|
||||
builder.DeserializeTransitions(routing::VehicleType::Car, reader);
|
||||
|
||||
static uint32_t counter = 0;
|
||||
colors.emplace(mwmId, colorList[counter++ % std::size(colorList)]);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> transitSegments;
|
||||
connector.ForEachTransitSegmentId(ft.GetID().m_index, [&transitSegments](uint32_t seg)
|
||||
{
|
||||
transitSegments.push_back(seg);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!transitSegments.empty())
|
||||
{
|
||||
auto const color = colors.find(mwmId)->second;
|
||||
|
||||
int segIdx = -1;
|
||||
m2::PointD prevPt;
|
||||
ft.ForEachPoint([&](m2::PointD const & pt)
|
||||
{
|
||||
if (base::IsExist(transitSegments, segIdx))
|
||||
{
|
||||
GetDrapeApi().AddLine(DebugPrint(ft.GetID()) + ", " + std::to_string(segIdx),
|
||||
df::DrapeApiLineData({prevPt, pt}, color).Width(10.0f));
|
||||
}
|
||||
|
||||
prevPt = pt;
|
||||
++segIdx;
|
||||
}, scales::GetUpperScale());
|
||||
}
|
||||
}, rect, scales::GetUpperScale());
|
||||
}
|
||||
335
libs/map/gps_track.cpp
Normal file
335
libs/map/gps_track.cpp
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
#include "map/gps_track.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace gps_track
|
||||
{
|
||||
|
||||
inline pair<size_t, size_t> UnionRanges(pair<size_t, size_t> const & a, pair<size_t, size_t> const & b)
|
||||
{
|
||||
if (a.first == GpsTrack::kInvalidId)
|
||||
{
|
||||
ASSERT_EQUAL(a.second, GpsTrack::kInvalidId, ());
|
||||
return b;
|
||||
}
|
||||
if (b.first == GpsTrack::kInvalidId)
|
||||
{
|
||||
ASSERT_EQUAL(b.second, GpsTrack::kInvalidId, ());
|
||||
return a;
|
||||
}
|
||||
ASSERT_LESS_OR_EQUAL(a.first, a.second, ());
|
||||
ASSERT_LESS_OR_EQUAL(b.first, b.second, ());
|
||||
return make_pair(min(a.first, b.first), max(a.second, b.second));
|
||||
}
|
||||
|
||||
size_t constexpr kItemBlockSize = 1000;
|
||||
|
||||
} // namespace gps_track
|
||||
|
||||
size_t const GpsTrack::kInvalidId = GpsTrackCollection::kInvalidId;
|
||||
|
||||
GpsTrack::GpsTrack(string const & filePath, unique_ptr<IGpsTrackFilter> && filter)
|
||||
: m_filePath(filePath)
|
||||
, m_needClear(false)
|
||||
, m_needSendSnapshop(false)
|
||||
, m_filter(std::move(filter))
|
||||
, m_threadExit(false)
|
||||
, m_threadWakeup(false)
|
||||
{
|
||||
if (!m_filter)
|
||||
m_filter = make_unique<GpsTrackNullFilter>();
|
||||
|
||||
ASSERT(!m_filePath.empty(), ());
|
||||
}
|
||||
|
||||
GpsTrack::~GpsTrack()
|
||||
{
|
||||
if (m_thread.joinable())
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> lg(m_threadGuard);
|
||||
m_threadExit = true;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void GpsTrack::AddPoint(location::GpsInfo const & point)
|
||||
{
|
||||
{
|
||||
std::lock_guard lg(m_dataGuard);
|
||||
m_points.emplace_back(point);
|
||||
}
|
||||
ScheduleTask();
|
||||
}
|
||||
|
||||
void GpsTrack::AddPoints(vector<location::GpsInfo> const & points)
|
||||
{
|
||||
{
|
||||
std::lock_guard lg(m_dataGuard);
|
||||
m_points.insert(m_points.end(), points.begin(), points.end());
|
||||
}
|
||||
ScheduleTask();
|
||||
}
|
||||
|
||||
TrackStatistics GpsTrack::GetTrackStatistics() const
|
||||
{
|
||||
return m_collection ? m_collection->GetTrackStatistics() : TrackStatistics();
|
||||
}
|
||||
|
||||
ElevationInfo const & GpsTrack::GetElevationInfo() const
|
||||
{
|
||||
return m_collection->UpdateAndGetElevationInfo();
|
||||
}
|
||||
|
||||
void GpsTrack::Clear()
|
||||
{
|
||||
{
|
||||
std::lock_guard lg(m_dataGuard);
|
||||
m_points.clear();
|
||||
m_needClear = true;
|
||||
}
|
||||
ScheduleTask();
|
||||
}
|
||||
|
||||
size_t GpsTrack::GetSize() const
|
||||
{
|
||||
CHECK(m_collection != nullptr, ());
|
||||
return m_collection->GetSize();
|
||||
}
|
||||
|
||||
bool GpsTrack::IsEmpty() const
|
||||
{
|
||||
if (!m_collection)
|
||||
return true;
|
||||
return m_collection->IsEmpty();
|
||||
}
|
||||
|
||||
void GpsTrack::SetCallback(TGpsTrackDiffCallback callback)
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> lg(m_callbackGuard);
|
||||
m_callback = callback;
|
||||
m_needSendSnapshop = true;
|
||||
}
|
||||
ScheduleTask();
|
||||
}
|
||||
|
||||
void GpsTrack::ScheduleTask()
|
||||
{
|
||||
lock_guard<mutex> lg(m_threadGuard);
|
||||
|
||||
if (m_thread.get_id() == std::thread::id())
|
||||
{
|
||||
m_thread = threads::SimpleThread([this]()
|
||||
{
|
||||
unique_lock<mutex> ul(m_threadGuard);
|
||||
while (true)
|
||||
{
|
||||
m_cv.wait(ul, [this]() -> bool { return m_threadExit || m_threadWakeup; });
|
||||
if (m_threadExit)
|
||||
break;
|
||||
m_threadWakeup = false;
|
||||
ProcessPoints();
|
||||
}
|
||||
|
||||
m_storage.reset();
|
||||
});
|
||||
}
|
||||
|
||||
m_threadWakeup = true;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
void GpsTrack::InitStorageIfNeed()
|
||||
{
|
||||
if (m_storage)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
m_storage = make_unique<GpsTrackStorage>(m_filePath);
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Track storage creation error:", e.Msg()));
|
||||
}
|
||||
}
|
||||
|
||||
void GpsTrack::InitCollection()
|
||||
{
|
||||
ASSERT(m_collection == nullptr, ());
|
||||
|
||||
m_collection = make_unique<GpsTrackCollection>();
|
||||
|
||||
InitStorageIfNeed();
|
||||
if (!m_storage)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// All origin points have been written in the storage,
|
||||
// and filtered points are inserted in the runtime collection.
|
||||
|
||||
vector<location::GpsInfo> originPoints;
|
||||
originPoints.reserve(gps_track::kItemBlockSize);
|
||||
|
||||
m_storage->ForEach([this, &originPoints](location::GpsInfo const & originPoint) -> bool
|
||||
{
|
||||
originPoints.emplace_back(originPoint);
|
||||
if (originPoints.size() == originPoints.capacity())
|
||||
{
|
||||
vector<location::GpsInfo> points;
|
||||
m_filter->Process(originPoints, points);
|
||||
|
||||
m_collection->Add(points);
|
||||
|
||||
originPoints.clear();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!originPoints.empty())
|
||||
{
|
||||
vector<location::GpsInfo> points;
|
||||
m_filter->Process(originPoints, points);
|
||||
|
||||
m_collection->Add(points);
|
||||
}
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Track storage exception:", e.Msg()));
|
||||
m_collection->Clear();
|
||||
m_storage.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void GpsTrack::ProcessPoints()
|
||||
{
|
||||
vector<location::GpsInfo> originPoints;
|
||||
bool needClear;
|
||||
// Steal data for processing
|
||||
{
|
||||
std::lock_guard lg(m_dataGuard);
|
||||
originPoints.swap(m_points);
|
||||
needClear = m_needClear;
|
||||
m_needClear = false;
|
||||
}
|
||||
|
||||
// Create collection only if callback appears
|
||||
if (!m_collection && HasCallback())
|
||||
InitCollection();
|
||||
|
||||
// All origin points are written in the storage,
|
||||
// and filtered points are inserted in the runtime collection.
|
||||
|
||||
UpdateStorage(needClear, originPoints);
|
||||
|
||||
if (!m_collection)
|
||||
return;
|
||||
|
||||
vector<location::GpsInfo> points;
|
||||
m_filter->Process(originPoints, points);
|
||||
|
||||
pair<size_t, size_t> addedIds;
|
||||
pair<size_t, size_t> evictedIds;
|
||||
UpdateCollection(needClear, points, addedIds, evictedIds);
|
||||
|
||||
NotifyCallback(addedIds, evictedIds);
|
||||
}
|
||||
|
||||
bool GpsTrack::HasCallback()
|
||||
{
|
||||
lock_guard<mutex> lg(m_callbackGuard);
|
||||
return m_callback != nullptr;
|
||||
}
|
||||
|
||||
void GpsTrack::UpdateStorage(bool needClear, vector<location::GpsInfo> const & points)
|
||||
{
|
||||
InitStorageIfNeed();
|
||||
if (!m_storage)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (needClear)
|
||||
m_storage->Clear();
|
||||
|
||||
m_storage->Append(points);
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Track storage exception:", e.Msg()));
|
||||
m_storage.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void GpsTrack::UpdateCollection(bool needClear, vector<location::GpsInfo> const & points,
|
||||
pair<size_t, size_t> & addedIds, pair<size_t, size_t> & evictedIds)
|
||||
{
|
||||
// Apply Clear and Add points
|
||||
// Clear points from collection, if need.
|
||||
evictedIds = needClear ? m_collection->Clear(false /* resetIds */) : make_pair(kInvalidId, kInvalidId);
|
||||
;
|
||||
|
||||
// Add points to the collection, if need
|
||||
if (!points.empty())
|
||||
addedIds = m_collection->Add(points);
|
||||
else
|
||||
addedIds = make_pair(kInvalidId, kInvalidId);
|
||||
}
|
||||
|
||||
void GpsTrack::NotifyCallback(pair<size_t, size_t> const & addedIds, pair<size_t, size_t> const & evictedIds)
|
||||
{
|
||||
lock_guard<mutex> lg(m_callbackGuard);
|
||||
|
||||
if (!m_callback)
|
||||
return;
|
||||
|
||||
if (m_needSendSnapshop)
|
||||
{
|
||||
m_needSendSnapshop = false;
|
||||
|
||||
vector<pair<size_t, location::GpsInfo>> toAdd;
|
||||
toAdd.reserve(m_collection->GetSize());
|
||||
m_collection->ForEach([&toAdd](location::GpsInfo const & point, size_t id) -> bool
|
||||
{
|
||||
toAdd.emplace_back(id, point);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (toAdd.empty())
|
||||
return; // nothing to send
|
||||
|
||||
m_callback(std::move(toAdd), make_pair(kInvalidId, kInvalidId), m_collection->GetTrackStatistics());
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<pair<size_t, location::GpsInfo>> toAdd;
|
||||
if (addedIds.first != kInvalidId)
|
||||
{
|
||||
size_t const addedCount = addedIds.second - addedIds.first + 1;
|
||||
ASSERT_GREATER_OR_EQUAL(m_collection->GetSize(), addedCount, ());
|
||||
toAdd.reserve(addedCount);
|
||||
m_collection->ForEach([&toAdd](location::GpsInfo const & point, size_t id) -> bool
|
||||
{
|
||||
toAdd.emplace_back(id, point);
|
||||
return true;
|
||||
}, m_collection->GetSize() - addedCount);
|
||||
ASSERT_EQUAL(toAdd.size(), addedCount, ());
|
||||
}
|
||||
|
||||
if (toAdd.empty() && evictedIds.first == kInvalidId)
|
||||
return; // nothing to send
|
||||
|
||||
m_callback(std::move(toAdd), evictedIds, m_collection->GetTrackStatistics());
|
||||
}
|
||||
}
|
||||
101
libs/map/gps_track.hpp
Normal file
101
libs/map/gps_track.hpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/gps_track_collection.hpp"
|
||||
#include "map/gps_track_filter.hpp"
|
||||
#include "map/gps_track_storage.hpp"
|
||||
|
||||
#include "base/thread.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class GpsTrack final
|
||||
{
|
||||
public:
|
||||
static size_t const kInvalidId; // = numeric_limits<size_t>::max();
|
||||
|
||||
/// @param filePath - path to the file on disk to persist track
|
||||
/// @param filter - filter object used for filtering points, GpsTrackNullFilter is created by default
|
||||
GpsTrack(std::string const & filePath,
|
||||
std::unique_ptr<IGpsTrackFilter> && filter = std::unique_ptr<IGpsTrackFilter>());
|
||||
~GpsTrack();
|
||||
|
||||
/// Adds point or collection of points to gps tracking
|
||||
/// @note Callback is called with 'toAdd' and 'toRemove' points, if some points were added or removed.
|
||||
/// @note Only points with good timestamp will be added, other will be skipped.
|
||||
void AddPoint(location::GpsInfo const & point);
|
||||
void AddPoints(std::vector<location::GpsInfo> const & points);
|
||||
|
||||
/// Returns track statistics
|
||||
TrackStatistics GetTrackStatistics() const;
|
||||
ElevationInfo const & GetElevationInfo() const;
|
||||
|
||||
/// Clears any previous tracking info
|
||||
/// @note Callback is called with 'toRemove' points, if some points were removed.
|
||||
void Clear();
|
||||
|
||||
bool IsEmpty() const;
|
||||
size_t GetSize() const;
|
||||
|
||||
/// Notification callback about a change of the gps track.
|
||||
/// @param toAdd - collection of points and ids to add.
|
||||
/// @param toRemove - range of point indices to remove, or pair(kInvalidId,kInvalidId) if nothing to remove
|
||||
/// @note Calling of a GpsTrack.SetCallback function from the callback causes deadlock.
|
||||
using TGpsTrackDiffCallback =
|
||||
std::function<void(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
|
||||
std::pair<size_t, size_t> const & toRemove, TrackStatistics const & trackStatistics)>;
|
||||
|
||||
/// Sets callback on change of gps track.
|
||||
/// @param callback - callback callable object
|
||||
/// @note Only one callback is supported at time.
|
||||
/// @note When sink is attached, it receives all points in 'toAdd' at first time,
|
||||
/// next time callbacks it receives only modifications. It simplifies getter/callback model.
|
||||
void SetCallback(TGpsTrackDiffCallback callback);
|
||||
|
||||
template <typename F>
|
||||
void ForEachPoint(F && f) const
|
||||
{
|
||||
m_collection->ForEach(std::move(f));
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_MOVE(GpsTrack);
|
||||
|
||||
void ScheduleTask();
|
||||
void ProcessPoints(); // called on the worker thread
|
||||
bool HasCallback();
|
||||
void InitStorageIfNeed();
|
||||
void InitCollection();
|
||||
void UpdateStorage(bool needClear, std::vector<location::GpsInfo> const & points);
|
||||
void UpdateCollection(bool needClear, std::vector<location::GpsInfo> const & points,
|
||||
std::pair<size_t, size_t> & addedIds, std::pair<size_t, size_t> & evictedIds);
|
||||
void NotifyCallback(std::pair<size_t, size_t> const & addedIds, std::pair<size_t, size_t> const & evictedIds);
|
||||
|
||||
std::string const m_filePath;
|
||||
|
||||
mutable std::mutex m_dataGuard; // protects data for stealing
|
||||
std::vector<location::GpsInfo> m_points; // accumulated points for adding
|
||||
bool m_needClear; // need clear file
|
||||
|
||||
std::mutex m_callbackGuard;
|
||||
// Callback is protected by m_callbackGuard. It ensures that SetCallback and call callback
|
||||
// will not be interleaved and after SetCallback(null) callback is never called. The negative side
|
||||
// is that GpsTrack.SetCallback must be never called from the callback.
|
||||
TGpsTrackDiffCallback m_callback;
|
||||
bool m_needSendSnapshop; // need send initial snapshot
|
||||
|
||||
std::unique_ptr<GpsTrackStorage> m_storage; // used in the worker thread
|
||||
std::unique_ptr<GpsTrackCollection> m_collection; // used in the worker thread
|
||||
std::unique_ptr<IGpsTrackFilter> m_filter; // used in the worker thread
|
||||
|
||||
std::mutex m_threadGuard;
|
||||
threads::SimpleThread m_thread;
|
||||
bool m_threadExit; // need exit thread
|
||||
bool m_threadWakeup; // need wakeup thread
|
||||
std::condition_variable m_cv;
|
||||
};
|
||||
117
libs/map/gps_track_collection.cpp
Normal file
117
libs/map/gps_track_collection.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#include "map/gps_track_collection.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Simple rollbacker which restores deque state
|
||||
template <typename T>
|
||||
class Rollbacker
|
||||
{
|
||||
public:
|
||||
Rollbacker(std::deque<T> & cont) : m_cont(&cont), m_size(cont.size()) {}
|
||||
~Rollbacker()
|
||||
{
|
||||
if (m_cont && m_cont->size() > m_size)
|
||||
m_cont->erase(m_cont->begin() + m_size, m_cont->end());
|
||||
}
|
||||
void Reset() { m_cont = nullptr; }
|
||||
|
||||
private:
|
||||
std::deque<T> * m_cont;
|
||||
size_t const m_size;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t const GpsTrackCollection::kInvalidId = std::numeric_limits<size_t>::max();
|
||||
|
||||
GpsTrackCollection::GpsTrackCollection() : m_lastId(0), m_elevationInfoDirty(true) {}
|
||||
|
||||
std::pair<size_t, size_t> GpsTrackCollection::Add(std::vector<TItem> const & items)
|
||||
{
|
||||
size_t startId = m_lastId;
|
||||
size_t added = 0;
|
||||
|
||||
// Rollbacker ensure strong guarantee if exception happens while adding items
|
||||
Rollbacker<TItem> rollbacker(m_items);
|
||||
|
||||
for (auto const & item : items)
|
||||
{
|
||||
if (!m_items.empty() && m_items.back().m_timestamp > item.m_timestamp)
|
||||
continue;
|
||||
|
||||
m_statistics.AddGpsInfoPoint(item);
|
||||
|
||||
m_items.emplace_back(item);
|
||||
++added;
|
||||
}
|
||||
|
||||
m_elevationInfoDirty = true;
|
||||
|
||||
rollbacker.Reset();
|
||||
|
||||
if (0 == added)
|
||||
{
|
||||
// Invalid timestamp order
|
||||
return std::make_pair(kInvalidId, kInvalidId); // Nothing was added
|
||||
}
|
||||
|
||||
m_lastId += added;
|
||||
|
||||
return std::make_pair(startId, startId + added - 1);
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> GpsTrackCollection::Clear(bool resetIds)
|
||||
{
|
||||
if (m_items.empty())
|
||||
{
|
||||
if (resetIds)
|
||||
m_lastId = 0;
|
||||
|
||||
return std::make_pair(kInvalidId, kInvalidId);
|
||||
}
|
||||
|
||||
ASSERT_GREATER_OR_EQUAL(m_lastId, m_items.size(), ());
|
||||
|
||||
// Range of evicted items
|
||||
auto const res = std::make_pair(m_lastId - m_items.size(), m_lastId - 1);
|
||||
|
||||
m_items.clear();
|
||||
m_items.shrink_to_fit();
|
||||
m_statistics = {};
|
||||
m_elevationInfo = {};
|
||||
|
||||
if (resetIds)
|
||||
m_lastId = 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t GpsTrackCollection::GetSize() const
|
||||
{
|
||||
return m_items.size();
|
||||
}
|
||||
|
||||
ElevationInfo const & GpsTrackCollection::UpdateAndGetElevationInfo()
|
||||
{
|
||||
if (!m_elevationInfoDirty)
|
||||
return m_elevationInfo;
|
||||
|
||||
auto const elevationInfoSize = m_elevationInfo.GetSize();
|
||||
if (elevationInfoSize < m_items.size())
|
||||
{
|
||||
std::vector<TItem> const missedPoints(m_items.begin() + elevationInfoSize, m_items.end());
|
||||
m_elevationInfo.AddGpsPoints(missedPoints);
|
||||
}
|
||||
m_elevationInfoDirty = false;
|
||||
return m_elevationInfo;
|
||||
}
|
||||
|
||||
bool GpsTrackCollection::IsEmpty() const
|
||||
{
|
||||
return m_items.size() < 2;
|
||||
}
|
||||
74
libs/map/gps_track_collection.hpp
Normal file
74
libs/map/gps_track_collection.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "map/elevation_info.hpp"
|
||||
#include "map/track_statistics.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class GpsTrackCollection final
|
||||
{
|
||||
public:
|
||||
static size_t const kInvalidId; // = numeric_limits<size_t>::max();
|
||||
|
||||
using TItem = location::GpsInfo;
|
||||
|
||||
/// Constructor
|
||||
GpsTrackCollection();
|
||||
|
||||
/// Adds set of new points in the collection.
|
||||
/// @param items - set of items to be added.
|
||||
/// @returns range of identifiers of added items or pair(kInvalidId,kInvalidId) if nothing was added
|
||||
/// @note items which does not conform to timestamp sequence, is not added.
|
||||
std::pair<size_t, size_t> Add(std::vector<TItem> const & items);
|
||||
|
||||
/// Removes all points from the collection.
|
||||
/// @param resetIds - if it is set to true, then new identifiers will start from 0,
|
||||
/// otherwise new identifiers will continue from returned value res.second + 1
|
||||
/// @return range of item identifiers, which were removed or
|
||||
/// pair(kInvalidId,kInvalidId) if nothing was removed
|
||||
std::pair<size_t, size_t> Clear(bool resetIds = true);
|
||||
|
||||
/// Returns true if collection is empty, otherwise returns false.
|
||||
bool IsEmpty() const;
|
||||
|
||||
/// Returns number of items in the collection
|
||||
size_t GetSize() const;
|
||||
|
||||
/// Returns track statistics.
|
||||
TrackStatistics const GetTrackStatistics() const { return m_statistics; }
|
||||
/// Updates the elevation info with the missed points and returns a reference.
|
||||
ElevationInfo const & UpdateAndGetElevationInfo();
|
||||
|
||||
/// Enumerates items in the collection.
|
||||
/// @param f - callable object, which is called with params - item and item id,
|
||||
/// if f returns false, then enumeration is stopped.
|
||||
/// @param pos - position index to start enumeration
|
||||
template <typename F>
|
||||
void ForEach(F && f, size_t pos = 0) const
|
||||
{
|
||||
if (pos >= m_items.size())
|
||||
return;
|
||||
auto i = m_items.cbegin() + pos, iend = m_items.cend();
|
||||
size_t id = m_lastId - m_items.size() + pos;
|
||||
for (; i != iend; ++i, ++id)
|
||||
{
|
||||
TItem const & item = *i;
|
||||
size_t const itemId = id;
|
||||
if (!f(item, itemId))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<TItem> m_items; // asc. sorted by timestamp
|
||||
|
||||
size_t m_lastId;
|
||||
TrackStatistics m_statistics;
|
||||
ElevationInfo m_elevationInfo;
|
||||
bool m_elevationInfoDirty;
|
||||
};
|
||||
178
libs/map/gps_track_filter.cpp
Normal file
178
libs/map/gps_track_filter.cpp
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
#include "map/gps_track_filter.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "platform/settings.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/macros.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
char constexpr kMinHorizontalAccuracyKey[] = "GpsTrackingMinAccuracy";
|
||||
|
||||
// Minimal horizontal accuracy is required to skip 'bad' points.
|
||||
// Use 250 meters to allow points from a pure GPS + GPS through wifi.
|
||||
double constexpr kMinHorizontalAccuracyMeters = 250;
|
||||
|
||||
// Required for points decimation to reduce number of close points.
|
||||
double constexpr kClosePointDistanceMeters = 10;
|
||||
|
||||
// Max acceptable acceleration to filter gps jumps
|
||||
double constexpr kMaxAcceptableAcceleration = 2; // m / sec ^ 2
|
||||
|
||||
double constexpr kCosine45degrees = 0.70710678118;
|
||||
|
||||
m2::PointD GetDirection(location::GpsInfo const & from, location::GpsInfo const & to)
|
||||
{
|
||||
m2::PointD const pt0 = mercator::FromLatLon(from.m_latitude, from.m_longitude);
|
||||
m2::PointD const pt1 = mercator::FromLatLon(to.m_latitude, to.m_longitude);
|
||||
m2::PointD const d = pt1 - pt0;
|
||||
return d.IsAlmostZero() ? m2::PointD::Zero() : d.Normalize();
|
||||
}
|
||||
|
||||
double GetDistance(location::GpsInfo const & from, location::GpsInfo const & to)
|
||||
{
|
||||
return ms::DistanceOnEarth(from.m_latitude, from.m_longitude, to.m_latitude, to.m_longitude);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void GpsTrackNullFilter::Process(std::vector<location::GpsInfo> const & inPoints,
|
||||
std::vector<location::GpsInfo> & outPoints)
|
||||
{
|
||||
outPoints.insert(outPoints.end(), inPoints.begin(), inPoints.end());
|
||||
}
|
||||
|
||||
void GpsTrackFilter::StoreMinHorizontalAccuracy(double value)
|
||||
{
|
||||
settings::Set(kMinHorizontalAccuracyKey, value);
|
||||
}
|
||||
|
||||
GpsTrackFilter::GpsTrackFilter()
|
||||
: m_minAccuracy(kMinHorizontalAccuracyMeters)
|
||||
, m_countLastInfo(0)
|
||||
, m_countAcceptedInfo(0)
|
||||
{
|
||||
settings::TryGet(kMinHorizontalAccuracyKey, m_minAccuracy);
|
||||
}
|
||||
|
||||
void GpsTrackFilter::Process(std::vector<location::GpsInfo> const & inPoints,
|
||||
std::vector<location::GpsInfo> & outPoints)
|
||||
{
|
||||
outPoints.reserve(inPoints.size());
|
||||
|
||||
if (m_minAccuracy == 0)
|
||||
{
|
||||
// Debugging trick to turn off filtering
|
||||
outPoints.insert(outPoints.end(), inPoints.begin(), inPoints.end());
|
||||
return;
|
||||
}
|
||||
|
||||
for (location::GpsInfo const & currInfo : inPoints)
|
||||
{
|
||||
// Do not accept points from the predictor
|
||||
if (currInfo.m_source == location::EPredictor)
|
||||
continue;
|
||||
|
||||
// Skip any function without speed
|
||||
if (!currInfo.HasSpeed())
|
||||
continue;
|
||||
|
||||
if (m_countAcceptedInfo < 2 || currInfo.m_timestamp < GetLastAcceptedInfo().m_timestamp)
|
||||
{
|
||||
AddLastInfo(currInfo);
|
||||
AddLastAcceptedInfo(currInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsGoodPoint(currInfo))
|
||||
{
|
||||
double const predictionDistance = GetDistance(m_lastInfo[1], m_lastInfo[0]); // meters
|
||||
double const realDistance = GetDistance(m_lastInfo[0], currInfo); // meters
|
||||
|
||||
m2::PointD const predictionDirection = GetDirection(m_lastInfo[1], m_lastInfo[0]);
|
||||
m2::PointD const realDirection = GetDirection(m_lastInfo[0], currInfo);
|
||||
|
||||
// Cosine of angle between prediction direction and real direction is
|
||||
double const cosine = m2::DotProduct(predictionDirection, realDirection);
|
||||
|
||||
// Acceptable angle must be from 0 to 45 or from 0 to -45.
|
||||
// Acceptable distance must be not great than 2x than predicted, otherwise it is jump.
|
||||
if (cosine >= kCosine45degrees && realDistance <= std::max(kClosePointDistanceMeters, 2. * predictionDistance))
|
||||
{
|
||||
outPoints.emplace_back(currInfo);
|
||||
AddLastAcceptedInfo(currInfo);
|
||||
}
|
||||
}
|
||||
|
||||
AddLastInfo(currInfo);
|
||||
}
|
||||
}
|
||||
|
||||
bool GpsTrackFilter::IsGoodPoint(location::GpsInfo const & info) const
|
||||
{
|
||||
// Filter by point accuracy
|
||||
if (info.m_horizontalAccuracy > m_minAccuracy)
|
||||
return false;
|
||||
|
||||
auto const & lastInfo = GetLastInfo();
|
||||
auto const & lastAcceptedInfo = GetLastAcceptedInfo();
|
||||
|
||||
// Distance in meters between last accepted and current point is, meters:
|
||||
double const distanceFromLastAccepted = GetDistance(lastAcceptedInfo, info);
|
||||
|
||||
// Filter point by close distance
|
||||
if (distanceFromLastAccepted < kClosePointDistanceMeters)
|
||||
return false;
|
||||
|
||||
// Filter point if accuracy areas are intersected
|
||||
if (distanceFromLastAccepted < lastAcceptedInfo.m_horizontalAccuracy &&
|
||||
info.m_horizontalAccuracy > 0.5 * lastAcceptedInfo.m_horizontalAccuracy)
|
||||
return false;
|
||||
|
||||
// Distance in meters between last and current point is, meters:
|
||||
double const distanceFromLast = GetDistance(lastInfo, info);
|
||||
|
||||
// Time spend to move from the last point to the current point, sec:
|
||||
double const timeFromLast = info.m_timestamp - lastInfo.m_timestamp;
|
||||
if (timeFromLast <= 0.0)
|
||||
return false;
|
||||
|
||||
// Speed to move from the last point to the current point
|
||||
double const speedFromLast = distanceFromLast / timeFromLast;
|
||||
|
||||
// Filter by acceleration: skip point if it jumps too far in short time
|
||||
double const accelerationFromLast = (speedFromLast - lastInfo.m_speed) / timeFromLast;
|
||||
if (accelerationFromLast > kMaxAcceptableAcceleration)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
location::GpsInfo const & GpsTrackFilter::GetLastInfo() const
|
||||
{
|
||||
ASSERT_GREATER(m_countLastInfo, 0, ());
|
||||
return m_lastInfo[0];
|
||||
}
|
||||
|
||||
location::GpsInfo const & GpsTrackFilter::GetLastAcceptedInfo() const
|
||||
{
|
||||
ASSERT_GREATER(m_countAcceptedInfo, 0, ());
|
||||
return m_lastAcceptedInfo[0];
|
||||
}
|
||||
|
||||
void GpsTrackFilter::AddLastInfo(location::GpsInfo const & info)
|
||||
{
|
||||
m_lastInfo[1] = m_lastInfo[0];
|
||||
m_lastInfo[0] = info;
|
||||
m_countLastInfo += 1;
|
||||
}
|
||||
|
||||
void GpsTrackFilter::AddLastAcceptedInfo(location::GpsInfo const & info)
|
||||
{
|
||||
m_lastAcceptedInfo[1] = m_lastAcceptedInfo[0];
|
||||
m_lastAcceptedInfo[0] = info;
|
||||
m_countAcceptedInfo += 1;
|
||||
}
|
||||
52
libs/map/gps_track_filter.hpp
Normal file
52
libs/map/gps_track_filter.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
class IGpsTrackFilter
|
||||
{
|
||||
public:
|
||||
virtual ~IGpsTrackFilter() = default;
|
||||
|
||||
virtual void Process(std::vector<location::GpsInfo> const & inPoints, std::vector<location::GpsInfo> & outPoints) = 0;
|
||||
};
|
||||
|
||||
class GpsTrackNullFilter : public IGpsTrackFilter
|
||||
{
|
||||
public:
|
||||
// IGpsTrackFilter overrides
|
||||
void Process(std::vector<location::GpsInfo> const & inPoints, std::vector<location::GpsInfo> & outPoints) override;
|
||||
};
|
||||
|
||||
class GpsTrackFilter : public IGpsTrackFilter
|
||||
{
|
||||
public:
|
||||
/// Store setting for minimal horizontal accuracy
|
||||
static void StoreMinHorizontalAccuracy(double value);
|
||||
|
||||
GpsTrackFilter();
|
||||
|
||||
// IGpsTrackFilter overrides
|
||||
void Process(std::vector<location::GpsInfo> const & inPoints, std::vector<location::GpsInfo> & outPoints) override;
|
||||
|
||||
private:
|
||||
bool IsGoodPoint(location::GpsInfo const & info) const;
|
||||
|
||||
location::GpsInfo const & GetLastInfo() const;
|
||||
location::GpsInfo const & GetLastAcceptedInfo() const;
|
||||
|
||||
void AddLastInfo(location::GpsInfo const & info);
|
||||
void AddLastAcceptedInfo(location::GpsInfo const & info);
|
||||
|
||||
double m_minAccuracy;
|
||||
|
||||
location::GpsInfo m_lastInfo[2];
|
||||
size_t m_countLastInfo;
|
||||
|
||||
location::GpsInfo m_lastAcceptedInfo[2];
|
||||
size_t m_countAcceptedInfo;
|
||||
};
|
||||
249
libs/map/gps_track_storage.cpp
Normal file
249
libs/map/gps_track_storage.cpp
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
#include "map/gps_track_storage.hpp"
|
||||
|
||||
#include "coding/endianness.hpp"
|
||||
#include "coding/internal/file_data.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Current file format version
|
||||
uint32_t constexpr kCurrentVersion = 1;
|
||||
|
||||
// Header size in bytes, header consists of uint32_t 'version' only
|
||||
uint32_t constexpr kHeaderSize = sizeof(uint32_t);
|
||||
|
||||
// Number of items for batch processing
|
||||
size_t constexpr kItemBlockSize = 1000;
|
||||
|
||||
// TODO
|
||||
// Now GpsInfo written as plain values, but values can be compressed.
|
||||
|
||||
// Size of point in bytes to write in file of read from file
|
||||
size_t constexpr kPointSize = 8 * sizeof(double) + sizeof(uint8_t);
|
||||
|
||||
// Writes value in memory in LittleEndian
|
||||
template <typename T>
|
||||
void MemWrite(void * ptr, T value)
|
||||
{
|
||||
value = SwapIfBigEndianMacroBased(value);
|
||||
memcpy(ptr, &value, sizeof(T));
|
||||
}
|
||||
|
||||
// Read value from memory, which is LittleEndian in memory
|
||||
template <typename T>
|
||||
T MemRead(void const * ptr)
|
||||
{
|
||||
T value;
|
||||
memcpy(&value, ptr, sizeof(T));
|
||||
return SwapIfBigEndianMacroBased(value);
|
||||
}
|
||||
|
||||
void Pack(char * p, location::GpsInfo const & info)
|
||||
{
|
||||
MemWrite<double>(p + 0 * sizeof(double), info.m_timestamp);
|
||||
MemWrite<double>(p + 1 * sizeof(double), info.m_latitude);
|
||||
MemWrite<double>(p + 2 * sizeof(double), info.m_longitude);
|
||||
MemWrite<double>(p + 3 * sizeof(double), info.m_altitude);
|
||||
MemWrite<double>(p + 4 * sizeof(double), info.m_speed);
|
||||
MemWrite<double>(p + 5 * sizeof(double), info.m_bearing);
|
||||
MemWrite<double>(p + 6 * sizeof(double), info.m_horizontalAccuracy);
|
||||
MemWrite<double>(p + 7 * sizeof(double), info.m_verticalAccuracy);
|
||||
ASSERT_LESS_OR_EQUAL(static_cast<int>(info.m_source), 255, ());
|
||||
uint8_t const source = static_cast<uint8_t>(info.m_source);
|
||||
MemWrite<uint8_t>(p + 8 * sizeof(double), source);
|
||||
}
|
||||
|
||||
void Unpack(char const * p, location::GpsInfo & info)
|
||||
{
|
||||
info.m_timestamp = MemRead<double>(p + 0 * sizeof(double));
|
||||
info.m_latitude = MemRead<double>(p + 1 * sizeof(double));
|
||||
info.m_longitude = MemRead<double>(p + 2 * sizeof(double));
|
||||
info.m_altitude = MemRead<double>(p + 3 * sizeof(double));
|
||||
info.m_speed = MemRead<double>(p + 4 * sizeof(double));
|
||||
info.m_bearing = MemRead<double>(p + 5 * sizeof(double));
|
||||
info.m_horizontalAccuracy = MemRead<double>(p + 6 * sizeof(double));
|
||||
info.m_verticalAccuracy = MemRead<double>(p + 7 * sizeof(double));
|
||||
uint8_t const source = MemRead<uint8_t>(p + 8 * sizeof(double));
|
||||
info.m_source = static_cast<location::TLocationSource>(source);
|
||||
}
|
||||
|
||||
inline size_t GetItemOffset(size_t itemIndex)
|
||||
{
|
||||
return kHeaderSize + itemIndex * kPointSize;
|
||||
}
|
||||
|
||||
inline size_t GetItemCount(size_t fileSize)
|
||||
{
|
||||
ASSERT_GREATER_OR_EQUAL(fileSize, kHeaderSize, ());
|
||||
return (fileSize - kHeaderSize) / kPointSize;
|
||||
}
|
||||
|
||||
inline bool WriteVersion(fstream & f, uint32_t version)
|
||||
{
|
||||
static_assert(kHeaderSize == sizeof(version));
|
||||
version = SwapIfBigEndianMacroBased(version);
|
||||
f.write(reinterpret_cast<char const *>(&version), kHeaderSize);
|
||||
return f.good() && f.flush().good();
|
||||
}
|
||||
|
||||
inline bool ReadVersion(fstream & f, uint32_t & version)
|
||||
{
|
||||
static_assert(kHeaderSize == sizeof(version));
|
||||
f.read(reinterpret_cast<char *>(&version), kHeaderSize);
|
||||
version = SwapIfBigEndianMacroBased(version);
|
||||
return f.good();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GpsTrackStorage::GpsTrackStorage(string const & filePath) : m_filePath(filePath), m_itemCount(0)
|
||||
{
|
||||
auto const createNewFile = [this]
|
||||
{
|
||||
m_stream.open(m_filePath, ios::in | ios::out | ios::binary | ios::trunc);
|
||||
|
||||
if (!m_stream)
|
||||
MYTHROW(OpenException, ("Open file error.", m_filePath));
|
||||
|
||||
if (!WriteVersion(m_stream, kCurrentVersion))
|
||||
MYTHROW(OpenException, ("Write version error.", m_filePath));
|
||||
|
||||
m_itemCount = 0;
|
||||
};
|
||||
|
||||
// Open existing file
|
||||
m_stream.open(m_filePath, ios::in | ios::out | ios::binary);
|
||||
|
||||
if (m_stream)
|
||||
{
|
||||
uint32_t version = 0;
|
||||
if (!ReadVersion(m_stream, version))
|
||||
{
|
||||
LOG(LWARNING, ("Recreating", m_filePath, "because can't read version from it."));
|
||||
m_stream.close();
|
||||
createNewFile();
|
||||
version = kCurrentVersion;
|
||||
}
|
||||
|
||||
if (version == kCurrentVersion)
|
||||
{
|
||||
// Seek to end to get file size
|
||||
m_stream.seekp(0, ios::end);
|
||||
if (!m_stream.good())
|
||||
MYTHROW(OpenException, ("Seek to the end error.", m_filePath));
|
||||
|
||||
auto const fileSize = static_cast<size_t>(m_stream.tellp());
|
||||
|
||||
m_itemCount = GetItemCount(fileSize);
|
||||
|
||||
// Set write position after last item position
|
||||
size_t const offset = GetItemOffset(m_itemCount);
|
||||
m_stream.seekp(offset, ios::beg);
|
||||
if (!m_stream.good())
|
||||
MYTHROW(OpenException, ("Seek to the offset error:", offset, m_filePath));
|
||||
|
||||
LOG(LINFO, ("Restored", m_itemCount, "points from gps track storage"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stream.close();
|
||||
// TODO: migration for file m_filePath from version 'version' to version 'kCurrentVersion'
|
||||
// TODO: For now we support only one version, but in future migration may be needed.
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_stream)
|
||||
createNewFile();
|
||||
}
|
||||
|
||||
void GpsTrackStorage::Append(vector<TItem> const & items)
|
||||
{
|
||||
ASSERT(m_stream.is_open(), ());
|
||||
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
// Write position must be after last item position
|
||||
ASSERT_EQUAL(m_stream.tellp(), static_cast<typename fstream::pos_type>(GetItemOffset(m_itemCount)), ());
|
||||
|
||||
vector<char> buff(min(kItemBlockSize, items.size()) * kPointSize);
|
||||
for (size_t i = 0; i < items.size();)
|
||||
{
|
||||
size_t const n = min(items.size() - i, kItemBlockSize);
|
||||
|
||||
for (size_t j = 0; j < n; ++j)
|
||||
Pack(&buff[0] + j * kPointSize, items[i + j]);
|
||||
|
||||
m_stream.write(&buff[0], n * kPointSize);
|
||||
if (!m_stream.good())
|
||||
MYTHROW(WriteException, ("File:", m_filePath));
|
||||
|
||||
i += n;
|
||||
}
|
||||
|
||||
m_stream.flush();
|
||||
if (!m_stream.good())
|
||||
MYTHROW(WriteException, ("File:", m_filePath));
|
||||
|
||||
m_itemCount += items.size();
|
||||
}
|
||||
|
||||
void GpsTrackStorage::Clear()
|
||||
{
|
||||
ASSERT(m_stream.is_open(), ());
|
||||
|
||||
m_itemCount = 0;
|
||||
|
||||
m_stream.close();
|
||||
|
||||
m_stream.open(m_filePath, ios::in | ios::out | ios::binary | ios::trunc);
|
||||
|
||||
if (!m_stream)
|
||||
MYTHROW(WriteException, ("File:", m_filePath));
|
||||
|
||||
if (!WriteVersion(m_stream, kCurrentVersion))
|
||||
MYTHROW(WriteException, ("File:", m_filePath));
|
||||
|
||||
// Write position is set to the first item in the file
|
||||
ASSERT_EQUAL(m_stream.tellp(), static_cast<typename fstream::pos_type>(GetItemOffset(0)), ());
|
||||
}
|
||||
|
||||
void GpsTrackStorage::ForEach(std::function<bool(TItem const & item)> const & fn)
|
||||
{
|
||||
ASSERT(m_stream.is_open(), ());
|
||||
|
||||
size_t i = 0;
|
||||
|
||||
// Set read position to the first item
|
||||
m_stream.seekg(GetItemOffset(i), ios::beg);
|
||||
if (!m_stream.good())
|
||||
MYTHROW(ReadException, ("File:", m_filePath));
|
||||
|
||||
vector<char> buff(min(kItemBlockSize, m_itemCount) * kPointSize);
|
||||
for (; i < m_itemCount;)
|
||||
{
|
||||
size_t const n = min(m_itemCount - i, kItemBlockSize);
|
||||
|
||||
m_stream.read(&buff[0], n * kPointSize);
|
||||
if (!m_stream.good())
|
||||
MYTHROW(ReadException, ("File:", m_filePath));
|
||||
|
||||
for (size_t j = 0; j < n; ++j)
|
||||
{
|
||||
TItem item;
|
||||
Unpack(&buff[0] + j * kPointSize, item);
|
||||
if (!fn(item))
|
||||
return;
|
||||
}
|
||||
|
||||
i += n;
|
||||
}
|
||||
}
|
||||
49
libs/map/gps_track_storage.hpp
Normal file
49
libs/map/gps_track_storage.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class GpsTrackStorage final
|
||||
{
|
||||
public:
|
||||
DECLARE_EXCEPTION(OpenException, RootException);
|
||||
DECLARE_EXCEPTION(WriteException, RootException);
|
||||
DECLARE_EXCEPTION(ReadException, RootException);
|
||||
|
||||
using TItem = location::GpsInfo;
|
||||
|
||||
/// Opens storage with track data.
|
||||
/// @param filePath - path to the file on disk
|
||||
/// @exception OpenException if seek fails.
|
||||
GpsTrackStorage(std::string const & filePath);
|
||||
|
||||
/// Appends new point to the storage
|
||||
/// @param items - collection of gps track points.
|
||||
/// @exceptions WriteException if write fails or ReadException if read fails.
|
||||
void Append(std::vector<TItem> const & items);
|
||||
|
||||
/// Removes all data from the storage
|
||||
/// @exceptions WriteException if write fails.
|
||||
void Clear();
|
||||
|
||||
/// Reads the storage and calls functor for each item
|
||||
/// @param fn - callable function, return true to stop ForEach
|
||||
/// @exceptions ReadException if read fails.
|
||||
void ForEach(std::function<bool(TItem const & item)> const & fn);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_MOVE(GpsTrackStorage);
|
||||
|
||||
size_t GetFirstItemIndex() const;
|
||||
|
||||
std::string const m_filePath;
|
||||
std::fstream m_stream;
|
||||
size_t m_itemCount;
|
||||
};
|
||||
111
libs/map/gps_tracker.cpp
Normal file
111
libs/map/gps_tracker.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include "map/gps_tracker.hpp"
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
char constexpr kEnabledKey[] = "GpsTrackingEnabled";
|
||||
|
||||
inline std::string GetFilePath()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().WritableDir(), GPS_TRACK_FILENAME);
|
||||
}
|
||||
|
||||
inline bool GetSettingsIsEnabled()
|
||||
{
|
||||
bool enabled;
|
||||
if (!settings::Get(kEnabledKey, enabled))
|
||||
enabled = false;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
inline void SetSettingsIsEnabled(bool enabled)
|
||||
{
|
||||
settings::Set(kEnabledKey, enabled);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GpsTracker & GpsTracker::Instance()
|
||||
{
|
||||
static GpsTracker instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
GpsTracker::GpsTracker() : m_enabled(GetSettingsIsEnabled()), m_track(GetFilePath(), std::make_unique<GpsTrackFilter>())
|
||||
{}
|
||||
|
||||
void GpsTracker::SetEnabled(bool enabled)
|
||||
{
|
||||
if (enabled == m_enabled)
|
||||
return;
|
||||
|
||||
SetSettingsIsEnabled(enabled);
|
||||
m_enabled = enabled;
|
||||
|
||||
if (enabled)
|
||||
m_track.Clear();
|
||||
}
|
||||
|
||||
void GpsTracker::Clear()
|
||||
{
|
||||
m_track.Clear();
|
||||
}
|
||||
|
||||
bool GpsTracker::IsEnabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
bool GpsTracker::IsEmpty() const
|
||||
{
|
||||
return m_track.IsEmpty();
|
||||
}
|
||||
|
||||
size_t GpsTracker::GetTrackSize() const
|
||||
{
|
||||
return m_track.GetSize();
|
||||
}
|
||||
|
||||
TrackStatistics GpsTracker::GetTrackStatistics() const
|
||||
{
|
||||
return m_track.GetTrackStatistics();
|
||||
}
|
||||
|
||||
ElevationInfo const & GpsTracker::GetElevationInfo() const
|
||||
{
|
||||
return m_track.GetElevationInfo();
|
||||
}
|
||||
|
||||
void GpsTracker::Connect(TGpsTrackDiffCallback const & fn)
|
||||
{
|
||||
m_track.SetCallback(fn);
|
||||
}
|
||||
|
||||
void GpsTracker::Disconnect()
|
||||
{
|
||||
m_track.SetCallback(nullptr);
|
||||
}
|
||||
|
||||
void GpsTracker::OnLocationUpdated(location::GpsInfo const & info)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
m_track.AddPoint(info);
|
||||
}
|
||||
|
||||
void GpsTracker::ForEachTrackPoint(GpsTrackCallback const & callback) const
|
||||
{
|
||||
CHECK(callback != nullptr, ("Callback should be provided"));
|
||||
m_track.ForEachPoint(callback);
|
||||
}
|
||||
41
libs/map/gps_tracker.hpp
Normal file
41
libs/map/gps_tracker.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/gps_track.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class GpsTracker
|
||||
{
|
||||
public:
|
||||
static GpsTracker & Instance();
|
||||
|
||||
bool IsEnabled() const;
|
||||
void SetEnabled(bool enabled);
|
||||
void Clear();
|
||||
|
||||
bool IsEmpty() const;
|
||||
size_t GetTrackSize() const;
|
||||
TrackStatistics GetTrackStatistics() const;
|
||||
ElevationInfo const & GetElevationInfo() const;
|
||||
|
||||
using TGpsTrackDiffCallback =
|
||||
std::function<void(std::vector<std::pair<size_t, location::GpsInfo>> && toAdd,
|
||||
std::pair<size_t, size_t> const & toRemove, TrackStatistics const & trackStatistics)>;
|
||||
|
||||
void Connect(TGpsTrackDiffCallback const & fn);
|
||||
void Disconnect();
|
||||
|
||||
void OnLocationUpdated(location::GpsInfo const & info);
|
||||
|
||||
using GpsTrackCallback = std::function<bool(location::GpsInfo const &, size_t)>;
|
||||
void ForEachTrackPoint(GpsTrackCallback const & callback) const;
|
||||
|
||||
private:
|
||||
GpsTracker();
|
||||
|
||||
std::atomic<bool> m_enabled;
|
||||
GpsTrack m_track;
|
||||
};
|
||||
185
libs/map/isolines_manager.cpp
Normal file
185
libs/map/isolines_manager.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "map/isolines_manager.hpp"
|
||||
|
||||
#include "drape_frontend/drape_engine.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "platform/mwm_traits.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
int constexpr kMinIsolinesZoom = 11;
|
||||
|
||||
IsolinesManager::IsolinesManager(DataSource & dataSource, GetMwmsByRectFn const & getMwmsByRectFn)
|
||||
: m_dataSource(dataSource)
|
||||
, m_getMwmsByRectFn(getMwmsByRectFn)
|
||||
{
|
||||
CHECK(m_getMwmsByRectFn != nullptr, ());
|
||||
}
|
||||
|
||||
IsolinesManager::IsolinesState IsolinesManager::GetState() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
void IsolinesManager::SetStateListener(IsolinesStateChangedFn const & onStateChangedFn)
|
||||
{
|
||||
m_onStateChangedFn = onStateChangedFn;
|
||||
}
|
||||
|
||||
void IsolinesManager::ChangeState(IsolinesState newState)
|
||||
{
|
||||
if (m_state == newState)
|
||||
return;
|
||||
m_state = newState;
|
||||
if (m_onStateChangedFn != nullptr)
|
||||
m_onStateChangedFn(newState);
|
||||
}
|
||||
|
||||
IsolinesManager::Info const & IsolinesManager::LoadIsolinesInfo(MwmSet::MwmId const & id) const
|
||||
{
|
||||
Availability status = Availability::NoData;
|
||||
isolines::Quality quality = isolines::Quality::None;
|
||||
isolines::IsolinesInfo info;
|
||||
if (isolines::LoadIsolinesInfo(m_dataSource, id, info))
|
||||
{
|
||||
LOG(LINFO, ("Isolines min altitude", info.m_minAltitude, "max altitude", info.m_maxAltitude, "altitude step",
|
||||
info.m_altStep));
|
||||
status = Availability::Available;
|
||||
quality = info.GetQuality();
|
||||
}
|
||||
|
||||
return m_mwmCache.emplace(id, Info(status, quality)).first->second;
|
||||
}
|
||||
|
||||
void IsolinesManager::SetDrapeEngine(ref_ptr<df::DrapeEngine> engine)
|
||||
{
|
||||
m_drapeEngine.Set(engine);
|
||||
}
|
||||
|
||||
void IsolinesManager::SetEnabled(bool enabled)
|
||||
{
|
||||
ChangeState(enabled ? IsolinesState::Enabled : IsolinesState::Disabled);
|
||||
m_drapeEngine.SafeCall(&df::DrapeEngine::EnableIsolines, enabled);
|
||||
if (enabled)
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lastMwms.clear();
|
||||
m_mwmCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsolinesManager::IsEnabled() const
|
||||
{
|
||||
return m_state != IsolinesState::Disabled;
|
||||
}
|
||||
|
||||
bool IsolinesManager::IsVisible() const
|
||||
{
|
||||
return m_currentModelView && df::GetDrawTileScale(*m_currentModelView) >= kMinIsolinesZoom;
|
||||
}
|
||||
|
||||
void IsolinesManager::UpdateViewport(ScreenBase const & screen)
|
||||
{
|
||||
if (screen.GlobalRect().GetLocalRect().IsEmptyInterior())
|
||||
return;
|
||||
|
||||
m_currentModelView = screen;
|
||||
if (!IsEnabled())
|
||||
return;
|
||||
|
||||
if (!IsVisible())
|
||||
{
|
||||
ChangeState(IsolinesState::Enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto mwms = m_getMwmsByRectFn(screen.ClipRect());
|
||||
if (m_lastMwms == mwms)
|
||||
return;
|
||||
m_lastMwms = std::move(mwms);
|
||||
for (auto const & mwmId : m_lastMwms)
|
||||
{
|
||||
if (!mwmId.IsAlive())
|
||||
continue;
|
||||
auto it = m_mwmCache.find(mwmId);
|
||||
if (it == m_mwmCache.end())
|
||||
LoadIsolinesInfo(mwmId);
|
||||
}
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
void IsolinesManager::UpdateState()
|
||||
{
|
||||
bool available = false;
|
||||
bool expired = false;
|
||||
bool noData = false;
|
||||
for (auto const & mwmId : m_lastMwms)
|
||||
{
|
||||
if (!mwmId.IsAlive())
|
||||
continue;
|
||||
auto const it = m_mwmCache.find(mwmId);
|
||||
CHECK(it != m_mwmCache.end(), ());
|
||||
switch (it->second.m_availability)
|
||||
{
|
||||
case Availability::Available: available = true; break;
|
||||
case Availability::ExpiredData: expired = true; break;
|
||||
case Availability::NoData: noData = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (expired)
|
||||
ChangeState(IsolinesState::ExpiredData);
|
||||
else if (!available && noData)
|
||||
ChangeState(IsolinesState::NoData);
|
||||
else
|
||||
ChangeState(IsolinesState::Enabled);
|
||||
}
|
||||
|
||||
void IsolinesManager::Invalidate()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
return;
|
||||
m_lastMwms.clear();
|
||||
if (m_currentModelView)
|
||||
UpdateViewport(*m_currentModelView);
|
||||
}
|
||||
|
||||
isolines::Quality IsolinesManager::GetDataQuality(MwmSet::MwmId const & id) const
|
||||
{
|
||||
if (!id.IsAlive())
|
||||
return isolines::Quality::None;
|
||||
|
||||
auto const it = m_mwmCache.find(id);
|
||||
if (it == m_mwmCache.cend())
|
||||
return LoadIsolinesInfo(id).m_quality;
|
||||
|
||||
return it->second.m_quality;
|
||||
}
|
||||
|
||||
void IsolinesManager::OnMwmDeregistered(platform::LocalCountryFile const & countryFile)
|
||||
{
|
||||
for (auto it = m_mwmCache.begin(); it != m_mwmCache.end(); ++it)
|
||||
{
|
||||
if (it->first.IsDeregistered(countryFile))
|
||||
{
|
||||
m_mwmCache.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string DebugPrint(IsolinesManager::IsolinesState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case IsolinesManager::IsolinesState::Disabled: return "Disabled";
|
||||
case IsolinesManager::IsolinesState::Enabled: return "Enabled";
|
||||
case IsolinesManager::IsolinesState::ExpiredData: return "ExpiredData";
|
||||
case IsolinesManager::IsolinesState::NoData: return "NoData";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
89
libs/map/isolines_manager.hpp
Normal file
89
libs/map/isolines_manager.hpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
#include "drape_frontend/drape_engine_safe_ptr.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/isolines_info.hpp"
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
class IsolinesManager final
|
||||
{
|
||||
public:
|
||||
enum class IsolinesState
|
||||
{
|
||||
Disabled,
|
||||
Enabled,
|
||||
ExpiredData,
|
||||
NoData
|
||||
};
|
||||
|
||||
using IsolinesStateChangedFn = std::function<void(IsolinesState)>;
|
||||
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
|
||||
|
||||
IsolinesManager(DataSource & dataSource, GetMwmsByRectFn const & getMwmsByRectFn);
|
||||
|
||||
IsolinesState GetState() const;
|
||||
void SetStateListener(IsolinesStateChangedFn const & onStateChangedFn);
|
||||
|
||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
||||
|
||||
void SetEnabled(bool enabled);
|
||||
bool IsEnabled() const;
|
||||
|
||||
bool IsVisible() const;
|
||||
|
||||
void UpdateViewport(ScreenBase const & screen);
|
||||
void Invalidate();
|
||||
|
||||
isolines::Quality GetDataQuality(MwmSet::MwmId const & id) const;
|
||||
|
||||
void OnMwmDeregistered(platform::LocalCountryFile const & countryFile);
|
||||
|
||||
private:
|
||||
enum class Availability
|
||||
{
|
||||
Available,
|
||||
NoData,
|
||||
ExpiredData
|
||||
};
|
||||
|
||||
struct Info
|
||||
{
|
||||
Info() = default;
|
||||
Info(Availability availability, isolines::Quality quality) : m_availability(availability), m_quality(quality) {}
|
||||
|
||||
Availability m_availability = Availability::NoData;
|
||||
isolines::Quality m_quality = isolines::Quality::None;
|
||||
};
|
||||
|
||||
void UpdateState();
|
||||
void ChangeState(IsolinesState newState);
|
||||
Info const & LoadIsolinesInfo(MwmSet::MwmId const & id) const;
|
||||
|
||||
IsolinesState m_state = IsolinesState::Disabled;
|
||||
IsolinesStateChangedFn m_onStateChangedFn;
|
||||
|
||||
DataSource & m_dataSource;
|
||||
GetMwmsByRectFn m_getMwmsByRectFn;
|
||||
|
||||
df::DrapeEngineSafePtr m_drapeEngine;
|
||||
|
||||
std::optional<ScreenBase> m_currentModelView;
|
||||
|
||||
std::vector<MwmSet::MwmId> m_lastMwms;
|
||||
mutable std::map<MwmSet::MwmId, Info> m_mwmCache;
|
||||
};
|
||||
|
||||
std::string DebugPrint(IsolinesManager::IsolinesState state);
|
||||
13
libs/map/map_integration_tests/CMakeLists.txt
Normal file
13
libs/map/map_integration_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
project(map_integration_tests)
|
||||
|
||||
set(SRC
|
||||
interactive_search_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
search_tests_support
|
||||
generator_tests_support
|
||||
map
|
||||
)
|
||||
162
libs/map/map_integration_tests/interactive_search_test.cpp
Normal file
162
libs/map/map_integration_tests/interactive_search_test.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/viewport_search_callback.hpp"
|
||||
|
||||
#include "search/mode.hpp"
|
||||
#include "search/search_tests_support/helpers.hpp"
|
||||
#include "search/search_tests_support/test_results_matching.hpp"
|
||||
#include "search/search_tests_support/test_search_request.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace interactive_search_test
|
||||
{
|
||||
using namespace generator::tests_support;
|
||||
using namespace search::tests_support;
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
struct Stats
|
||||
{
|
||||
size_t m_numShownResults = 0;
|
||||
};
|
||||
|
||||
class TestDelegate : public ViewportSearchCallback::Delegate
|
||||
{
|
||||
public:
|
||||
explicit TestDelegate(Stats & stats) : m_stats(stats) {}
|
||||
|
||||
~TestDelegate() override = default;
|
||||
|
||||
// ViewportSearchCallback::Delegate overrides:
|
||||
void RunUITask(function<void()> fn) override { fn(); }
|
||||
|
||||
bool IsViewportSearchActive() const override { return true; }
|
||||
|
||||
void ShowViewportSearchResults(Results::ConstIter begin, Results::ConstIter end, bool clear) override
|
||||
{
|
||||
if (clear)
|
||||
m_stats.m_numShownResults = 0;
|
||||
m_stats.m_numShownResults += distance(begin, end);
|
||||
}
|
||||
|
||||
private:
|
||||
Stats & m_stats;
|
||||
};
|
||||
|
||||
class InteractiveSearchRequest
|
||||
: public TestDelegate
|
||||
, public TestSearchRequest
|
||||
{
|
||||
public:
|
||||
InteractiveSearchRequest(TestSearchEngine & engine, string const & query, m2::RectD const & viewport, Stats & stats)
|
||||
: TestDelegate(stats)
|
||||
, TestSearchRequest(engine, query, "en" /* locale */, Mode::Viewport, viewport)
|
||||
{
|
||||
SetCustomOnResults(ViewportSearchCallback(viewport, static_cast<ViewportSearchCallback::Delegate &>(*this),
|
||||
bind(&InteractiveSearchRequest::OnResults, this, placeholders::_1)));
|
||||
}
|
||||
};
|
||||
|
||||
class InteractiveSearchTest : public SearchTest
|
||||
{};
|
||||
|
||||
double const kDX[] = {-0.01, 0, 0, 0.01};
|
||||
double const kDY[] = {0, -0.01, 0.01, 0};
|
||||
|
||||
static_assert(ARRAY_SIZE(kDX) == ARRAY_SIZE(kDY), "Wrong deltas lists");
|
||||
|
||||
UNIT_CLASS_TEST(InteractiveSearchTest, Smoke)
|
||||
{
|
||||
m2::PointD const cafesPivot(-1, -1);
|
||||
m2::PointD const hotelsPivot(1, 1);
|
||||
|
||||
vector<TestCafe> cafes;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(kDX); ++i)
|
||||
cafes.emplace_back(m2::Shift(cafesPivot, kDX[i], kDY[i]));
|
||||
|
||||
vector<TestHotel> hotels;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(kDX); ++i)
|
||||
hotels.emplace_back(m2::Shift(hotelsPivot, kDX[i], kDY[i]));
|
||||
|
||||
auto const id = BuildCountry("Wonderland", [&](TestMwmBuilder & builder)
|
||||
{
|
||||
for (auto const & cafe : cafes)
|
||||
builder.Add(cafe);
|
||||
for (auto const & hotel : hotels)
|
||||
builder.Add(hotel);
|
||||
});
|
||||
|
||||
{
|
||||
Stats stats;
|
||||
InteractiveSearchRequest request(m_engine, "cafe", m2::RectD(m2::PointD(-1.5, -1.5), m2::PointD(-0.5, -0.5)),
|
||||
stats);
|
||||
request.Run();
|
||||
|
||||
Rules const rules = {ExactMatch(id, cafes[0]), ExactMatch(id, cafes[1]), ExactMatch(id, cafes[2]),
|
||||
ExactMatch(id, cafes[3])};
|
||||
|
||||
TEST_EQUAL(stats.m_numShownResults, 4, ());
|
||||
TEST(MatchResults(m_dataSource, rules, request.Results()), ());
|
||||
}
|
||||
|
||||
{
|
||||
Stats stats;
|
||||
InteractiveSearchRequest request(m_engine, "hotel", m2::RectD(m2::PointD(0.5, 0.5), m2::PointD(1.5, 1.5)), stats);
|
||||
request.Run();
|
||||
|
||||
Rules const rules = {ExactMatch(id, hotels[0]), ExactMatch(id, hotels[1]), ExactMatch(id, hotels[2]),
|
||||
ExactMatch(id, hotels[3])};
|
||||
|
||||
TEST_EQUAL(stats.m_numShownResults, 4, ());
|
||||
TEST(MatchResults(m_dataSource, rules, request.Results()), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(InteractiveSearchTest, NearbyFeaturesInViewport)
|
||||
{
|
||||
static double constexpr kEps = 0.1;
|
||||
TestCafe cafe1(m2::PointD(0.0, 0.0));
|
||||
TestCafe cafe2(m2::PointD(0.0, kEps));
|
||||
TestCafe cafe3(m2::PointD(0.0, 2 * kEps));
|
||||
|
||||
auto const id = BuildCountry("Wonderland", [&](TestMwmBuilder & builder)
|
||||
{
|
||||
builder.Add(cafe1);
|
||||
builder.Add(cafe2);
|
||||
builder.Add(cafe3);
|
||||
});
|
||||
|
||||
SearchParams params;
|
||||
params.m_query = "cafe";
|
||||
params.m_inputLocale = "en";
|
||||
params.m_viewport = {-0.5, -0.5, 0.5, 0.5};
|
||||
params.m_mode = Mode::Viewport;
|
||||
params.m_minDistanceOnMapBetweenResults = {kEps * 0.9, kEps * 0.9};
|
||||
params.m_suggestsEnabled = false;
|
||||
|
||||
{
|
||||
TestSearchRequest request(m_engine, params);
|
||||
request.Run();
|
||||
|
||||
TEST(MatchResults(m_dataSource, Rules{ExactMatch(id, cafe1), ExactMatch(id, cafe2), ExactMatch(id, cafe3)},
|
||||
request.Results()),
|
||||
());
|
||||
}
|
||||
|
||||
params.m_minDistanceOnMapBetweenResults = {kEps * 1.1, kEps * 1.1};
|
||||
|
||||
{
|
||||
TestSearchRequest request(m_engine, params);
|
||||
request.Run();
|
||||
|
||||
auto const & results = request.Results();
|
||||
|
||||
TEST(MatchResults(m_dataSource, Rules{ExactMatch(id, cafe1), ExactMatch(id, cafe3)}, results) ||
|
||||
MatchResults(m_dataSource, Rules{ExactMatch(id, cafe2)}, results),
|
||||
());
|
||||
}
|
||||
}
|
||||
} // namespace interactive_search_test
|
||||
29
libs/map/map_tests/CMakeLists.txt
Normal file
29
libs/map/map_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
project(map_tests)
|
||||
|
||||
set(SRC
|
||||
address_tests.cpp
|
||||
bookmarks_test.cpp
|
||||
chart_generator_tests.cpp
|
||||
check_mwms.cpp
|
||||
countries_names_tests.cpp
|
||||
extrapolator_tests.cpp
|
||||
feature_getters_tests.cpp
|
||||
gps_track_collection_test.cpp
|
||||
gps_track_storage_test.cpp
|
||||
gps_track_test.cpp
|
||||
kmz_unarchive_test.cpp
|
||||
mwm_url_tests.cpp
|
||||
power_manager_tests.cpp
|
||||
search_api_tests.cpp
|
||||
transliteration_test.cpp
|
||||
elevation_info_tests.cpp
|
||||
track_statistics_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT REQUIRE_SERVER)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
search_tests_support
|
||||
generator_tests_support
|
||||
map
|
||||
)
|
||||
106
libs/map/map_tests/address_tests.cpp
Normal file
106
libs/map/map_tests/address_tests.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/reverse_geocoder.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/feature_utils.hpp"
|
||||
#include "indexer/mwm_set.hpp"
|
||||
#include "indexer/search_string_utils.hpp"
|
||||
|
||||
#include "platform/preferred_languages.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace address_tests
|
||||
{
|
||||
using namespace search;
|
||||
using namespace platform;
|
||||
|
||||
void TestAddress(ReverseGeocoder & coder, ms::LatLon const & ll, std::string_view street,
|
||||
std::string const & houseNumber)
|
||||
{
|
||||
ReverseGeocoder::Address addr;
|
||||
coder.GetNearbyAddress(mercator::FromLatLon(ll), addr);
|
||||
|
||||
std::string const expectedKey = strings::ToUtf8(GetStreetNameAsKey(street, false /* ignoreStreetSynonyms */));
|
||||
std::string const resultKey =
|
||||
strings::ToUtf8(GetStreetNameAsKey(addr.m_street.m_name, false /* ignoreStreetSynonyms */));
|
||||
|
||||
TEST_EQUAL(resultKey, expectedKey, (addr));
|
||||
TEST_EQUAL(houseNumber, addr.GetHouseNumber(), (addr));
|
||||
}
|
||||
|
||||
void TestAddress(ReverseGeocoder & coder, std::shared_ptr<MwmInfo> mwmInfo, ms::LatLon const & ll,
|
||||
StringUtf8Multilang const & streetNames, std::string const & houseNumber)
|
||||
{
|
||||
feature::NameParamsOut out;
|
||||
feature::GetReadableName(
|
||||
{streetNames, mwmInfo->GetRegionData(), languages::GetCurrentMapLanguage(), false /* allowTranslit */}, out);
|
||||
|
||||
TestAddress(coder, ll, out.primary, houseNumber);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReverseGeocoder_Smoke)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
LocalCountryFile file = LocalCountryFile::MakeForTesting("minsk-pass");
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto const regResult = dataSource.RegisterMap(file);
|
||||
TEST_EQUAL(regResult.second, MwmSet::RegResult::Success, ());
|
||||
|
||||
auto mwmInfo = regResult.first.GetInfo();
|
||||
|
||||
TEST(mwmInfo != nullptr, ());
|
||||
|
||||
ReverseGeocoder coder(dataSource);
|
||||
|
||||
auto const currentLocale = languages::GetCurrentMapLanguage();
|
||||
|
||||
{
|
||||
StringUtf8Multilang streetNames;
|
||||
streetNames.AddString("default", "улица Мясникова");
|
||||
streetNames.AddString("int_name", "vulica Miasnikova");
|
||||
streetNames.AddString("be", "вуліца Мяснікова");
|
||||
streetNames.AddString("ru", "улица Мясникова");
|
||||
TestAddress(coder, mwmInfo, {53.89815, 27.54265}, streetNames, "32");
|
||||
}
|
||||
{
|
||||
StringUtf8Multilang streetNames;
|
||||
streetNames.AddString("default", "улица Немига");
|
||||
streetNames.AddString("int_name", "vulica Niamiha");
|
||||
streetNames.AddString("be", "вуліца Няміга");
|
||||
streetNames.AddString("ru", "улица Немига");
|
||||
TestAddress(coder, mwmInfo, {53.8997617, 27.5429365}, streetNames, "40");
|
||||
}
|
||||
{
|
||||
StringUtf8Multilang streetNames;
|
||||
streetNames.AddString("default", "Советская улица");
|
||||
streetNames.AddString("int_name", "Savieckaja vulica");
|
||||
streetNames.AddString("be", "Савецкая вуліца");
|
||||
streetNames.AddString("ru", "Советская улица");
|
||||
TestAddress(coder, mwmInfo, {53.89666, 27.54904}, streetNames, "19");
|
||||
}
|
||||
{
|
||||
StringUtf8Multilang streetNames;
|
||||
streetNames.AddString("default", "проспект Независимости");
|
||||
streetNames.AddString("int_name", "praspiekt Niezaliežnasci");
|
||||
streetNames.AddString("be", "праспект Незалежнасці");
|
||||
streetNames.AddString("ru", "проспект Независимости");
|
||||
TestAddress(coder, mwmInfo, {53.89724, 27.54983}, streetNames, "11");
|
||||
}
|
||||
{
|
||||
StringUtf8Multilang streetNames;
|
||||
streetNames.AddString("int_name", "vulica Karla Marksa");
|
||||
streetNames.AddString("default", "улица Карла Маркса");
|
||||
streetNames.AddString("be", "вуліца Карла Маркса");
|
||||
streetNames.AddString("ru", "улица Карла Маркса");
|
||||
TestAddress(coder, mwmInfo, {53.89745, 27.55835}, streetNames, "18А");
|
||||
}
|
||||
}
|
||||
} // namespace address_tests
|
||||
1631
libs/map/map_tests/bookmarks_test.cpp
Normal file
1631
libs/map/map_tests/bookmarks_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
263
libs/map/map_tests/chart_generator_tests.cpp
Normal file
263
libs/map/map_tests/chart_generator_tests.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/chart_generator.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace chart_generator_tests
|
||||
{
|
||||
using maps::kAltitudeChartBPP;
|
||||
using std::vector;
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kEpsilon = 0.00001;
|
||||
|
||||
bool AlmostEqualAbs(vector<double> const & v1, vector<double> const & v2)
|
||||
{
|
||||
if (v1.size() != v2.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < v1.size(); ++i)
|
||||
if (!::AlmostEqualAbs(v1[i], v2[i], kEpsilon))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsColor(vector<uint8_t> const & frameBuffer, size_t startColorIdx, uint8_t expectedR, uint8_t expectedG,
|
||||
uint8_t expectedB, uint8_t expectedA)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(startColorIdx + kAltitudeChartBPP, frameBuffer.size(), ());
|
||||
|
||||
return frameBuffer[startColorIdx] == expectedR && frameBuffer[startColorIdx + 1] == expectedG &&
|
||||
frameBuffer[startColorIdx + 2] == expectedB && frameBuffer[startColorIdx + 3] == expectedA;
|
||||
}
|
||||
|
||||
void TestAngleColors(size_t width, size_t height, vector<uint8_t> const & frameBuffer, uint8_t expectedR,
|
||||
uint8_t expectedG, uint8_t expectedB, uint8_t expectedA)
|
||||
{
|
||||
TEST_EQUAL(frameBuffer.size(), width * height * kAltitudeChartBPP, ());
|
||||
TEST(IsColor(frameBuffer, 0 /* startColorIdx */, expectedR, expectedG, expectedB, expectedA), ());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * (width - 1) /* startColorIdx */, expectedR, expectedG, expectedB,
|
||||
expectedA),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * height * (width - 1) /* startColorIdx */, expectedR, expectedG,
|
||||
expectedB, expectedA),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * height * width - kAltitudeChartBPP /* startColorIdx */, expectedR,
|
||||
expectedG, expectedB, expectedA),
|
||||
());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(ScaleChartData_Test)
|
||||
{
|
||||
vector<double> chartData = {0.0, -1.0, 2.0};
|
||||
maps::ScaleChartData(chartData, 2.0 /* scale */);
|
||||
vector<double> const expectedChartData = {0.0, -2.0, 4.0};
|
||||
TEST_EQUAL(chartData, expectedChartData, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ShiftChartData_Test)
|
||||
{
|
||||
vector<double> chartData = {0.0, -1.0, 2.0};
|
||||
maps::ShiftChartData(chartData, 1 /* shift */);
|
||||
vector<double> const expectedChartData = {1.0, 0.0, 3.0};
|
||||
TEST_EQUAL(chartData, expectedChartData, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ReflectChartData_Test)
|
||||
{
|
||||
vector<double> chartData = {0.0, -1.0, 2.0};
|
||||
maps::ReflectChartData(chartData);
|
||||
vector<double> const expectedChartData = {0.0, 1.0, -2.0};
|
||||
TEST_EQUAL(chartData, expectedChartData, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(NormalizeChartData_SmokeTest)
|
||||
{
|
||||
vector<double> const distanceDataM = {0.0, 0.0, 0.0};
|
||||
geometry::Altitudes const altitudeDataM = {0, 0, 0};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM), ());
|
||||
|
||||
vector<double> const expectedUniformAltitudeDataM = {0.0, 0.0};
|
||||
TEST_EQUAL(expectedUniformAltitudeDataM, uniformAltitudeDataM, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(NormalizeChartData_NoResultPointTest)
|
||||
{
|
||||
vector<double> const distanceDataM = {0.0, 0.0, 0.0};
|
||||
geometry::Altitudes const altitudeDataM = {0, 0, 0};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 0 /* resultPointCount */, uniformAltitudeDataM), ());
|
||||
|
||||
TEST(uniformAltitudeDataM.empty(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(NormalizeChartData_NoPointTest)
|
||||
{
|
||||
vector<double> const distanceDataM = {};
|
||||
geometry::Altitudes const altitudeDataM = {};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 2 /* resultPointCount */, uniformAltitudeDataM), ());
|
||||
|
||||
TEST(uniformAltitudeDataM.empty(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(NormalizeChartData_Test)
|
||||
{
|
||||
vector<double> const distanceDataM = {0.0, 2.0, 4.0, 6.0};
|
||||
geometry::Altitudes const altitudeDataM = {-9, 0, 9, 18};
|
||||
|
||||
vector<double> uniformAltitudeDataM;
|
||||
TEST(maps::NormalizeChartData(distanceDataM, altitudeDataM, 10 /* resultPointCount */, uniformAltitudeDataM), ());
|
||||
|
||||
vector<double> const expectedUniformAltitudeDataM = {-9.0, -6.0, -3.0, 0.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0};
|
||||
TEST(AlmostEqualAbs(uniformAltitudeDataM, expectedUniformAltitudeDataM), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateYAxisChartData_SmokeTest)
|
||||
{
|
||||
vector<double> const altitudeDataM = {0.0, 0.0};
|
||||
vector<double> yAxisDataPxl;
|
||||
|
||||
TEST(maps::GenerateYAxisChartData(30 /* height */, 1.0 /* minMetersPerPxl */, altitudeDataM, yAxisDataPxl), ());
|
||||
vector<double> expectedYAxisDataPxl = {15.0, 15.0};
|
||||
TEST(AlmostEqualAbs(yAxisDataPxl, expectedYAxisDataPxl), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateYAxisChartData_EmptyAltitudeDataTest)
|
||||
{
|
||||
vector<double> const altitudeDataM = {};
|
||||
vector<double> yAxisDataPxl;
|
||||
|
||||
TEST(maps::GenerateYAxisChartData(30 /* height */, 1.0 /* minMetersPerPxl */, altitudeDataM, yAxisDataPxl), ());
|
||||
TEST(yAxisDataPxl.empty(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateYAxisChartData_Test)
|
||||
{
|
||||
vector<double> const altitudeDataM = {0.0, 2.0, 0.0, -2.0, 1.0};
|
||||
vector<double> yAxisDataPxl;
|
||||
|
||||
TEST(maps::GenerateYAxisChartData(100 /* height */, 1.0 /* minMetersPerPxl */, altitudeDataM, yAxisDataPxl), ());
|
||||
vector<double> expectedYAxisDataPxl = {50.0, 48.0, 50.0, 52.0, 49.0};
|
||||
TEST(AlmostEqualAbs(yAxisDataPxl, expectedYAxisDataPxl), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChartByPoints_NoGeometryTest)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {};
|
||||
size_t constexpr width = 100;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */,
|
||||
0 /* expectedA */);
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChartByPoints_OnePointTest)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {{20.0, 20.0}};
|
||||
size_t constexpr width = 40;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
TestAngleColors(width, height, frameBuffer, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */,
|
||||
0 /* expectedA */);
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChartByPoints_Test)
|
||||
{
|
||||
vector<m2::PointD> const geometry = {{0.0, 0.0}, {10.0, 10.0}};
|
||||
|
||||
size_t constexpr width = 40;
|
||||
size_t constexpr height = 40;
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChartByPoints(width, height, geometry, MapStyleDefaultLight /* mapStyle */, frameBuffer), ());
|
||||
|
||||
TEST_EQUAL(frameBuffer.size(), width * height * kAltitudeChartBPP, ());
|
||||
TEST(IsColor(frameBuffer, 0 /* startColorIdx */, 30 /* expectedR */, 150 /* expectedG */, 240 /* expectedB */,
|
||||
255 /* expectedA */),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * (width - 1) /* startColorIdx */, 255 /* expectedR */,
|
||||
255 /* expectedG */, 255 /* expectedB */, 0 /* expectedA */),
|
||||
());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChart_NoPointsTest)
|
||||
{
|
||||
size_t constexpr width = 50;
|
||||
vector<double> const distanceDataM = {};
|
||||
geometry::Altitudes const & altitudeDataM = {};
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChart(width, 50 /* height */, distanceDataM, altitudeDataM, MapStyleDefaultDark /* mapStyle */,
|
||||
frameBuffer),
|
||||
());
|
||||
TestAngleColors(width, 50 /* height */, frameBuffer, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */,
|
||||
0 /* expectedA */);
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChart_OnePointTest)
|
||||
{
|
||||
size_t constexpr width = 50;
|
||||
size_t constexpr height = 50;
|
||||
vector<double> const distanceDataM = {0.0};
|
||||
geometry::Altitudes const & altitudeDataM = {0};
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(
|
||||
maps::GenerateChart(width, height, distanceDataM, altitudeDataM, MapStyleDefaultDark /* mapStyle */, frameBuffer),
|
||||
());
|
||||
TEST_EQUAL(frameBuffer.size(), width * height * kAltitudeChartBPP, ());
|
||||
TEST(IsColor(frameBuffer, 0 /* startColorIdx */, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */,
|
||||
0 /* expectedA */),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * (width - 1) /* startColorIdx */, 255 /* expectedR */,
|
||||
255 /* expectedG */, 255 /* expectedB */, 0 /* expectedA */),
|
||||
());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChart_EmptyRectTest)
|
||||
{
|
||||
size_t constexpr width = 0;
|
||||
vector<double> const distanceDataM = {};
|
||||
geometry::Altitudes const & altitudeDataM = {};
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(!maps::GenerateChart(width, 50 /* height */, distanceDataM, altitudeDataM, MapStyleDefaultDark /* mapStyle */,
|
||||
frameBuffer),
|
||||
());
|
||||
TEST(frameBuffer.empty(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GenerateChart_Test)
|
||||
{
|
||||
size_t constexpr width = 50;
|
||||
vector<double> const distanceDataM = {0.0, 100.0};
|
||||
geometry::Altitudes const & altitudeDataM = {0, 1000};
|
||||
vector<uint8_t> frameBuffer;
|
||||
|
||||
TEST(maps::GenerateChart(width, 50 /* height */, distanceDataM, altitudeDataM, MapStyleDefaultDark /* mapStyle */,
|
||||
frameBuffer),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, 0 /* startColorIdx */, 255 /* expectedR */, 255 /* expectedG */, 255 /* expectedB */,
|
||||
0 /* expectedA */),
|
||||
());
|
||||
TEST(IsColor(frameBuffer, kAltitudeChartBPP * 3 * width - kAltitudeChartBPP /* startColorIdx */, 255 /* expectedR */,
|
||||
230 /* expectedG */, 140 /* expectedB */, 255 /* expectedA */),
|
||||
());
|
||||
}
|
||||
} // namespace chart_generator_tests
|
||||
88
libs/map/map_tests/check_mwms.cpp
Normal file
88
libs/map/map_tests/check_mwms.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "indexer/data_header.hpp"
|
||||
#include "indexer/interval_index.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/var_serial_vector.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace check_mwms
|
||||
{
|
||||
using namespace platform;
|
||||
using namespace std;
|
||||
|
||||
UNIT_TEST(CheckMWM_LoadAll)
|
||||
{
|
||||
// Parse root WritableDir folder. Expect at least World, WorldCoasts, minsk-pass.
|
||||
vector<LocalCountryFile> localFiles;
|
||||
size_t const count = FindAllLocalMapsInDirectoryAndCleanup(GetPlatform().WritableDir(), 0 /* version */,
|
||||
-1 /* latestVersion */, localFiles);
|
||||
TEST_EQUAL(count, localFiles.size(), ());
|
||||
TEST_GREATER_OR_EQUAL(count, 3, ());
|
||||
|
||||
FeaturesFetcher m;
|
||||
m.InitClassificator();
|
||||
|
||||
for (auto const & localFile : localFiles)
|
||||
{
|
||||
LOG(LINFO, ("Found mwm:", localFile));
|
||||
try
|
||||
{
|
||||
auto p = m.RegisterMap(localFile);
|
||||
|
||||
TEST(p.first.IsAlive(), ());
|
||||
TEST_EQUAL(MwmSet::RegResult::Success, p.second, ());
|
||||
}
|
||||
catch (RootException const & ex)
|
||||
{
|
||||
TEST(false, ("Bad mwm file:", localFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(CheckMWM_GeomIndex)
|
||||
{
|
||||
// Open mwm file from data path.
|
||||
FilesContainerR cont(GetPlatform().GetReader("minsk-pass.mwm"));
|
||||
|
||||
// Initialize index reader section inside mwm.
|
||||
typedef ModelReaderPtr ReaderT;
|
||||
ReaderT reader = cont.GetReader(INDEX_FILE_TAG);
|
||||
ReaderSource<ReaderT> source(reader);
|
||||
VarSerialVectorReader<ReaderT> treesReader(source);
|
||||
|
||||
// Make interval index objects for each scale bucket.
|
||||
vector<unique_ptr<IntervalIndex<ReaderT, uint32_t>>> scale2Index;
|
||||
for (size_t i = 0; i < treesReader.Size(); ++i)
|
||||
{
|
||||
scale2Index.emplace_back(
|
||||
make_unique<IntervalIndex<ReaderT, uint32_t>>(treesReader.SubReader(static_cast<uint32_t>(i))));
|
||||
}
|
||||
|
||||
// Pass full coverage as input for test.
|
||||
uint64_t beg = 0;
|
||||
uint64_t end = static_cast<uint64_t>((1ULL << 63) - 1);
|
||||
|
||||
// Count objects for each scale bucket.
|
||||
map<size_t, uint64_t> resCount;
|
||||
for (size_t i = 0; i < scale2Index.size(); ++i)
|
||||
scale2Index[i]->ForEach([i, &resCount](uint64_t, uint32_t) { ++resCount[i]; }, beg, end);
|
||||
|
||||
// Print results.
|
||||
LOG(LINFO, (resCount));
|
||||
}
|
||||
} // namespace check_mwms
|
||||
87
libs/map/map_tests/countries_names_tests.cpp
Normal file
87
libs/map/map_tests/countries_names_tests.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "search/categories_cache.hpp"
|
||||
#include "search/downloader_search_callback.hpp"
|
||||
#include "search/mwm_context.hpp"
|
||||
|
||||
#include "storage/storage.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/mwm_set.hpp"
|
||||
#include "indexer/utils.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
#include "base/cancellable.hpp"
|
||||
#include "base/checked_cast.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
UNIT_TEST(CountriesNamesTest)
|
||||
{
|
||||
using namespace platform;
|
||||
using namespace storage;
|
||||
using namespace std;
|
||||
|
||||
Framework f(FrameworkParams(false /* m_enableDiffs */));
|
||||
auto & storage = f.GetStorage();
|
||||
auto const & synonyms = storage.GetCountryNameSynonyms();
|
||||
|
||||
auto handle = indexer::FindWorld(f.GetDataSource());
|
||||
TEST(handle.IsAlive(), ());
|
||||
|
||||
FeaturesLoaderGuard g(f.GetDataSource(), handle.GetId());
|
||||
|
||||
auto & value = *handle.GetValue();
|
||||
TEST(value.HasSearchIndex(), ());
|
||||
search::MwmContext const mwmContext(std::move(handle));
|
||||
base::Cancellable const cancellable;
|
||||
search::CategoriesCache cache(ftypes::IsLocalityChecker::Instance(), cancellable);
|
||||
|
||||
int8_t const langIndices[] = {StringUtf8Multilang::kEnglishCode, StringUtf8Multilang::kDefaultCode,
|
||||
StringUtf8Multilang::kInternationalCode};
|
||||
|
||||
set<string> const kIgnoreList = {
|
||||
"Northern Cyprus",
|
||||
"Transnistria",
|
||||
"Nagorno-Karabakh Republic",
|
||||
"Republic of Artsakh",
|
||||
"Saint Helena, Ascension and Tristan da Cunha",
|
||||
"Somaliland",
|
||||
};
|
||||
|
||||
auto const features = cache.Get(mwmContext);
|
||||
features.ForEach([&](uint64_t fid)
|
||||
{
|
||||
auto ft = g.GetFeatureByIndex(base::asserted_cast<uint32_t>(fid));
|
||||
TEST(ft, ());
|
||||
|
||||
ftypes::LocalityType const type = ftypes::IsLocalityChecker::Instance().GetType(*ft);
|
||||
if (type != ftypes::LocalityType::Country)
|
||||
return;
|
||||
|
||||
bool found = false;
|
||||
for (auto const lang : langIndices)
|
||||
{
|
||||
std::string const name(ft->GetName(lang));
|
||||
if (!name.empty())
|
||||
{
|
||||
auto const it = synonyms.find(name);
|
||||
if (it == synonyms.end())
|
||||
found = storage.IsNode(name) || kIgnoreList.count(name) != 0;
|
||||
else
|
||||
found = storage.IsNode(it->second);
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this test fails, most likely somebody added fake place=country object into OSM.
|
||||
TEST(found, ("Cannot find countries.txt record for country feature:", ft->DebugString()));
|
||||
});
|
||||
}
|
||||
93
libs/map/map_tests/elevation_info_tests.cpp
Normal file
93
libs/map/map_tests/elevation_info_tests.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/elevation_info.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
namespace elevation_info_tests
|
||||
{
|
||||
using namespace geometry;
|
||||
using namespace location;
|
||||
|
||||
GpsInfo const BuildGpsInfo(double latitude, double longitude, double altitude)
|
||||
{
|
||||
GpsInfo gpsInfo;
|
||||
gpsInfo.m_latitude = latitude;
|
||||
gpsInfo.m_longitude = longitude;
|
||||
gpsInfo.m_altitude = altitude;
|
||||
return gpsInfo;
|
||||
}
|
||||
|
||||
UNIT_TEST(ElevationInfo_EmptyMultiGeometry)
|
||||
{
|
||||
ElevationInfo ei;
|
||||
TEST_EQUAL(0, ei.GetSize(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ElevationInfo_FromMultiGeometry)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
auto const point1 = PointWithAltitude({0.0, 0.0}, 100);
|
||||
auto const point2 = PointWithAltitude({1.0, 1.0}, 150);
|
||||
auto const point3 = PointWithAltitude({2.0, 2.0}, 50);
|
||||
geometry.AddLine({point1, point2, point3});
|
||||
ElevationInfo ei(geometry.m_lines);
|
||||
|
||||
TEST_EQUAL(3, ei.GetSize(), ());
|
||||
|
||||
double distance = 0;
|
||||
TEST_EQUAL(ei.GetPoints()[0].m_distance, distance, ());
|
||||
distance += mercator::DistanceOnEarth(point1, point2);
|
||||
TEST_EQUAL(ei.GetPoints()[1].m_distance, distance, ());
|
||||
distance += mercator::DistanceOnEarth(point2, point3);
|
||||
TEST_EQUAL(ei.GetPoints()[2].m_distance, distance, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ElevationInfo_MultipleLines)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
geometry.AddLine(
|
||||
{PointWithAltitude({0.0, 0.0}, 100), PointWithAltitude({1.0, 1.0}, 150), PointWithAltitude({1.0, 1.0}, 140)});
|
||||
geometry.AddLine(
|
||||
{PointWithAltitude({2.0, 2.0}, 50), PointWithAltitude({3.0, 3.0}, 75), PointWithAltitude({3.0, 3.0}, 60)});
|
||||
geometry.AddLine({PointWithAltitude({4.0, 4.0}, 200), PointWithAltitude({5.0, 5.0}, 250)});
|
||||
ElevationInfo ei(geometry.m_lines);
|
||||
|
||||
TEST_EQUAL(8, ei.GetSize(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ElevationInfo_SegmentDistances)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
geometry.AddLine({PointWithAltitude({0.0, 0.0}), PointWithAltitude({1.0, 0.0})});
|
||||
geometry.AddLine({PointWithAltitude({2.0, 0.0}), PointWithAltitude({3.0, 0.0})});
|
||||
geometry.AddLine({PointWithAltitude({4.0, 0.0}), PointWithAltitude({5.0, 0.0})});
|
||||
|
||||
ElevationInfo ei(geometry.m_lines);
|
||||
auto const & segmentDistances = ei.GetSegmentsDistances();
|
||||
auto const points = ei.GetPoints();
|
||||
|
||||
TEST_EQUAL(segmentDistances.size(), 2, ());
|
||||
TEST_EQUAL(segmentDistances[0], ei.GetPoints()[2].m_distance, ());
|
||||
TEST_EQUAL(segmentDistances[1], ei.GetPoints()[4].m_distance, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ElevationInfo_BuildWithGpsPoints)
|
||||
{
|
||||
auto ei = ElevationInfo();
|
||||
ei.AddGpsPoints({
|
||||
BuildGpsInfo(0.0, 0.0, 0),
|
||||
BuildGpsInfo(1.0, 1.0, 50),
|
||||
BuildGpsInfo(2.0, 2.0, 100),
|
||||
});
|
||||
ei.AddGpsPoints({BuildGpsInfo(3.0, 3.0, -50)});
|
||||
ei.AddGpsPoints({BuildGpsInfo(4.0, 4.0, 0)});
|
||||
ei.AddGpsPoints({});
|
||||
|
||||
TEST_EQUAL(5, ei.GetSize(), ());
|
||||
TEST_EQUAL(ei.GetSegmentsDistances().size(), 0, ());
|
||||
}
|
||||
} // namespace elevation_info_tests
|
||||
147
libs/map/map_tests/extrapolator_tests.cpp
Normal file
147
libs/map/map_tests/extrapolator_tests.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/extrapolation/extrapolator.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace location;
|
||||
using namespace extrapolation;
|
||||
|
||||
void TestGpsInfo(GpsInfo const & tested, GpsInfo const & expected)
|
||||
{
|
||||
double constexpr kEpsilon = 1e-5;
|
||||
TEST_EQUAL(tested.m_source, expected.m_source, ());
|
||||
TEST(AlmostEqualAbs(tested.m_latitude, expected.m_latitude, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_longitude, expected.m_longitude, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_horizontalAccuracy, expected.m_horizontalAccuracy, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_altitude, expected.m_altitude, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_verticalAccuracy, expected.m_verticalAccuracy, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_bearing, expected.m_bearing, kEpsilon), ());
|
||||
TEST(AlmostEqualAbs(tested.m_speed, expected.m_speed, kEpsilon), ());
|
||||
}
|
||||
|
||||
GpsInfo GetGpsInfo(double timestampS, double lat, double lon, double altitude, double speed)
|
||||
{
|
||||
return GpsInfo{EAppleNative,
|
||||
timestampS,
|
||||
lat,
|
||||
lon,
|
||||
10.0 /* m_horizontalAccuracy */,
|
||||
altitude,
|
||||
10.0 /* m_verticalAccuracy */,
|
||||
0.0 /* m_bearing */,
|
||||
speed};
|
||||
}
|
||||
|
||||
UNIT_TEST(LinearExtrapolation)
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestampS */, 1.0 /* m_latitude */, 1.0 /* m_longitude */,
|
||||
1.0 /* m_altitude */, 10.0 /* m_speed */);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestampS */, 1.01 /* m_latitude */, 1.01 /* m_longitude */,
|
||||
2.0 /* m_altitude */, 12.0 /* m_speed */);
|
||||
|
||||
// 0 ms after |point2|.
|
||||
TestGpsInfo(LinearExtrapolation(loc1, loc2, 0 /* timeAfterPoint2Ms */), loc2);
|
||||
|
||||
// 100 ms after |point2|.
|
||||
{
|
||||
GpsInfo const expected = GetGpsInfo(1.1 /* timestampS */, 1.011 /* m_latitude */, 1.011 /* m_longitude */,
|
||||
2.1 /* m_altitude */, 12.2 /* m_speed */);
|
||||
TestGpsInfo(LinearExtrapolation(loc1, loc2, 100 /* timeAfterPoint2Ms */), expected);
|
||||
}
|
||||
|
||||
// 200 ms after |point2|.
|
||||
{
|
||||
GpsInfo const expected = GetGpsInfo(1.2 /* timestampS */, 1.012 /* m_latitude */, 1.012 /* m_longitude */,
|
||||
2.2 /* m_altitude */, 12.4 /* m_speed */);
|
||||
TestGpsInfo(LinearExtrapolation(loc1, loc2, 200 /* timeAfterPoint2Ms */), expected);
|
||||
}
|
||||
|
||||
// 1000 ms after |point2|.
|
||||
{
|
||||
GpsInfo const expected = GetGpsInfo(2.0 /* timestampS */, 1.02 /* m_latitude */, 1.02 /* m_longitude */,
|
||||
3.0 /* m_altitude */, 14.0 /* m_speed */);
|
||||
TestGpsInfo(LinearExtrapolation(loc1, loc2, 1000 /* timeAfterPoint2Ms */), expected);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(AreCoordsGoodForExtrapolation)
|
||||
{
|
||||
double constexpr kAltitude = 1.0;
|
||||
double constexpr kSpeed = 10.0;
|
||||
{
|
||||
GpsInfo loc1;
|
||||
GpsInfo loc2;
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Locations are not valid."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 10.0 /* lat */, 179.999999 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 10.0 /* lat */, -179.999999 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Crossing meridian 180."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 10.0 /* lat */, 179.999997 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 10.0 /* lat */, 179.999999 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Near meridian 180."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 10.0 /* lat */, 179.999995 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 10.0 /* lat */, 179.999996 /* lon */, kAltitude, kSpeed);
|
||||
TEST(AreCoordsGoodForExtrapolation(loc1, loc2), ("Near meridian 180 but ok."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 10.0 /* lat */, -179.999997 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 10.0 /* lat */, -179.999999 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Near meridian -180."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 89.9997 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 89.9999 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Close to North Pole."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 89.9997 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 89.9998 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
TEST(AreCoordsGoodForExtrapolation(loc1, loc2), ("Close to North Pole but ok."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, -89.9997 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, -89.9999 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Close to South Pole."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, -89.9997 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, -89.9998 /* lat */, -10.0 /* lon */, kAltitude, kSpeed);
|
||||
TEST(AreCoordsGoodForExtrapolation(loc1, loc2), ("Close to South Pole but ok."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 10.0 /* lat */, -179.999995 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 10.0 /* lat */, -179.999996 /* lon */, kAltitude, kSpeed);
|
||||
TEST(AreCoordsGoodForExtrapolation(loc1, loc2), ("Near meridian -180 but ok."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 1.0 /* lat */, 10.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 2.0 /* lat */, 10.0 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Locations are too far."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 1.0 /* lat */, 1.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(1.0 /* timestamp */, 1.0 /* lat */, 1.00001 /* lon */, kAltitude, kSpeed);
|
||||
TEST(AreCoordsGoodForExtrapolation(loc1, loc2), ("Locations are close enough."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 1.0 /* lat */, 1.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(0.0 /* timestamp */, 1.0 /* lat */, 1.00001 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Time is the same."));
|
||||
}
|
||||
{
|
||||
GpsInfo const loc1 = GetGpsInfo(0.0 /* timestamp */, 1.0 /* lat */, 1.0 /* lon */, kAltitude, kSpeed);
|
||||
GpsInfo const loc2 = GetGpsInfo(3.0 /* timestamp */, 1.0 /* lat */, 1.00001 /* lon */, kAltitude, kSpeed);
|
||||
TEST(!AreCoordsGoodForExtrapolation(loc1, loc2), ("Too rare locations."));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
65
libs/map/map_tests/feature_getters_tests.cpp
Normal file
65
libs/map/map_tests/feature_getters_tests.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
UNIT_TEST(Framework_ForEachFeatureAtPoint_And_Others)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
Framework frm(FrameworkParams(false /* m_enableDiffs */));
|
||||
frm.DeregisterAllMaps();
|
||||
frm.RegisterMap(platform::LocalCountryFile::MakeForTesting("minsk-pass"));
|
||||
|
||||
// May vary according to the new minsk-pass data.
|
||||
vector<char const *> types = {
|
||||
"highway|footway|", "hwtag|yesbicycle|", "psurface|paved_good|",
|
||||
|
||||
"highway|service|", "psurface|paved_good|",
|
||||
|
||||
"amenity|parking|",
|
||||
|
||||
"barrier|lift_gate|",
|
||||
};
|
||||
frm.ForEachFeatureAtPoint([&](FeatureType & ft)
|
||||
{
|
||||
ft.ForEachType([&types](uint32_t type)
|
||||
{
|
||||
string const strType = classif().GetFullObjectName(type);
|
||||
auto found = find(types.begin(), types.end(), strType);
|
||||
TEST(found != types.end(), (strType));
|
||||
types.erase(found);
|
||||
});
|
||||
}, mercator::FromLatLon(53.8826576, 27.5378385));
|
||||
TEST_EQUAL(0, types.size(), (types));
|
||||
|
||||
ftypes::IsBuildingChecker const & isBuilding = ftypes::IsBuildingChecker::Instance();
|
||||
{
|
||||
// Restaurant in the building.
|
||||
auto const id = frm.GetFeatureAtPoint(mercator::FromLatLon(53.89395, 27.567365));
|
||||
TEST(id.IsValid(), ());
|
||||
frm.GetDataSource().ReadFeature([&](FeatureType & ft)
|
||||
{
|
||||
TEST_EQUAL("Родны Кут", ft.GetName(StringUtf8Multilang::kDefaultCode), ());
|
||||
TEST(!isBuilding(ft), ());
|
||||
}, id);
|
||||
}
|
||||
|
||||
{
|
||||
// Same building as above, very close to the restaurant.
|
||||
auto const id = frm.GetFeatureAtPoint(mercator::FromLatLon(53.893603, 27.567032));
|
||||
TEST(id.IsValid(), ());
|
||||
frm.GetDataSource().ReadFeature([&](FeatureType & ft) { TEST(isBuilding(ft), ()); }, id);
|
||||
}
|
||||
}
|
||||
65
libs/map/map_tests/gps_track_collection_test.cpp
Normal file
65
libs/map/map_tests/gps_track_collection_test.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/gps_track_collection.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace gps_track_collection_test
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
location::GpsInfo MakeGpsTrackInfo(double timestamp, ms::LatLon const & ll, double speed)
|
||||
{
|
||||
location::GpsInfo info;
|
||||
info.m_timestamp = timestamp;
|
||||
info.m_speed = speed;
|
||||
info.m_latitude = ll.m_lat;
|
||||
info.m_longitude = ll.m_lon;
|
||||
return info;
|
||||
}
|
||||
|
||||
UNIT_TEST(GpsTrackCollection_Simple)
|
||||
{
|
||||
time_t const t = system_clock::to_time_t(system_clock::now());
|
||||
double const timestamp = t;
|
||||
LOG(LINFO, ("Timestamp", ctime(&t), timestamp));
|
||||
|
||||
GpsTrackCollection collection;
|
||||
|
||||
std::map<size_t, location::GpsInfo> data;
|
||||
|
||||
for (size_t i = 0; i < 50; ++i)
|
||||
{
|
||||
auto info = MakeGpsTrackInfo(timestamp + i, ms::LatLon(-90 + i, -180 + i), i);
|
||||
std::pair<size_t, size_t> addedIds = collection.Add({info});
|
||||
TEST_EQUAL(addedIds.second, i, ());
|
||||
data[addedIds.second] = info;
|
||||
}
|
||||
|
||||
TEST_EQUAL(50, collection.GetSize(), ());
|
||||
|
||||
collection.ForEach([&data](location::GpsInfo const & info, size_t id) -> bool
|
||||
{
|
||||
TEST(data.end() != data.find(id), ());
|
||||
location::GpsInfo const & originInfo = data[id];
|
||||
TEST_EQUAL(info.m_latitude, originInfo.m_latitude, ());
|
||||
TEST_EQUAL(info.m_longitude, originInfo.m_longitude, ());
|
||||
TEST_EQUAL(info.m_speed, originInfo.m_speed, ());
|
||||
TEST_EQUAL(info.m_timestamp, originInfo.m_timestamp, ());
|
||||
return true;
|
||||
});
|
||||
|
||||
auto res = collection.Clear();
|
||||
TEST_EQUAL(res.first, 0, ());
|
||||
TEST_EQUAL(res.second, 49, ());
|
||||
|
||||
TEST_EQUAL(0, collection.GetSize(), ());
|
||||
}
|
||||
} // namespace gps_track_collection_test
|
||||
109
libs/map/map_tests/gps_track_storage_test.cpp
Normal file
109
libs/map/map_tests/gps_track_storage_test.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/gps_track_storage.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gps_track_storage_test
|
||||
{
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
location::GpsInfo Make(double timestamp, ms::LatLon const & ll, double speed)
|
||||
{
|
||||
location::GpsInfo info;
|
||||
info.m_timestamp = timestamp;
|
||||
info.m_speed = speed;
|
||||
info.m_latitude = ll.m_lat;
|
||||
info.m_longitude = ll.m_lon;
|
||||
info.m_source = location::EAndroidNative;
|
||||
return info;
|
||||
}
|
||||
|
||||
inline string GetGpsTrackFilePath()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().WritableDir(), "gpstrack_test.bin");
|
||||
}
|
||||
|
||||
UNIT_TEST(GpsTrackStorage_WriteRead)
|
||||
{
|
||||
time_t const t = system_clock::to_time_t(system_clock::now());
|
||||
double const timestamp = t;
|
||||
LOG(LINFO, ("Timestamp", ctime(&t), timestamp));
|
||||
|
||||
string const filePath = GetGpsTrackFilePath();
|
||||
SCOPE_GUARD(gpsTestFileDeleter, bind(FileWriter::DeleteFileX, filePath));
|
||||
FileWriter::DeleteFileX(filePath);
|
||||
|
||||
size_t const itemCount = 100000;
|
||||
|
||||
vector<location::GpsInfo> points;
|
||||
points.reserve(itemCount);
|
||||
for (size_t i = 0; i < itemCount; ++i)
|
||||
points.emplace_back(Make(timestamp + i, ms::LatLon(-90 + i, -180 + i), 60 + i));
|
||||
|
||||
// Open storage, write data and check written data
|
||||
{
|
||||
GpsTrackStorage stg(filePath);
|
||||
|
||||
stg.Append(points);
|
||||
|
||||
size_t i = 0;
|
||||
stg.ForEach([&](location::GpsInfo const & point) -> bool
|
||||
{
|
||||
TEST_EQUAL(point.m_latitude, points[i].m_latitude, ());
|
||||
TEST_EQUAL(point.m_longitude, points[i].m_longitude, ());
|
||||
TEST_EQUAL(point.m_timestamp, points[i].m_timestamp, ());
|
||||
TEST_EQUAL(point.m_speed, points[i].m_speed, ());
|
||||
++i;
|
||||
return true;
|
||||
});
|
||||
TEST_EQUAL(i, itemCount, ());
|
||||
}
|
||||
|
||||
// Open storage and check previously written data
|
||||
{
|
||||
GpsTrackStorage stg(filePath);
|
||||
|
||||
size_t i = 0;
|
||||
stg.ForEach([&](location::GpsInfo const & point) -> bool
|
||||
{
|
||||
TEST_EQUAL(point.m_latitude, points[i].m_latitude, ());
|
||||
TEST_EQUAL(point.m_longitude, points[i].m_longitude, ());
|
||||
TEST_EQUAL(point.m_timestamp, points[i].m_timestamp, ());
|
||||
TEST_EQUAL(point.m_speed, points[i].m_speed, ());
|
||||
++i;
|
||||
return true;
|
||||
});
|
||||
TEST_EQUAL(i, itemCount, ());
|
||||
|
||||
// Clear data
|
||||
stg.Clear();
|
||||
}
|
||||
|
||||
// Open storage and check there is no data
|
||||
{
|
||||
GpsTrackStorage stg(filePath);
|
||||
|
||||
size_t i = 0;
|
||||
stg.ForEach([&](location::GpsInfo const & point) -> bool
|
||||
{
|
||||
++i;
|
||||
return true;
|
||||
});
|
||||
TEST_EQUAL(i, 0, ());
|
||||
}
|
||||
}
|
||||
} // namespace gps_track_storage_test
|
||||
145
libs/map/map_tests/gps_track_test.cpp
Normal file
145
libs/map/map_tests/gps_track_test.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/gps_track.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace gps_track_test
|
||||
{
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
inline location::GpsInfo Make(double timestamp, ms::LatLon const & ll, double speed)
|
||||
{
|
||||
location::GpsInfo info;
|
||||
info.m_timestamp = timestamp;
|
||||
info.m_speed = speed;
|
||||
info.m_latitude = ll.m_lat;
|
||||
info.m_longitude = ll.m_lon;
|
||||
info.m_horizontalAccuracy = 15;
|
||||
info.m_source = location::EAndroidNative;
|
||||
return info;
|
||||
}
|
||||
|
||||
inline string GetGpsTrackFilePath()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().WritableDir(), "gpstrack_test.bin");
|
||||
}
|
||||
|
||||
class GpsTrackCallback
|
||||
{
|
||||
public:
|
||||
GpsTrackCallback() : m_toRemove(make_pair(GpsTrack::kInvalidId, GpsTrack::kInvalidId)), m_gotCallback(false) {}
|
||||
void OnUpdate(vector<pair<size_t, location::GpsInfo>> && toAdd, pair<size_t, size_t> const & toRemove)
|
||||
{
|
||||
m_toAdd = std::move(toAdd);
|
||||
m_toRemove = toRemove;
|
||||
|
||||
lock_guard<mutex> lg(m_mutex);
|
||||
m_gotCallback = true;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
void Reset()
|
||||
{
|
||||
m_toAdd.clear();
|
||||
m_toRemove = make_pair(GpsTrack::kInvalidId, GpsTrack::kInvalidId);
|
||||
|
||||
lock_guard<mutex> lg(m_mutex);
|
||||
m_gotCallback = false;
|
||||
}
|
||||
bool WaitForCallback(seconds t)
|
||||
{
|
||||
unique_lock<mutex> ul(m_mutex);
|
||||
return m_cv.wait_for(ul, t, [this]() -> bool { return m_gotCallback; });
|
||||
}
|
||||
|
||||
vector<pair<size_t, location::GpsInfo>> m_toAdd;
|
||||
pair<size_t, size_t> m_toRemove;
|
||||
|
||||
private:
|
||||
mutex m_mutex;
|
||||
condition_variable m_cv;
|
||||
bool m_gotCallback;
|
||||
};
|
||||
|
||||
seconds const kWaitForCallbackTimeout = seconds(5);
|
||||
|
||||
UNIT_TEST(GpsTrack_Simple)
|
||||
{
|
||||
string const filePath = GetGpsTrackFilePath();
|
||||
SCOPE_GUARD(gpsTestFileDeleter, bind(FileWriter::DeleteFileX, filePath));
|
||||
FileWriter::DeleteFileX(filePath);
|
||||
|
||||
time_t const t = system_clock::to_time_t(system_clock::now());
|
||||
double const timestamp = t;
|
||||
LOG(LINFO, ("Timestamp", ctime(&t), timestamp));
|
||||
|
||||
size_t const writeItemCount = 50000;
|
||||
|
||||
vector<location::GpsInfo> points;
|
||||
points.reserve(writeItemCount);
|
||||
for (size_t i = 0; i < writeItemCount; ++i)
|
||||
points.emplace_back(Make(timestamp + i, ms::LatLon(-90.0 + i, -180.0 + i), 10 + i));
|
||||
|
||||
// Store points
|
||||
{
|
||||
GpsTrack track(filePath);
|
||||
|
||||
track.AddPoints(points);
|
||||
|
||||
GpsTrackCallback callback;
|
||||
|
||||
track.SetCallback(bind(&GpsTrackCallback::OnUpdate, &callback, placeholders::_1, placeholders::_2));
|
||||
|
||||
TEST(callback.WaitForCallback(kWaitForCallbackTimeout), ());
|
||||
|
||||
TEST_EQUAL(callback.m_toRemove.first, GpsTrack::kInvalidId, ());
|
||||
TEST_EQUAL(callback.m_toRemove.second, GpsTrack::kInvalidId, ());
|
||||
TEST_EQUAL(callback.m_toAdd.size(), writeItemCount, ());
|
||||
for (size_t i = 0; i < writeItemCount; ++i)
|
||||
{
|
||||
TEST_EQUAL(i, callback.m_toAdd[i].first, ());
|
||||
TEST_EQUAL(points[i].m_timestamp, callback.m_toAdd[i].second.m_timestamp, ());
|
||||
TEST_EQUAL(points[i].m_speed, callback.m_toAdd[i].second.m_speed, ());
|
||||
TEST_EQUAL(points[i].m_latitude, callback.m_toAdd[i].second.m_latitude, ());
|
||||
TEST_EQUAL(points[i].m_longitude, callback.m_toAdd[i].second.m_longitude, ());
|
||||
}
|
||||
}
|
||||
|
||||
// Restore points
|
||||
{
|
||||
GpsTrack track(filePath);
|
||||
|
||||
GpsTrackCallback callback;
|
||||
|
||||
track.SetCallback(bind(&GpsTrackCallback::OnUpdate, &callback, placeholders::_1, placeholders::_2));
|
||||
|
||||
TEST(callback.WaitForCallback(kWaitForCallbackTimeout), ());
|
||||
|
||||
TEST_EQUAL(callback.m_toRemove.first, GpsTrack::kInvalidId, ());
|
||||
TEST_EQUAL(callback.m_toRemove.second, GpsTrack::kInvalidId, ());
|
||||
TEST_EQUAL(callback.m_toAdd.size(), writeItemCount, ());
|
||||
for (size_t i = 0; i < writeItemCount; ++i)
|
||||
{
|
||||
TEST_EQUAL(i, callback.m_toAdd[i].first, ());
|
||||
TEST_EQUAL(points[i].m_timestamp, callback.m_toAdd[i].second.m_timestamp, ());
|
||||
TEST_EQUAL(points[i].m_speed, callback.m_toAdd[i].second.m_speed, ());
|
||||
TEST_EQUAL(points[i].m_latitude, callback.m_toAdd[i].second.m_latitude, ());
|
||||
TEST_EQUAL(points[i].m_longitude, callback.m_toAdd[i].second.m_longitude, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace gps_track_test
|
||||
68
libs/map/map_tests/kmz_unarchive_test.cpp
Normal file
68
libs/map/map_tests/kmz_unarchive_test.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
UNIT_TEST(KMZ_UnzipTest)
|
||||
{
|
||||
std::string const kmzFile = GetPlatform().TestsDataPathForFile("test_data/kml/test.kmz");
|
||||
auto const filePaths = GetKMLOrGPXFilesPathsToLoad(kmzFile);
|
||||
TEST_EQUAL(1, filePaths.size(), ());
|
||||
std::string const filePath = filePaths[0];
|
||||
TEST(!filePath.empty(), ());
|
||||
SCOPE_GUARD(fileGuard, std::bind(&base::DeleteFileX, filePath));
|
||||
|
||||
TEST(filePath.ends_with("doc.kml"), (filePath));
|
||||
|
||||
auto const kmlData = LoadKmlFile(filePath, KmlFileType::Text);
|
||||
TEST(kmlData != nullptr, ());
|
||||
|
||||
TEST_EQUAL(kmlData->m_bookmarksData.size(), 6, ("Category wrong number of bookmarks"));
|
||||
|
||||
{
|
||||
Bookmark const bm(std::move(kmlData->m_bookmarksData[0]));
|
||||
TEST_EQUAL(kml::GetDefaultStr(bm.GetName()), ("Lahaina Breakwall"), ("KML wrong name!"));
|
||||
TEST_EQUAL(bm.GetColor(), kml::PredefinedColor::Red, ("KML wrong type!"));
|
||||
TEST_ALMOST_EQUAL_ULPS(bm.GetPivot().x, -156.6777046791284, ("KML wrong org x!"));
|
||||
TEST_ALMOST_EQUAL_ULPS(bm.GetPivot().y, 21.34256685860084, ("KML wrong org y!"));
|
||||
TEST_EQUAL(bm.GetScale(), 0, ("KML wrong scale!"));
|
||||
}
|
||||
{
|
||||
Bookmark const bm(std::move(kmlData->m_bookmarksData[1]));
|
||||
TEST_EQUAL(kml::GetDefaultStr(bm.GetName()), ("Seven Sacred Pools, Kipahulu"), ("KML wrong name!"));
|
||||
TEST_EQUAL(bm.GetColor(), kml::PredefinedColor::Red, ("KML wrong type!"));
|
||||
TEST_ALMOST_EQUAL_ULPS(bm.GetPivot().x, -156.0405130750025, ("KML wrong org x!"));
|
||||
TEST_ALMOST_EQUAL_ULPS(bm.GetPivot().y, 21.12480639056074, ("KML wrong org y!"));
|
||||
TEST_EQUAL(bm.GetScale(), 0, ("KML wrong scale!"));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Multi_KML_KMZ_UnzipTest)
|
||||
{
|
||||
std::string const kmzFile = GetPlatform().TestsDataPathForFile("test_data/kml/BACRNKMZ.kmz");
|
||||
auto const filePaths = GetKMLOrGPXFilesPathsToLoad(kmzFile);
|
||||
std::vector<std::string> expectedFileNames = {
|
||||
"BACRNKMZfilesCampgrounds 26may2022 green and tree icon",
|
||||
"BACRNKMZfilesIndoor Accommodations 26may2022 placemark purple and bed icon",
|
||||
"BACRNKMZfilesRoute 1 Canada - West-East Daily Segments",
|
||||
"BACRNKMZfilesRoute 2 Canada - West-East Daily Segments",
|
||||
"BACRNKMZfilesRoute Connector Canada - West-East Daily Segments",
|
||||
"BACRNKMZdoc"
|
||||
|
||||
};
|
||||
TEST_EQUAL(expectedFileNames.size(), filePaths.size(), ());
|
||||
for (auto const & filePath : filePaths)
|
||||
{
|
||||
auto matched = false;
|
||||
for (auto const & expectedFileName : expectedFileNames)
|
||||
{
|
||||
matched = filePath.find(expectedFileName) != std::string::npos;
|
||||
if (matched)
|
||||
break;
|
||||
}
|
||||
TEST(matched, ("Unexpected file path: " + filePath));
|
||||
}
|
||||
}
|
||||
60
libs/map/map_tests/mwm_set_test.cpp
Normal file
60
libs/map/map_tests/mwm_set_test.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#ifndef OMIM_OS_WINDOWS
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
using namespace base;
|
||||
using namespace platform;
|
||||
|
||||
/*
|
||||
* This test is useless because of we don't build offsets index from now.
|
||||
#ifndef OMIM_OS_WINDOWS
|
||||
UNIT_TEST(MwmSet_FileSystemErrors)
|
||||
{
|
||||
string const dir = GetPlatform().WritableDir();
|
||||
|
||||
CountryFile file("minsk-pass");
|
||||
LocalCountryFile localFile(dir, file, 0);
|
||||
TEST(CountryIndexes::DeleteFromDisk(localFile), ());
|
||||
|
||||
// Maximum level to check exception handling logic.
|
||||
LogLevel oldLevel = g_LogAbortLevel;
|
||||
g_LogAbortLevel = LCRITICAL;
|
||||
|
||||
// Remove writable permission.
|
||||
int const readOnlyMode = S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH;
|
||||
TEST_EQUAL(chmod(dir.c_str(), readOnlyMode), 0, ());
|
||||
|
||||
auto restoreFn = [oldLevel, &dir, readOnlyMode] ()
|
||||
{
|
||||
g_LogAbortLevel = oldLevel;
|
||||
TEST_EQUAL(chmod(dir.c_str(), readOnlyMode | S_IWUSR), 0, ());
|
||||
};
|
||||
SCOPE_GUARD(restoreGuard, restoreFn);
|
||||
|
||||
DataSource dataSource;
|
||||
auto p = dataSource.RegisterMap(localFile);
|
||||
TEST_EQUAL(p.second, DataSource::RegResult::Success, ());
|
||||
|
||||
// Registering should pass ok.
|
||||
TEST(dataSource.GetMwmIdByCountryFile(file) != DataSource::MwmId(), ());
|
||||
|
||||
// Getting handle causes feature offsets index building which should fail
|
||||
// because of write permissions.
|
||||
TEST(!dataSource.GetMwmHandleById(p.first).IsAlive(), ());
|
||||
|
||||
// Map is automatically deregistered after the fail.
|
||||
vector<shared_ptr<MwmInfo>> infos;
|
||||
dataSource.GetMwmsInfo(infos);
|
||||
TEST(infos.empty(), ());
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
577
libs/map/map_tests/mwm_url_tests.cpp
Normal file
577
libs/map/map_tests/mwm_url_tests.cpp
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/mwm_url.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "coding/url.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace mwm_url_tests
|
||||
{
|
||||
using namespace std;
|
||||
using namespace url_scheme;
|
||||
using UrlType = ParsedMapApi::UrlType;
|
||||
|
||||
double const kEps = 1e-10;
|
||||
|
||||
UNIT_TEST(MapApiSmoke)
|
||||
{
|
||||
string urlString =
|
||||
"mapswithme://"
|
||||
"map?ll=38.970559,-9.419289&ignoreThisParam=Yes&z=17&n=Point%20Name&s=black&backurl=https%3A%2F%2Fcomaps.app";
|
||||
TEST(url::Url(urlString).IsValid(), ());
|
||||
|
||||
ParsedMapApi test(urlString);
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(test.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = test.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 38.970559, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, -9.419289, kEps, ());
|
||||
TEST_EQUAL(p0.m_name, "Point Name", ());
|
||||
TEST_EQUAL(p0.m_id, "", ());
|
||||
TEST_EQUAL(p0.m_style, "black", ());
|
||||
TEST_ALMOST_EQUAL_ABS(test.GetZoomLevel(), 17.0, kEps, ());
|
||||
TEST_EQUAL(test.GetGlobalBackUrl(), "https://comaps.app", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(RouteApiSmoke)
|
||||
{
|
||||
string const urlString = "mapswithme://route?sll=1,1&saddr=name0&dll=2,2&daddr=name1&type=vehicle";
|
||||
TEST(url::Url(urlString).IsValid(), ());
|
||||
|
||||
ParsedMapApi test(urlString);
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Route, ());
|
||||
TEST_EQUAL(test.GetRoutePoints().size(), 2, ());
|
||||
RoutePoint const & p0 = test.GetRoutePoints()[0];
|
||||
RoutePoint const & p1 = test.GetRoutePoints()[1];
|
||||
TEST_EQUAL(p0.m_org, mercator::FromLatLon(1, 1), ());
|
||||
TEST_EQUAL(p0.m_name, "name0", ());
|
||||
TEST_EQUAL(p1.m_org, mercator::FromLatLon(2, 2), ());
|
||||
TEST_EQUAL(p1.m_name, "name1", ());
|
||||
TEST_EQUAL(test.GetRoutingType(), "vehicle", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(SearchApiSmoke)
|
||||
{
|
||||
string const urlString =
|
||||
"mapsme://search?query=Saint%20Hilarion&cll=35.3166654,33.2833322&locale=ru&map&appname=CoMaps";
|
||||
TEST(url::Url(urlString).IsValid(), ());
|
||||
|
||||
ParsedMapApi test(urlString);
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = test.GetSearchRequest();
|
||||
ms::LatLon latlon = test.GetCenterLatLon();
|
||||
TEST_EQUAL(request.m_query, "Saint Hilarion", ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lat, 35.3166654, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lon, 33.2833322, kEps, ());
|
||||
TEST_EQUAL(request.m_locale, "ru", ());
|
||||
TEST_EQUAL(test.GetAppName(), "CoMaps", ());
|
||||
TEST(request.m_isSearchOnMap, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(SearchApiAdvanced)
|
||||
{
|
||||
{
|
||||
// Ignore wrong cll=.
|
||||
ParsedMapApi test("cm://search?query=aaa&cll=1,1,1");
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = test.GetSearchRequest();
|
||||
ms::LatLon latlon = test.GetCenterLatLon();
|
||||
TEST_EQUAL(request.m_query, "aaa", ());
|
||||
TEST_EQUAL(request.m_locale, "", ());
|
||||
TEST(!request.m_isSearchOnMap, ());
|
||||
TEST_EQUAL(latlon.m_lat, ms::LatLon::kInvalid, ());
|
||||
TEST_EQUAL(latlon.m_lon, ms::LatLon::kInvalid, ());
|
||||
}
|
||||
|
||||
{
|
||||
// Don't fail on unsupported parameters.
|
||||
ParsedMapApi test("cm://search?query=aaa&ignoreThisParam=sure");
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = test.GetSearchRequest();
|
||||
ms::LatLon latlon = test.GetCenterLatLon();
|
||||
TEST_EQUAL(request.m_query, "aaa", ());
|
||||
TEST_EQUAL(request.m_locale, "", ());
|
||||
TEST(!request.m_isSearchOnMap, ());
|
||||
TEST_EQUAL(latlon.m_lat, ms::LatLon::kInvalid, ());
|
||||
TEST_EQUAL(latlon.m_lon, ms::LatLon::kInvalid, ());
|
||||
}
|
||||
|
||||
{
|
||||
// Query parameter position doesn't matter
|
||||
ParsedMapApi test("cm://search?cll=1,1&locale=ru&query=aaa");
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = test.GetSearchRequest();
|
||||
ms::LatLon latlon = test.GetCenterLatLon();
|
||||
TEST_EQUAL(request.m_query, "aaa", ());
|
||||
TEST_EQUAL(request.m_locale, "ru", ());
|
||||
TEST(!request.m_isSearchOnMap, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lat, 1.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lon, 1.0, kEps, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SearchApiInvalidUrl)
|
||||
{
|
||||
ParsedMapApi test;
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://search?"), UrlType::Incorrect, ("Empty query string"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://search?query"), UrlType::Incorrect, ("Search query can't be empty"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://serch?cll=1,1&locale=ru&query=aaa"), UrlType::Incorrect,
|
||||
("Incorrect url type"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://search?Query=fff"), UrlType::Incorrect, ("The parser is case sensitive"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("incorrect://search?query=aaa"), UrlType::Incorrect, ("Wrong prefix"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("http://search?query=aaa"), UrlType::Incorrect, ("Wrong prefix"));
|
||||
}
|
||||
|
||||
UNIT_TEST(LeadApiSmoke)
|
||||
{
|
||||
ParsedMapApi test;
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://lead?utm_source=a&utm_medium=b&utm_campaign=c&utm_content=d&utm_term=e"),
|
||||
UrlType::Incorrect, ("Lead API is not supported"));
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiInvalidUrl)
|
||||
{
|
||||
ParsedMapApi test;
|
||||
TEST_EQUAL(test.SetUrlAndParse("competitors://map?ll=12.3,34.54"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://ggg?ll=12.3,34.54"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mwm://"), UrlType::Incorrect, ("No parameters"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://map?"), UrlType::Incorrect, ("No longtitude"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://map?ll=1,2,3"), UrlType::Incorrect, ("Too many values for ll"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://fffff://map?ll=1,2"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapsme://map?LL=1,1"), UrlType::Incorrect, ("The parser is case sensitive"));
|
||||
}
|
||||
|
||||
UNIT_TEST(RouteApiInvalidUrl)
|
||||
{
|
||||
ParsedMapApi test;
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&saddr=name0&dll=2,2&daddr=name2"), UrlType::Incorrect,
|
||||
("Route type doesn't exist"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&saddr=name0"), UrlType::Incorrect,
|
||||
("Destination doesn't exist"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&dll=2,2&type=vehicle"), UrlType::Incorrect,
|
||||
("Source or destination name doesn't exist"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?saddr=name0&daddr=name1&type=vehicle"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&sll=2.2&type=vehicle"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&dll=2.2&type=666"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&saddr=name0&sll=2,2&saddr=name1&type=vehicle"),
|
||||
UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?sll=1,1&type=vehicle"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse(
|
||||
"mapswithme://route?sll=1,1&saddr=name0&sll=2,2&saddr=name1&sll=1,1&saddr=name0&type=vehicle"),
|
||||
UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://route?type=vehicle"), UrlType::Incorrect, ());
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://rout?sll=1,1&saddr=name0&dll=2,2&daddr=name1&type=vehicle"),
|
||||
UrlType::Incorrect, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiLatLonLimits)
|
||||
{
|
||||
ParsedMapApi test;
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://map?ll=-91,10"), UrlType::Incorrect, ("Invalid latitude"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mwm://map?ll=523.55,10"), UrlType::Incorrect, ("Invalid latitude"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://map?ll=23.55,450"), UrlType::Incorrect, ("Invalid longtitude"));
|
||||
TEST_EQUAL(test.SetUrlAndParse("mapswithme://map?ll=23.55,-450"), UrlType::Incorrect, ("Invalid longtitude"));
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiPointNameBeforeLatLon)
|
||||
{
|
||||
ParsedMapApi test("mapswithme://map?n=Name&ll=1,2");
|
||||
TEST_EQUAL(test.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiPointNameOverwritten)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("mapswithme://map?ll=1,2&n=A&N=B");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
TEST_EQUAL(api.GetMapPoints()[0].m_name, "A", ());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedMapApi api("mapswithme://map?ll=1,2&n=A&n=B");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
TEST_EQUAL(api.GetMapPoints()[0].m_name, "B", ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiMultiplePoints)
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1.1,1.2&n=A&ll=2.1,2.2&ll=-3.1,-3.2&n=C");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 3, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 1.1, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 1.2, kEps, ());
|
||||
TEST_EQUAL(p0.m_name, "A", ());
|
||||
MapPoint const & p1 = api.GetMapPoints()[1];
|
||||
TEST_ALMOST_EQUAL_ABS(p1.m_lat, 2.1, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p1.m_lon, 2.2, kEps, ());
|
||||
TEST_EQUAL(p1.m_name, "", ());
|
||||
MapPoint const & p2 = api.GetMapPoints()[2];
|
||||
TEST_ALMOST_EQUAL_ABS(p2.m_lat, -3.1, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p2.m_lon, -3.2, kEps, ());
|
||||
TEST_EQUAL(p2.m_name, "C", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiInvalidPointLatLonButValidOtherParts)
|
||||
{
|
||||
ParsedMapApi api("mapswithme://map?ll=1,1,1&n=A&ll=2,2&n=B&ll=3,3,3&n=C");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiPointURLEncoded)
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=%D0%9C%D0%B8%D0%BD%D1%81%D0%BA&id=http%3A%2F%2Fmap%3Fll%3D1%2C2%26n%3Dtest");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_EQUAL(p0.m_name, "\xd0\x9c\xd0\xb8\xd0\xbd\xd1\x81\xd0\xba", ());
|
||||
TEST_EQUAL(p0.m_id, "http://map?ll=1,2&n=test", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiUrl)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("https://www.google.com/maps?q=55.751809,37.6130029");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 55.751809, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 37.6130029, kEps, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("https://www.openstreetmap.org/#map=16/33.89041/35.50664");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 33.89041, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 35.50664, kEps, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("ftp://www.google.com/maps?q=55.751809,37.6130029");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiGe0)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("cm://o4B4pYZsRs");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 47.3859, 1e-4, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 8.5766, 1e-4, ());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedMapApi api("cm://o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 47.3859, 1e-4, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 8.5766, 1e-4, ());
|
||||
TEST_EQUAL(p0.m_name, "Zoo Zürich", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("http://comaps.at/o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("https://comaps.at/o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("ge0://o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("http://ge0.me/o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("https://ge0.me/o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mapsme://o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mapswithme://o4B4pYZsRs/Zoo_Zürich");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(MapApiGeoScheme)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("geo:0,0?q=35.341714,33.32231 (Custom%20Title)");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 35.341714, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 33.32231, kEps, ());
|
||||
TEST_EQUAL(p0.m_name, "Custom Title", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("geo:0,0?q=");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 0.0, kEps, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("geo:0,0");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), 1, ());
|
||||
MapPoint const & p0 = api.GetMapPoints()[0];
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lat, 0.0, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(p0.m_lon, 0.0, kEps, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SearchApiGeoScheme)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("geo:0,0?q=Kyrenia%20Castle");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = api.GetSearchRequest();
|
||||
ms::LatLon latlon = api.GetCenterLatLon();
|
||||
TEST(!latlon.IsValid(), ());
|
||||
TEST_EQUAL(request.m_query, "Kyrenia Castle", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("geo:35.3381607,33.3290564?q=Kyrenia%20Castle");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = api.GetSearchRequest();
|
||||
ms::LatLon latlon = api.GetCenterLatLon();
|
||||
TEST_EQUAL(request.m_query, "Kyrenia Castle", ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lat, 35.3381607, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lon, 33.3290564, kEps, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("geo:0,0?q=123+Main+St,+Seattle,+WA+98101");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Search, ());
|
||||
auto const & request = api.GetSearchRequest();
|
||||
ms::LatLon latlon = api.GetCenterLatLon();
|
||||
TEST(!latlon.IsValid(), ());
|
||||
TEST_EQUAL(request.m_query, "123 Main St, Seattle, WA 98101", ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(CrosshairApi)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("cm://crosshair?cll=47.3813,8.5889&appname=Google%20Maps");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Crosshair, ());
|
||||
ms::LatLon latlon = api.GetCenterLatLon();
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lat, 47.3813, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lon, 8.5889, kEps, ());
|
||||
TEST_EQUAL(api.GetAppName(), "Google Maps", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("https://comaps.at/crosshair?cll=47.3813,8.5889&appname=Google%20Maps");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Crosshair, ());
|
||||
ms::LatLon latlon = api.GetCenterLatLon();
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lat, 47.3813, kEps, ());
|
||||
TEST_ALMOST_EQUAL_ABS(latlon.m_lon, 8.5889, kEps, ());
|
||||
TEST_EQUAL(api.GetAppName(), "Google Maps", ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(GlobalBackUrl)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName&backurl=someTestAppBackUrl");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "someTestAppBackUrl://", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName&backurl=ge0://");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "ge0://", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("cm://map?ll=1,2&n=PointName&backurl=cm://");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "cm://", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName&backurl=ge0%3A%2F%2F");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "ge0://", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName&backurl=http://mapswithme.com");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "http://mapswithme.com", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api(
|
||||
"mwm://map?ll=1,2&n=PointName&backurl=someapp://"
|
||||
"%D0%9C%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5%20%D0%9A%D0%B0%D1%80%D1%82%D1%8B");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(),
|
||||
"someapp://\xd0\x9c\xd0\xbe\xd0\xb1\xd0\xb8\xd0\xbb\xd1\x8c\xd0\xbd\xd1\x8b\xd0\xb5 "
|
||||
"\xd0\x9a\xd0\xb0\xd1\x80\xd1\x82\xd1\x8b",
|
||||
());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api(
|
||||
"mwm://"
|
||||
"map?ll=1,2&n=PointName&backurl=%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%3A%2F%2F%D0%BE%D1%"
|
||||
"82%D0%BA%D1%80%D0%BE%D0%B9%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D1%83");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "приложение://откройСсылку", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api(
|
||||
"mwm://"
|
||||
"map?ll=1,2&n=PointName&backurl=%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%3A%2F%2F%D0%BE%D1%"
|
||||
"82%D0%BA%D1%80%D0%BE%D0%B9%D0%A1%D1%81%D1%8B%D0%BB%D0%BA%D1%83");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "приложение://откройСсылку", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName&backurl=%E6%88%91%E6%84%9Bmapswithme");
|
||||
TEST_EQUAL(api.GetGlobalBackUrl(), "我愛mapswithme://", ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(VersionTest)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=1&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 1, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=kotik&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 0, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=APacanyVoobsheKotjata&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 0, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 0, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?V=666&ll=1,2&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 0, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?v=666&ll=1,2&n=PointName");
|
||||
TEST_EQUAL(api.GetApiVersion(), 666, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(AppNameTest)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=1&n=PointName&appname=Google");
|
||||
TEST_EQUAL(api.GetAppName(), "Google", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=1&n=PointName&appname=%D0%AF%D0%BD%D0%B4%D0%B5%D0%BA%D1%81");
|
||||
TEST_EQUAL(api.GetAppName(), "Яндекс", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("mwm://map?ll=1,2&v=1&n=PointName");
|
||||
TEST_EQUAL(api.GetAppName(), "", ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(OAuth2Test)
|
||||
{
|
||||
{
|
||||
ParsedMapApi api("cm://oauth2/osm/callback?code=THE_MEGA_CODE");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::OAuth2, ());
|
||||
TEST_EQUAL(api.GetOAuth2Code(), "THE_MEGA_CODE", ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("cm://oauth2/google/callback?code=THE_MEGA_CODE");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
{
|
||||
ParsedMapApi api("cm://oauth2/osm/callback?code=");
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
string generatePartOfUrl(url_scheme::MapPoint const & point)
|
||||
{
|
||||
stringstream stream;
|
||||
stream << "&ll=" << std::to_string(point.m_lat) << "," << std::to_string(point.m_lon) << "&n=" << point.m_name
|
||||
<< "&id=" << point.m_id;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
string randomString(size_t size, uint32_t seed)
|
||||
{
|
||||
string result(size, '0');
|
||||
mt19937 rng(seed);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = 'a' + rng() % 26;
|
||||
return result;
|
||||
}
|
||||
|
||||
void generateRandomTest(uint32_t numberOfPoints, size_t stringLength)
|
||||
{
|
||||
vector<url_scheme::MapPoint> vect(numberOfPoints);
|
||||
for (uint32_t i = 0; i < numberOfPoints; ++i)
|
||||
{
|
||||
url_scheme::MapPoint point;
|
||||
mt19937 rng(i);
|
||||
point.m_lat = rng() % 90;
|
||||
point.m_lat *= rng() % 2 == 0 ? 1 : -1;
|
||||
point.m_lon = rng() % 180;
|
||||
point.m_lon *= rng() % 2 == 0 ? 1 : -1;
|
||||
point.m_name = randomString(stringLength, i);
|
||||
point.m_id = randomString(stringLength, i);
|
||||
vect[i] = point;
|
||||
}
|
||||
string result = "mapswithme://map?v=1";
|
||||
for (size_t i = 0; i < vect.size(); ++i)
|
||||
result += generatePartOfUrl(vect[i]);
|
||||
|
||||
ParsedMapApi api(result);
|
||||
TEST_EQUAL(api.GetRequestType(), UrlType::Map, ());
|
||||
TEST_EQUAL(api.GetMapPoints().size(), vect.size(), ());
|
||||
for (size_t i = 0; i < vect.size(); ++i)
|
||||
{
|
||||
MapPoint const & p = api.GetMapPoints()[i];
|
||||
TEST_EQUAL(p.m_name, vect[i].m_name, ());
|
||||
TEST_EQUAL(p.m_id, vect[i].m_id, ());
|
||||
TEST(AlmostEqualULPs(p.m_lat, vect[i].m_lat), ());
|
||||
TEST(AlmostEqualULPs(p.m_lon, vect[i].m_lon), ());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(100FullEnteriesRandomTest)
|
||||
{
|
||||
generateRandomTest(100, 10);
|
||||
}
|
||||
|
||||
UNIT_TEST(StressTestRandomTest)
|
||||
{
|
||||
generateRandomTest(10000, 100);
|
||||
}
|
||||
|
||||
} // namespace mwm_url_tests
|
||||
275
libs/map/map_tests/power_manager_tests.cpp
Normal file
275
libs/map/map_tests/power_manager_tests.cpp
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/power_management/power_management_schemas.hpp"
|
||||
#include "map/power_management/power_manager.hpp"
|
||||
|
||||
#include "platform//platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "base/scope_guard.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
using namespace power_management;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SubscriberForTesting : public PowerManager::Subscriber
|
||||
{
|
||||
public:
|
||||
// PowerManager::Subscriber overrides:
|
||||
void OnPowerFacilityChanged(Facility const facility, bool enabled) override
|
||||
{
|
||||
m_onFacilityEvents.push_back({facility, enabled});
|
||||
}
|
||||
|
||||
void OnPowerSchemeChanged(Scheme const actualConfig) override { m_onShemeEvents.push_back(actualConfig); }
|
||||
|
||||
struct FacilityState
|
||||
{
|
||||
Facility m_facility;
|
||||
bool m_state;
|
||||
};
|
||||
|
||||
std::vector<FacilityState> m_onFacilityEvents;
|
||||
std::vector<Scheme> m_onShemeEvents;
|
||||
};
|
||||
|
||||
void TestIsAllFacilitiesInState(PowerManager const & manager, bool state)
|
||||
{
|
||||
auto const count = static_cast<size_t>(Facility::Count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
TEST_EQUAL(manager.IsFacilityEnabled(static_cast<Facility>(i)), state, ());
|
||||
}
|
||||
|
||||
void TestAllFacilitiesEnabledExcept(PowerManager const & manager, std::vector<Facility> const & disabledFacilities)
|
||||
{
|
||||
auto const count = static_cast<size_t>(Facility::Count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto const facility = static_cast<Facility>(i);
|
||||
TEST_EQUAL(manager.IsFacilityEnabled(facility), !base::IsExist(disabledFacilities, facility), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(PowerManager_SetFacility)
|
||||
{
|
||||
auto const configPath = PowerManager::GetConfigPath();
|
||||
SCOPE_GUARD(deleteFileGuard, bind(&FileWriter::DeleteFileX, std::cref(configPath)));
|
||||
PowerManager manager;
|
||||
SubscriberForTesting subscriber;
|
||||
|
||||
manager.Subscribe(&subscriber);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Normal, ());
|
||||
manager.SetFacility(Facility::Buildings3d, false);
|
||||
TestAllFacilitiesEnabledExcept(manager, {Facility::Buildings3d});
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::None, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::Buildings3d, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::None, ());
|
||||
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
|
||||
manager.SetFacility(Facility::MapDownloader, false);
|
||||
TestAllFacilitiesEnabledExcept(manager, {Facility::Buildings3d, Facility::MapDownloader});
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::None, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::MapDownloader, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PowerManager_SetScheme)
|
||||
{
|
||||
auto const configPath = PowerManager::GetConfigPath();
|
||||
SCOPE_GUARD(deleteFileGuard, bind(&FileWriter::DeleteFileX, std::cref(configPath)));
|
||||
Platform::ThreadRunner m_runner;
|
||||
PowerManager manager;
|
||||
SubscriberForTesting subscriber;
|
||||
|
||||
manager.Subscribe(&subscriber);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Normal, ());
|
||||
manager.SetScheme(Scheme::EconomyMaximum);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::EconomyMaximum, ());
|
||||
TestAllFacilitiesEnabledExcept(
|
||||
manager, {Facility::Buildings3d, Facility::PerspectiveView, Facility::TrackRecording, Facility::TrafficJams,
|
||||
Facility::GpsTrackingForTraffic, Facility::OsmEditsUploading, Facility::UgcUploading,
|
||||
Facility::BookmarkCloudUploading});
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::EconomyMaximum, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 8, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::Buildings3d, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_facility, Facility::PerspectiveView, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_facility, Facility::TrackRecording, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_facility, Facility::TrafficJams, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_facility, Facility::GpsTrackingForTraffic, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[5].m_facility, Facility::OsmEditsUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[5].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[6].m_facility, Facility::UgcUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[6].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[7].m_facility, Facility::BookmarkCloudUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[7].m_state, false, ());
|
||||
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
|
||||
manager.SetScheme(Scheme::EconomyMedium);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::EconomyMedium, ());
|
||||
|
||||
TestAllFacilitiesEnabledExcept(manager, {Facility::PerspectiveView, Facility::BookmarkCloudUploading});
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::EconomyMedium, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 6, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::Buildings3d, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, true, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_facility, Facility::TrackRecording, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_state, true, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_facility, Facility::TrafficJams, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_state, true, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_facility, Facility::GpsTrackingForTraffic, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_state, true, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_facility, Facility::OsmEditsUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_state, true, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[5].m_facility, Facility::UgcUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[5].m_state, true, ());
|
||||
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
|
||||
manager.SetScheme(Scheme::Auto);
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::Auto, ());
|
||||
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
|
||||
manager.SetScheme(Scheme::Normal);
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::Normal, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PowerManager_OnBatteryLevelChanged)
|
||||
{
|
||||
auto const configPath = PowerManager::GetConfigPath();
|
||||
SCOPE_GUARD(deleteFileGuard, bind(&FileWriter::DeleteFileX, std::cref(configPath)));
|
||||
Platform::ThreadRunner m_runner;
|
||||
PowerManager manager;
|
||||
SubscriberForTesting subscriber;
|
||||
|
||||
manager.Subscribe(&subscriber);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Normal, ());
|
||||
|
||||
manager.OnBatteryLevelReceived(50);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Normal, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 0, ());
|
||||
|
||||
manager.OnBatteryLevelReceived(10);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Normal, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 0, ());
|
||||
|
||||
manager.SetScheme(Scheme::Auto);
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 1, ());
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents[0], Scheme::Auto, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 0, ());
|
||||
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
|
||||
manager.OnBatteryLevelReceived(50);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Auto, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 0, ());
|
||||
|
||||
manager.OnBatteryLevelReceived(28);
|
||||
|
||||
TestAllFacilitiesEnabledExcept(manager, {Facility::PerspectiveView, Facility::GpsTrackingForTraffic,
|
||||
Facility::BookmarkCloudUploading, Facility::MapDownloader});
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Auto, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 4, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::PerspectiveView, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_facility, Facility::GpsTrackingForTraffic, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_facility, Facility::BookmarkCloudUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_facility, Facility::MapDownloader, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_state, false, ());
|
||||
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
|
||||
manager.OnBatteryLevelReceived(10);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, false);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Auto, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 5, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_facility, Facility::Buildings3d, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[0].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_facility, Facility::TrackRecording, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[1].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_facility, Facility::TrafficJams, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[2].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_facility, Facility::OsmEditsUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[3].m_state, false, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_facility, Facility::UgcUploading, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[4].m_state, false, ());
|
||||
|
||||
subscriber.m_onShemeEvents.clear();
|
||||
subscriber.m_onFacilityEvents.clear();
|
||||
|
||||
manager.OnBatteryLevelReceived(100);
|
||||
|
||||
TestIsAllFacilitiesInState(manager, true);
|
||||
TEST_EQUAL(manager.GetScheme(), Scheme::Auto, ());
|
||||
|
||||
TEST_EQUAL(subscriber.m_onShemeEvents.size(), 0, ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents.size(), 9, ());
|
||||
|
||||
auto const & facilityEvents = subscriber.m_onFacilityEvents;
|
||||
for (size_t i = 0; i < facilityEvents.size(); ++i)
|
||||
{
|
||||
TEST_EQUAL(facilityEvents[i].m_facility, static_cast<Facility>(i), ());
|
||||
TEST_EQUAL(subscriber.m_onFacilityEvents[i].m_state, true, ());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
272
libs/map/map_tests/search_api_tests.cpp
Normal file
272
libs/map/map_tests/search_api_tests.cpp
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
// NOTE: the purpose of this test is to test interaction between
|
||||
// SearchAPI and search engine. If you would like to test search
|
||||
// engine behaviour, please, consider implementing another search
|
||||
// integration test.
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_feature.hpp"
|
||||
|
||||
#include "search/search_tests_support/test_results_matching.hpp"
|
||||
#include "search/search_tests_support/test_with_custom_mwms.hpp"
|
||||
|
||||
#include "map/bookmarks_search_params.hpp"
|
||||
#include "map/search_api.hpp"
|
||||
#include "map/search_product_info.hpp"
|
||||
#include "map/viewport_search_params.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace search::tests_support;
|
||||
using namespace generator::tests_support;
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
using namespace storage;
|
||||
|
||||
namespace
|
||||
{
|
||||
using Rules = vector<shared_ptr<MatchingRule>>;
|
||||
|
||||
struct TestCafe : public TestPOI
|
||||
{
|
||||
public:
|
||||
TestCafe(m2::PointD const & center, string const & name, string const & lang) : TestPOI(center, name, lang)
|
||||
{
|
||||
SetTypes({{"amenity", "cafe"}});
|
||||
}
|
||||
|
||||
~TestCafe() override = default;
|
||||
};
|
||||
|
||||
class Delegate : public SearchAPI::Delegate
|
||||
{
|
||||
public:
|
||||
~Delegate() override = default;
|
||||
|
||||
// SearchAPI::Delegate overrides:
|
||||
void RunUITask(function<void()> fn) override { fn(); }
|
||||
};
|
||||
|
||||
class SearchAPITest : public generator::tests_support::TestWithCustomMwms
|
||||
{
|
||||
public:
|
||||
SearchAPITest()
|
||||
: m_infoGetter(CountryInfoReader::CreateCountryInfoGetter(GetPlatform()))
|
||||
, m_api(m_dataSource, m_storage, *m_infoGetter, 1 /* numThreads */, m_delegate)
|
||||
{}
|
||||
|
||||
protected:
|
||||
Storage m_storage;
|
||||
unique_ptr<CountryInfoGetter> m_infoGetter;
|
||||
Delegate m_delegate;
|
||||
SearchAPI m_api;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(SearchAPITest, MultipleViewportsRequests)
|
||||
{
|
||||
TestCafe cafe1(m2::PointD(0, 0), "cafe 1", "en");
|
||||
TestCafe cafe2(m2::PointD(0.5, 0.5), "cafe 2", "en");
|
||||
TestCafe cafe3(m2::PointD(10, 10), "cafe 3", "en");
|
||||
TestCafe cafe4(m2::PointD(10.5, 10.5), "cafe 4", "en");
|
||||
|
||||
auto const id = BuildCountry("Wonderland", [&](TestMwmBuilder & builder)
|
||||
{
|
||||
builder.Add(cafe1);
|
||||
builder.Add(cafe2);
|
||||
builder.Add(cafe3);
|
||||
builder.Add(cafe4);
|
||||
});
|
||||
|
||||
atomic<int> stage{0};
|
||||
|
||||
promise<void> promise0;
|
||||
auto future0 = promise0.get_future();
|
||||
|
||||
promise<void> promise1;
|
||||
auto future1 = promise1.get_future();
|
||||
|
||||
ViewportSearchParams params;
|
||||
params.m_query = "cafe ";
|
||||
params.m_inputLocale = "en";
|
||||
|
||||
params.m_onCompleted = [&](Results const & results)
|
||||
{
|
||||
TEST(!results.IsEndedCancelled(), ());
|
||||
|
||||
if (!results.IsEndMarker())
|
||||
return;
|
||||
|
||||
if (stage == 0)
|
||||
{
|
||||
Rules const rules = {ExactMatch(id, cafe1), ExactMatch(id, cafe2)};
|
||||
TEST(MatchResults(m_dataSource, rules, results), ());
|
||||
|
||||
promise0.set_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
TEST_EQUAL(stage, 1, ());
|
||||
Rules const rules = {ExactMatch(id, cafe3), ExactMatch(id, cafe4)};
|
||||
TEST(MatchResults(m_dataSource, rules, results), ());
|
||||
|
||||
promise1.set_value();
|
||||
}
|
||||
};
|
||||
|
||||
m_api.OnViewportChanged(m2::RectD(-1, -1, 1, 1));
|
||||
m_api.SearchInViewport(params);
|
||||
future0.wait();
|
||||
|
||||
++stage;
|
||||
m_api.OnViewportChanged(m2::RectD(9, 9, 11, 11));
|
||||
future1.wait();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(SearchAPITest, Cancellation)
|
||||
{
|
||||
TestCafe cafe(m2::PointD(0, 0), "cafe", "en");
|
||||
|
||||
auto const id = BuildCountry("Wonderland", [&](TestMwmBuilder & builder) { builder.Add(cafe); });
|
||||
|
||||
EverywhereSearchParams commonParams;
|
||||
commonParams.m_query = "cafe ";
|
||||
commonParams.m_inputLocale = "en";
|
||||
|
||||
{
|
||||
auto params = commonParams;
|
||||
|
||||
promise<void> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
params.m_onResults = [&](Results const & results, vector<ProductInfo> const &)
|
||||
{
|
||||
TEST(!results.IsEndedCancelled(), ());
|
||||
|
||||
if (!results.IsEndMarker())
|
||||
return;
|
||||
|
||||
Rules const rules = {ExactMatch(id, cafe)};
|
||||
TEST(MatchResults(m_dataSource, rules, results), ());
|
||||
|
||||
promise.set_value();
|
||||
};
|
||||
|
||||
m_api.OnViewportChanged(m2::RectD(0.0, 0.0, 1.0, 1.0));
|
||||
m_api.SearchEverywhere(params);
|
||||
future.wait();
|
||||
}
|
||||
|
||||
{
|
||||
auto params = commonParams;
|
||||
|
||||
promise<void> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
params.m_timeout = chrono::seconds(-1);
|
||||
|
||||
params.m_onResults = [&](Results const & results, vector<ProductInfo> const &)
|
||||
{
|
||||
// The deadline has fired but Search API does not expose it.
|
||||
TEST(!results.IsEndedCancelled(), ());
|
||||
|
||||
if (!results.IsEndMarker())
|
||||
return;
|
||||
|
||||
Rules const rules = {ExactMatch(id, cafe)};
|
||||
TEST(MatchResults(m_dataSource, rules, results), ());
|
||||
|
||||
promise.set_value();
|
||||
};
|
||||
|
||||
// Force the search by changing the viewport.
|
||||
m_api.OnViewportChanged(m2::RectD(0.0, 0.0, 2.0, 2.0));
|
||||
m_api.SearchEverywhere(params);
|
||||
future.wait();
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(SearchAPITest, BookmarksSearch)
|
||||
{
|
||||
vector<BookmarkInfo> marks;
|
||||
|
||||
kml::BookmarkData data;
|
||||
kml::SetDefaultStr(data.m_name, "R&R dinner");
|
||||
kml::SetDefaultStr(data.m_description, "They've got a cherry pie there that'll kill ya!");
|
||||
marks.emplace_back(0, data);
|
||||
kml::SetDefaultStr(data.m_name, "Silver Mustang Casino");
|
||||
kml::SetDefaultStr(data.m_description, "Joyful place, owners Bradley and Rodney are very friendly!");
|
||||
marks.emplace_back(1, data);
|
||||
kml::SetDefaultStr(data.m_name, "Great Northern Hotel");
|
||||
kml::SetDefaultStr(data.m_description, "Clean place with a reasonable price");
|
||||
marks.emplace_back(2, data);
|
||||
m_api.EnableIndexingOfBookmarksDescriptions(true);
|
||||
m_api.EnableIndexingOfBookmarkGroup(10, true /* enable */);
|
||||
m_api.OnBookmarksCreated(marks);
|
||||
m_api.OnViewportChanged(m2::RectD(-1, -1, 1, 1));
|
||||
|
||||
auto runTest = [&](string const & query, kml::MarkGroupId const & groupId, vector<kml::MarkId> const & expected)
|
||||
{
|
||||
promise<vector<kml::MarkId>> idsPromise;
|
||||
auto idsFuture = idsPromise.get_future();
|
||||
|
||||
BookmarksSearchParams params;
|
||||
params.m_query = query;
|
||||
params.m_onResults = [&](vector<kml::MarkId> const & results, BookmarksSearchParams::Status status)
|
||||
{
|
||||
if (status != BookmarksSearchParams::Status::Completed)
|
||||
return;
|
||||
idsPromise.set_value(results);
|
||||
};
|
||||
params.m_groupId = groupId;
|
||||
|
||||
m_api.SearchInBookmarks(params);
|
||||
|
||||
auto const ids = idsFuture.get();
|
||||
TEST_EQUAL(ids, expected, ());
|
||||
};
|
||||
|
||||
string const query = "gread silver hotel";
|
||||
runTest(query, kml::kInvalidMarkGroupId, vector<kml::MarkId>());
|
||||
|
||||
{
|
||||
vector<BookmarkGroupInfo> groupInfos;
|
||||
groupInfos.emplace_back(kml::MarkGroupId(10), vector<kml::MarkId>({0, 1}));
|
||||
groupInfos.emplace_back(kml::MarkGroupId(11), vector<kml::MarkId>({2}));
|
||||
m_api.OnBookmarksAttached(groupInfos);
|
||||
}
|
||||
|
||||
runTest(query, kml::kInvalidMarkGroupId, vector<kml::MarkId>({1}));
|
||||
runTest(query, kml::MarkGroupId(11), {});
|
||||
m_api.EnableIndexingOfBookmarkGroup(11, true /* enable */);
|
||||
runTest(query, kml::kInvalidMarkGroupId, vector<kml::MarkId>({2, 1}));
|
||||
runTest(query, kml::MarkGroupId(11), vector<kml::MarkId>({2}));
|
||||
m_api.EnableIndexingOfBookmarkGroup(11, false /* enable */);
|
||||
runTest(query, kml::kInvalidMarkGroupId, vector<kml::MarkId>({1}));
|
||||
runTest(query, kml::MarkGroupId(11), {});
|
||||
m_api.EnableIndexingOfBookmarkGroup(11, true /* enable */);
|
||||
|
||||
{
|
||||
vector<BookmarkGroupInfo> groupInfos;
|
||||
groupInfos.emplace_back(kml::MarkGroupId(10), vector<kml::MarkId>({1}));
|
||||
m_api.OnBookmarksDetached(groupInfos);
|
||||
}
|
||||
{
|
||||
vector<BookmarkGroupInfo> groupInfos;
|
||||
groupInfos.emplace_back(kml::MarkGroupId(11), vector<kml::MarkId>({1}));
|
||||
m_api.OnBookmarksAttached(groupInfos);
|
||||
}
|
||||
runTest(query, kml::MarkGroupId(11), vector<kml::MarkId>({2, 1}));
|
||||
|
||||
m_api.ResetBookmarksEngine();
|
||||
runTest(query, kml::MarkGroupId(11), {});
|
||||
}
|
||||
} // namespace
|
||||
158
libs/map/map_tests/track_statistics_tests.cpp
Normal file
158
libs/map/map_tests/track_statistics_tests.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/track_statistics.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
namespace track_statistics_tests
|
||||
{
|
||||
using namespace geometry;
|
||||
using namespace location;
|
||||
|
||||
GpsInfo const BuildGpsInfo(double latitude, double longitude, double altitude, double timestamp = 0)
|
||||
{
|
||||
GpsInfo gpsInfo;
|
||||
gpsInfo.m_latitude = latitude;
|
||||
gpsInfo.m_longitude = longitude;
|
||||
gpsInfo.m_altitude = altitude;
|
||||
gpsInfo.m_timestamp = timestamp;
|
||||
return gpsInfo;
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_EmptyMultiGeometry)
|
||||
{
|
||||
TrackStatistics ts;
|
||||
TEST_EQUAL(0, ts.m_ascent, ());
|
||||
TEST_EQUAL(0, ts.m_descent, ());
|
||||
TEST_EQUAL(ts.m_minElevation, kDefaultAltitudeMeters, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, kDefaultAltitudeMeters, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_FromMultiGeometry)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
auto const point1 = PointWithAltitude({0.0, 0.0}, 100);
|
||||
auto const point2 = PointWithAltitude({1.0, 1.0}, 150);
|
||||
auto const point3 = PointWithAltitude({2.0, 2.0}, 50);
|
||||
geometry.AddLine({point1, point2, point3});
|
||||
geometry.AddTimestamps({0, 1, 2});
|
||||
auto const ts = TrackStatistics(geometry);
|
||||
TEST_EQUAL(ts.m_minElevation, 50, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 150, ());
|
||||
TEST_EQUAL(ts.m_ascent, 50, ()); // Ascent from 100 -> 150
|
||||
TEST_EQUAL(ts.m_descent, 100, ()); // Descent from 150 -> 50
|
||||
TEST_EQUAL(ts.m_duration, 2, ());
|
||||
|
||||
double distance = 0;
|
||||
distance += mercator::DistanceOnEarth(point1, point2);
|
||||
distance += mercator::DistanceOnEarth(point2, point3);
|
||||
TEST_EQUAL(ts.m_length, distance, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_NoAltitudeAndTimestampPoints)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
geometry.AddLine({PointWithAltitude({0.0, 0.0}), PointWithAltitude({1.0, 1.0}), PointWithAltitude({2.0, 2.0})});
|
||||
|
||||
auto const ts = TrackStatistics(geometry);
|
||||
|
||||
TEST_EQUAL(ts.m_minElevation, kDefaultAltitudeMeters, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, kDefaultAltitudeMeters, ());
|
||||
TEST_EQUAL(ts.m_ascent, 0, ());
|
||||
TEST_EQUAL(ts.m_descent, 0, ());
|
||||
TEST_EQUAL(ts.m_duration, 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_MultipleLines)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
geometry.AddLine(
|
||||
{PointWithAltitude({0.0, 0.0}, 100), PointWithAltitude({1.0, 1.0}, 150), PointWithAltitude({1.0, 1.0}, 140)});
|
||||
geometry.AddTimestamps({0, 1, 2});
|
||||
geometry.AddLine(
|
||||
{PointWithAltitude({2.0, 2.0}, 50), PointWithAltitude({3.0, 3.0}, 75), PointWithAltitude({3.0, 3.0}, 60)});
|
||||
geometry.AddTimestamps({0, 0, 0});
|
||||
geometry.AddLine({PointWithAltitude({4.0, 4.0}, 200), PointWithAltitude({5.0, 5.0}, 250)});
|
||||
geometry.AddTimestamps({4, 5});
|
||||
auto const ts = TrackStatistics(geometry);
|
||||
|
||||
TEST_EQUAL(ts.m_minElevation, 50, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 250, ());
|
||||
TEST_EQUAL(ts.m_ascent, 125, ()); // Ascent from 100 -> 150, 50 -> 75, 200 -> 250
|
||||
TEST_EQUAL(ts.m_descent, 25, ()); // Descent from 150 -> 140, 75 -> 60
|
||||
TEST_EQUAL(ts.m_duration, 3, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_WithGpsPoints)
|
||||
{
|
||||
std::vector<std::vector<GpsInfo>> const pointsData = {
|
||||
{BuildGpsInfo(0.0, 0.0, 0, 0), BuildGpsInfo(1.0, 1.0, 50, 1), BuildGpsInfo(2.0, 2.0, 100, 2)},
|
||||
{BuildGpsInfo(3.0, 3.0, -50, 5)},
|
||||
{BuildGpsInfo(4.0, 4.0, 0, 10)}};
|
||||
TrackStatistics ts;
|
||||
for (auto const & pointsList : pointsData)
|
||||
for (auto const & point : pointsList)
|
||||
ts.AddGpsInfoPoint(point);
|
||||
TEST_EQUAL(ts.m_minElevation, -50, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 100, ());
|
||||
TEST_EQUAL(ts.m_ascent, 150, ()); // Ascent from 0 -> 50, 50 -> 100, -50 -> 0
|
||||
TEST_EQUAL(ts.m_descent, 150, ()); // Descent from 100 -> -50
|
||||
TEST_EQUAL(ts.m_duration, 10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_PositiveAndNegativeAltitudes)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
geometry.AddLine({PointWithAltitude({0.0, 0.0}, -10), PointWithAltitude({1.0, 1.0}, 20),
|
||||
PointWithAltitude({2.0, 2.0}, -5), PointWithAltitude({3.0, 3.0}, 15)});
|
||||
auto const ts = TrackStatistics(geometry);
|
||||
|
||||
TEST_EQUAL(ts.m_minElevation, -10, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 20, ());
|
||||
TEST_EQUAL(ts.m_ascent, 50, ()); // Ascent from -10 -> 20 and -5 -> 15
|
||||
TEST_EQUAL(ts.m_descent, 25, ()); // Descent from 20 -> -5
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_SmallAltitudeDelta)
|
||||
{
|
||||
std::vector<GpsInfo> const points = {BuildGpsInfo(0.0, 0.0, 0), BuildGpsInfo(1.0, 1.0, 0.2),
|
||||
BuildGpsInfo(2.0, 2.0, 0.4), BuildGpsInfo(3.0, 3.0, 0.6),
|
||||
BuildGpsInfo(4.0, 4.0, 0.8), BuildGpsInfo(5.0, 5.0, 1.0)};
|
||||
|
||||
TrackStatistics ts;
|
||||
for (auto const & point : points)
|
||||
ts.AddGpsInfoPoint(point);
|
||||
|
||||
TEST_EQUAL(ts.m_minElevation, 0, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 1.0, ());
|
||||
TEST_EQUAL(ts.m_ascent, 1.0, ());
|
||||
TEST_EQUAL(ts.m_descent, 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TrackStatistics_MixedMultiGeometryAndGpsPoints)
|
||||
{
|
||||
kml::MultiGeometry geometry;
|
||||
auto const point1 = PointWithAltitude({0.0, 0.0}, 100);
|
||||
auto const point2 = PointWithAltitude({1.0, 1.0}, 150);
|
||||
auto const point3 = PointWithAltitude({2.0, 2.0}, 50);
|
||||
geometry.AddLine({point1, point2, point3});
|
||||
geometry.AddTimestamps({5, 6, 7});
|
||||
|
||||
auto ts = TrackStatistics(geometry);
|
||||
|
||||
std::vector<GpsInfo> const points = {BuildGpsInfo(3.0, 3.0, 60, 8), BuildGpsInfo(4.0, 4.0, 160, 10),
|
||||
BuildGpsInfo(4.0, 4.0, 20, 15)};
|
||||
|
||||
for (auto const & point : points)
|
||||
ts.AddGpsInfoPoint(point);
|
||||
|
||||
TEST_EQUAL(ts.m_minElevation, 20, ());
|
||||
TEST_EQUAL(ts.m_maxElevation, 160, ());
|
||||
TEST_EQUAL(ts.m_ascent, 160, ()); // 50 + 10 + 100
|
||||
TEST_EQUAL(ts.m_descent, 240, ()); // 100 + 140
|
||||
TEST_EQUAL(ts.m_duration, 10, ()); // 10
|
||||
}
|
||||
} // namespace track_statistics_tests
|
||||
48
libs/map/map_tests/transliteration_test.cpp
Normal file
48
libs/map/map_tests/transliteration_test.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
#include "coding/transliteration.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
void TestTransliteration(Transliteration const & translit, std::string const & locale, std::string const & original,
|
||||
std::string const & expected)
|
||||
{
|
||||
std::string out;
|
||||
translit.Transliterate(original, StringUtf8Multilang::GetLangIndex(locale), out);
|
||||
TEST_EQUAL(expected, out, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// This test is inside of the map_tests because it uses Platform for obtaining the resource directory.
|
||||
UNIT_TEST(Transliteration_CompareSamples)
|
||||
{
|
||||
Transliteration & translit = Transliteration::Instance();
|
||||
translit.Init(GetPlatform().ResourcesDir());
|
||||
|
||||
TestTransliteration(translit, "ar", "العربية", "alrbyt");
|
||||
TestTransliteration(translit, "ru", "Русский", "Russkiy");
|
||||
TestTransliteration(translit, "zh", "中文", "zhong wen");
|
||||
TestTransliteration(translit, "be", "Беларуская", "Byelaruskaya");
|
||||
TestTransliteration(translit, "ka", "ქართული", "kartuli");
|
||||
TestTransliteration(translit, "ko", "한국어", "hangug-eo");
|
||||
TestTransliteration(translit, "he", "עברית", "bryt");
|
||||
TestTransliteration(translit, "el", "Ελληνικά", "Ellenika");
|
||||
TestTransliteration(translit, "zh_pinyin", "拼音", "pin yin");
|
||||
TestTransliteration(translit, "th", "ไทย", ""); // Thai-Latin transliteration is off.
|
||||
TestTransliteration(translit, "sr", "Српски", "Srpski");
|
||||
TestTransliteration(translit, "uk", "Українська", "Ukrayinska");
|
||||
TestTransliteration(translit, "fa", "فارسی", "farsy");
|
||||
TestTransliteration(translit, "hy", "Հայերէն", "Hayeren");
|
||||
TestTransliteration(translit, "am", "አማርኛ", "amarinya");
|
||||
TestTransliteration(translit, "ja_kana", "カタカナ", "katakana");
|
||||
TestTransliteration(translit, "ja_kana", "ひらがな", "hiragana");
|
||||
TestTransliteration(translit, "ja_kana", "カタカナ ひらがな", "katakana hiragana");
|
||||
TestTransliteration(translit, "bg", "Български", "Bulgarski");
|
||||
TestTransliteration(translit, "kk", "Қазақ", "Qazaq");
|
||||
TestTransliteration(translit, "mn", "Монгол хэл", "Mongol hel");
|
||||
TestTransliteration(translit, "mk", "Македонски", "Makedonski");
|
||||
TestTransliteration(translit, "hi", "हिन्दी", "hindi");
|
||||
}
|
||||
12
libs/map/mwm_tests/CMakeLists.txt
Normal file
12
libs/map/mwm_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
project(mwm_tests)
|
||||
|
||||
set(SRC
|
||||
multithread_mwm_test.cpp
|
||||
mwm_foreach_test.cpp
|
||||
mwm_index_test.cpp
|
||||
world_map_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} map)
|
||||
98
libs/map/mwm_tests/multithread_mwm_test.cpp
Normal file
98
libs/map/mwm_tests/multithread_mwm_test.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/thread_pool_computational.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace multithread_mwm_test
|
||||
{
|
||||
using SourceT = FeaturesFetcher;
|
||||
|
||||
class FeaturesLoader
|
||||
{
|
||||
public:
|
||||
explicit FeaturesLoader(SourceT const & src) : m_src(src) {}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
size_t const kCount = 2000;
|
||||
|
||||
for (size_t i = 0; i < kCount; ++i)
|
||||
{
|
||||
m2::RectD const r = GetRandomRect();
|
||||
m_scale = scales::GetScaleLevel(r);
|
||||
|
||||
m_src.ForEachFeature(r, [this](FeatureType & ft)
|
||||
{
|
||||
ft.ParseAllBeforeGeometry();
|
||||
|
||||
(void)ft.GetOuterGeometryStats();
|
||||
(void)ft.GetOuterTrianglesStats();
|
||||
|
||||
// Force load feature. We check asserts here. There is no any other constrains here.
|
||||
CHECK(!ft.IsEmptyGeometry(m_scale), (ft.GetID()));
|
||||
}, m_scale);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Get random rect inside m_src.
|
||||
m2::RectD GetRandomRect() const
|
||||
{
|
||||
int const count = std::max(1, rand() % 50);
|
||||
|
||||
int const x = rand() % count;
|
||||
int const y = rand() % count;
|
||||
|
||||
m2::RectD const r = m_src.GetWorldRect();
|
||||
double const sizeX = r.SizeX() / count;
|
||||
double const sizeY = r.SizeY() / count;
|
||||
|
||||
double const minX = r.minX() + x * sizeX;
|
||||
double const minY = r.minY() + y * sizeY;
|
||||
|
||||
return m2::RectD(minX, minY, minX + sizeX, minY + sizeY);
|
||||
}
|
||||
|
||||
SourceT const & m_src;
|
||||
int m_scale = 0;
|
||||
};
|
||||
|
||||
void RunTest(std::string const & file)
|
||||
{
|
||||
SourceT src;
|
||||
src.InitClassificator();
|
||||
|
||||
UNUSED_VALUE(src.RegisterMap(platform::LocalCountryFile::MakeForTesting(file)));
|
||||
|
||||
// Check that country rect is valid and not infinity.
|
||||
m2::RectD const r = src.GetWorldRect();
|
||||
TEST(r.IsValid(), ());
|
||||
|
||||
m2::RectD world(mercator::Bounds::FullRect());
|
||||
world.Inflate(-10.0, -10.0);
|
||||
|
||||
TEST(world.IsRectInside(r), ());
|
||||
|
||||
srand(666);
|
||||
|
||||
size_t const kCount = 20;
|
||||
base::ComputationalThreadPool pool(kCount);
|
||||
|
||||
for (size_t i = 0; i < kCount; ++i)
|
||||
pool.SubmitWork(FeaturesLoader(src));
|
||||
|
||||
pool.WaitingStop();
|
||||
}
|
||||
|
||||
UNIT_TEST(Threading_ForEachFeature)
|
||||
{
|
||||
RunTest("minsk-pass");
|
||||
}
|
||||
|
||||
} // namespace multithread_mwm_test
|
||||
315
libs/map/mwm_tests/mwm_foreach_test.cpp
Normal file
315
libs/map/mwm_tests/mwm_foreach_test.cpp
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/data_header.hpp"
|
||||
#include "indexer/feature_processor.hpp"
|
||||
#include "indexer/feature_visibility.hpp"
|
||||
#include "indexer/map_style_reader.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
|
||||
#include "geometry/rect_intersect.hpp"
|
||||
#include "geometry/robust_orientation.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mwm_for_each_test
|
||||
{
|
||||
using Cont = vector<uint32_t>;
|
||||
|
||||
bool IsDrawable(FeatureType & f, int scale)
|
||||
{
|
||||
if (f.IsEmptyGeometry(scale))
|
||||
return false;
|
||||
|
||||
if (feature::IsDrawableForIndex(f, scale))
|
||||
return true;
|
||||
|
||||
// Scale index ends on GetUpperScale(), but the actual visibility goes until GetUpperStyleScale().
|
||||
if (scale != scales::GetUpperScale())
|
||||
return false;
|
||||
|
||||
while (++scale <= scales::GetUpperStyleScale())
|
||||
if (feature::IsDrawableForIndex(f, scale))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class AccumulatorBase
|
||||
{
|
||||
Cont & m_cont;
|
||||
|
||||
protected:
|
||||
int m_scale;
|
||||
|
||||
bool is_drawable(FeatureType & f) const
|
||||
{
|
||||
f.ParseGeometry(m_scale);
|
||||
f.ParseTriangles(m_scale);
|
||||
// TODO(pastk): need a better / more comprehensive lazy-loading consistency check,
|
||||
// e.g. ATM geometry is checked by its first point only, metadata is not checked at all..
|
||||
TEST_EQUAL(f.DebugString(), f.DebugString(), ());
|
||||
|
||||
return IsDrawable(f, m_scale);
|
||||
}
|
||||
|
||||
void add(FeatureType const & f) const
|
||||
{
|
||||
TEST(f.GetID().IsValid(), ());
|
||||
m_cont.push_back(f.GetID().m_index);
|
||||
}
|
||||
|
||||
void add(FeatureType const &, uint32_t index) const { m_cont.push_back(index); }
|
||||
|
||||
public:
|
||||
AccumulatorBase(int scale, Cont & cont) : m_cont(cont), m_scale(scale) {}
|
||||
|
||||
void operator()(FeatureType & f) const
|
||||
{
|
||||
TEST(is_drawable(f), (m_scale, f.DebugString()));
|
||||
add(f);
|
||||
}
|
||||
};
|
||||
|
||||
class IntersectCheck
|
||||
{
|
||||
m2::RectD m_rect;
|
||||
|
||||
m2::PointD m_prev;
|
||||
bool m_isPrev, m_intersect;
|
||||
|
||||
public:
|
||||
explicit IntersectCheck(m2::RectD const & r) : m_rect(r), m_isPrev(false), m_intersect(false) {}
|
||||
|
||||
void TestPoint(m2::PointD const & p) { m_intersect = m_rect.IsPointInside(p); }
|
||||
|
||||
void operator()(m2::PointD const & pt)
|
||||
{
|
||||
if (m_intersect)
|
||||
return;
|
||||
|
||||
if (m_isPrev)
|
||||
{
|
||||
m2::PointD d1 = m_prev;
|
||||
m2::PointD d2 = pt;
|
||||
m_intersect = m2::Intersect(m_rect, d1, d2);
|
||||
}
|
||||
else
|
||||
m_isPrev = true;
|
||||
|
||||
m_prev = pt;
|
||||
}
|
||||
|
||||
void operator()(m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
if (m_intersect)
|
||||
return;
|
||||
|
||||
m2::PointD arrP[] = {p1, p2, p3};
|
||||
|
||||
// make right-oriented triangle
|
||||
if (m2::robust::OrientedS(arrP[0], arrP[1], arrP[2]) < 0.0)
|
||||
swap(arrP[1], arrP[2]);
|
||||
|
||||
bool isInside = true;
|
||||
m2::PointD const pt = m_rect.LeftTop();
|
||||
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
// intersect edge with rect
|
||||
m_intersect = m2::Intersect(m_rect, arrP[i], arrP[(i + 1) % 3]);
|
||||
if (m_intersect)
|
||||
break;
|
||||
|
||||
if (isInside)
|
||||
{
|
||||
// or check if rect inside triangle (any point of rect)
|
||||
double const s = m2::robust::OrientedS(arrP[i], arrP[(i + 1) % 3], pt);
|
||||
isInside = (s >= 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
m_intersect = m_intersect || isInside;
|
||||
}
|
||||
|
||||
bool IsIntersect() const { return m_intersect; }
|
||||
};
|
||||
|
||||
class AccumulatorEtalon : public AccumulatorBase
|
||||
{
|
||||
typedef AccumulatorBase base_type;
|
||||
|
||||
m2::RectD m_rect;
|
||||
|
||||
bool is_intersect(FeatureType & f) const
|
||||
{
|
||||
IntersectCheck check(m_rect);
|
||||
|
||||
using namespace feature;
|
||||
switch (f.GetGeomType())
|
||||
{
|
||||
case GeomType::Point: check.TestPoint(f.GetCenter()); break;
|
||||
case GeomType::Line: f.ForEachPoint(check, m_scale); break;
|
||||
case GeomType::Area: f.ForEachTriangle(check, m_scale); break;
|
||||
default: CHECK(false, ());
|
||||
}
|
||||
|
||||
return check.IsIntersect();
|
||||
}
|
||||
|
||||
public:
|
||||
AccumulatorEtalon(m2::RectD const & r, int scale, Cont & cont) : base_type(scale, cont), m_rect(r) {}
|
||||
|
||||
void operator()(FeatureType & f, uint32_t index) const
|
||||
{
|
||||
if (is_drawable(f) && is_intersect(f))
|
||||
add(f, index);
|
||||
}
|
||||
};
|
||||
|
||||
// Use this comparator for "sort" and "compare_sequence".
|
||||
struct FeatureIDCmp
|
||||
{
|
||||
int compare(uint32_t r1, uint32_t r2) const
|
||||
{
|
||||
if (r1 < r2)
|
||||
return -1;
|
||||
if (r2 < r1)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
bool operator()(uint32_t r1, uint32_t r2) const { return (compare(r1, r2) == -1); }
|
||||
};
|
||||
|
||||
/// Check that "test" contains all elements from "etalon".
|
||||
template <class TCont, class TCompare>
|
||||
bool compare_sequence(TCont const & etalon, TCont const & test, TCompare comp, size_t & errInd)
|
||||
{
|
||||
auto i1 = etalon.begin();
|
||||
auto i2 = test.begin();
|
||||
while (i1 != etalon.end() && i2 != test.end())
|
||||
{
|
||||
switch (comp.compare(*i1, *i2))
|
||||
{
|
||||
case 0: // equal
|
||||
++i1;
|
||||
++i2;
|
||||
break;
|
||||
case -1: // present in etalon, but missing in test - error
|
||||
{
|
||||
errInd = distance(etalon.begin(), i1);
|
||||
return false;
|
||||
}
|
||||
case 1: // present in test, but missing in etalon - actually it may be ok
|
||||
++i2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i1 != etalon.end())
|
||||
{
|
||||
errInd = distance(etalon.begin(), i1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class FindOffset
|
||||
{
|
||||
int m_level;
|
||||
uint32_t m_index;
|
||||
|
||||
public:
|
||||
FindOffset(int level, uint32_t index) : m_level(level), m_index(index) {}
|
||||
|
||||
void operator()(FeatureType & ft, uint32_t index)
|
||||
{
|
||||
if (index == m_index)
|
||||
{
|
||||
TEST(IsDrawable(ft, m_level), ());
|
||||
|
||||
LOG(LINFO, ("Feature index:", index));
|
||||
LOG(LINFO, ("Feature:", ft.DebugString()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void RunTest(string const & countryFileName)
|
||||
{
|
||||
FeaturesFetcher src1;
|
||||
src1.InitClassificator();
|
||||
|
||||
platform::LocalCountryFile localFile(platform::LocalCountryFile::MakeForTesting(countryFileName));
|
||||
// Clean indexes to prevent mwm and indexes versions mismatch error.
|
||||
platform::CountryIndexes::DeleteFromDisk(localFile);
|
||||
UNUSED_VALUE(src1.RegisterMap(localFile));
|
||||
|
||||
vector<m2::RectD> rects;
|
||||
rects.push_back(src1.GetWorldRect());
|
||||
|
||||
ModelReaderPtr reader = platform::GetCountryReader(localFile, MapFileType::Map);
|
||||
|
||||
while (!rects.empty())
|
||||
{
|
||||
m2::RectD const r = rects.back();
|
||||
rects.pop_back();
|
||||
|
||||
int const scale = max(scales::GetUpperCountryScale(), scales::GetScaleLevel(r));
|
||||
|
||||
Cont v1, v2;
|
||||
{
|
||||
AccumulatorBase acc(scale, v1);
|
||||
src1.ForEachFeature(r, acc, scale);
|
||||
sort(v1.begin(), v1.end(), FeatureIDCmp());
|
||||
}
|
||||
{
|
||||
AccumulatorEtalon acc(r, scale, v2);
|
||||
feature::ForEachFeature(reader, acc);
|
||||
sort(v2.begin(), v2.end(), FeatureIDCmp());
|
||||
}
|
||||
|
||||
size_t const emptyInd = size_t(-1);
|
||||
size_t errInd = emptyInd;
|
||||
if (!compare_sequence(v2, v1, FeatureIDCmp(), errInd))
|
||||
{
|
||||
if (errInd != emptyInd)
|
||||
{
|
||||
FindOffset doFind(scale, v2[errInd]);
|
||||
feature::ForEachFeature(reader, doFind);
|
||||
}
|
||||
|
||||
TEST(false,
|
||||
("Failed for rect:", r, "; Scale level:", scale, "; Etalon size:", v2.size(), "; Index size:", v1.size()));
|
||||
}
|
||||
|
||||
if (!v2.empty() && (scale < scales::GetUpperScale()))
|
||||
{
|
||||
m2::RectD r1, r2;
|
||||
r.DivideByGreaterSize(r1, r2);
|
||||
rects.push_back(r1);
|
||||
rects.push_back(r2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @todo The concept of this test became invalid after POI filtering when overlap in geometry index.
|
||||
/// Probably, will restore it with a new idea someday. Like build and check separate index without filtering.
|
||||
// UNIT_TEST(ForEach_QueryResults)
|
||||
//{
|
||||
// GetStyleReader().SetCurrentStyle(MapStyleMerged);
|
||||
// RunTest("minsk-pass");
|
||||
// }
|
||||
|
||||
} // namespace mwm_for_each_test
|
||||
75
libs/map/mwm_tests/mwm_index_test.cpp
Normal file
75
libs/map/mwm_tests/mwm_index_test.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/features_fetcher.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
class CheckNonEmptyGeometry
|
||||
{
|
||||
public:
|
||||
vector<FeatureID> m_ids;
|
||||
|
||||
void operator()(FeatureID const & id) { m_ids.push_back(id); }
|
||||
|
||||
void operator()(FeatureType & ft)
|
||||
{
|
||||
bool res = false;
|
||||
ft.ForEachPoint([&res](m2::PointD const &) { res = true; }, m_scale);
|
||||
ft.ForEachTriangle([&res](m2::PointD const &, m2::PointD const &, m2::PointD const &) { res = true; }, m_scale);
|
||||
|
||||
TEST(res, (ft.DebugString(), "Scale =", m_scale));
|
||||
}
|
||||
|
||||
void SetScale(int scale)
|
||||
{
|
||||
m_ids.clear();
|
||||
m_scale = scale;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_scale;
|
||||
};
|
||||
|
||||
bool RunTest(string const & countryFileName, int lowS, int highS)
|
||||
{
|
||||
FeaturesFetcher src;
|
||||
auto p = src.RegisterMap(platform::LocalCountryFile::MakeForTesting(countryFileName));
|
||||
if (p.second != MwmSet::RegResult::Success)
|
||||
return false;
|
||||
|
||||
MwmSet::MwmId const & id = p.first;
|
||||
ASSERT(id.IsAlive(), ());
|
||||
|
||||
version::Format const version = id.GetInfo()->m_version.GetFormat();
|
||||
if (version == version::Format::unknownFormat)
|
||||
return false;
|
||||
|
||||
CheckNonEmptyGeometry doCheck;
|
||||
for (int scale = lowS; scale <= highS; ++scale)
|
||||
{
|
||||
doCheck.SetScale(scale);
|
||||
src.ForEachFeatureID(mercator::Bounds::FullRect(), doCheck, scale);
|
||||
src.ReadFeatures(doCheck, doCheck.m_ids);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(ForEachFeatureID_Test)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
TEST(RunTest("World", 0, scales::GetUpperWorldScale()), ());
|
||||
TEST(RunTest("WorldCoasts", 0, scales::GetUpperWorldScale()), ());
|
||||
// TEST(RunTest("Belarus", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ());
|
||||
TEST(RunTest("minsk-pass", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ());
|
||||
}
|
||||
51
libs/map/mwm_tests/world_map_test.cpp
Normal file
51
libs/map/mwm_tests/world_map_test.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
UNIT_TEST(World_Capitals)
|
||||
{
|
||||
classificator::Load();
|
||||
auto const capitalType = classif().GetTypeByPath({"place", "city", "capital", "2"});
|
||||
std::set<std::string_view> testCapitals = {"Lisbon", "Warsaw", "Kyiv", "Roseau"};
|
||||
|
||||
platform::LocalCountryFile localFile(platform::LocalCountryFile::MakeForTesting(WORLD_FILE_NAME));
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto const res = dataSource.RegisterMap(localFile);
|
||||
TEST_EQUAL(res.second, MwmSet::RegResult::Success, ());
|
||||
|
||||
size_t capitalsCount = 0;
|
||||
|
||||
FeaturesLoaderGuard guard(dataSource, res.first);
|
||||
size_t const count = guard.GetNumFeatures();
|
||||
for (size_t id = 0; id < count; ++id)
|
||||
{
|
||||
auto ft = guard.GetFeatureByIndex(id);
|
||||
if (ft->GetGeomType() != feature::GeomType::Point)
|
||||
continue;
|
||||
|
||||
bool found = false;
|
||||
ft->ForEachType([&found, capitalType](uint32_t t)
|
||||
{
|
||||
if (t == capitalType)
|
||||
found = true;
|
||||
});
|
||||
|
||||
if (found)
|
||||
++capitalsCount;
|
||||
|
||||
std::string_view const name = ft->GetName(StringUtf8Multilang::kEnglishCode);
|
||||
if (testCapitals.count(name) > 0)
|
||||
TEST(found, (name));
|
||||
}
|
||||
|
||||
// Got 225 values from the first launch. May vary slightly ..
|
||||
TEST_GREATER_OR_EQUAL(capitalsCount, 215, ());
|
||||
}
|
||||
13
libs/map/mwm_tree.hpp
Normal file
13
libs/map/mwm_tree.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing_common/num_mwm_id.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
|
||||
#include "geometry/tree4d.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
std::unique_ptr<m4::Tree<routing::NumMwmId>> MakeNumMwmTree(routing::NumMwmIds const & numMwmIds,
|
||||
storage::CountryInfoGetter const & countryInfoGetter);
|
||||
504
libs/map/mwm_url.cpp
Normal file
504
libs/map/mwm_url.cpp
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
#include "map/mwm_url.hpp"
|
||||
|
||||
#include "map/api_mark_point.hpp"
|
||||
#include "map/bookmark_manager.hpp"
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "ge0/geo_url_parser.hpp"
|
||||
#include "ge0/parser.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
namespace url_scheme
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string_view constexpr kCenterLatLon = "cll";
|
||||
std::string_view constexpr kAppName = "appname";
|
||||
|
||||
namespace map
|
||||
{
|
||||
std::string_view constexpr kLatLon = "ll";
|
||||
std::string_view constexpr kZoomLevel = "z";
|
||||
std::string_view constexpr kName = "n";
|
||||
std::string_view constexpr kId = "id";
|
||||
std::string_view constexpr kStyle = "s";
|
||||
std::string_view constexpr kBackUrl = "backurl";
|
||||
std::string_view constexpr kVersion = "v";
|
||||
std::string_view constexpr kBalloonAction = "balloonaction";
|
||||
} // namespace map
|
||||
|
||||
namespace route
|
||||
{
|
||||
std::string_view constexpr kSourceLatLon = "sll";
|
||||
std::string_view constexpr kDestLatLon = "dll";
|
||||
std::string_view constexpr kSourceName = "saddr";
|
||||
std::string_view constexpr kDestName = "daddr";
|
||||
std::string_view constexpr kRouteType = "type";
|
||||
std::string_view constexpr kRouteTypeVehicle = "vehicle";
|
||||
std::string_view constexpr kRouteTypePedestrian = "pedestrian";
|
||||
std::string_view constexpr kRouteTypeBicycle = "bicycle";
|
||||
std::string_view constexpr kRouteTypeTransit = "transit";
|
||||
} // namespace route
|
||||
|
||||
namespace search
|
||||
{
|
||||
std::string_view constexpr kQuery = "query";
|
||||
std::string_view constexpr kLocale = "locale";
|
||||
std::string_view constexpr kSearchOnMap = "map";
|
||||
} // namespace search
|
||||
|
||||
namespace highlight_feature
|
||||
{
|
||||
std::string_view constexpr kHighlight = "highlight";
|
||||
} // namespace highlight_feature
|
||||
|
||||
// See also kGe0Prefixes in ge0/parser.hpp
|
||||
constexpr std::array<std::string_view, 3> kLegacyMwmPrefixes = {{"mapsme://", "mwm://", "mapswithme://"}};
|
||||
|
||||
bool ParseLatLon(std::string const & key, std::string const & value, double & lat, double & lon)
|
||||
{
|
||||
size_t const firstComma = value.find(',');
|
||||
if (firstComma == std::string::npos)
|
||||
{
|
||||
LOG(LWARNING, ("Map API: no comma between lat and lon for key:", key, " value:", value));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strings::to_double(value.substr(0, firstComma), lat) || !strings::to_double(value.substr(firstComma + 1), lon))
|
||||
{
|
||||
LOG(LWARNING, ("Map API: can't parse lat,lon for key:", key, " value:", value));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mercator::ValidLat(lat) || !mercator::ValidLon(lon))
|
||||
{
|
||||
LOG(LWARNING, ("Map API: incorrect value for lat and/or lon", key, value, lat, lon));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A helper for SetUrlAndParse() below.
|
||||
// kGe0Prefixes supports API and ge0 links, kLegacyMwmPrefixes - only API.
|
||||
std::tuple<size_t, bool> FindUrlPrefix(std::string const & url)
|
||||
{
|
||||
for (auto const prefix : ge0::Ge0Parser::kGe0Prefixes)
|
||||
if (url.starts_with(prefix))
|
||||
return {prefix.size(), true};
|
||||
for (auto const prefix : kLegacyMwmPrefixes)
|
||||
if (url.starts_with(prefix))
|
||||
return {prefix.size(), false};
|
||||
return {std::string::npos, false};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ParsedMapApi::UrlType ParsedMapApi::SetUrlAndParse(std::string const & raw)
|
||||
{
|
||||
Reset();
|
||||
SCOPE_GUARD(guard, [this]
|
||||
{
|
||||
if (m_requestType == UrlType::Incorrect)
|
||||
Reset();
|
||||
});
|
||||
|
||||
if (auto const [prefix, checkForGe0Link] = FindUrlPrefix(raw); prefix != std::string::npos)
|
||||
{
|
||||
url::Url const url{"cm://" + raw.substr(prefix)};
|
||||
if (!url.IsValid())
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
std::string const & type = url.GetHost();
|
||||
// The URL is prefixed by one of the kGe0Prefixes or kLegacyMwmPrefixes prefixes
|
||||
// => check for well-known API methods first.
|
||||
if (type == "map")
|
||||
{
|
||||
bool correctOrder = true;
|
||||
url.ForEachParam([&correctOrder, this](auto const & key, auto const & value)
|
||||
{ ParseMapParam(key, value, correctOrder); });
|
||||
|
||||
if (m_mapPoints.empty() || !correctOrder)
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
return m_requestType = UrlType::Map;
|
||||
}
|
||||
else if (type == "route")
|
||||
{
|
||||
m_routePoints.clear();
|
||||
using namespace route;
|
||||
std::vector pattern{kSourceLatLon, kSourceName, kDestLatLon, kDestName, kRouteType};
|
||||
url.ForEachParam([&pattern, this](auto const & key, auto const & value)
|
||||
{ ParseRouteParam(key, value, pattern); });
|
||||
|
||||
if (pattern.size() != 0)
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
if (m_routePoints.size() != 2)
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
return m_requestType = UrlType::Route;
|
||||
}
|
||||
else if (type == "search")
|
||||
{
|
||||
url.ForEachParam([this](auto const & key, auto const & value) { ParseSearchParam(key, value); });
|
||||
if (m_searchRequest.m_query.empty())
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
return m_requestType = UrlType::Search;
|
||||
}
|
||||
else if (type == "crosshair")
|
||||
{
|
||||
url.ForEachParam([this](auto const & key, auto const & value) { ParseCommonParam(key, value); });
|
||||
|
||||
return m_requestType = UrlType::Crosshair;
|
||||
}
|
||||
else if (type == "menu")
|
||||
{
|
||||
url.ForEachParam([this](auto const & key, auto const & value) { ParseInAppFeatureHighlightParam(key, value); });
|
||||
return m_requestType = UrlType::Menu;
|
||||
}
|
||||
else if (type == "settings")
|
||||
{
|
||||
url.ForEachParam([this](auto const & key, auto const & value) { ParseInAppFeatureHighlightParam(key, value); });
|
||||
return m_requestType = UrlType::Settings;
|
||||
}
|
||||
else if (type == "oauth2")
|
||||
{
|
||||
if (url.GetPath() != "osm/callback")
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
url.ForEachParam([this](auto const & key, auto const & value)
|
||||
{
|
||||
if (key == "code")
|
||||
m_oauth2code = value;
|
||||
});
|
||||
|
||||
if (m_oauth2code.empty())
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
else
|
||||
return m_requestType = UrlType::OAuth2;
|
||||
}
|
||||
else if (checkForGe0Link)
|
||||
{
|
||||
// The URL is prefixed by one of the kGe0Prefixes AND doesn't match any supported API call:
|
||||
// => try to decode ge0 short link.
|
||||
ge0::Ge0Parser::Result info;
|
||||
if (!ge0::Ge0Parser{}.ParseAfterPrefix(raw, prefix, info))
|
||||
return UrlType::Incorrect;
|
||||
|
||||
m_zoomLevel = info.m_zoomLevel;
|
||||
m_mapPoints.push_back({info.m_lat, info.m_lon, info.m_name, "" /* m_id */, "" /* m_style */});
|
||||
|
||||
return m_requestType = UrlType::Map;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The URL is prefixed by one of the kLegacyPrefixes AND doesn't match any supported API call => error.
|
||||
LOG(LWARNING, ("Unsupported legacy API URL:", raw));
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The URL is not prefixed by any special prefixes => try to parse for lat,lon and other geo parameters.
|
||||
geo::GeoURLInfo info;
|
||||
if (!geo::UnifiedParser().Parse(raw, info))
|
||||
return m_requestType = UrlType::Incorrect;
|
||||
|
||||
if (!info.m_query.empty())
|
||||
{
|
||||
// The URL has "q=" parameter => convert to a Search API request.
|
||||
m_searchRequest = SearchRequest();
|
||||
m_searchRequest.m_query = info.m_query;
|
||||
m_centerLatLon = {info.m_lat, info.m_lon};
|
||||
m_zoomLevel = info.m_zoom;
|
||||
return m_requestType = UrlType::Search;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The URL doesn't have "q=" parameter => convert to a Map API request.
|
||||
ASSERT(info.IsLatLonValid(), ());
|
||||
m_centerLatLon = {info.m_lat, info.m_lon};
|
||||
m_zoomLevel = info.m_zoom;
|
||||
m_mapPoints.push_back({info.m_lat, info.m_lon, info.m_label, "" /* m_id */, "" /* m_style */});
|
||||
return m_requestType = UrlType::Map;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void ParsedMapApi::ParseMapParam(std::string const & key, std::string const & value, bool & correctOrder)
|
||||
{
|
||||
using namespace map;
|
||||
|
||||
if (key == kLatLon)
|
||||
{
|
||||
double lat = 0.0;
|
||||
double lon = 0.0;
|
||||
if (!ParseLatLon(key, value, lat, lon))
|
||||
{
|
||||
LOG(LWARNING, ("Incorrect 'll':", value));
|
||||
return;
|
||||
}
|
||||
|
||||
m_mapPoints.push_back({lat, lon, "" /* m_name */, "" /* m_id */, "" /* m_style */});
|
||||
}
|
||||
else if (key == kZoomLevel)
|
||||
{
|
||||
double zoomLevel;
|
||||
if (strings::to_double(value, zoomLevel))
|
||||
m_zoomLevel = zoomLevel;
|
||||
}
|
||||
else if (key == kName)
|
||||
{
|
||||
if (!m_mapPoints.empty())
|
||||
{
|
||||
m_mapPoints.back().m_name = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Map API: Point name with no point. 'll' should come first!"));
|
||||
correctOrder = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (key == kId)
|
||||
{
|
||||
if (!m_mapPoints.empty())
|
||||
{
|
||||
m_mapPoints.back().m_id = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Map API: Point url with no point. 'll' should come first!"));
|
||||
correctOrder = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (key == kStyle)
|
||||
{
|
||||
if (!m_mapPoints.empty())
|
||||
{
|
||||
m_mapPoints.back().m_style = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Map API: Point style with no point. 'll' should come first!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (key == kBackUrl)
|
||||
{
|
||||
// Fix missing :// in back url, it's important for iOS
|
||||
if (value.find("://") == std::string::npos)
|
||||
m_globalBackUrl = value + "://";
|
||||
else
|
||||
m_globalBackUrl = value;
|
||||
}
|
||||
else if (key == kVersion)
|
||||
{
|
||||
if (!strings::to_int(value, m_version))
|
||||
m_version = 0;
|
||||
}
|
||||
else if (key == kBalloonAction)
|
||||
{
|
||||
m_goBackOnBalloonClick = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseCommonParam(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void ParsedMapApi::ParseRouteParam(std::string const & key, std::string const & value,
|
||||
std::vector<std::string_view> & pattern)
|
||||
{
|
||||
using namespace route;
|
||||
|
||||
if (pattern.empty() || key != pattern.front())
|
||||
return;
|
||||
|
||||
if (key == kSourceLatLon || key == kDestLatLon)
|
||||
{
|
||||
double lat = 0.0;
|
||||
double lon = 0.0;
|
||||
if (!ParseLatLon(key, value, lat, lon))
|
||||
{
|
||||
LOG(LWARNING, ("Incorrect 'sll':", value));
|
||||
return;
|
||||
}
|
||||
|
||||
RoutePoint p;
|
||||
p.m_org = mercator::FromLatLon(lat, lon);
|
||||
m_routePoints.push_back(p);
|
||||
}
|
||||
else if (key == kSourceName || key == kDestName)
|
||||
{
|
||||
m_routePoints.back().m_name = value;
|
||||
}
|
||||
else if (key == kRouteType)
|
||||
{
|
||||
std::string const lowerValue = strings::MakeLowerCase(value);
|
||||
if (lowerValue == kRouteTypePedestrian || lowerValue == kRouteTypeVehicle || lowerValue == kRouteTypeBicycle ||
|
||||
lowerValue == kRouteTypeTransit)
|
||||
{
|
||||
m_routingType = lowerValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Incorrect routing type:", value));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pattern.erase(pattern.begin());
|
||||
}
|
||||
|
||||
void ParsedMapApi::ParseSearchParam(std::string const & key, std::string const & value)
|
||||
{
|
||||
using namespace search;
|
||||
|
||||
if (key == kQuery)
|
||||
m_searchRequest.m_query = value;
|
||||
else if (key == kLocale)
|
||||
m_searchRequest.m_locale = value;
|
||||
else if (key == kSearchOnMap)
|
||||
m_searchRequest.m_isSearchOnMap = true;
|
||||
else
|
||||
ParseCommonParam(key, value);
|
||||
}
|
||||
|
||||
void ParsedMapApi::ParseCommonParam(std::string const & key, std::string const & value)
|
||||
{
|
||||
if (key == kAppName)
|
||||
{
|
||||
m_appName = value;
|
||||
}
|
||||
else if (key == kCenterLatLon)
|
||||
{
|
||||
if (double lat, lon; ParseLatLon(key, value, lat, lon))
|
||||
m_centerLatLon = {lat, lon};
|
||||
else
|
||||
LOG(LWARNING, ("Incorrect 'cll':", value));
|
||||
}
|
||||
}
|
||||
|
||||
InAppFeatureHighlightRequest::InAppFeatureType ParseInAppFeatureType(std::string const & value)
|
||||
{
|
||||
if (value == "track-recorder")
|
||||
return InAppFeatureHighlightRequest::InAppFeatureType::TrackRecorder;
|
||||
if (value == "icloud")
|
||||
return InAppFeatureHighlightRequest::InAppFeatureType::iCloud;
|
||||
LOG(LERROR, ("Incorrect InAppFeatureType:", value));
|
||||
return InAppFeatureHighlightRequest::InAppFeatureType::None;
|
||||
}
|
||||
|
||||
void ParsedMapApi::ParseInAppFeatureHighlightParam(std::string const & key, std::string const & value)
|
||||
{
|
||||
if (key == highlight_feature::kHighlight)
|
||||
m_inAppFeatureHighlightRequest.m_feature = ParseInAppFeatureType(value);
|
||||
}
|
||||
|
||||
void ParsedMapApi::Reset()
|
||||
{
|
||||
m_requestType = UrlType::Incorrect;
|
||||
m_mapPoints.clear();
|
||||
m_routePoints.clear();
|
||||
m_searchRequest = {};
|
||||
m_oauth2code = {};
|
||||
m_globalBackUrl = {};
|
||||
m_appName = {};
|
||||
m_centerLatLon = ms::LatLon::Invalid();
|
||||
m_routingType = {};
|
||||
m_version = 0;
|
||||
m_zoomLevel = 0.0;
|
||||
m_goBackOnBalloonClick = false;
|
||||
m_inAppFeatureHighlightRequest = {};
|
||||
}
|
||||
|
||||
void ParsedMapApi::ExecuteMapApiRequest(Framework & fm) const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Map, ("Must be a Map API request"));
|
||||
VERIFY(m_mapPoints.size() > 0, ("Map API request must have at least one point"));
|
||||
|
||||
// Clear every current API-mark.
|
||||
auto editSession = fm.GetBookmarkManager().GetEditSession();
|
||||
editSession.ClearGroup(UserMark::Type::API);
|
||||
editSession.SetIsVisible(UserMark::Type::API, true);
|
||||
|
||||
// Add marks from the request.
|
||||
m2::RectD viewport;
|
||||
for (auto const & [lat, lon, name, id, style] : m_mapPoints)
|
||||
{
|
||||
m2::PointD const glPoint(mercator::FromLatLon(lat, lon));
|
||||
auto * mark = editSession.CreateUserMark<ApiMarkPoint>(glPoint);
|
||||
mark->SetName(name);
|
||||
mark->SetApiID(id);
|
||||
mark->SetStyle(style::GetSupportedStyle(style));
|
||||
viewport.Add(glPoint);
|
||||
}
|
||||
|
||||
// Calculate the optimal viewport.
|
||||
VERIFY(viewport.IsValid(), ());
|
||||
m2::PointD const center = viewport.Center();
|
||||
|
||||
// Calculate the best zoom.
|
||||
int zoomLevel;
|
||||
if (m_mapPoints.size() == 1)
|
||||
if (m_zoomLevel >= 1.0) // 0 means uninitialized/not passed to the API.
|
||||
zoomLevel = std::min(scales::GetUpperComfortScale(), static_cast<int>(m_zoomLevel));
|
||||
else
|
||||
zoomLevel = scales::GetUpperComfortScale();
|
||||
else
|
||||
zoomLevel = df::GetDrawTileScale(viewport);
|
||||
|
||||
// Always hide current map selection.
|
||||
fm.DeactivateMapSelection();
|
||||
|
||||
// Set viewport and stop follow mode.
|
||||
fm.StopLocationFollow();
|
||||
|
||||
// ShowRect function interferes with ActivateMapSelection and we have strange behaviour as a result.
|
||||
// Use more obvious SetModelViewCenter here.
|
||||
/// @todo Funny, but animation is still present, but now centering works fine.
|
||||
/// Looks like there is one more set viewport call somewhere.
|
||||
fm.SetViewportCenter(center, zoomLevel, false /* isAnim */, true /* trackVisibleViewport */);
|
||||
|
||||
// Don't show the place page in case of multiple points.
|
||||
if (m_mapPoints.size() > 1)
|
||||
return;
|
||||
|
||||
place_page::BuildInfo info;
|
||||
info.m_needAnimationOnSelection = false;
|
||||
info.m_mercator = mercator::FromLatLon(m_mapPoints[0].m_lat, m_mapPoints[0].m_lon);
|
||||
// Other details will be filled in by BuildPlacePageInfo().
|
||||
fm.BuildAndSetPlacePageInfo(info);
|
||||
}
|
||||
|
||||
std::string DebugPrint(ParsedMapApi::UrlType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ParsedMapApi::UrlType::Incorrect: return "Incorrect";
|
||||
case ParsedMapApi::UrlType::Map: return "Map";
|
||||
case ParsedMapApi::UrlType::Route: return "Route";
|
||||
case ParsedMapApi::UrlType::Search: return "Search";
|
||||
case ParsedMapApi::UrlType::Crosshair: return "Crosshair";
|
||||
case ParsedMapApi::UrlType::OAuth2: return "OAuth2";
|
||||
case ParsedMapApi::UrlType::Menu: return "Menu";
|
||||
case ParsedMapApi::UrlType::Settings: return "Settings";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace url_scheme
|
||||
149
libs/map/mwm_url.hpp
Normal file
149
libs/map/mwm_url.hpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Framework;
|
||||
|
||||
namespace url_scheme
|
||||
{
|
||||
struct MapPoint
|
||||
{
|
||||
double m_lat;
|
||||
double m_lon;
|
||||
std::string m_name;
|
||||
std::string m_id;
|
||||
std::string m_style;
|
||||
};
|
||||
|
||||
struct RoutePoint
|
||||
{
|
||||
RoutePoint() = default;
|
||||
RoutePoint(m2::PointD const & org, std::string const & name) : m_org(org), m_name(name) {}
|
||||
m2::PointD m_org = m2::PointD::Zero();
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
struct SearchRequest
|
||||
{
|
||||
std::string m_query;
|
||||
std::string m_locale;
|
||||
bool m_isSearchOnMap = false;
|
||||
};
|
||||
|
||||
struct InAppFeatureHighlightRequest
|
||||
{
|
||||
enum class InAppFeatureType
|
||||
{
|
||||
None = 0,
|
||||
TrackRecorder = 1,
|
||||
iCloud = 2,
|
||||
};
|
||||
|
||||
InAppFeatureType m_feature = InAppFeatureType::None;
|
||||
};
|
||||
|
||||
/// Handles [mapswithme|mwm|mapsme]://map|route|search?params - everything related to displaying info on a map
|
||||
class ParsedMapApi
|
||||
{
|
||||
public:
|
||||
enum class UrlType
|
||||
{
|
||||
Incorrect = 0,
|
||||
Map = 1,
|
||||
Route = 2,
|
||||
Search = 3,
|
||||
Crosshair = 4,
|
||||
OAuth2 = 5,
|
||||
Menu = 6,
|
||||
Settings = 7
|
||||
};
|
||||
|
||||
ParsedMapApi() = default;
|
||||
explicit ParsedMapApi(std::string const & url) { SetUrlAndParse(url); }
|
||||
|
||||
UrlType SetUrlAndParse(std::string const & url);
|
||||
UrlType GetRequestType() const { return m_requestType; }
|
||||
std::string const & GetGlobalBackUrl() const { return m_globalBackUrl; }
|
||||
std::string const & GetAppName() const { return m_appName; }
|
||||
ms::LatLon GetCenterLatLon() const { return m_centerLatLon; }
|
||||
int GetApiVersion() const { return m_version; }
|
||||
void Reset();
|
||||
bool GoBackOnBalloonClick() const { return m_goBackOnBalloonClick; }
|
||||
|
||||
void ExecuteMapApiRequest(Framework & fm) const;
|
||||
|
||||
// Unit test only.
|
||||
std::vector<MapPoint> const & GetMapPoints() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Map, ("Expected Map API"));
|
||||
return m_mapPoints;
|
||||
}
|
||||
|
||||
// Unit test only.
|
||||
double GetZoomLevel() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Map, ("Expected Map API"));
|
||||
return m_zoomLevel;
|
||||
}
|
||||
|
||||
std::vector<RoutePoint> const & GetRoutePoints() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Route, ("Expected Route API"));
|
||||
return m_routePoints;
|
||||
}
|
||||
|
||||
std::string const & GetRoutingType() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Route, ("Expected Route API"));
|
||||
return m_routingType;
|
||||
}
|
||||
|
||||
SearchRequest const & GetSearchRequest() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Search, ("Expected Search API"));
|
||||
return m_searchRequest;
|
||||
}
|
||||
|
||||
std::string const & GetOAuth2Code() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::OAuth2, ("Expected OAuth2 API"));
|
||||
return m_oauth2code;
|
||||
}
|
||||
|
||||
InAppFeatureHighlightRequest const & GetInAppFeatureHighlightRequest() const
|
||||
{
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Menu, ("Expected Menu API"));
|
||||
ASSERT_EQUAL(m_requestType, UrlType::Settings, ("Expected Settings API"));
|
||||
return m_inAppFeatureHighlightRequest;
|
||||
}
|
||||
|
||||
private:
|
||||
void ParseMapParam(std::string const & key, std::string const & value, bool & correctOrder);
|
||||
void ParseRouteParam(std::string const & key, std::string const & value, std::vector<std::string_view> & pattern);
|
||||
void ParseSearchParam(std::string const & key, std::string const & value);
|
||||
void ParseInAppFeatureHighlightParam(std::string const & key, std::string const & value);
|
||||
void ParseCommonParam(std::string const & key, std::string const & value);
|
||||
|
||||
UrlType m_requestType;
|
||||
std::vector<MapPoint> m_mapPoints;
|
||||
std::vector<RoutePoint> m_routePoints;
|
||||
SearchRequest m_searchRequest;
|
||||
InAppFeatureHighlightRequest m_inAppFeatureHighlightRequest;
|
||||
std::string m_globalBackUrl;
|
||||
std::string m_appName;
|
||||
std::string m_oauth2code;
|
||||
ms::LatLon m_centerLatLon = ms::LatLon::Invalid();
|
||||
std::string m_routingType;
|
||||
int m_version = 0;
|
||||
/// Zoom level in OSM format (e.g. from 1.0 to 20.0)
|
||||
/// Taken into an account when calculating viewport rect, but only if points count is == 1
|
||||
double m_zoomLevel = 0.0;
|
||||
bool m_goBackOnBalloonClick = false;
|
||||
};
|
||||
|
||||
std::string DebugPrint(ParsedMapApi::UrlType type);
|
||||
} // namespace url_scheme
|
||||
442
libs/map/place_page_info.cpp
Normal file
442
libs/map/place_page_info.cpp
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
#include "map/place_page_info.hpp"
|
||||
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
|
||||
#include "indexer/feature_utils.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/road_shields_parser.hpp"
|
||||
|
||||
#include "platform/distance.hpp"
|
||||
#include "platform/duration.hpp"
|
||||
#include "platform/localization.hpp"
|
||||
#include "platform/measurement_utils.hpp"
|
||||
#include "platform/preferred_languages.hpp"
|
||||
#include "platform/settings.hpp"
|
||||
#include "platform/utm_mgrs_utils.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include "3party/open-location-code/openlocationcode.h"
|
||||
|
||||
namespace place_page
|
||||
{
|
||||
|
||||
bool Info::IsBookmark() const
|
||||
{
|
||||
return BookmarkManager::IsBookmarkCategory(m_markGroupId) && BookmarkManager::IsBookmark(m_bookmarkId);
|
||||
}
|
||||
|
||||
bool Info::ShouldShowAddPlace() const
|
||||
{
|
||||
return !IsTrack() && !(IsFeature() && IsPointType());
|
||||
}
|
||||
|
||||
void Info::SetFromFeatureType(FeatureType & ft)
|
||||
{
|
||||
MapObject::SetFromFeatureType(ft);
|
||||
m_hasMetadata = true;
|
||||
|
||||
feature::NameParamsOut out;
|
||||
auto const mwmInfo = GetID().m_mwmId.GetInfo();
|
||||
if (mwmInfo)
|
||||
{
|
||||
feature::GetPreferredNames(
|
||||
{m_name, mwmInfo->GetRegionData(), languages::GetCurrentMapLanguage(), true /* allowTranslit */}, out);
|
||||
}
|
||||
|
||||
bool emptyTitle = false;
|
||||
|
||||
m_primaryFeatureName = out.GetPrimary();
|
||||
m_uiAddress = feature::GetReadableAddress(m_address);
|
||||
|
||||
if (IsBookmark())
|
||||
{
|
||||
m_uiTitle = GetBookmarkName();
|
||||
|
||||
std::string secondaryTitle;
|
||||
|
||||
if (!m_customName.empty())
|
||||
secondaryTitle = m_customName;
|
||||
else if (!out.secondary.empty())
|
||||
secondaryTitle = out.secondary;
|
||||
else
|
||||
secondaryTitle = m_primaryFeatureName;
|
||||
|
||||
if (m_uiTitle != secondaryTitle)
|
||||
m_uiSecondaryTitle = std::move(secondaryTitle);
|
||||
}
|
||||
else if (!m_primaryFeatureName.empty())
|
||||
{
|
||||
m_uiTitle = m_primaryFeatureName;
|
||||
m_uiSecondaryTitle = out.secondary;
|
||||
}
|
||||
else if (IsBuilding())
|
||||
{
|
||||
emptyTitle = m_address.empty();
|
||||
if (!emptyTitle)
|
||||
m_uiTitle = m_address;
|
||||
m_uiAddress.clear(); // already in main title
|
||||
}
|
||||
else
|
||||
emptyTitle = true;
|
||||
|
||||
// Assign Feature's type if main title is empty.
|
||||
if (emptyTitle)
|
||||
m_uiTitle = GetLocalizedType();
|
||||
|
||||
// Append local_ref tag into main title.
|
||||
if (IsPublicTransportStop())
|
||||
{
|
||||
auto const lRef = GetMetadata(feature::Metadata::FMD_LOCAL_REF);
|
||||
if (!lRef.empty())
|
||||
m_uiTitle.append(" (").append(lRef).append(")");
|
||||
}
|
||||
|
||||
m_uiSubtitle = FormatSubtitle(IsFeature() /* withTypes */, !emptyTitle /* withMainType */);
|
||||
|
||||
auto const branch = GetMetadata(feature::Metadata::FMD_BRANCH);
|
||||
if (!branch.empty())
|
||||
m_uiBranch = std::string(branch);
|
||||
|
||||
// apply to all types after checks
|
||||
m_isHotel = ftypes::IsHotelChecker::Instance()(ft);
|
||||
}
|
||||
|
||||
void Info::SetMercator(m2::PointD const & mercator)
|
||||
{
|
||||
m_mercator = mercator;
|
||||
m_buildInfo.m_mercator = mercator;
|
||||
}
|
||||
|
||||
std::string Info::FormatSubtitle(bool withTypes, bool withMainType) const
|
||||
{
|
||||
std::string result;
|
||||
auto const append = [&result](std::string_view sv)
|
||||
{
|
||||
if (!result.empty())
|
||||
result += feature::kFieldsSeparator;
|
||||
result += sv;
|
||||
};
|
||||
|
||||
if (!withTypes)
|
||||
return result;
|
||||
|
||||
// Types
|
||||
append(GetLocalizedAllTypes(withMainType));
|
||||
|
||||
// Flats.
|
||||
auto const flats = GetMetadata(feature::Metadata::FMD_FLATS);
|
||||
if (!flats.empty())
|
||||
append(flats);
|
||||
|
||||
// Cuisines.
|
||||
for (auto const & cuisine : GetLocalizedCuisines())
|
||||
append(cuisine);
|
||||
|
||||
// Recycling types.
|
||||
for (auto const & recycling : GetLocalizedRecyclingTypes())
|
||||
append(recycling);
|
||||
|
||||
// Airport IATA code.
|
||||
auto const iata = GetMetadata(feature::Metadata::FMD_AIRPORT_IATA);
|
||||
if (!iata.empty())
|
||||
append(iata);
|
||||
|
||||
// Road numbers/ids.
|
||||
auto const roadShields = FormatRoadShields();
|
||||
if (!roadShields.empty())
|
||||
append(roadShields);
|
||||
|
||||
// Stars.
|
||||
auto const stars = feature::FormatStars(GetStars());
|
||||
if (!stars.empty())
|
||||
append(stars);
|
||||
|
||||
// Operator.
|
||||
auto const op = GetMetadata(feature::Metadata::FMD_OPERATOR);
|
||||
if (!op.empty())
|
||||
append(op);
|
||||
|
||||
// Brand.
|
||||
auto const brand = GetMetadata(feature::Metadata::FMD_BRAND);
|
||||
if (!brand.empty() && brand != op)
|
||||
{
|
||||
/// @todo May not work as expected because we store raw value from OSM,
|
||||
/// while current localizations assume to have some string ids (like "mcdonalds").
|
||||
auto const locBrand = platform::GetLocalizedBrandName(std::string(brand));
|
||||
|
||||
// Do not duplicate for commonly used titles like McDonald's, Starbucks, etc.
|
||||
if (m_uiTitle.find(locBrand) == std::string::npos && m_uiSecondaryTitle.find(locBrand) == std::string::npos)
|
||||
append(locBrand);
|
||||
}
|
||||
|
||||
// Elevation.
|
||||
auto const eleStr = feature::FormatElevation(GetMetadata(MetadataID::FMD_ELE));
|
||||
if (!eleStr.empty())
|
||||
append(eleStr);
|
||||
|
||||
// ATM
|
||||
if (HasAtm())
|
||||
append(feature::kAtmSymbol);
|
||||
|
||||
// Internet.
|
||||
if (HasWifi())
|
||||
append(feature::kWifiSymbol);
|
||||
|
||||
// Toilets.
|
||||
if (HasToilets())
|
||||
append(feature::kToiletsSymbol);
|
||||
|
||||
// Drinking Water
|
||||
auto const drinkingWater = feature::FormatDrinkingWater(GetTypes());
|
||||
if (!drinkingWater.empty())
|
||||
append(drinkingWater);
|
||||
|
||||
// Wheelchair
|
||||
if (feature::GetWheelchairType(m_types) == ftraits::WheelchairAvailability::Yes)
|
||||
append(feature::kWheelchairSymbol);
|
||||
|
||||
// Fee.
|
||||
auto const fee = GetLocalizedFeeType();
|
||||
if (!fee.empty())
|
||||
append(fee);
|
||||
|
||||
// Debug types
|
||||
bool debugAllTypesSetting = false;
|
||||
settings::TryGet(kDebugAllTypesSetting, debugAllTypesSetting);
|
||||
if (debugAllTypesSetting)
|
||||
append(GetAllReadableTypes());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Info::GetBookmarkName()
|
||||
{
|
||||
std::string bookmarkName;
|
||||
|
||||
auto const mwmInfo = GetID().m_mwmId.GetInfo();
|
||||
if (mwmInfo)
|
||||
{
|
||||
bookmarkName = GetPreferredBookmarkStr(m_bookmarkData.m_customName, mwmInfo->GetRegionData());
|
||||
if (bookmarkName.empty())
|
||||
bookmarkName = GetPreferredBookmarkStr(m_bookmarkData.m_name, mwmInfo->GetRegionData());
|
||||
}
|
||||
|
||||
if (bookmarkName.empty())
|
||||
bookmarkName = GetPreferredBookmarkName(m_bookmarkData);
|
||||
|
||||
return bookmarkName;
|
||||
}
|
||||
|
||||
void Info::SetTitlesForBookmark()
|
||||
{
|
||||
m_uiTitle = GetBookmarkName();
|
||||
|
||||
std::vector<std::string> subtitle;
|
||||
|
||||
if (!m_bookmarkData.m_featureTypes.empty())
|
||||
subtitle.push_back(GetLocalizedFeatureType(m_bookmarkData.m_featureTypes));
|
||||
m_uiSubtitle = strings::JoinStrings(subtitle, feature::kFieldsSeparator);
|
||||
}
|
||||
|
||||
void Info::SetCustomName(std::string const & name)
|
||||
{
|
||||
if (IsBookmark())
|
||||
SetTitlesForBookmark();
|
||||
else
|
||||
m_uiTitle = name;
|
||||
|
||||
m_customName = name;
|
||||
}
|
||||
|
||||
void Info::SetTitlesForTrack(Track const & track)
|
||||
{
|
||||
m_uiTitle = track.GetName();
|
||||
|
||||
std::vector<std::string> statistics;
|
||||
auto const length = track.GetLengthMeters();
|
||||
auto const duration = track.GetDurationInSeconds();
|
||||
statistics.push_back(platform::Distance::CreateFormatted(length).ToString());
|
||||
if (duration > 0)
|
||||
statistics.push_back(platform::Duration(duration).GetPlatformLocalizedString());
|
||||
m_uiTrackStatistics = strings::JoinStrings(statistics, feature::kFieldsSeparator);
|
||||
}
|
||||
|
||||
void Info::SetCustomNames(std::string const & title, std::string const & subtitle)
|
||||
{
|
||||
m_uiTitle = title;
|
||||
m_uiSubtitle = subtitle;
|
||||
m_customName = title;
|
||||
}
|
||||
|
||||
void Info::SetCustomNameWithCoordinates(m2::PointD const & mercator, std::string const & name)
|
||||
{
|
||||
if (IsBookmark())
|
||||
{
|
||||
SetTitlesForBookmark();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_uiTitle = name;
|
||||
m_uiSubtitle = measurement_utils::FormatLatLon(mercator::YToLat(mercator.y), mercator::XToLon(mercator.x),
|
||||
true /* withComma */);
|
||||
}
|
||||
m_customName = name;
|
||||
}
|
||||
|
||||
void Info::SetFromBookmarkProperties(kml::Properties const & p)
|
||||
{
|
||||
if (auto const hours = p.find("hours"); hours != p.end() && !hours->second.empty())
|
||||
m_metadata.Set(feature::Metadata::EType::FMD_OPEN_HOURS, hours->second);
|
||||
if (auto const phone = p.find("phone"); phone != p.end() && !phone->second.empty())
|
||||
m_metadata.Set(feature::Metadata::EType::FMD_PHONE_NUMBER, phone->second);
|
||||
if (auto const email = p.find("email"); email != p.end() && !email->second.empty())
|
||||
m_metadata.Set(feature::Metadata::EType::FMD_EMAIL, email->second);
|
||||
if (auto const url = p.find("url"); url != p.end() && !url->second.empty())
|
||||
m_metadata.Set(feature::Metadata::EType::FMD_WEBSITE, url->second);
|
||||
m_hasMetadata = true;
|
||||
}
|
||||
|
||||
void Info::SetBookmarkId(kml::MarkId bookmarkId)
|
||||
{
|
||||
m_bookmarkId = bookmarkId;
|
||||
m_uiSubtitle = FormatSubtitle(IsFeature() /* withTypes */, IsFeature() /* withMainType */);
|
||||
}
|
||||
|
||||
bool Info::ShouldShowEditPlace() const
|
||||
{
|
||||
// TODO(mgsergio): Does IsFeature() imply !IsMyPosition()?
|
||||
return !IsMyPosition() && IsFeature();
|
||||
}
|
||||
|
||||
kml::LocalizableString Info::FormatNewBookmarkName() const
|
||||
{
|
||||
kml::LocalizableString bookmarkName;
|
||||
if (IsFeature())
|
||||
{
|
||||
m_name.ForEach([&bookmarkName](int8_t langCode, std::string_view localName)
|
||||
{
|
||||
if (!localName.empty())
|
||||
bookmarkName[langCode] = localName;
|
||||
});
|
||||
|
||||
if (bookmarkName.empty() && IsBuilding() && !m_address.empty())
|
||||
kml::SetDefaultStr(bookmarkName, m_address);
|
||||
}
|
||||
else if (!m_uiTitle.empty())
|
||||
{
|
||||
if (IsMyPosition())
|
||||
kml::SetDefaultStr(bookmarkName, platform::GetLocalizedMyPositionBookmarkName());
|
||||
else
|
||||
kml::SetDefaultStr(bookmarkName, m_uiTitle);
|
||||
}
|
||||
|
||||
return bookmarkName;
|
||||
}
|
||||
|
||||
std::string Info::GetFormattedCoordinate(CoordinatesFormat coordsFormat) const
|
||||
{
|
||||
auto const & ll = GetLatLon();
|
||||
auto const lat = ll.m_lat;
|
||||
auto const lon = ll.m_lon;
|
||||
switch (coordsFormat)
|
||||
{
|
||||
default:
|
||||
case CoordinatesFormat::LatLonDMS: // DMS, comma separated
|
||||
return measurement_utils::FormatLatLonAsDMS(lat, lon, false /*withComma*/, 2);
|
||||
case CoordinatesFormat::LatLonDecimal: // Decimal, comma separated
|
||||
return measurement_utils::FormatLatLon(lat, lon, true /* withComma */);
|
||||
case CoordinatesFormat::OLCFull: // Open location code, long format
|
||||
return openlocationcode::Encode({lat, lon});
|
||||
case CoordinatesFormat::OSMLink: // Link to osm.org
|
||||
return measurement_utils::FormatOsmLink(lat, lon, 14);
|
||||
case CoordinatesFormat::UTM: // Universal Transverse Mercator
|
||||
{
|
||||
std::string utmCoords = utm_mgrs_utils::FormatUTM(lat, lon);
|
||||
if (utmCoords.empty())
|
||||
return "UTM: N/A";
|
||||
else
|
||||
return "UTM: " + utmCoords;
|
||||
}
|
||||
case CoordinatesFormat::MGRS: // Military Grid Reference System
|
||||
{
|
||||
std::string mgrsCoords = utm_mgrs_utils::FormatMGRS(lat, lon, 5);
|
||||
if (mgrsCoords.empty())
|
||||
return "MGRS: N/A";
|
||||
else
|
||||
return "MGRS: " + mgrsCoords;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Info::SetRoadType(RoadWarningMarkType type, std::string const & localizedType, std::string const & distance)
|
||||
{
|
||||
m_roadType = type;
|
||||
m_uiTitle = localizedType;
|
||||
m_uiSubtitle = distance;
|
||||
}
|
||||
|
||||
void Info::SetRoadType(FeatureType & ft, RoadWarningMarkType type, std::string const & localizedType,
|
||||
std::string const & distance)
|
||||
{
|
||||
auto const addTitle = [this](std::string && str)
|
||||
{
|
||||
if (!m_uiTitle.empty())
|
||||
{
|
||||
m_uiTitle += feature::kFieldsSeparator;
|
||||
m_uiTitle += str;
|
||||
}
|
||||
else
|
||||
m_uiTitle = std::move(str);
|
||||
};
|
||||
|
||||
auto const addSubtitle = [this](std::string_view sv)
|
||||
{
|
||||
if (!m_uiSubtitle.empty())
|
||||
m_uiSubtitle += feature::kFieldsSeparator;
|
||||
m_uiSubtitle += sv;
|
||||
};
|
||||
|
||||
CHECK_NOT_EQUAL(type, RoadWarningMarkType::Count, ());
|
||||
m_roadType = type;
|
||||
|
||||
std::vector<std::string> subtitle;
|
||||
if (type == RoadWarningMarkType::Toll)
|
||||
{
|
||||
std::vector<std::string> title;
|
||||
for (auto const & shield : ftypes::GetRoadShields(ft))
|
||||
{
|
||||
auto name = shield.m_name;
|
||||
if (!shield.m_additionalText.empty())
|
||||
name += " " + shield.m_additionalText;
|
||||
addTitle(std::move(name));
|
||||
}
|
||||
|
||||
if (m_uiTitle.empty())
|
||||
m_uiTitle = m_primaryFeatureName;
|
||||
|
||||
if (m_uiTitle.empty())
|
||||
m_uiTitle = localizedType;
|
||||
else
|
||||
addSubtitle(localizedType);
|
||||
addSubtitle(distance);
|
||||
}
|
||||
else if (type == RoadWarningMarkType::Dirty)
|
||||
{
|
||||
m_uiTitle = localizedType;
|
||||
addSubtitle(distance);
|
||||
}
|
||||
else if (type == RoadWarningMarkType::Ferry)
|
||||
{
|
||||
m_uiTitle = m_primaryFeatureName;
|
||||
addSubtitle(localizedType);
|
||||
|
||||
auto const operatorName = GetMetadata(feature::Metadata::FMD_OPERATOR);
|
||||
if (!operatorName.empty())
|
||||
addSubtitle(operatorName);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace place_page
|
||||
287
libs/map/place_page_info.hpp
Normal file
287
libs/map/place_page_info.hpp
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/routing_mark.hpp"
|
||||
|
||||
#include "storage/storage_defines.hpp"
|
||||
|
||||
#include "drape_frontend/frontend_renderer.hpp"
|
||||
#include "drape_frontend/selection_shape.hpp"
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_source.hpp"
|
||||
#include "indexer/map_object.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace place_page
|
||||
{
|
||||
std::string_view constexpr kDebugAllTypesSetting = "DebugAllTypes";
|
||||
|
||||
enum class OpeningMode
|
||||
{
|
||||
Preview = 0,
|
||||
PreviewPlus,
|
||||
Details,
|
||||
Full
|
||||
};
|
||||
|
||||
enum class CoordinatesFormat
|
||||
{
|
||||
LatLonDMS = 0, // DMS, comma separated
|
||||
LatLonDecimal, // Decimal, comma separated
|
||||
OLCFull, // Open location code, long format
|
||||
OSMLink, // Link to osm.org
|
||||
UTM, // Universal Transverse Mercator
|
||||
MGRS // Military Grid Reference System
|
||||
};
|
||||
|
||||
struct BuildInfo
|
||||
{
|
||||
enum class Source : uint8_t
|
||||
{
|
||||
User,
|
||||
Search,
|
||||
Other
|
||||
};
|
||||
|
||||
enum class Match : uint8_t
|
||||
{
|
||||
Everything = 0,
|
||||
FeatureOnly,
|
||||
UserMarkOnly,
|
||||
TrackOnly,
|
||||
Nothing
|
||||
};
|
||||
|
||||
BuildInfo() = default;
|
||||
|
||||
explicit BuildInfo(df::TapInfo const & info)
|
||||
: m_source(Source::User)
|
||||
, m_mercator(info.m_mercator)
|
||||
, m_isLongTap(info.m_isLong)
|
||||
, m_isMyPosition(info.m_isMyPositionTapped)
|
||||
, m_featureId(info.m_featureTapped)
|
||||
, m_userMarkId(info.m_markTapped)
|
||||
{}
|
||||
|
||||
bool IsFeatureMatchingEnabled() const { return m_match == Match::Everything || m_match == Match::FeatureOnly; }
|
||||
|
||||
bool IsUserMarkMatchingEnabled() const { return m_match == Match::Everything || m_match == Match::UserMarkOnly; }
|
||||
|
||||
bool IsTrackMatchingEnabled() const { return m_match == Match::Everything || m_match == Match::TrackOnly; }
|
||||
|
||||
Source m_source = Source::Other;
|
||||
m2::PointD m_mercator;
|
||||
bool m_isLongTap = false;
|
||||
bool m_isMyPosition = false;
|
||||
FeatureID m_featureId;
|
||||
Match m_match = Match::Everything;
|
||||
kml::MarkId m_userMarkId = kml::kInvalidMarkId;
|
||||
kml::TrackId m_trackId = kml::kInvalidTrackId;
|
||||
bool m_isGeometrySelectionAllowed = false;
|
||||
bool m_needAnimationOnSelection = true;
|
||||
std::string m_postcode;
|
||||
};
|
||||
|
||||
class Info : public osm::MapObject
|
||||
{
|
||||
public:
|
||||
void SetBuildInfo(place_page::BuildInfo const & info) { m_buildInfo = info; }
|
||||
place_page::BuildInfo const & GetBuildInfo() const { return m_buildInfo; }
|
||||
|
||||
/// Place traits
|
||||
bool IsFeature() const { return m_featureID.IsValid(); }
|
||||
bool IsBookmark() const;
|
||||
bool IsTrack() const { return m_trackId != kml::kInvalidTrackId; }
|
||||
bool IsMyPosition() const { return m_selectedObject == df::SelectionShape::ESelectedObject::OBJECT_MY_POSITION; }
|
||||
bool IsRoutePoint() const { return m_isRoutePoint; }
|
||||
bool IsRoadType() const { return m_roadType != RoadWarningMarkType::Count; }
|
||||
bool HasMetadata() const { return m_hasMetadata; }
|
||||
|
||||
/// Edit and add
|
||||
bool ShouldShowAddPlace() const;
|
||||
bool ShouldShowEditPlace() const;
|
||||
|
||||
bool ShouldEnableAddPlace() const { return m_canEditOrAdd; }
|
||||
bool ShouldEnableEditPlace() const { return m_canEditOrAdd; }
|
||||
|
||||
/// @returns true if Back API button should be displayed.
|
||||
bool HasApiUrl() const { return !m_apiUrl.empty(); }
|
||||
/// TODO: Support all possible Internet types in UI. @See MapObject::GetInternet().
|
||||
bool HasWifi() const { return GetInternet() == feature::Internet::Wlan; }
|
||||
/// Should be used by UI code to generate cool name for new bookmarks.
|
||||
// TODO: Tune new bookmark name. May be add address or some other data.
|
||||
kml::LocalizableString FormatNewBookmarkName() const;
|
||||
|
||||
/// For showing in UI
|
||||
std::string const & GetTitle() const { return m_uiTitle; }
|
||||
std::string const & GetBranch() const { return m_uiBranch; }
|
||||
/// Convenient wrapper for secondary feature name.
|
||||
std::string const & GetSecondaryTitle() const { return m_uiSecondaryTitle; }
|
||||
/// Convenient wrapper for type, cuisines, elevation, stars, wifi etc.
|
||||
std::string const & GetSubtitle() const { return m_uiSubtitle; }
|
||||
std::string const & GetSecondarySubtitle() const
|
||||
{
|
||||
return !m_uiTrackStatistics.empty() ? m_uiTrackStatistics : m_uiAddress;
|
||||
}
|
||||
std::string const & GetWikiDescription() const { return m_description; }
|
||||
/// @returns coordinate in DMS format if isDMS is true
|
||||
std::string GetFormattedCoordinate(CoordinatesFormat format) const;
|
||||
|
||||
/// UI setters
|
||||
void SetCustomName(std::string const & name);
|
||||
void SetTitlesForBookmark();
|
||||
void SetTitlesForTrack(Track const & track);
|
||||
void SetCustomNames(std::string const & title, std::string const & subtitle);
|
||||
void SetCustomNameWithCoordinates(m2::PointD const & mercator, std::string const & name);
|
||||
void SetAddress(std::string && address) { m_address = std::move(address); }
|
||||
void SetCanEditOrAdd(bool canEditOrAdd) { m_canEditOrAdd = canEditOrAdd; }
|
||||
|
||||
/// Bookmark
|
||||
void SetFromBookmarkProperties(kml::Properties const & p);
|
||||
void SetBookmarkId(kml::MarkId bookmarkId);
|
||||
kml::MarkId GetBookmarkId() const { return m_bookmarkId; }
|
||||
void SetBookmarkCategoryId(kml::MarkGroupId markGroupId) { m_markGroupId = markGroupId; }
|
||||
kml::MarkGroupId GetBookmarkCategoryId() const { return m_markGroupId; }
|
||||
std::string const & GetBookmarkCategoryName() const { return m_bookmarkCategoryName; }
|
||||
void SetBookmarkCategoryName(std::string const & name) { m_bookmarkCategoryName = name; }
|
||||
void SetBookmarkData(kml::BookmarkData const & data) { m_bookmarkData = data; }
|
||||
kml::BookmarkData const & GetBookmarkData() const { return m_bookmarkData; }
|
||||
|
||||
/// Track
|
||||
void SetTrackId(kml::TrackId trackId) { m_trackId = trackId; }
|
||||
kml::TrackId GetTrackId() const { return m_trackId; }
|
||||
|
||||
/// Api
|
||||
void SetApiId(std::string const & apiId) { m_apiId = apiId; }
|
||||
void SetApiUrl(std::string const & url) { m_apiUrl = url; }
|
||||
std::string const & GetApiUrl() const { return m_apiUrl; }
|
||||
|
||||
void SetOpeningMode(OpeningMode openingMode) { m_openingMode = openingMode; }
|
||||
OpeningMode GetOpeningMode() const { return m_openingMode; }
|
||||
|
||||
/// Feature status
|
||||
void SetFeatureStatus(FeatureStatus const status) { m_featureStatus = status; }
|
||||
FeatureStatus GetFeatureStatus() const { return m_featureStatus; }
|
||||
|
||||
/// Routing
|
||||
void SetRouteMarkType(RouteMarkType type) { m_routeMarkType = type; }
|
||||
RouteMarkType GetRouteMarkType() const { return m_routeMarkType; }
|
||||
void SetIntermediateIndex(size_t index) { m_intermediateIndex = index; }
|
||||
size_t GetIntermediateIndex() const { return m_intermediateIndex; }
|
||||
void SetIsRoutePoint() { m_isRoutePoint = true; }
|
||||
|
||||
/// Road type
|
||||
void SetRoadType(FeatureType & ft, RoadWarningMarkType type, std::string const & localizedType,
|
||||
std::string const & distance);
|
||||
void SetRoadType(RoadWarningMarkType type, std::string const & localizedType, std::string const & distance);
|
||||
RoadWarningMarkType GetRoadType() const { return m_roadType; }
|
||||
|
||||
/// CountryId
|
||||
/// Which mwm this MapObject is in.
|
||||
/// Exception: for a country-name point it will be set to the topmost node for the mwm.
|
||||
/// TODO(@a): use m_topmostCountryIds in exceptional case.
|
||||
void SetCountryId(storage::CountryId const & countryId) { m_countryId = countryId; }
|
||||
storage::CountryId const & GetCountryId() const { return m_countryId; }
|
||||
template <typename Countries>
|
||||
void SetTopmostCountryIds(Countries && ids)
|
||||
{
|
||||
m_topmostCountryIds = std::forward<Countries>(ids);
|
||||
}
|
||||
storage::CountriesVec const & GetTopmostCountryIds() const { return m_topmostCountryIds; }
|
||||
|
||||
/// MapObject
|
||||
void SetFromFeatureType(FeatureType & ft);
|
||||
|
||||
void SetWikiDescription(std::string && description) { m_description = std::move(description); }
|
||||
|
||||
void SetMercator(m2::PointD const & mercator);
|
||||
std::vector<std::string> GetRawTypes() const { return m_types.ToObjectNames(); }
|
||||
|
||||
bool IsHotel() const { return m_isHotel; }
|
||||
|
||||
// void SetPopularity(uint8_t popularity) { m_popularity = popularity; }
|
||||
// uint8_t GetPopularity() const { return m_popularity; }
|
||||
std::string const & GetPrimaryFeatureName() const { return m_primaryFeatureName; }
|
||||
|
||||
void SetSelectedObject(df::SelectionShape::ESelectedObject selectedObject) { m_selectedObject = selectedObject; }
|
||||
df::SelectionShape::ESelectedObject GetSelectedObject() const { return m_selectedObject; }
|
||||
|
||||
private:
|
||||
std::string FormatSubtitle(bool withTypes, bool withMainType) const;
|
||||
std::string GetBookmarkName();
|
||||
|
||||
place_page::BuildInfo m_buildInfo;
|
||||
|
||||
/// UI
|
||||
std::string m_uiTitle;
|
||||
std::string m_uiBranch;
|
||||
std::string m_uiSubtitle;
|
||||
std::string m_uiSecondaryTitle;
|
||||
std::string m_uiAddress;
|
||||
std::string m_uiTrackStatistics;
|
||||
std::string m_description;
|
||||
/// Booking rating string
|
||||
std::string m_localizedRatingString;
|
||||
|
||||
/// CountryId
|
||||
storage::CountryId m_countryId = storage::kInvalidCountryId;
|
||||
/// The topmost downloader nodes this MapObject is in, i.e.
|
||||
/// the country name for an object whose mwm represents only
|
||||
/// one part of the country (or several countries for disputed territories).
|
||||
storage::CountriesVec m_topmostCountryIds;
|
||||
|
||||
/// Comes from API, shared links etc.
|
||||
std::string m_customName;
|
||||
|
||||
/// Bookmark or track
|
||||
kml::MarkGroupId m_markGroupId = kml::kInvalidMarkGroupId;
|
||||
/// If not invalid, bookmark is bound to this place page.
|
||||
kml::MarkId m_bookmarkId = kml::kInvalidMarkId;
|
||||
/// Bookmark category name. Empty, if it's not bookmark;
|
||||
std::string m_bookmarkCategoryName;
|
||||
kml::BookmarkData m_bookmarkData;
|
||||
/// If not invalid, track is bound to this place page.
|
||||
kml::TrackId m_trackId = kml::kInvalidTrackId;
|
||||
/// Whether to treat it as plain feature.
|
||||
bool m_hasMetadata = false;
|
||||
|
||||
/// Api ID passed for the selected object. It's automatically included in api url below.
|
||||
std::string m_apiId;
|
||||
/// [Deep] link to open when "Back" button is pressed in a Place Page.
|
||||
std::string m_apiUrl;
|
||||
/// Formatted feature address for inner using.
|
||||
std::string m_address;
|
||||
|
||||
/// Routing
|
||||
RouteMarkType m_routeMarkType;
|
||||
size_t m_intermediateIndex = 0;
|
||||
bool m_isRoutePoint = false;
|
||||
|
||||
/// Road type
|
||||
RoadWarningMarkType m_roadType = RoadWarningMarkType::Count;
|
||||
|
||||
/// Editor
|
||||
/// True if editing of a selected point is allowed by basic logic.
|
||||
/// See initialization in framework.
|
||||
bool m_canEditOrAdd = false;
|
||||
|
||||
/// Feature status
|
||||
FeatureStatus m_featureStatus = FeatureStatus::Untouched;
|
||||
|
||||
bool m_isHotel = false;
|
||||
|
||||
// uint8_t m_popularity = 0;
|
||||
|
||||
std::string m_primaryFeatureName;
|
||||
|
||||
OpeningMode m_openingMode = OpeningMode::Preview;
|
||||
|
||||
df::SelectionShape::ESelectedObject m_selectedObject = df::SelectionShape::ESelectedObject::OBJECT_EMPTY;
|
||||
};
|
||||
} // namespace place_page
|
||||
13
libs/map/position_provider.hpp
Normal file
13
libs/map/position_provider.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
class PositionProvider
|
||||
{
|
||||
public:
|
||||
virtual ~PositionProvider() = default;
|
||||
|
||||
virtual std::optional<m2::PointD> GetCurrentPosition() const = 0;
|
||||
};
|
||||
135
libs/map/power_management/power_management_schemas.cpp
Normal file
135
libs/map/power_management/power_management_schemas.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#include "map/power_management/power_management_schemas.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace power_management;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<Scheme, FacilitiesState> const kSchemeToState = {
|
||||
{Scheme::Normal,
|
||||
{{
|
||||
/* Buildings3d */ true,
|
||||
/* PerspectiveView */ true,
|
||||
/* TrackRecording */ true,
|
||||
/* TrafficJams */ true,
|
||||
/* GpsTrackingForTraffic */ true,
|
||||
/* OsmEditsUploading */ true,
|
||||
/* UgcUploading */ true,
|
||||
/* BookmarkCloudUploading */ true,
|
||||
/* MapDownloader */ true,
|
||||
}}},
|
||||
{Scheme::EconomyMedium,
|
||||
{{
|
||||
/* Buildings3d */ true,
|
||||
/* PerspectiveView */ false,
|
||||
/* TrackRecording */ true,
|
||||
/* TrafficJams */ true,
|
||||
/* GpsTrackingForTraffic */ true,
|
||||
/* OsmEditsUploading */ true,
|
||||
/* UgcUploading */ true,
|
||||
/* BookmarkCloudUploading */ false,
|
||||
/* MapDownloader */ true,
|
||||
}}},
|
||||
{Scheme::EconomyMaximum,
|
||||
{{
|
||||
/* Buildings3d */ false,
|
||||
/* PerspectiveView */ false,
|
||||
/* TrackRecording */ false,
|
||||
/* TrafficJams */ false,
|
||||
/* GpsTrackingForTraffic */ false,
|
||||
/* OsmEditsUploading */ false,
|
||||
/* UgcUploading */ false,
|
||||
/* BookmarkCloudUploading */ false,
|
||||
/* MapDownloader */ true,
|
||||
}}},
|
||||
};
|
||||
|
||||
std::unordered_map<AutoScheme, FacilitiesState> const kAutoSchemeToState = {
|
||||
{AutoScheme::Normal,
|
||||
{{
|
||||
/* Buildings3d */ true,
|
||||
/* PerspectiveView */ true,
|
||||
/* TrackRecording */ true,
|
||||
/* TrafficJams */ true,
|
||||
/* GpsTrackingForTraffic */ true,
|
||||
/* OsmEditsUploading */ true,
|
||||
/* UgcUploading */ true,
|
||||
/* BookmarkCloudUploading */ true,
|
||||
/* MapDownloader */ true,
|
||||
}}},
|
||||
{AutoScheme::EconomyMedium,
|
||||
{{
|
||||
/* Buildings3d */ true,
|
||||
/* PerspectiveView */ false,
|
||||
/* TrackRecording */ true,
|
||||
/* TrafficJams */ true,
|
||||
/* GpsTrackingForTraffic */ false,
|
||||
/* OsmEditsUploading */ true,
|
||||
/* UgcUploading */ true,
|
||||
/* BookmarkCloudUploading */ false,
|
||||
/* MapDownloader */ false,
|
||||
}}},
|
||||
{AutoScheme::EconomyMaximum,
|
||||
{{
|
||||
/* Buildings3d */ false,
|
||||
/* PerspectiveView */ false,
|
||||
/* TrackRecording */ false,
|
||||
/* TrafficJams */ false,
|
||||
/* GpsTrackingForTraffic */ false,
|
||||
/* OsmEditsUploading */ false,
|
||||
/* UgcUploading */ false,
|
||||
/* BookmarkCloudUploading */ false,
|
||||
/* MapDownloader */ false,
|
||||
}}},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace power_management
|
||||
{
|
||||
FacilitiesState const & GetFacilitiesState(Scheme const scheme)
|
||||
{
|
||||
CHECK_NOT_EQUAL(scheme, Scheme::None, ());
|
||||
CHECK_NOT_EQUAL(scheme, Scheme::Auto, ());
|
||||
|
||||
return kSchemeToState.at(scheme);
|
||||
}
|
||||
|
||||
FacilitiesState const & GetFacilitiesState(AutoScheme const autoScheme)
|
||||
{
|
||||
return kAutoSchemeToState.at(autoScheme);
|
||||
}
|
||||
|
||||
std::string DebugPrint(Facility const facility)
|
||||
{
|
||||
switch (facility)
|
||||
{
|
||||
case Facility::Buildings3d: return "Buildings3d";
|
||||
case Facility::PerspectiveView: return "PerspectiveView";
|
||||
case Facility::TrackRecording: return "TrackRecording";
|
||||
case Facility::TrafficJams: return "TrafficJams";
|
||||
case Facility::GpsTrackingForTraffic: return "GpsTrackingForTraffic";
|
||||
case Facility::OsmEditsUploading: return "OsmEditsUploading";
|
||||
case Facility::UgcUploading: return "UgcUploading";
|
||||
case Facility::BookmarkCloudUploading: return "BookmarkCloudUploading";
|
||||
case Facility::MapDownloader: return "MapDownloader";
|
||||
case Facility::Count: return "Count";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(Scheme const scheme)
|
||||
{
|
||||
switch (scheme)
|
||||
{
|
||||
case Scheme::None: return "None";
|
||||
case Scheme::Normal: return "Normal";
|
||||
case Scheme::EconomyMedium: return "EconomyMedium";
|
||||
case Scheme::EconomyMaximum: return "EconomyMaximum";
|
||||
case Scheme::Auto: return "Auto";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace power_management
|
||||
51
libs/map/power_management/power_management_schemas.hpp
Normal file
51
libs/map/power_management/power_management_schemas.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace power_management
|
||||
{
|
||||
// Note: the order is important.
|
||||
// Note: new facilities must be added before Facility::Count.
|
||||
// Note: do not use Facility::Count in external code, this value for internal use only.
|
||||
enum class Facility : uint8_t
|
||||
{
|
||||
Buildings3d,
|
||||
PerspectiveView,
|
||||
TrackRecording,
|
||||
TrafficJams,
|
||||
GpsTrackingForTraffic,
|
||||
OsmEditsUploading,
|
||||
UgcUploading,
|
||||
BookmarkCloudUploading,
|
||||
MapDownloader,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
// Note: the order is important.
|
||||
enum class Scheme : uint8_t
|
||||
{
|
||||
None,
|
||||
Normal,
|
||||
EconomyMedium,
|
||||
EconomyMaximum,
|
||||
Auto,
|
||||
};
|
||||
|
||||
enum class AutoScheme : uint8_t
|
||||
{
|
||||
Normal,
|
||||
EconomyMedium,
|
||||
EconomyMaximum,
|
||||
};
|
||||
|
||||
using FacilitiesState = std::array<bool, static_cast<size_t>(Facility::Count)>;
|
||||
|
||||
FacilitiesState const & GetFacilitiesState(Scheme const scheme);
|
||||
FacilitiesState const & GetFacilitiesState(AutoScheme const autoScheme);
|
||||
|
||||
std::string DebugPrint(Facility const facility);
|
||||
std::string DebugPrint(Scheme const scheme);
|
||||
} // namespace power_management
|
||||
222
libs/map/power_management/power_manager.cpp
Normal file
222
libs/map/power_management/power_manager.cpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#include "map/power_management/power_manager.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/internal/file_data.hpp"
|
||||
#include "coding/serdes_json.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
using namespace power_management;
|
||||
|
||||
namespace
|
||||
{
|
||||
using Subscribers = std::vector<PowerManager::Subscriber *>;
|
||||
|
||||
void NotifySubscribers(Subscribers & subscribers, Scheme const scheme)
|
||||
{
|
||||
for (auto & subscriber : subscribers)
|
||||
subscriber->OnPowerSchemeChanged(scheme);
|
||||
}
|
||||
|
||||
void NotifySubscribers(Subscribers & subscribers, Facility const facility, bool enabled)
|
||||
{
|
||||
for (auto & subscriber : subscribers)
|
||||
subscriber->OnPowerFacilityChanged(facility, enabled);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace power_management
|
||||
{
|
||||
// static
|
||||
std::string PowerManager::GetConfigPath()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().SettingsDir(), "power_manager_config");
|
||||
}
|
||||
|
||||
void PowerManager::Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
FileReader reader(GetConfigPath());
|
||||
NonOwningReaderSource source(reader);
|
||||
|
||||
coding::DeserializerJson des(source);
|
||||
Config result;
|
||||
des(result);
|
||||
|
||||
m_config = result;
|
||||
|
||||
if (m_config.m_scheme == Scheme::Auto)
|
||||
GetPlatform().GetBatteryTracker().Subscribe(this);
|
||||
|
||||
for (size_t i = 0; i < m_config.m_facilities.size(); ++i)
|
||||
NotifySubscribers(m_subscribers, static_cast<Facility>(i), m_config.m_facilities[i]);
|
||||
|
||||
NotifySubscribers(m_subscribers, m_config.m_scheme);
|
||||
return;
|
||||
}
|
||||
catch (base::Json::Exception & ex)
|
||||
{
|
||||
LOG(LERROR, ("Cannot deserialize power manager data from file. Exception:", ex.Msg()));
|
||||
}
|
||||
catch (FileReader::Exception const & ex)
|
||||
{
|
||||
LOG(LWARNING, ("Cannot read power manager config file. Exception:", ex.Msg()));
|
||||
}
|
||||
|
||||
// Reset to default state.
|
||||
m_config = {};
|
||||
}
|
||||
|
||||
void PowerManager::SetFacility(Facility const facility, bool enabled)
|
||||
{
|
||||
CHECK_NOT_EQUAL(facility, Facility::Count, ());
|
||||
|
||||
if (m_config.m_facilities[static_cast<size_t>(facility)] == enabled)
|
||||
return;
|
||||
|
||||
m_config.m_facilities[static_cast<size_t>(facility)] = enabled;
|
||||
|
||||
auto const isSchemeChanged = m_config.m_scheme != Scheme::None;
|
||||
|
||||
if (m_config.m_scheme == Scheme::Auto)
|
||||
GetPlatform().GetBatteryTracker().Unsubscribe(this);
|
||||
|
||||
m_config.m_scheme = Scheme::None;
|
||||
|
||||
if (!Save())
|
||||
return;
|
||||
|
||||
NotifySubscribers(m_subscribers, facility, enabled);
|
||||
|
||||
if (isSchemeChanged)
|
||||
NotifySubscribers(m_subscribers, m_config.m_scheme);
|
||||
}
|
||||
|
||||
void PowerManager::SetScheme(Scheme const scheme)
|
||||
{
|
||||
if (m_config.m_scheme == scheme)
|
||||
return;
|
||||
|
||||
m_config.m_scheme = scheme;
|
||||
|
||||
if (m_config.m_scheme == Scheme::Auto)
|
||||
GetPlatform().GetBatteryTracker().Subscribe(this);
|
||||
else
|
||||
GetPlatform().GetBatteryTracker().Unsubscribe(this);
|
||||
|
||||
if (m_config.m_scheme == Scheme::None || m_config.m_scheme == Scheme::Auto)
|
||||
{
|
||||
if (!Save())
|
||||
return;
|
||||
|
||||
NotifySubscribers(m_subscribers, m_config.m_scheme);
|
||||
return;
|
||||
}
|
||||
|
||||
auto actualState = GetFacilitiesState(scheme);
|
||||
|
||||
std::swap(m_config.m_facilities, actualState);
|
||||
|
||||
if (!Save())
|
||||
return;
|
||||
|
||||
NotifySubscribers(m_subscribers, m_config.m_scheme);
|
||||
|
||||
for (size_t i = 0; i < actualState.size(); ++i)
|
||||
if (m_config.m_facilities[i] != actualState[i])
|
||||
NotifySubscribers(m_subscribers, static_cast<Facility>(i), m_config.m_facilities[i]);
|
||||
}
|
||||
|
||||
bool PowerManager::IsFacilityEnabled(Facility const facility) const
|
||||
{
|
||||
CHECK_NOT_EQUAL(facility, Facility::Count, ());
|
||||
|
||||
return m_config.m_facilities[static_cast<size_t>(facility)];
|
||||
}
|
||||
|
||||
FacilitiesState const & PowerManager::GetFacilities() const
|
||||
{
|
||||
return m_config.m_facilities;
|
||||
}
|
||||
|
||||
Scheme const & PowerManager::GetScheme() const
|
||||
{
|
||||
return m_config.m_scheme;
|
||||
}
|
||||
|
||||
void PowerManager::OnBatteryLevelReceived(uint8_t level)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(level, 100, ());
|
||||
|
||||
if (m_config.m_scheme != Scheme::Auto)
|
||||
return;
|
||||
|
||||
AutoScheme actualScheme = AutoScheme::Normal;
|
||||
if (level < 20)
|
||||
actualScheme = AutoScheme::EconomyMaximum;
|
||||
else if (level < 30)
|
||||
actualScheme = AutoScheme::EconomyMedium;
|
||||
|
||||
auto actualState = GetFacilitiesState(actualScheme);
|
||||
|
||||
if (m_config.m_facilities == actualState)
|
||||
return;
|
||||
|
||||
std::swap(m_config.m_facilities, actualState);
|
||||
|
||||
if (!Save())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < actualState.size(); ++i)
|
||||
if (m_config.m_facilities[i] != actualState[i])
|
||||
NotifySubscribers(m_subscribers, static_cast<Facility>(i), m_config.m_facilities[i]);
|
||||
}
|
||||
|
||||
void PowerManager::Subscribe(Subscriber * subscriber)
|
||||
{
|
||||
ASSERT(subscriber, ());
|
||||
m_subscribers.push_back(subscriber);
|
||||
}
|
||||
|
||||
void PowerManager::UnsubscribeAll()
|
||||
{
|
||||
m_subscribers.clear();
|
||||
}
|
||||
|
||||
bool PowerManager::Save()
|
||||
{
|
||||
auto const result = base::WriteToTempAndRenameToFile(GetConfigPath(), [this](std::string const & fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileWriter writer(fileName);
|
||||
coding::SerializerJson<FileWriter> ser(writer);
|
||||
ser(m_config);
|
||||
return true;
|
||||
}
|
||||
catch (base::Json::Exception & ex)
|
||||
{
|
||||
LOG(LERROR, ("Cannot serialize power manager data into file. Exception:", ex.Msg()));
|
||||
}
|
||||
catch (FileWriter::Exception const & ex)
|
||||
{
|
||||
LOG(LERROR, ("Cannot write power manager file. Exception:", ex.Msg()));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (result)
|
||||
return true;
|
||||
|
||||
// Try to load last correct state and notify subscribers.
|
||||
Load();
|
||||
return false;
|
||||
}
|
||||
} // namespace power_management
|
||||
62
libs/map/power_management/power_manager.hpp
Normal file
62
libs/map/power_management/power_manager.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/power_management/power_management_schemas.hpp"
|
||||
|
||||
#include "platform/battery_tracker.hpp"
|
||||
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace power_management
|
||||
{
|
||||
// Note: this class is NOT thread-safe.
|
||||
class PowerManager : public platform::BatteryLevelTracker::Subscriber
|
||||
{
|
||||
public:
|
||||
class Subscriber
|
||||
{
|
||||
public:
|
||||
virtual void OnPowerFacilityChanged(Facility const facility, bool enabled) = 0;
|
||||
virtual void OnPowerSchemeChanged(Scheme const actualScheme) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Subscriber() = default;
|
||||
};
|
||||
|
||||
static std::string GetConfigPath();
|
||||
|
||||
void Load();
|
||||
// Set some facility state manually, it turns current scheme to Scheme::None.
|
||||
void SetFacility(Facility const facility, bool enabled);
|
||||
void SetScheme(Scheme const scheme);
|
||||
bool IsFacilityEnabled(Facility const facility) const;
|
||||
FacilitiesState const & GetFacilities() const;
|
||||
Scheme const & GetScheme() const;
|
||||
|
||||
// BatteryLevelTracker::Subscriber overrides:
|
||||
void OnBatteryLevelReceived(uint8_t level) override;
|
||||
|
||||
void Subscribe(Subscriber * subscriber);
|
||||
void UnsubscribeAll();
|
||||
|
||||
private:
|
||||
struct Config
|
||||
{
|
||||
DECLARE_VISITOR(visitor(m_facilities, "current_state"), visitor(m_scheme, "scheme"))
|
||||
|
||||
Config() { m_facilities.fill(true); }
|
||||
|
||||
FacilitiesState m_facilities;
|
||||
Scheme m_scheme = Scheme::Normal;
|
||||
};
|
||||
|
||||
bool Save();
|
||||
|
||||
std::vector<Subscriber *> m_subscribers;
|
||||
|
||||
Config m_config;
|
||||
};
|
||||
} // namespace power_management
|
||||
1593
libs/map/routing_manager.cpp
Normal file
1593
libs/map/routing_manager.cpp
Normal file
File diff suppressed because it is too large
Load diff
382
libs/map/routing_manager.hpp
Normal file
382
libs/map/routing_manager.hpp
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/bookmark_manager.hpp"
|
||||
#include "map/extrapolation/extrapolator.hpp"
|
||||
#include "map/routing_mark.hpp"
|
||||
#include "map/transit/transit_display.hpp"
|
||||
#include "map/transit/transit_reader.hpp"
|
||||
|
||||
#include "routing/following_info.hpp"
|
||||
#include "routing/route.hpp"
|
||||
#include "routing/router.hpp"
|
||||
#include "routing/routing_callbacks.hpp"
|
||||
#include "routing/routing_session.hpp"
|
||||
#include "routing/speed_camera_manager.hpp"
|
||||
|
||||
#include "storage/storage_defines.hpp"
|
||||
|
||||
#include "drape_frontend/drape_engine_safe_ptr.hpp"
|
||||
|
||||
#include "drape/pointers.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/thread_checker.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class CountryInfoGetter;
|
||||
}
|
||||
|
||||
namespace routing
|
||||
{
|
||||
class NumMwmIds;
|
||||
}
|
||||
|
||||
class DataSource;
|
||||
|
||||
namespace power_management
|
||||
{
|
||||
class PowerManager;
|
||||
}
|
||||
|
||||
struct RoutePointInfo
|
||||
{
|
||||
std::string m_name;
|
||||
RouteMarkType m_markType = RouteMarkType::Start;
|
||||
size_t m_intermediateIndex = 0;
|
||||
bool m_isPassed = false;
|
||||
bool m_isMyPosition = false;
|
||||
m2::PointD m_position;
|
||||
};
|
||||
|
||||
class RoutingManager final
|
||||
{
|
||||
public:
|
||||
class Delegate
|
||||
{
|
||||
public:
|
||||
virtual void OnRouteFollow(routing::RouterType type) = 0;
|
||||
virtual void RegisterCountryFilesOnRoute(std::shared_ptr<routing::NumMwmIds> ptr) const = 0;
|
||||
|
||||
virtual ~Delegate() = default;
|
||||
};
|
||||
|
||||
struct Callbacks
|
||||
{
|
||||
using DataSourceGetterFn = std::function<DataSource &()>;
|
||||
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
|
||||
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
|
||||
using GetStringsBundleFn = std::function<StringsBundle const &()>;
|
||||
using PowerManagerGetter = std::function<power_management::PowerManager const &()>;
|
||||
|
||||
template <typename DataSourceGetter, typename CountryInfoGetter, typename CountryParentNameGetter,
|
||||
typename StringsBundleGetter, typename PowerManagerGetter>
|
||||
Callbacks(DataSourceGetter && dataSourceGetter, CountryInfoGetter && countryInfoGetter,
|
||||
CountryParentNameGetter && countryParentNameGetter, StringsBundleGetter && stringsBundleGetter,
|
||||
PowerManagerGetter && powerManagerGetter)
|
||||
: m_dataSourceGetter(std::forward<DataSourceGetter>(dataSourceGetter))
|
||||
, m_countryInfoGetter(std::forward<CountryInfoGetter>(countryInfoGetter))
|
||||
, m_countryParentNameGetterFn(std::forward<CountryParentNameGetter>(countryParentNameGetter))
|
||||
, m_stringsBundleGetter(std::forward<StringsBundleGetter>(stringsBundleGetter))
|
||||
, m_powerManagerGetter(std::forward<PowerManagerGetter>(powerManagerGetter))
|
||||
{}
|
||||
|
||||
DataSourceGetterFn m_dataSourceGetter;
|
||||
CountryInfoGetterFn m_countryInfoGetter;
|
||||
CountryParentNameGetterFn m_countryParentNameGetterFn;
|
||||
GetStringsBundleFn m_stringsBundleGetter;
|
||||
PowerManagerGetter m_powerManagerGetter;
|
||||
};
|
||||
|
||||
using RouteBuildingCallback = std::function<void(routing::RouterResultCode, storage::CountriesSet const &)>;
|
||||
using RouteSpeedCamShowCallback = std::function<void(m2::PointD const &, double)>;
|
||||
using RouteSpeedCamsClearCallback = std::function<void()>;
|
||||
|
||||
using RouteStartBuildCallback = std::function<void(std::vector<RouteMarkData> const & points)>;
|
||||
|
||||
enum class Recommendation
|
||||
{
|
||||
// It can be recommended if location is found almost immediately
|
||||
// after restoring route points from file. In this case we can
|
||||
// rebuild route using "my position".
|
||||
RebuildAfterPointsLoading = 0,
|
||||
};
|
||||
using RouteRecommendCallback = std::function<void(Recommendation)>;
|
||||
|
||||
RoutingManager(Callbacks && callbacks, Delegate & delegate);
|
||||
|
||||
void SetBookmarkManager(BookmarkManager * bmManager);
|
||||
void SetTransitManager(TransitReadManager * transitManager);
|
||||
|
||||
routing::RoutingSession const & RoutingSession() const { return m_routingSession; }
|
||||
routing::RoutingSession & RoutingSession() { return m_routingSession; }
|
||||
void SetRouter(routing::RouterType type);
|
||||
routing::RouterType GetRouter() const { return m_currentRouterType; }
|
||||
bool IsRoutingActive() const { return m_routingSession.IsActive(); }
|
||||
bool IsRouteBuilt() const { return m_routingSession.IsBuilt(); }
|
||||
bool IsRouteBuilding() const { return m_routingSession.IsBuilding(); }
|
||||
bool IsRouteRebuildingOnly() const { return m_routingSession.IsRebuildingOnly(); }
|
||||
bool IsRouteFinished() const { return m_routingSession.IsFinished(); }
|
||||
bool IsOnRoute() const { return m_routingSession.IsOnRoute(); }
|
||||
bool IsRoutingFollowing() const { return m_routingSession.IsFollowing(); }
|
||||
bool IsRouteValid() const { return m_routingSession.IsRouteValid(); }
|
||||
void BuildRoute(uint32_t timeoutSec = routing::RouterDelegate::kNoTimeout);
|
||||
void SetUserCurrentPosition(m2::PointD const & position);
|
||||
void ResetRoutingSession() { m_routingSession.Reset(); }
|
||||
// FollowRoute has a bug where the router follows the route even if the method hads't been called.
|
||||
// This method was added because we do not want to break the behaviour that is familiar to our
|
||||
// users.
|
||||
bool DisableFollowMode();
|
||||
kml::TrackId SaveRoute();
|
||||
|
||||
void SetRouteBuildingListener(RouteBuildingCallback const & buildingCallback)
|
||||
{
|
||||
m_routingBuildingCallback = buildingCallback;
|
||||
}
|
||||
|
||||
void SetRouteSpeedCamShowListener(RouteSpeedCamShowCallback const & speedCamShowCallback)
|
||||
{
|
||||
m_routeSpeedCamShowCallback = speedCamShowCallback;
|
||||
}
|
||||
|
||||
void SetRouteSpeedCamsClearListener(RouteSpeedCamsClearCallback const & speedCamsClearCallback)
|
||||
{
|
||||
m_routeSpeedCamsClearCallback = speedCamsClearCallback;
|
||||
}
|
||||
|
||||
/// See warning above.
|
||||
void SetRouteProgressListener(routing::ProgressCallback const & progressCallback)
|
||||
{
|
||||
m_routingSession.SetProgressCallback(progressCallback);
|
||||
}
|
||||
void SetRouteRecommendationListener(RouteRecommendCallback const & recommendCallback)
|
||||
{
|
||||
m_routeRecommendCallback = recommendCallback;
|
||||
}
|
||||
void FollowRoute();
|
||||
void CloseRouting(bool removeRoutePoints);
|
||||
void GetRouteFollowingInfo(routing::FollowingInfo & info) const { m_routingSession.GetRouteFollowingInfo(info); }
|
||||
|
||||
TransitRouteInfo GetTransitRouteInfo() const;
|
||||
|
||||
m2::PointD GetRouteEndPoint() const { return m_routingSession.GetEndPoint(); }
|
||||
/// Returns the most situable router engine type.
|
||||
routing::RouterType GetBestRouter(m2::PointD const & startPoint, m2::PointD const & finalPoint) const;
|
||||
routing::RouterType GetLastUsedRouter() const;
|
||||
void SetLastUsedRouter(routing::RouterType type);
|
||||
// Sound notifications for turn instructions.
|
||||
void EnableTurnNotifications(bool enable) { m_routingSession.EnableTurnNotifications(enable); }
|
||||
bool AreTurnNotificationsEnabled() const { return m_routingSession.AreTurnNotificationsEnabled(); }
|
||||
/// \brief Sets a locale for TTS.
|
||||
/// \param locale is a string with locale code. For example "en", "ru", "zh-Hant" and so on.
|
||||
/// \note See sound/tts/languages.txt for the full list of available locales.
|
||||
void SetTurnNotificationsLocale(std::string const & locale) { m_routingSession.SetTurnNotificationsLocale(locale); }
|
||||
/// @return current TTS locale. For example "en", "ru", "zh-Hant" and so on.
|
||||
/// In case of error returns an empty string.
|
||||
/// \note The method returns correct locale after SetTurnNotificationsLocale has been called.
|
||||
/// If not, it returns an empty string.
|
||||
std::string GetTurnNotificationsLocale() const { return m_routingSession.GetTurnNotificationsLocale(); }
|
||||
// @return polyline of the route.
|
||||
routing::FollowedPolyline const & GetRoutePolyline() const
|
||||
{
|
||||
return m_routingSession.GetRouteForTests()->GetFollowedPolyline();
|
||||
}
|
||||
// @return generated turns on the route.
|
||||
std::vector<routing::turns::TurnItem> GetTurnsOnRouteForTests() const
|
||||
{
|
||||
std::vector<routing::turns::TurnItem> turns;
|
||||
m_routingSession.GetRouteForTests()->GetTurnsForTesting(turns);
|
||||
return turns;
|
||||
}
|
||||
|
||||
Callbacks & GetCallbacksForTests() { return m_callbacks; }
|
||||
/// \brief Adds to @param notifications strings - notifications, which are ready to be
|
||||
/// pronounced to end user right now.
|
||||
/// Adds notifications about turns and speed camera on the road.
|
||||
/// \param announceStreets is true when TTS street names should be pronounced
|
||||
///
|
||||
/// \note Current notifications will be deleted after call and second call
|
||||
/// will not return previous data, only newer.
|
||||
void GenerateNotifications(std::vector<std::string> & notifications, bool announceStreets);
|
||||
|
||||
void AddRoutePoint(RouteMarkData && markData, bool reorderIntermediatePoints = true);
|
||||
void ContinueRouteToPoint(RouteMarkData && markData);
|
||||
std::vector<RouteMarkData> GetRoutePoints() const;
|
||||
size_t GetRoutePointsCount() const;
|
||||
void RemoveRoutePoint(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
void RemoveRoutePoints();
|
||||
void RemoveIntermediateRoutePoints();
|
||||
void MoveRoutePoint(size_t currentIndex, size_t targetIndex);
|
||||
void MoveRoutePoint(RouteMarkType currentType, size_t currentIntermediateIndex, RouteMarkType targetType,
|
||||
size_t targetIntermediateIndex);
|
||||
void HideRoutePoint(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
bool CouldAddIntermediatePoint() const;
|
||||
bool IsMyPosition(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
|
||||
void SetRouterImpl(routing::RouterType type);
|
||||
void RemoveRoute(bool deactivateFollowing);
|
||||
|
||||
void CheckLocationForRouting(location::GpsInfo const & info);
|
||||
void CallRouteBuilded(routing::RouterResultCode code, storage::CountriesSet const & absentCountries);
|
||||
void OnBuildRouteReady(routing::Route const & route, routing::RouterResultCode code);
|
||||
void OnRebuildRouteReady(routing::Route const & route, routing::RouterResultCode code);
|
||||
void OnNeedMoreMaps(uint64_t routeId, storage::CountriesSet const & absentCountries);
|
||||
void OnRemoveRoute(routing::RouterResultCode code);
|
||||
void OnRoutePointPassed(RouteMarkType type, size_t intermediateIndex);
|
||||
void OnLocationUpdate(location::GpsInfo const & info);
|
||||
|
||||
routing::SpeedCameraManager & GetSpeedCamManager() { return m_routingSession.GetSpeedCamManager(); }
|
||||
bool IsSpeedCamLimitExceeded() const;
|
||||
|
||||
void SetTurnNotificationsUnits(measurement_utils::Units const units)
|
||||
{
|
||||
m_routingSession.SetTurnNotificationsUnits(units);
|
||||
}
|
||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine, bool is3dAllowed);
|
||||
/// \returns true if altitude information along |m_route| is available and
|
||||
/// false otherwise.
|
||||
bool HasRouteAltitude() const;
|
||||
|
||||
struct DistanceAltitude
|
||||
{
|
||||
std::vector<double> m_distances;
|
||||
geometry::Altitudes m_altitudes;
|
||||
|
||||
size_t GetSize() const
|
||||
{
|
||||
ASSERT_EQUAL(m_distances.size(), m_altitudes.size(), ());
|
||||
return m_distances.size();
|
||||
}
|
||||
|
||||
// Default altitudeDeviation ~ sqrt(2).
|
||||
void Simplify(double altitudeDeviation = 1.415);
|
||||
|
||||
/// \brief Generates 4 bytes per point image (RGBA) and put the data to |imageRGBAData|.
|
||||
/// \param width is width of chart shall be generated in pixels.
|
||||
/// \param height is height of chart shall be generated in pixels.
|
||||
/// \param imageRGBAData is bits of result image in RGBA.
|
||||
/// \returns If there is valid route info and the chart was generated returns true
|
||||
/// and false otherwise. If the method returns true it is guaranteed that the size of
|
||||
/// |imageRGBAData| is not zero.
|
||||
bool GenerateRouteAltitudeChart(uint32_t width, uint32_t height, std::vector<uint8_t> & imageRGBAData) const;
|
||||
|
||||
/// \param totalAscent is total ascent of the route in meters.
|
||||
/// \param totalDescent is total descent of the route in meters.
|
||||
void CalculateAscentDescent(uint32_t & totalAscentM, uint32_t & totalDescentM) const;
|
||||
|
||||
friend std::string DebugPrint(DistanceAltitude const & da);
|
||||
};
|
||||
|
||||
/// \brief Fills altitude of current route points and distance in meters form the beginning
|
||||
/// of the route point based on the route in RoutingSession.
|
||||
/// \return False if current route is invalid or doesn't have altitudes.
|
||||
bool GetRouteAltitudesAndDistancesM(DistanceAltitude & da) const;
|
||||
|
||||
uint32_t OpenRoutePointsTransaction();
|
||||
void ApplyRoutePointsTransaction(uint32_t transactionId);
|
||||
void CancelRoutePointsTransaction(uint32_t transactionId);
|
||||
static uint32_t InvalidRoutePointsTransactionId();
|
||||
|
||||
/// \returns true if there are route points saved in file and false otherwise.
|
||||
bool HasSavedRoutePoints() const;
|
||||
/// \brief It loads road points from file and delete file after loading.
|
||||
/// The result of the loading will be sent via SafeCallback.
|
||||
using LoadRouteHandler = platform::SafeCallback<void(bool success)>;
|
||||
void LoadRoutePoints(LoadRouteHandler const & handler);
|
||||
/// \brief It saves route points to file.
|
||||
void SaveRoutePoints();
|
||||
/// \brief It deletes file with saved route points if it exists.
|
||||
void DeleteSavedRoutePoints();
|
||||
|
||||
void UpdatePreviewMode();
|
||||
void CancelPreviewMode();
|
||||
|
||||
routing::RouterType GetCurrentRouterType() const { return m_currentRouterType; }
|
||||
|
||||
private:
|
||||
/// \returns true if the route has warnings.
|
||||
bool InsertRoute(routing::Route const & route);
|
||||
|
||||
struct RoadInfo
|
||||
{
|
||||
RoadInfo() = default;
|
||||
|
||||
explicit RoadInfo(m2::PointD const & pt, FeatureID const & featureId) : m_startPoint(pt), m_featureId(featureId) {}
|
||||
|
||||
m2::PointD m_startPoint;
|
||||
FeatureID m_featureId;
|
||||
double m_distance = 0.0;
|
||||
};
|
||||
using RoadWarningsCollection = std::map<routing::RoutingOptions::Road, std::vector<RoadInfo>>;
|
||||
|
||||
using GetMwmIdFn = std::function<MwmSet::MwmId(routing::NumMwmId numMwmId)>;
|
||||
void CollectRoadWarnings(std::vector<routing::RouteSegment> const & segments, m2::PointD const & startPt,
|
||||
double baseDistance, GetMwmIdFn const & getMwmIdFn, RoadWarningsCollection & roadWarnings);
|
||||
void CreateRoadWarningMarks(RoadWarningsCollection && roadWarnings);
|
||||
|
||||
/// \returns false if the location could not be matched to the route and should be matched to the
|
||||
/// road graph. Otherwise returns true.
|
||||
void MatchLocationToRoute(location::GpsInfo & info, location::RouteMatchingInfo & routeMatchingInfo);
|
||||
location::RouteMatchingInfo GetRouteMatchingInfo(location::GpsInfo & info);
|
||||
uint32_t GenerateRoutePointsTransactionId() const;
|
||||
|
||||
void SetPointsFollowingMode(bool enabled);
|
||||
|
||||
void RemovePassedPoints();
|
||||
|
||||
void ReorderIntermediatePoints();
|
||||
|
||||
m2::RectD ShowPreviewSegments(std::vector<RouteMarkData> const & routePoints);
|
||||
void HidePreviewSegments();
|
||||
|
||||
void SetSubroutesVisibility(bool visible);
|
||||
|
||||
void CancelRecommendation(Recommendation recommendation);
|
||||
|
||||
std::vector<RouteMarkData> GetRoutePointsToSave() const;
|
||||
|
||||
void OnExtrapolatedLocationUpdate(location::GpsInfo const & info);
|
||||
|
||||
RouteBuildingCallback m_routingBuildingCallback;
|
||||
RouteSpeedCamShowCallback m_routeSpeedCamShowCallback;
|
||||
RouteSpeedCamsClearCallback m_routeSpeedCamsClearCallback;
|
||||
RouteRecommendCallback m_routeRecommendCallback;
|
||||
Callbacks m_callbacks;
|
||||
df::DrapeEngineSafePtr m_drapeEngine;
|
||||
routing::RouterType m_currentRouterType = routing::RouterType::Count;
|
||||
bool m_loadAltitudes = false;
|
||||
routing::RoutingSession m_routingSession;
|
||||
Delegate & m_delegate;
|
||||
|
||||
BookmarkManager * m_bmManager = nullptr;
|
||||
extrapolation::Extrapolator m_extrapolator;
|
||||
|
||||
std::vector<dp::DrapeID> m_drapeSubroutes;
|
||||
mutable std::mutex m_drapeSubroutesMutex;
|
||||
|
||||
std::unique_ptr<location::GpsInfo> m_gpsInfoCache;
|
||||
|
||||
TransitRouteInfo m_transitRouteInfo;
|
||||
|
||||
struct RoutePointsTransaction
|
||||
{
|
||||
std::vector<RouteMarkData> m_routeMarks;
|
||||
};
|
||||
std::map<uint32_t, RoutePointsTransaction> m_routePointsTransactions;
|
||||
std::chrono::steady_clock::time_point m_loadRoutePointsTimestamp;
|
||||
std::map<std::string, m2::PointF> m_transitSymbolSizes;
|
||||
|
||||
TransitReadManager * m_transitReadManager = nullptr;
|
||||
|
||||
DECLARE_THREAD_CHECKER(m_threadChecker);
|
||||
};
|
||||
707
libs/map/routing_mark.cpp
Normal file
707
libs/map/routing_mark.cpp
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
#include "map/routing_mark.hpp"
|
||||
|
||||
#include "drape_frontend/color_constants.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "platform/localization.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::string const kRouteMarkPrimaryText = "RouteMarkPrimaryText";
|
||||
static std::string const kRouteMarkPrimaryTextOutline = "RouteMarkPrimaryTextOutline";
|
||||
static std::string const kRouteMarkSecondaryText = "RouteMarkSecondaryText";
|
||||
static std::string const kRouteMarkSecondaryTextOutline = "RouteMarkSecondaryTextOutline";
|
||||
|
||||
static std::string const kTransitMarkPrimaryText = "TransitMarkPrimaryText";
|
||||
static std::string const kTransitMarkPrimaryTextOutline = "TransitMarkPrimaryTextOutline";
|
||||
static std::string const kTransitMarkSecondaryText = "TransitMarkSecondaryText";
|
||||
static std::string const kTransitMarkSecondaryTextOutline = "TransitMarkSecondaryTextOutline";
|
||||
|
||||
float constexpr kRouteMarkPrimaryTextSize = 10.5f;
|
||||
float constexpr kRouteMarkSecondaryTextSize = 10.0f;
|
||||
float constexpr kRouteMarkSecondaryOffsetY = 2.0f;
|
||||
float constexpr kTransitMarkTextSize = 12.0f;
|
||||
|
||||
static std::string const kSpeedCameraMarkText = "SpeedCameraMarkText";
|
||||
static std::string const kSpeedCameraMarkBg = "SpeedCameraMarkBg";
|
||||
static std::string const kSpeedCameraMarkOutline = "SpeedCameraMarkOutline";
|
||||
|
||||
float constexpr kSpeedCameraMarkTextSize = 11.0f;
|
||||
float constexpr kSpeedCameraMarkTextMargin = 1.5f;
|
||||
float constexpr kSpeedCameraRadius = 3.5f;
|
||||
float constexpr kSpeedCameraOutlineWidth = 2.0f;
|
||||
|
||||
int constexpr kMinSpeedCameraZoom = 13;
|
||||
int constexpr kMinSpeedCameraTitleZoom = 13;
|
||||
} // namespace
|
||||
|
||||
RouteMarkPoint::RouteMarkPoint(m2::PointD const & ptOrg) : UserMark(ptOrg, Type::ROUTING)
|
||||
{
|
||||
m_titleDecl.m_anchor = dp::Top;
|
||||
m_titleDecl.m_primaryTextFont.m_color = df::GetColorConstant(kRouteMarkPrimaryText);
|
||||
m_titleDecl.m_primaryTextFont.m_outlineColor = df::GetColorConstant(kRouteMarkPrimaryTextOutline);
|
||||
m_titleDecl.m_primaryTextFont.m_size = kRouteMarkPrimaryTextSize;
|
||||
m_titleDecl.m_secondaryTextFont.m_color = df::GetColorConstant(kRouteMarkSecondaryText);
|
||||
m_titleDecl.m_secondaryTextFont.m_outlineColor = df::GetColorConstant(kRouteMarkSecondaryTextOutline);
|
||||
m_titleDecl.m_secondaryTextFont.m_size = kRouteMarkSecondaryTextSize;
|
||||
m_titleDecl.m_secondaryOffset = m2::PointF(0, kRouteMarkSecondaryOffsetY);
|
||||
|
||||
m_markData.m_position = ptOrg;
|
||||
}
|
||||
|
||||
dp::Anchor RouteMarkPoint::GetAnchor() const
|
||||
{
|
||||
if (m_markData.m_pointType == RouteMarkType::Finish)
|
||||
return dp::Bottom;
|
||||
return dp::Center;
|
||||
}
|
||||
|
||||
df::DepthLayer RouteMarkPoint::GetDepthLayer() const
|
||||
{
|
||||
return df::DepthLayer::RoutingMarkLayer;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetIsVisible(bool isVisible)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData.m_isVisible = isVisible;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetRoutePointType(RouteMarkType type)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData.m_pointType = type;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetIntermediateIndex(size_t index)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData.m_intermediateIndex = index;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetRoutePointFullType(RouteMarkType type, size_t intermediateIndex)
|
||||
{
|
||||
SetRoutePointType(type);
|
||||
SetIntermediateIndex(intermediateIndex);
|
||||
}
|
||||
|
||||
bool RouteMarkPoint::IsEqualFullType(RouteMarkType type, size_t intermediateIndex) const
|
||||
{
|
||||
if (type == RouteMarkType::Intermediate)
|
||||
return m_markData.m_pointType == type && m_markData.m_intermediateIndex == intermediateIndex;
|
||||
return m_markData.m_pointType == type;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetIsMyPosition(bool isMyPosition)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData.m_isMyPosition = isMyPosition;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetPassed(bool isPassed)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData.m_isPassed = isPassed;
|
||||
}
|
||||
|
||||
uint16_t RouteMarkPoint::GetPriority() const
|
||||
{
|
||||
switch (m_markData.m_pointType)
|
||||
{
|
||||
case RouteMarkType::Start: return static_cast<uint16_t>(Priority::RouteStart);
|
||||
case RouteMarkType::Finish: return static_cast<uint16_t>(Priority::RouteFinish);
|
||||
case RouteMarkType::Intermediate:
|
||||
{
|
||||
switch (m_markData.m_intermediateIndex)
|
||||
{
|
||||
case 0: return static_cast<uint16_t>(Priority::RouteIntermediateA);
|
||||
case 1: return static_cast<uint16_t>(Priority::RouteIntermediateB);
|
||||
default: return static_cast<uint16_t>(Priority::RouteIntermediateC);
|
||||
}
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
uint32_t RouteMarkPoint::GetIndex() const
|
||||
{
|
||||
switch (m_markData.m_pointType)
|
||||
{
|
||||
case RouteMarkType::Start: return 0;
|
||||
case RouteMarkType::Finish: return 1;
|
||||
case RouteMarkType::Intermediate: return static_cast<uint32_t>(m_markData.m_intermediateIndex + 2);
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetMarkData(RouteMarkData && data)
|
||||
{
|
||||
SetDirty();
|
||||
m_markData = std::move(data);
|
||||
m_titleDecl.m_primaryText = m_markData.m_title;
|
||||
if (!m_titleDecl.m_primaryText.empty())
|
||||
{
|
||||
m_titleDecl.m_secondaryText = m_markData.m_subTitle;
|
||||
m_titleDecl.m_secondaryOptional = true;
|
||||
}
|
||||
else
|
||||
m_titleDecl.m_secondaryText.clear();
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::TitlesInfo> RouteMarkPoint::GetTitleDecl() const
|
||||
{
|
||||
if (m_followingMode)
|
||||
return nullptr;
|
||||
|
||||
auto titles = make_unique_dp<TitlesInfo>();
|
||||
titles->push_back(m_titleDecl);
|
||||
return titles;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::ColoredSymbolZoomInfo> RouteMarkPoint::GetColoredSymbols() const
|
||||
{
|
||||
auto coloredSymbol = make_unique_dp<ColoredSymbolZoomInfo>();
|
||||
coloredSymbol->m_isSymbolStub = true;
|
||||
return coloredSymbol;
|
||||
}
|
||||
|
||||
void RouteMarkPoint::SetFollowingMode(bool enabled)
|
||||
{
|
||||
if (m_followingMode == enabled)
|
||||
return;
|
||||
|
||||
SetDirty();
|
||||
m_followingMode = enabled;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> RouteMarkPoint::GetSymbolNames() const
|
||||
{
|
||||
std::string name;
|
||||
switch (m_markData.m_pointType)
|
||||
{
|
||||
case RouteMarkType::Start: name = "route-point-start"; break;
|
||||
case RouteMarkType::Finish: name = "route-point-finish"; break;
|
||||
case RouteMarkType::Intermediate:
|
||||
{
|
||||
/// @todo Draw RouteMarkPoint icons dynamically like SpeedCameraMark.
|
||||
if (m_markData.m_intermediateIndex < 19)
|
||||
name = "route-point-" + std::to_string(m_markData.m_intermediateIndex + 1);
|
||||
else
|
||||
name = "route-point-20";
|
||||
}
|
||||
}
|
||||
auto symbol = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbol->insert(std::make_pair(1 /* zoomLevel */, name));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
// This should be tested if the routing algorithm can handle this
|
||||
size_t const RoutePointsLayout::kMaxIntermediatePointsCount = 100;
|
||||
|
||||
RoutePointsLayout::RoutePointsLayout(BookmarkManager & manager)
|
||||
: m_manager(manager)
|
||||
, m_editSession(manager.GetEditSession())
|
||||
{}
|
||||
|
||||
void RoutePointsLayout::AddRoutePoint(RouteMarkData && data)
|
||||
{
|
||||
auto const count = m_manager.GetUserMarkIds(UserMark::Type::ROUTING).size();
|
||||
if (count == kMaxIntermediatePointsCount + 2)
|
||||
return;
|
||||
|
||||
RouteMarkPoint * sameTypePoint = GetRoutePointForEdit(data.m_pointType, data.m_intermediateIndex);
|
||||
if (sameTypePoint != nullptr)
|
||||
{
|
||||
if (data.m_pointType == RouteMarkType::Finish)
|
||||
{
|
||||
if (count > 1)
|
||||
{
|
||||
size_t const intermediatePointsCount = count - 2;
|
||||
sameTypePoint->SetRoutePointFullType(RouteMarkType::Intermediate, intermediatePointsCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sameTypePoint->SetRoutePointFullType(RouteMarkType::Start, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t const offsetIndex = data.m_pointType == RouteMarkType::Start ? 0 : data.m_intermediateIndex;
|
||||
|
||||
ForEachIntermediatePoint([offsetIndex](RouteMarkPoint * mark)
|
||||
{
|
||||
if (mark->GetIntermediateIndex() >= offsetIndex)
|
||||
mark->SetIntermediateIndex(mark->GetIntermediateIndex() + 1);
|
||||
});
|
||||
|
||||
if (data.m_pointType == RouteMarkType::Start)
|
||||
{
|
||||
if (count > 1)
|
||||
sameTypePoint->SetRoutePointFullType(RouteMarkType::Intermediate, 0);
|
||||
else
|
||||
sameTypePoint->SetRoutePointFullType(RouteMarkType::Finish, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto * newPoint = m_editSession.CreateUserMark<RouteMarkPoint>(data.m_position);
|
||||
newPoint->SetMarkData(std::move(data));
|
||||
}
|
||||
|
||||
bool RoutePointsLayout::RemoveRoutePoint(RouteMarkType type, size_t intermediateIndex)
|
||||
{
|
||||
RouteMarkPoint const * point = nullptr;
|
||||
for (auto markId : m_manager.GetUserMarkIds(UserMark::Type::ROUTING))
|
||||
{
|
||||
auto const * mark = m_manager.GetMark<RouteMarkPoint>(markId);
|
||||
if (mark->IsEqualFullType(type, intermediateIndex))
|
||||
{
|
||||
point = mark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (point == nullptr)
|
||||
return false;
|
||||
|
||||
if (type == RouteMarkType::Finish)
|
||||
{
|
||||
RouteMarkPoint * lastIntermediate = nullptr;
|
||||
size_t maxIntermediateIndex = 0;
|
||||
ForEachIntermediatePoint([&lastIntermediate, &maxIntermediateIndex](RouteMarkPoint * mark)
|
||||
{
|
||||
if (lastIntermediate == nullptr || mark->GetIntermediateIndex() > maxIntermediateIndex)
|
||||
{
|
||||
lastIntermediate = mark;
|
||||
maxIntermediateIndex = mark->GetIntermediateIndex();
|
||||
}
|
||||
});
|
||||
|
||||
if (lastIntermediate != nullptr)
|
||||
lastIntermediate->SetRoutePointFullType(RouteMarkType::Finish, 0);
|
||||
}
|
||||
else if (type == RouteMarkType::Start)
|
||||
{
|
||||
ForEachIntermediatePoint([](RouteMarkPoint * mark)
|
||||
{
|
||||
if (mark->GetIntermediateIndex() == 0)
|
||||
mark->SetRoutePointFullType(RouteMarkType::Start, 0);
|
||||
else
|
||||
mark->SetIntermediateIndex(mark->GetIntermediateIndex() - 1);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ForEachIntermediatePoint([point](RouteMarkPoint * mark)
|
||||
{
|
||||
if (mark->GetIntermediateIndex() > point->GetIntermediateIndex())
|
||||
mark->SetIntermediateIndex(mark->GetIntermediateIndex() - 1);
|
||||
});
|
||||
}
|
||||
|
||||
m_editSession.DeleteUserMark(point->GetId());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoutePointsLayout::RemoveRoutePoints()
|
||||
{
|
||||
m_editSession.ClearGroup(UserMark::Type::ROUTING);
|
||||
}
|
||||
|
||||
void RoutePointsLayout::RemoveIntermediateRoutePoints()
|
||||
{
|
||||
m_editSession.DeleteUserMarks<RouteMarkPoint>(UserMark::Type::ROUTING, [](RouteMarkPoint const * mark)
|
||||
{ return mark->GetRoutePointType() == RouteMarkType::Intermediate; });
|
||||
}
|
||||
|
||||
bool RoutePointsLayout::MoveRoutePoint(RouteMarkType currentType, size_t currentIntermediateIndex,
|
||||
RouteMarkType destType, size_t destIntermediateIndex)
|
||||
{
|
||||
RouteMarkPoint const * point = GetRoutePoint(currentType, currentIntermediateIndex);
|
||||
if (point == nullptr)
|
||||
return false;
|
||||
|
||||
RouteMarkData data = point->GetMarkData();
|
||||
data.m_pointType = destType;
|
||||
data.m_intermediateIndex = destIntermediateIndex;
|
||||
|
||||
RemoveRoutePoint(currentType, currentIntermediateIndex);
|
||||
|
||||
AddRoutePoint(std::move(data));
|
||||
return true;
|
||||
}
|
||||
|
||||
void RoutePointsLayout::PassRoutePoint(RouteMarkType type, size_t intermediateIndex)
|
||||
{
|
||||
RouteMarkPoint * point = GetRoutePointForEdit(type, intermediateIndex);
|
||||
if (point == nullptr)
|
||||
return;
|
||||
point->SetPassed(true);
|
||||
point->SetIsVisible(false);
|
||||
}
|
||||
|
||||
void RoutePointsLayout::SetFollowingMode(bool enabled)
|
||||
{
|
||||
for (auto markId : m_manager.GetUserMarkIds(UserMark::Type::ROUTING))
|
||||
m_editSession.GetMarkForEdit<RouteMarkPoint>(markId)->SetFollowingMode(enabled);
|
||||
}
|
||||
|
||||
void RoutePointsLayout::RemovePassedPoints()
|
||||
{
|
||||
// Prevent recalculation of markIds at every iteration, since we are removing elements
|
||||
auto markIds = m_manager.GetUserMarkIds(UserMark::Type::ROUTING);
|
||||
for (auto markId : markIds) {
|
||||
auto * mark = m_editSession.GetMarkForEdit<RouteMarkPoint>(markId);
|
||||
if (mark && mark->IsPassed() && mark->GetRoutePointType() == RouteMarkType::Intermediate)
|
||||
m_editSession.DeleteUserMark(mark->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RouteMarkPoint const * RoutePointsLayout::GetRoutePoint(RouteMarkType type, size_t intermediateIndex) const
|
||||
{
|
||||
for (auto markId : m_manager.GetUserMarkIds(UserMark::Type::ROUTING))
|
||||
{
|
||||
auto const * mark = m_manager.GetMark<RouteMarkPoint>(markId);
|
||||
if (mark->IsEqualFullType(type, intermediateIndex))
|
||||
return mark;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RouteMarkPoint * RoutePointsLayout::GetRoutePointForEdit(RouteMarkType type, size_t intermediateIndex)
|
||||
{
|
||||
auto const * mark = GetRoutePoint(type, intermediateIndex);
|
||||
return mark ? m_editSession.GetMarkForEdit<RouteMarkPoint>(mark->GetId()) : nullptr;
|
||||
}
|
||||
|
||||
RouteMarkPoint const * RoutePointsLayout::GetMyPositionPoint() const
|
||||
{
|
||||
for (auto markId : m_manager.GetUserMarkIds(UserMark::Type::ROUTING))
|
||||
{
|
||||
auto const * mark = m_manager.GetMark<RouteMarkPoint>(markId);
|
||||
if (mark->IsMyPosition())
|
||||
return mark;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<RouteMarkPoint *> RoutePointsLayout::GetRoutePoints()
|
||||
{
|
||||
auto const & markIds = m_manager.GetUserMarkIds(UserMark::Type::ROUTING);
|
||||
std::vector<RouteMarkPoint *> points;
|
||||
points.reserve(markIds.size());
|
||||
RouteMarkPoint * startPoint = nullptr;
|
||||
RouteMarkPoint * finishPoint = nullptr;
|
||||
for (auto markId : markIds)
|
||||
{
|
||||
auto * p = m_editSession.GetMarkForEdit<RouteMarkPoint>(markId);
|
||||
if (p->GetRoutePointType() == RouteMarkType::Start)
|
||||
startPoint = p;
|
||||
else if (p->GetRoutePointType() == RouteMarkType::Finish)
|
||||
finishPoint = p;
|
||||
else
|
||||
points.push_back(p);
|
||||
}
|
||||
std::sort(points.begin(), points.end(), [](RouteMarkPoint const * p1, RouteMarkPoint const * p2)
|
||||
{ return p1->GetIntermediateIndex() < p2->GetIntermediateIndex(); });
|
||||
if (startPoint != nullptr)
|
||||
points.insert(points.begin(), startPoint);
|
||||
if (finishPoint != nullptr)
|
||||
points.push_back(finishPoint);
|
||||
return points;
|
||||
}
|
||||
|
||||
size_t RoutePointsLayout::GetRoutePointsCount() const
|
||||
{
|
||||
return m_manager.GetUserMarkIds(UserMark::Type::ROUTING).size();
|
||||
}
|
||||
|
||||
void RoutePointsLayout::ForEachIntermediatePoint(TRoutePointCallback const & fn)
|
||||
{
|
||||
for (auto markId : m_manager.GetUserMarkIds(UserMark::Type::ROUTING))
|
||||
{
|
||||
auto * mark = m_editSession.GetMarkForEdit<RouteMarkPoint>(markId);
|
||||
if (mark->GetRoutePointType() == RouteMarkType::Intermediate)
|
||||
fn(mark);
|
||||
}
|
||||
}
|
||||
|
||||
TransitMark::TransitMark(m2::PointD const & ptOrg) : UserMark(ptOrg, Type::TRANSIT) {}
|
||||
|
||||
void TransitMark::SetFeatureId(FeatureID const & featureId)
|
||||
{
|
||||
SetDirty();
|
||||
m_featureId = featureId;
|
||||
}
|
||||
|
||||
void TransitMark::SetIndex(uint32_t index)
|
||||
{
|
||||
SetDirty();
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
void TransitMark::SetPriority(Priority priority)
|
||||
{
|
||||
SetDirty();
|
||||
m_priority = priority;
|
||||
}
|
||||
|
||||
void TransitMark::SetMinZoom(int minZoom)
|
||||
{
|
||||
SetDirty();
|
||||
m_minZoom = minZoom;
|
||||
}
|
||||
|
||||
void TransitMark::SetMinTitleZoom(int minTitleZoom)
|
||||
{
|
||||
SetDirty();
|
||||
m_minTitleZoom = minTitleZoom;
|
||||
}
|
||||
|
||||
void TransitMark::AddTitle(dp::TitleDecl const & titleDecl)
|
||||
{
|
||||
SetDirty();
|
||||
m_titles.push_back(titleDecl);
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::TitlesInfo> TransitMark::GetTitleDecl() const
|
||||
{
|
||||
auto titles = make_unique_dp<TitlesInfo>(m_titles);
|
||||
return titles;
|
||||
}
|
||||
|
||||
void TransitMark::SetSymbolNames(std::map<int, std::string> const & symbolNames)
|
||||
{
|
||||
SetDirty();
|
||||
m_symbolNames = symbolNames;
|
||||
}
|
||||
|
||||
void TransitMark::SetColoredSymbols(ColoredSymbolZoomInfo const & symbolParams)
|
||||
{
|
||||
SetDirty();
|
||||
m_coloredSymbols = symbolParams;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::ColoredSymbolZoomInfo> TransitMark::GetColoredSymbols() const
|
||||
{
|
||||
if (m_coloredSymbols.m_zoomInfo.empty())
|
||||
return nullptr;
|
||||
return make_unique_dp<ColoredSymbolZoomInfo>(m_coloredSymbols);
|
||||
}
|
||||
|
||||
void TransitMark::SetSymbolSizes(SymbolSizes const & symbolSizes)
|
||||
{
|
||||
m_symbolSizes = symbolSizes;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolSizes> TransitMark::GetSymbolSizes() const
|
||||
{
|
||||
if (m_symbolSizes.empty())
|
||||
return nullptr;
|
||||
return make_unique_dp<SymbolSizes>(m_symbolSizes);
|
||||
}
|
||||
|
||||
void TransitMark::SetSymbolOffsets(SymbolOffsets const & symbolOffsets)
|
||||
{
|
||||
m_symbolOffsets = symbolOffsets;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolOffsets> TransitMark::GetSymbolOffsets() const
|
||||
{
|
||||
if (m_symbolOffsets.empty())
|
||||
return nullptr;
|
||||
return make_unique_dp<SymbolOffsets>(m_symbolOffsets);
|
||||
}
|
||||
|
||||
void TransitMark::SetAnchor(dp::Anchor anchor)
|
||||
{
|
||||
m_anchor = anchor;
|
||||
}
|
||||
|
||||
dp::Anchor TransitMark::GetAnchor() const
|
||||
{
|
||||
return m_anchor;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> TransitMark::GetSymbolNames() const
|
||||
{
|
||||
if (m_symbolNames.empty())
|
||||
return nullptr;
|
||||
return make_unique_dp<SymbolNameZoomInfo>(m_symbolNames);
|
||||
}
|
||||
|
||||
// static
|
||||
void TransitMark::GetDefaultTransitTitle(dp::TitleDecl & titleDecl)
|
||||
{
|
||||
titleDecl = dp::TitleDecl();
|
||||
titleDecl.m_primaryTextFont.m_color = df::GetColorConstant(kTransitMarkPrimaryText);
|
||||
titleDecl.m_primaryTextFont.m_outlineColor = df::GetColorConstant(kTransitMarkPrimaryTextOutline);
|
||||
titleDecl.m_primaryTextFont.m_size = kTransitMarkTextSize;
|
||||
titleDecl.m_secondaryTextFont.m_color = df::GetColorConstant(kTransitMarkSecondaryText);
|
||||
titleDecl.m_secondaryTextFont.m_outlineColor = df::GetColorConstant(kTransitMarkSecondaryTextOutline);
|
||||
titleDecl.m_secondaryTextFont.m_size = kTransitMarkTextSize;
|
||||
}
|
||||
|
||||
SpeedCameraMark::SpeedCameraMark(m2::PointD const & ptOrg) : UserMark(ptOrg, Type::SPEED_CAM)
|
||||
{
|
||||
auto const vs = static_cast<float>(df::VisualParams::Instance().GetVisualScale());
|
||||
|
||||
m_titleDecl.m_primaryTextFont.m_color = df::GetColorConstant(kSpeedCameraMarkText);
|
||||
m_titleDecl.m_primaryTextFont.m_size = kSpeedCameraMarkTextSize;
|
||||
m_titleDecl.m_primaryOffset.x = kSpeedCameraOutlineWidth + kSpeedCameraMarkTextMargin;
|
||||
m_titleDecl.m_anchor = dp::Left;
|
||||
|
||||
m_symbolNames.insert(std::make_pair(kMinSpeedCameraZoom, "speedcam-alert-l"));
|
||||
|
||||
df::ColoredSymbolViewParams params;
|
||||
params.m_color = df::GetColorConstant(kSpeedCameraMarkBg);
|
||||
params.m_anchor = dp::Left;
|
||||
params.m_shape = df::ColoredSymbolViewParams::Shape::RoundedRectangle;
|
||||
params.m_radiusInPixels = kSpeedCameraRadius * vs;
|
||||
auto const minSize = 2.0f * (kSpeedCameraOutlineWidth + kSpeedCameraMarkTextMargin);
|
||||
params.m_sizeInPixels = m2::PointF(minSize, minSize) * vs;
|
||||
params.m_outlineColor = df::GetColorConstant(kSpeedCameraMarkOutline);
|
||||
params.m_outlineWidth = kSpeedCameraOutlineWidth;
|
||||
m_textBg.m_zoomInfo[kMinSpeedCameraTitleZoom] = params;
|
||||
m_textBg.m_addTextSize = true;
|
||||
}
|
||||
|
||||
void SpeedCameraMark::SetTitle(std::string const & title)
|
||||
{
|
||||
SetDirty();
|
||||
m_titleDecl.m_primaryText = title;
|
||||
}
|
||||
|
||||
std::string const & SpeedCameraMark::GetTitle() const
|
||||
{
|
||||
return m_titleDecl.m_primaryText;
|
||||
}
|
||||
|
||||
void SpeedCameraMark::SetIndex(uint32_t index)
|
||||
{
|
||||
SetDirty();
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> SpeedCameraMark::GetSymbolNames() const
|
||||
{
|
||||
return make_unique_dp<SymbolNameZoomInfo>(m_symbolNames);
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::TitlesInfo> SpeedCameraMark::GetTitleDecl() const
|
||||
{
|
||||
if (m_titleDecl.m_primaryText.empty())
|
||||
return nullptr;
|
||||
auto titleInfo = make_unique_dp<TitlesInfo>();
|
||||
titleInfo->push_back(m_titleDecl);
|
||||
return titleInfo;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::ColoredSymbolZoomInfo> SpeedCameraMark::GetColoredSymbols() const
|
||||
{
|
||||
if (m_titleDecl.m_primaryText.empty())
|
||||
return nullptr;
|
||||
return make_unique_dp<ColoredSymbolZoomInfo>(m_textBg);
|
||||
}
|
||||
|
||||
int SpeedCameraMark::GetMinZoom() const
|
||||
{
|
||||
return kMinSpeedCameraZoom;
|
||||
}
|
||||
|
||||
int SpeedCameraMark::GetMinTitleZoom() const
|
||||
{
|
||||
return kMinSpeedCameraTitleZoom;
|
||||
}
|
||||
|
||||
dp::Anchor SpeedCameraMark::GetAnchor() const
|
||||
{
|
||||
return dp::Center;
|
||||
}
|
||||
|
||||
RoadWarningMark::RoadWarningMark(m2::PointD const & ptOrg) : UserMark(ptOrg, Type::ROAD_WARNING) {}
|
||||
|
||||
uint16_t RoadWarningMark::GetPriority() const
|
||||
{
|
||||
if (m_index == 0)
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
using enum RoadWarningMarkType;
|
||||
case Toll: return static_cast<uint16_t>(Priority::RoadWarningFirstToll);
|
||||
case Ferry: return static_cast<uint16_t>(Priority::RoadWarningFirstFerry);
|
||||
case Dirty: return static_cast<uint16_t>(Priority::RoadWarningFirstDirty);
|
||||
case Count: CHECK(false, ()); break;
|
||||
}
|
||||
}
|
||||
return static_cast<uint16_t>(Priority::RoadWarning);
|
||||
}
|
||||
|
||||
void RoadWarningMark::SetIndex(uint32_t index)
|
||||
{
|
||||
SetDirty();
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
void RoadWarningMark::SetRoadWarningType(RoadWarningMarkType type)
|
||||
{
|
||||
SetDirty();
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
void RoadWarningMark::SetFeatureId(FeatureID const & featureId)
|
||||
{
|
||||
SetDirty();
|
||||
m_featureId = featureId;
|
||||
}
|
||||
|
||||
void RoadWarningMark::SetDistance(std::string const & distance)
|
||||
{
|
||||
SetDirty();
|
||||
m_distance = distance;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> RoadWarningMark::GetSymbolNames() const
|
||||
{
|
||||
std::string_view symbolName;
|
||||
switch (m_type)
|
||||
{
|
||||
using enum RoadWarningMarkType;
|
||||
case Toll: symbolName = "warning-paid_road"; break;
|
||||
case Ferry: symbolName = "warning-ferry"; break;
|
||||
case Dirty: symbolName = "warning-unpaved_road"; break;
|
||||
case Count: CHECK(false, ()); break;
|
||||
}
|
||||
auto symbol = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbol->emplace(1 /* zoomLevel */, symbolName);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
// static
|
||||
std::string RoadWarningMark::GetLocalizedRoadWarningType(RoadWarningMarkType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
using enum RoadWarningMarkType;
|
||||
case Toll: return platform::GetLocalizedString("toll_road");
|
||||
case Ferry: return platform::GetLocalizedString("ferry_crossing");
|
||||
case Dirty: return platform::GetLocalizedString("unpaved_road");
|
||||
case Count: CHECK(false, ("Invalid road warning mark type", type)); break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string DebugPrint(RoadWarningMarkType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
using enum RoadWarningMarkType;
|
||||
case Toll: return "Toll";
|
||||
case Ferry: return "Ferry";
|
||||
case Dirty: return "Dirty";
|
||||
case Count: return "Count";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
244
libs/map/routing_mark.hpp
Normal file
244
libs/map/routing_mark.hpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/bookmark_manager.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
enum class RouteMarkType : uint8_t
|
||||
{
|
||||
// Do not change the order!
|
||||
Start = 0,
|
||||
Intermediate = 1,
|
||||
Finish = 2
|
||||
};
|
||||
|
||||
struct RouteMarkData
|
||||
{
|
||||
std::string m_title;
|
||||
std::string m_subTitle;
|
||||
RouteMarkType m_pointType = RouteMarkType::Start;
|
||||
size_t m_intermediateIndex = 0;
|
||||
bool m_isVisible = true;
|
||||
bool m_isMyPosition = false;
|
||||
bool m_isPassed = false;
|
||||
bool m_replaceWithMyPositionAfterRestart = false;
|
||||
m2::PointD m_position;
|
||||
};
|
||||
|
||||
class RouteMarkPoint : public UserMark
|
||||
{
|
||||
public:
|
||||
RouteMarkPoint(m2::PointD const & ptOrg);
|
||||
virtual ~RouteMarkPoint() {}
|
||||
|
||||
bool IsVisible() const override { return m_markData.m_isVisible; }
|
||||
void SetIsVisible(bool isVisible);
|
||||
|
||||
dp::Anchor GetAnchor() const override;
|
||||
df::DepthLayer GetDepthLayer() const override;
|
||||
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
bool IsAvailableForSearch() const override { return !IsPassed(); }
|
||||
|
||||
RouteMarkType GetRoutePointType() const { return m_markData.m_pointType; }
|
||||
void SetRoutePointType(RouteMarkType type);
|
||||
|
||||
void SetIntermediateIndex(size_t index);
|
||||
size_t GetIntermediateIndex() const { return m_markData.m_intermediateIndex; }
|
||||
|
||||
void SetRoutePointFullType(RouteMarkType type, size_t intermediateIndex);
|
||||
bool IsEqualFullType(RouteMarkType type, size_t intermediateIndex) const;
|
||||
|
||||
void SetIsMyPosition(bool isMyPosition);
|
||||
bool IsMyPosition() const { return m_markData.m_isMyPosition; }
|
||||
|
||||
void SetPassed(bool isPassed);
|
||||
bool IsPassed() const { return m_markData.m_isPassed; }
|
||||
|
||||
uint16_t GetPriority() const override;
|
||||
uint32_t GetIndex() const override;
|
||||
|
||||
RouteMarkData const & GetMarkData() const { return m_markData; }
|
||||
void SetMarkData(RouteMarkData && data);
|
||||
|
||||
drape_ptr<TitlesInfo> GetTitleDecl() const override;
|
||||
|
||||
drape_ptr<ColoredSymbolZoomInfo> GetColoredSymbols() const override;
|
||||
|
||||
bool HasTitlePriority() const override { return true; }
|
||||
df::SpecialDisplacement GetDisplacement() const override { return df::SpecialDisplacement::SpecialModeUserMark; }
|
||||
|
||||
void SetFollowingMode(bool enabled);
|
||||
|
||||
private:
|
||||
RouteMarkData m_markData;
|
||||
dp::TitleDecl m_titleDecl;
|
||||
bool m_followingMode = false;
|
||||
};
|
||||
|
||||
class RoutePointsLayout
|
||||
{
|
||||
public:
|
||||
static size_t const kMaxIntermediatePointsCount;
|
||||
|
||||
RoutePointsLayout(BookmarkManager & manager);
|
||||
|
||||
void AddRoutePoint(RouteMarkData && data);
|
||||
RouteMarkPoint const * GetRoutePoint(RouteMarkType type, size_t intermediateIndex = 0) const;
|
||||
RouteMarkPoint * GetRoutePointForEdit(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
RouteMarkPoint const * GetMyPositionPoint() const;
|
||||
std::vector<RouteMarkPoint *> GetRoutePoints();
|
||||
size_t GetRoutePointsCount() const;
|
||||
bool RemoveRoutePoint(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
void RemoveRoutePoints();
|
||||
void RemoveIntermediateRoutePoints();
|
||||
bool MoveRoutePoint(RouteMarkType currentType, size_t currentIntermediateIndex, RouteMarkType destType,
|
||||
size_t destIntermediateIndex);
|
||||
void PassRoutePoint(RouteMarkType type, size_t intermediateIndex = 0);
|
||||
void SetFollowingMode(bool enabled);
|
||||
void RemovePassedPoints();
|
||||
private:
|
||||
using TRoutePointCallback = std::function<void(RouteMarkPoint * mark)>;
|
||||
void ForEachIntermediatePoint(TRoutePointCallback const & fn);
|
||||
|
||||
BookmarkManager & m_manager;
|
||||
BookmarkManager::EditSession m_editSession;
|
||||
};
|
||||
|
||||
class TransitMark : public UserMark
|
||||
{
|
||||
public:
|
||||
explicit TransitMark(m2::PointD const & ptOrg);
|
||||
|
||||
df::DepthLayer GetDepthLayer() const override { return df::DepthLayer::RoutingBottomMarkLayer; }
|
||||
|
||||
bool SymbolIsPOI() const override { return true; }
|
||||
bool HasTitlePriority() const override { return true; }
|
||||
df::SpecialDisplacement GetDisplacement() const override { return df::SpecialDisplacement::SpecialModeUserMark; }
|
||||
|
||||
void SetAnchor(dp::Anchor anchor);
|
||||
dp::Anchor GetAnchor() const override;
|
||||
|
||||
void SetFeatureId(FeatureID const & featureId);
|
||||
FeatureID GetFeatureID() const override { return m_featureId; }
|
||||
|
||||
void SetIndex(uint32_t index);
|
||||
uint32_t GetIndex() const override { return m_index; }
|
||||
|
||||
void SetPriority(Priority priority);
|
||||
uint16_t GetPriority() const override { return static_cast<uint16_t>(m_priority); }
|
||||
|
||||
void SetMinZoom(int minZoom);
|
||||
int GetMinZoom() const override { return m_minZoom; }
|
||||
|
||||
void SetMinTitleZoom(int minTitleZoom);
|
||||
int GetMinTitleZoom() const override { return m_minTitleZoom; }
|
||||
|
||||
void SetColoredSymbols(ColoredSymbolZoomInfo const & symbolParams);
|
||||
drape_ptr<ColoredSymbolZoomInfo> GetColoredSymbols() const override;
|
||||
|
||||
void SetSymbolNames(SymbolNameZoomInfo const & symbolNames);
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
|
||||
void SetSymbolSizes(SymbolSizes const & symbolSizes);
|
||||
drape_ptr<SymbolSizes> GetSymbolSizes() const override;
|
||||
|
||||
void SetSymbolOffsets(SymbolOffsets const & symbolSizes);
|
||||
drape_ptr<SymbolOffsets> GetSymbolOffsets() const override;
|
||||
|
||||
void AddTitle(dp::TitleDecl const & titleDecl);
|
||||
drape_ptr<TitlesInfo> GetTitleDecl() const override;
|
||||
|
||||
static void GetDefaultTransitTitle(dp::TitleDecl & titleDecl);
|
||||
|
||||
private:
|
||||
int m_minZoom = 1;
|
||||
int m_minTitleZoom = 1;
|
||||
uint32_t m_index = 0;
|
||||
Priority m_priority = Priority::Default;
|
||||
FeatureID m_featureId;
|
||||
TitlesInfo m_titles;
|
||||
SymbolNameZoomInfo m_symbolNames;
|
||||
ColoredSymbolZoomInfo m_coloredSymbols;
|
||||
SymbolSizes m_symbolSizes;
|
||||
SymbolOffsets m_symbolOffsets;
|
||||
dp::Anchor m_anchor = dp::Center;
|
||||
};
|
||||
|
||||
class SpeedCameraMark : public UserMark
|
||||
{
|
||||
public:
|
||||
explicit SpeedCameraMark(m2::PointD const & ptOrg);
|
||||
|
||||
void SetTitle(std::string const & title);
|
||||
std::string const & GetTitle() const;
|
||||
|
||||
void SetIndex(uint32_t index);
|
||||
uint32_t GetIndex() const override { return m_index; }
|
||||
|
||||
df::DepthLayer GetDepthLayer() const override { return df::DepthLayer::RoutingMarkLayer; }
|
||||
bool SymbolIsPOI() const override { return true; }
|
||||
bool HasTitlePriority() const override { return true; }
|
||||
uint16_t GetPriority() const override { return static_cast<uint16_t>(Priority::SpeedCamera); }
|
||||
df::SpecialDisplacement GetDisplacement() const override { return df::SpecialDisplacement::SpecialModeUserMark; }
|
||||
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
drape_ptr<TitlesInfo> GetTitleDecl() const override;
|
||||
drape_ptr<ColoredSymbolZoomInfo> GetColoredSymbols() const override;
|
||||
|
||||
int GetMinZoom() const override;
|
||||
int GetMinTitleZoom() const override;
|
||||
dp::Anchor GetAnchor() const override;
|
||||
|
||||
private:
|
||||
uint32_t m_index = 0;
|
||||
SymbolNameZoomInfo m_symbolNames;
|
||||
ColoredSymbolZoomInfo m_textBg;
|
||||
dp::TitleDecl m_titleDecl;
|
||||
};
|
||||
|
||||
enum class RoadWarningMarkType : uint8_t
|
||||
{
|
||||
// Do not change the order, it uses in platforms.
|
||||
Toll = 0,
|
||||
Ferry = 1,
|
||||
Dirty = 2,
|
||||
Count = 3
|
||||
};
|
||||
|
||||
class RoadWarningMark : public UserMark
|
||||
{
|
||||
public:
|
||||
explicit RoadWarningMark(m2::PointD const & ptOrg);
|
||||
|
||||
bool SymbolIsPOI() const override { return true; }
|
||||
dp::Anchor GetAnchor() const override { return dp::Anchor::Bottom; }
|
||||
df::DepthLayer GetDepthLayer() const override { return df::DepthLayer::RoutingBottomMarkLayer; }
|
||||
uint16_t GetPriority() const override;
|
||||
df::SpecialDisplacement GetDisplacement() const override { return df::SpecialDisplacement::SpecialModeUserMark; }
|
||||
|
||||
void SetIndex(uint32_t index);
|
||||
uint32_t GetIndex() const override { return m_index; }
|
||||
|
||||
void SetRoadWarningType(RoadWarningMarkType type);
|
||||
RoadWarningMarkType GetRoadWarningType() const { return m_type; }
|
||||
|
||||
void SetFeatureId(FeatureID const & featureId);
|
||||
FeatureID GetFeatureID() const override { return m_featureId; }
|
||||
|
||||
void SetDistance(std::string const & distance);
|
||||
std::string GetDistance() const { return m_distance; }
|
||||
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
|
||||
static std::string GetLocalizedRoadWarningType(RoadWarningMarkType type);
|
||||
|
||||
private:
|
||||
RoadWarningMarkType m_type = RoadWarningMarkType::Count;
|
||||
FeatureID m_featureId;
|
||||
uint32_t m_index = 0;
|
||||
std::string m_distance;
|
||||
};
|
||||
|
||||
std::string DebugPrint(RoadWarningMarkType type);
|
||||
445
libs/map/search_api.cpp
Normal file
445
libs/map/search_api.cpp
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
#include "map/search_api.hpp"
|
||||
|
||||
#include "map/bookmarks_search_params.hpp"
|
||||
#include "map/everywhere_search_params.hpp"
|
||||
|
||||
#include "search/geometry_utils.hpp"
|
||||
#include "search/utils.hpp"
|
||||
|
||||
#include "storage/downloader_search_params.hpp"
|
||||
|
||||
#include "platform/preferred_languages.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace search;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
using BookmarkIdDoc = pair<bookmarks::Id, bookmarks::Doc>;
|
||||
|
||||
double constexpr kDistEqualQueryMeters = 100.0;
|
||||
double const kDistEqualQueryMercator = mercator::MetersToMercator(kDistEqualQueryMeters);
|
||||
|
||||
// Cancels search query by |handle|.
|
||||
void CancelQuery(weak_ptr<ProcessorHandle> & handle)
|
||||
{
|
||||
if (auto queryHandle = handle.lock())
|
||||
queryHandle->Cancel();
|
||||
handle.reset();
|
||||
}
|
||||
|
||||
bookmarks::Id KmlMarkIdToSearchBookmarkId(kml::MarkId id)
|
||||
{
|
||||
static_assert(is_integral<kml::MarkId>::value, "");
|
||||
static_assert(is_integral<bookmarks::Id>::value, "");
|
||||
|
||||
static_assert(is_unsigned<kml::MarkId>::value, "");
|
||||
static_assert(is_unsigned<bookmarks::Id>::value, "");
|
||||
|
||||
static_assert(sizeof(bookmarks::Id) == sizeof(kml::MarkId), "");
|
||||
|
||||
return base::asserted_cast<bookmarks::Id>(id);
|
||||
}
|
||||
|
||||
bookmarks::GroupId KmlGroupIdToSearchGroupId(kml::MarkGroupId id)
|
||||
{
|
||||
static_assert(is_integral<kml::MarkGroupId>::value, "");
|
||||
static_assert(is_integral<bookmarks::GroupId>::value, "");
|
||||
|
||||
static_assert(is_unsigned<kml::MarkGroupId>::value, "");
|
||||
static_assert(is_unsigned<bookmarks::GroupId>::value, "");
|
||||
|
||||
static_assert(sizeof(bookmarks::GroupId) >= sizeof(kml::MarkGroupId), "");
|
||||
|
||||
if (id == kml::kInvalidMarkGroupId)
|
||||
return bookmarks::kInvalidGroupId;
|
||||
|
||||
return base::asserted_cast<bookmarks::GroupId>(id);
|
||||
}
|
||||
|
||||
kml::MarkId SearchBookmarkIdToKmlMarkId(bookmarks::Id id)
|
||||
{
|
||||
return static_cast<kml::MarkId>(id);
|
||||
}
|
||||
|
||||
void AppendBookmarkIdDocs(vector<BookmarkInfo> const & marks, vector<BookmarkIdDoc> & result)
|
||||
{
|
||||
result.reserve(result.size() + marks.size());
|
||||
|
||||
auto const locale = languages::GetCurrentOrig();
|
||||
for (auto const & mark : marks)
|
||||
result.emplace_back(KmlMarkIdToSearchBookmarkId(mark.m_bookmarkId), bookmarks::Doc(mark.m_bookmarkData, locale));
|
||||
}
|
||||
|
||||
void AppendBookmarkIds(vector<kml::MarkId> const & marks, vector<bookmarks::Id> & result)
|
||||
{
|
||||
result.reserve(result.size() + marks.size());
|
||||
transform(marks.begin(), marks.end(), back_inserter(result), KmlMarkIdToSearchBookmarkId);
|
||||
}
|
||||
|
||||
class BookmarksSearchCallback
|
||||
{
|
||||
public:
|
||||
using OnResults = BookmarksSearchParams::OnResults;
|
||||
|
||||
BookmarksSearchCallback(SearchAPI::Delegate & delegate, OnResults onResults)
|
||||
: m_delegate(delegate)
|
||||
, m_onResults(std::move(onResults))
|
||||
{}
|
||||
|
||||
void operator()(Results const & results)
|
||||
{
|
||||
if (results.IsEndMarker())
|
||||
{
|
||||
if (results.IsEndedNormal())
|
||||
{
|
||||
m_status = BookmarksSearchParams::Status::Completed;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(results.IsEndedCancelled(), ());
|
||||
m_status = BookmarksSearchParams::Status::Cancelled;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_EQUAL(m_status, BookmarksSearchParams::Status::InProgress, ());
|
||||
}
|
||||
|
||||
auto const & rs = results.GetBookmarksResults();
|
||||
ASSERT_LESS_OR_EQUAL(m_results.size(), rs.size(), ());
|
||||
|
||||
for (size_t i = m_results.size(); i < rs.size(); ++i)
|
||||
m_results.emplace_back(SearchBookmarkIdToKmlMarkId(rs[i].m_id));
|
||||
|
||||
m_delegate.RunUITask([onResults = m_onResults, results = m_results, status = m_status]() mutable
|
||||
{ onResults(std::move(results), status); });
|
||||
}
|
||||
|
||||
private:
|
||||
BookmarksSearchParams::Results m_results;
|
||||
BookmarksSearchParams::Status m_status = BookmarksSearchParams::Status::InProgress;
|
||||
|
||||
SearchAPI::Delegate & m_delegate;
|
||||
OnResults m_onResults;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
SearchAPI::SearchAPI(DataSource & dataSource, storage::Storage const & storage,
|
||||
storage::CountryInfoGetter const & infoGetter, size_t numThreads, Delegate & delegate)
|
||||
: m_dataSource(dataSource)
|
||||
, m_storage(storage)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_delegate(delegate)
|
||||
, m_engine(m_dataSource, GetDefaultCategories(), m_infoGetter,
|
||||
Engine::Params(languages::GetCurrentMapTwine() /* locale */, numThreads))
|
||||
{}
|
||||
|
||||
void SearchAPI::OnViewportChanged(m2::RectD const & viewport)
|
||||
{
|
||||
m_viewport = viewport;
|
||||
|
||||
auto const forceSearchInViewport = !m_isViewportInitialized;
|
||||
if (!m_isViewportInitialized)
|
||||
{
|
||||
m_isViewportInitialized = true;
|
||||
for (size_t i = 0; i < static_cast<size_t>(Mode::Count); i++)
|
||||
{
|
||||
auto & intent = m_searchIntents[i];
|
||||
// Viewport search will be triggered below, in PokeSearchInViewport().
|
||||
if (!intent.m_isDelayed || static_cast<Mode>(i) == Mode::Viewport)
|
||||
continue;
|
||||
intent.m_params.m_viewport = m_viewport;
|
||||
intent.m_params.m_position = m_delegate.GetCurrentPosition();
|
||||
Search(intent);
|
||||
}
|
||||
}
|
||||
|
||||
PokeSearchInViewport(forceSearchInViewport);
|
||||
}
|
||||
|
||||
bool SearchAPI::SearchEverywhere(EverywhereSearchParams params)
|
||||
{
|
||||
SearchParams p;
|
||||
p.m_query = std::move(params.m_query);
|
||||
p.m_inputLocale = std::move(params.m_inputLocale);
|
||||
p.m_mode = Mode::Everywhere;
|
||||
p.m_position = m_delegate.GetCurrentPosition();
|
||||
SetViewportIfPossible(p); // Search request will be delayed if viewport is not available.
|
||||
p.m_maxNumResults = SearchParams::kDefaultNumResultsEverywhere;
|
||||
p.m_suggestsEnabled = true;
|
||||
p.m_needAddress = true;
|
||||
p.m_needHighlighting = true;
|
||||
p.m_categorialRequest = params.m_isCategory;
|
||||
if (params.m_timeout)
|
||||
p.m_timeout = *params.m_timeout;
|
||||
|
||||
p.m_onResults = EverywhereSearchCallback(*this, std::move(params.m_onResults));
|
||||
|
||||
return Search(std::move(p), true /* forceSearch */);
|
||||
}
|
||||
|
||||
bool SearchAPI::SearchInViewport(ViewportSearchParams params)
|
||||
{
|
||||
// Save params first for the PokeSearchInViewport function.
|
||||
m_viewportParams = params;
|
||||
|
||||
SearchParams p;
|
||||
p.m_query = std::move(params.m_query);
|
||||
p.m_inputLocale = std::move(params.m_inputLocale);
|
||||
p.m_position = m_delegate.GetCurrentPosition();
|
||||
SetViewportIfPossible(p); // Search request will be delayed if viewport is not available.
|
||||
p.m_maxNumResults = SearchParams::kDefaultNumResultsInViewport;
|
||||
p.m_mode = Mode::Viewport;
|
||||
p.m_suggestsEnabled = false;
|
||||
p.m_needAddress = false;
|
||||
p.m_needHighlighting = false;
|
||||
p.m_categorialRequest = params.m_isCategory;
|
||||
|
||||
if (params.m_timeout)
|
||||
p.m_timeout = *params.m_timeout;
|
||||
|
||||
if (params.m_onStarted)
|
||||
{
|
||||
p.m_onStarted = [this, onStarted = std::move(params.m_onStarted)]() mutable
|
||||
{ RunUITask([onStarted = std::move(onStarted)]() { onStarted(); }); };
|
||||
}
|
||||
|
||||
p.m_onResults = ViewportSearchCallback(m_viewport, *this, std::move(params.m_onCompleted));
|
||||
|
||||
return Search(std::move(p), false /* forceSearch */);
|
||||
}
|
||||
|
||||
bool SearchAPI::SearchInDownloader(storage::DownloaderSearchParams params)
|
||||
{
|
||||
SearchParams p;
|
||||
p.m_query = params.m_query;
|
||||
p.m_inputLocale = params.m_inputLocale;
|
||||
p.m_position = m_delegate.GetCurrentPosition();
|
||||
SetViewportIfPossible(p); // Search request will be delayed if viewport is not available.
|
||||
p.m_maxNumResults = SearchParams::kDefaultNumResultsEverywhere;
|
||||
p.m_mode = Mode::Downloader;
|
||||
p.m_suggestsEnabled = false;
|
||||
p.m_needAddress = false;
|
||||
p.m_needHighlighting = false;
|
||||
|
||||
p.m_onResults = DownloaderSearchCallback(*this, m_dataSource, m_infoGetter, m_storage, std::move(params));
|
||||
|
||||
return Search(std::move(p), true /* forceSearch */);
|
||||
}
|
||||
|
||||
bool SearchAPI::SearchInBookmarks(search::BookmarksSearchParams params)
|
||||
{
|
||||
SearchParams p;
|
||||
p.m_query = std::move(params.m_query);
|
||||
p.m_position = m_delegate.GetCurrentPosition();
|
||||
SetViewportIfPossible(p); // Search request will be delayed if viewport is not available.
|
||||
p.m_maxNumResults = SearchParams::kDefaultNumBookmarksResults;
|
||||
p.m_mode = Mode::Bookmarks;
|
||||
p.m_suggestsEnabled = false;
|
||||
p.m_needAddress = false;
|
||||
|
||||
p.m_bookmarksGroupId = params.m_groupId;
|
||||
p.m_onResults = BookmarksSearchCallback(m_delegate, std::move(params.m_onResults));
|
||||
|
||||
return Search(std::move(p), true /* forceSearch */);
|
||||
}
|
||||
|
||||
void SearchAPI::PokeSearchInViewport(bool forceSearch)
|
||||
{
|
||||
if (!m_isViewportInitialized || !IsViewportSearchActive())
|
||||
return;
|
||||
|
||||
// Copy is intentional here, to skip possible duplicating requests.
|
||||
auto params = m_searchIntents[static_cast<size_t>(Mode::Viewport)].m_params;
|
||||
SetViewportIfPossible(params);
|
||||
params.m_position = m_delegate.GetCurrentPosition();
|
||||
params.m_onResults = ViewportSearchCallback(m_viewport, *this, m_viewportParams.m_onCompleted);
|
||||
|
||||
Search(std::move(params), forceSearch);
|
||||
}
|
||||
|
||||
void SearchAPI::CancelSearch(Mode mode)
|
||||
{
|
||||
ASSERT_NOT_EQUAL(mode, Mode::Count, ());
|
||||
|
||||
if (mode == Mode::Viewport)
|
||||
m_delegate.ClearViewportSearchResults();
|
||||
|
||||
auto & intent = m_searchIntents[static_cast<size_t>(mode)];
|
||||
intent.m_params.Clear();
|
||||
CancelQuery(intent.m_handle);
|
||||
}
|
||||
|
||||
void SearchAPI::CancelAllSearches()
|
||||
{
|
||||
for (size_t i = 0; i < static_cast<size_t>(Mode::Count); ++i)
|
||||
CancelSearch(static_cast<Mode>(i));
|
||||
}
|
||||
|
||||
void SearchAPI::RunUITask(function<void()> fn)
|
||||
{
|
||||
return m_delegate.RunUITask(std::move(fn));
|
||||
}
|
||||
|
||||
bool SearchAPI::IsViewportSearchActive() const
|
||||
{
|
||||
return !m_searchIntents[static_cast<size_t>(Mode::Viewport)].m_params.m_query.empty();
|
||||
}
|
||||
|
||||
void SearchAPI::ShowViewportSearchResults(Results::ConstIter begin, Results::ConstIter end, bool clear)
|
||||
{
|
||||
return m_delegate.ShowViewportSearchResults(begin, end, clear);
|
||||
}
|
||||
|
||||
ProductInfo SearchAPI::GetProductInfo(Result const & result) const
|
||||
{
|
||||
return m_delegate.GetProductInfo(result);
|
||||
}
|
||||
|
||||
void SearchAPI::EnableIndexingOfBookmarksDescriptions(bool enable)
|
||||
{
|
||||
m_engine.EnableIndexingOfBookmarksDescriptions(enable);
|
||||
}
|
||||
|
||||
void SearchAPI::EnableIndexingOfBookmarkGroup(kml::MarkGroupId const & groupId, bool enable)
|
||||
{
|
||||
if (enable)
|
||||
m_indexableGroups.insert(groupId);
|
||||
else
|
||||
m_indexableGroups.erase(groupId);
|
||||
|
||||
m_engine.EnableIndexingOfBookmarkGroup(KmlGroupIdToSearchGroupId(groupId), enable);
|
||||
}
|
||||
|
||||
unordered_set<kml::MarkGroupId> const & SearchAPI::GetIndexableGroups() const
|
||||
{
|
||||
return m_indexableGroups;
|
||||
}
|
||||
|
||||
void SearchAPI::ResetBookmarksEngine()
|
||||
{
|
||||
m_indexableGroups.clear();
|
||||
m_engine.ResetBookmarks();
|
||||
}
|
||||
|
||||
void SearchAPI::OnBookmarksCreated(vector<BookmarkInfo> const & marks)
|
||||
{
|
||||
vector<BookmarkIdDoc> data;
|
||||
AppendBookmarkIdDocs(marks, data);
|
||||
m_engine.OnBookmarksCreated(data);
|
||||
}
|
||||
|
||||
void SearchAPI::OnBookmarksUpdated(vector<BookmarkInfo> const & marks)
|
||||
{
|
||||
vector<BookmarkIdDoc> data;
|
||||
AppendBookmarkIdDocs(marks, data);
|
||||
m_engine.OnBookmarksUpdated(data);
|
||||
}
|
||||
|
||||
void SearchAPI::OnBookmarksDeleted(vector<kml::MarkId> const & marks)
|
||||
{
|
||||
vector<bookmarks::Id> data;
|
||||
AppendBookmarkIds(marks, data);
|
||||
m_engine.OnBookmarksDeleted(data);
|
||||
}
|
||||
|
||||
void SearchAPI::OnBookmarksAttached(vector<BookmarkGroupInfo> const & groupInfos)
|
||||
{
|
||||
for (auto const & info : groupInfos)
|
||||
{
|
||||
vector<bookmarks::Id> data;
|
||||
AppendBookmarkIds(info.m_bookmarkIds, data);
|
||||
m_engine.OnBookmarksAttachedToGroup(KmlGroupIdToSearchGroupId(info.m_groupId), data);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchAPI::OnBookmarksDetached(vector<BookmarkGroupInfo> const & groupInfos)
|
||||
{
|
||||
for (auto const & info : groupInfos)
|
||||
{
|
||||
vector<bookmarks::Id> data;
|
||||
AppendBookmarkIds(info.m_bookmarkIds, data);
|
||||
m_engine.OnBookmarksDetachedFromGroup(KmlGroupIdToSearchGroupId(info.m_groupId), data);
|
||||
}
|
||||
}
|
||||
|
||||
bool SearchAPI::Search(SearchParams params, bool forceSearch)
|
||||
{
|
||||
if (m_delegate.ParseSearchQueryCommand(params))
|
||||
return false;
|
||||
|
||||
auto const mode = params.m_mode;
|
||||
auto & intent = m_searchIntents[static_cast<size_t>(mode)];
|
||||
|
||||
if (!forceSearch && QueryMayBeSkipped(intent.m_params, params))
|
||||
return false;
|
||||
|
||||
intent.m_params = std::move(params);
|
||||
|
||||
// Cancels previous search request (if any) and initiates a new search request.
|
||||
CancelQuery(intent.m_handle);
|
||||
|
||||
intent.m_params.m_minDistanceOnMapBetweenResults = m_delegate.GetMinDistanceBetweenResults();
|
||||
|
||||
Search(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SearchAPI::Search(SearchIntent & intent)
|
||||
{
|
||||
if (!m_isViewportInitialized)
|
||||
{
|
||||
intent.m_isDelayed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
intent.m_handle = m_engine.Search(intent.m_params);
|
||||
intent.m_isDelayed = false;
|
||||
}
|
||||
|
||||
void SearchAPI::SetViewportIfPossible(SearchParams & params)
|
||||
{
|
||||
if (m_isViewportInitialized)
|
||||
params.m_viewport = m_viewport;
|
||||
}
|
||||
|
||||
void SearchAPI::SetLocale(std::string const & locale)
|
||||
{
|
||||
m_engine.SetLocale(locale);
|
||||
}
|
||||
|
||||
bool SearchAPI::QueryMayBeSkipped(SearchParams const & prevParams, SearchParams const & currParams) const
|
||||
{
|
||||
auto const & prevViewport = prevParams.m_viewport;
|
||||
auto const & currViewport = currParams.m_viewport;
|
||||
|
||||
if (!prevParams.IsEqualCommon(currParams))
|
||||
return false;
|
||||
|
||||
if (!prevViewport.IsValid() || !IsEqualMercator(prevViewport, currViewport, kDistEqualQueryMercator))
|
||||
return false;
|
||||
|
||||
if (prevParams.m_position && currParams.m_position &&
|
||||
mercator::DistanceOnEarth(*prevParams.m_position, *currParams.m_position) > kDistEqualQueryMercator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<bool>(prevParams.m_position) != static_cast<bool>(currParams.m_position))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
179
libs/map/search_api.hpp
Normal file
179
libs/map/search_api.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
#include "map/everywhere_search_callback.hpp"
|
||||
#include "map/search_product_info.hpp"
|
||||
#include "map/viewport_search_callback.hpp"
|
||||
#include "map/viewport_search_params.hpp"
|
||||
|
||||
#include "search/downloader_search_callback.hpp"
|
||||
#include "search/engine.hpp"
|
||||
#include "search/mode.hpp"
|
||||
#include "search/query_saver.hpp"
|
||||
#include "search/result.hpp"
|
||||
#include "search/search_params.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class DataSource;
|
||||
|
||||
namespace search
|
||||
{
|
||||
struct BookmarksSearchParams;
|
||||
struct EverywhereSearchParams;
|
||||
struct DiscoverySearchParams;
|
||||
} // namespace search
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class CountryInfoGetter;
|
||||
class Storage;
|
||||
struct DownloaderSearchParams;
|
||||
} // namespace storage
|
||||
|
||||
class SearchAPI
|
||||
: public search::DownloaderSearchCallback::Delegate
|
||||
, public search::ViewportSearchCallback::Delegate
|
||||
, public search::EverywhereSearchCallback::Delegate
|
||||
{
|
||||
public:
|
||||
struct Delegate
|
||||
{
|
||||
virtual ~Delegate() = default;
|
||||
|
||||
virtual void RunUITask(std::function<void()> /* fn */) {}
|
||||
|
||||
using ResultsIterT = search::Results::ConstIter;
|
||||
virtual void ShowViewportSearchResults(ResultsIterT begin, ResultsIterT end, bool clear) {}
|
||||
|
||||
virtual void ClearViewportSearchResults() {}
|
||||
|
||||
virtual std::optional<m2::PointD> GetCurrentPosition() const { return {}; }
|
||||
|
||||
virtual bool ParseSearchQueryCommand(search::SearchParams const & /* params */) { return false; }
|
||||
|
||||
virtual m2::PointD GetMinDistanceBetweenResults() const { return {0, 0}; }
|
||||
|
||||
virtual search::ProductInfo GetProductInfo(search::Result const & result) const { return {}; }
|
||||
};
|
||||
|
||||
SearchAPI(DataSource & dataSource, storage::Storage const & storage, storage::CountryInfoGetter const & infoGetter,
|
||||
size_t numThreads, Delegate & delegate);
|
||||
virtual ~SearchAPI() = default;
|
||||
|
||||
void OnViewportChanged(m2::RectD const & viewport);
|
||||
|
||||
void InitAfterWorldLoaded()
|
||||
{
|
||||
m_engine.CacheWorldLocalities();
|
||||
m_engine.LoadCitiesBoundaries();
|
||||
}
|
||||
|
||||
// Search everywhere.
|
||||
bool SearchEverywhere(search::EverywhereSearchParams params);
|
||||
|
||||
// Search in the viewport.
|
||||
bool SearchInViewport(search::ViewportSearchParams params);
|
||||
|
||||
// Search for maps by countries or cities.
|
||||
bool SearchInDownloader(storage::DownloaderSearchParams params);
|
||||
|
||||
// Search for bookmarks.
|
||||
bool SearchInBookmarks(search::BookmarksSearchParams params);
|
||||
|
||||
search::Engine & GetEngine() { return m_engine; }
|
||||
search::Engine const & GetEngine() const { return m_engine; }
|
||||
|
||||
// When search in viewport is active or delayed, restarts search in
|
||||
// viewport. When |forceSearch| is false, request is skipped when it
|
||||
// is similar to the previous request in the current
|
||||
// search-in-viewport session.
|
||||
void PokeSearchInViewport(bool forceSearch = true);
|
||||
|
||||
void CancelSearch(search::Mode mode);
|
||||
void CancelAllSearches();
|
||||
void ClearCaches() { return m_engine.ClearCaches(); }
|
||||
|
||||
// *SearchCallback::Delegate overrides:
|
||||
void RunUITask(std::function<void()> fn) override;
|
||||
bool IsViewportSearchActive() const override;
|
||||
void ShowViewportSearchResults(search::Results::ConstIter begin, search::Results::ConstIter end, bool clear) override;
|
||||
search::ProductInfo GetProductInfo(search::Result const & result) const override;
|
||||
|
||||
std::list<search::QuerySaver::SearchRequest> const & GetLastSearchQueries() const { return m_searchQuerySaver.Get(); }
|
||||
void SaveSearchQuery(search::QuerySaver::SearchRequest const & query) { m_searchQuerySaver.Add(query); }
|
||||
void ClearSearchHistory() { m_searchQuerySaver.Clear(); }
|
||||
|
||||
void EnableIndexingOfBookmarksDescriptions(bool enable);
|
||||
|
||||
void SetLocale(std::string const & locale);
|
||||
|
||||
// By default all created bookmarks are saved in BookmarksProcessor
|
||||
// but we do not index them in an attempt to save time and memory.
|
||||
// This method must be used to enable or disable indexing all current and future
|
||||
// bookmarks belonging to |groupId|.
|
||||
void EnableIndexingOfBookmarkGroup(kml::MarkGroupId const & groupId, bool enable);
|
||||
std::unordered_set<kml::MarkGroupId> const & GetIndexableGroups() const;
|
||||
|
||||
// Returns the bookmarks search to its default, pre-launch state.
|
||||
// This includes dropping all bookmark data for created bookmarks (efficiently
|
||||
// calling OnBookmarksDeleted with all known bookmarks as an argument),
|
||||
// clearing the bookmark search index, and resetting all parameters to
|
||||
// their default values.
|
||||
void ResetBookmarksEngine();
|
||||
|
||||
void OnBookmarksCreated(std::vector<BookmarkInfo> const & marks);
|
||||
void OnBookmarksUpdated(std::vector<BookmarkInfo> const & marks);
|
||||
void OnBookmarksDeleted(std::vector<kml::MarkId> const & marks);
|
||||
void OnBookmarksAttached(std::vector<BookmarkGroupInfo> const & groupInfos);
|
||||
void OnBookmarksDetached(std::vector<BookmarkGroupInfo> const & groupInfos);
|
||||
|
||||
private:
|
||||
struct SearchIntent
|
||||
{
|
||||
search::SearchParams m_params;
|
||||
std::weak_ptr<search::ProcessorHandle> m_handle;
|
||||
bool m_isDelayed = false;
|
||||
};
|
||||
|
||||
bool Search(search::SearchParams params, bool forceSearch);
|
||||
void Search(SearchIntent & intent);
|
||||
|
||||
void SetViewportIfPossible(search::SearchParams & params);
|
||||
|
||||
bool QueryMayBeSkipped(search::SearchParams const & prevParams, search::SearchParams const & currParams) const;
|
||||
|
||||
DataSource & m_dataSource;
|
||||
storage::Storage const & m_storage;
|
||||
storage::CountryInfoGetter const & m_infoGetter;
|
||||
Delegate & m_delegate;
|
||||
|
||||
search::Engine m_engine;
|
||||
|
||||
search::QuerySaver m_searchQuerySaver;
|
||||
|
||||
// Descriptions of last search queries for different modes. May be
|
||||
// used for search requests skipping. This field is not guarded
|
||||
// because it must be used from the UI thread only.
|
||||
SearchIntent m_searchIntents[static_cast<size_t>(search::Mode::Count)];
|
||||
|
||||
m2::RectD m_viewport;
|
||||
bool m_isViewportInitialized = false;
|
||||
|
||||
// Viewport search callback should be changed every time when SearchAPI::PokeSearchInViewport
|
||||
// is called and we need viewport search params to construct it.
|
||||
search::ViewportSearchParams m_viewportParams;
|
||||
|
||||
// Same as the one in bookmarks::Processor. Duplicated here because
|
||||
// it is easier than obtaining the information about a group asynchronously
|
||||
// from |m_engine|.
|
||||
std::unordered_set<kml::MarkGroupId> m_indexableGroups;
|
||||
};
|
||||
676
libs/map/search_mark.cpp
Normal file
676
libs/map/search_mark.cpp
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
#include "map/search_mark.hpp"
|
||||
|
||||
#include "map/bookmark_manager.hpp"
|
||||
|
||||
#include "drape_frontend/drape_engine.hpp"
|
||||
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
enum SearchMarkPoint::SearchMarkType : uint8_t
|
||||
{
|
||||
Default = 0,
|
||||
Hotel,
|
||||
Hostel,
|
||||
Chalet,
|
||||
Apartment,
|
||||
Campsite,
|
||||
CaravanSite,
|
||||
Cafe,
|
||||
Bakery,
|
||||
Bar,
|
||||
Pub,
|
||||
Restaurant,
|
||||
FastFood,
|
||||
Casino,
|
||||
Lottery,
|
||||
Cinema,
|
||||
Theatre,
|
||||
Stadium,
|
||||
Museum,
|
||||
Art,
|
||||
Attraction,
|
||||
Viewpoint,
|
||||
Remains,
|
||||
ArchaeologicalSite,
|
||||
Information,
|
||||
Marketplace,
|
||||
Nightclub,
|
||||
Playground,
|
||||
Bank,
|
||||
Fuel,
|
||||
ChargingStation,
|
||||
ShopAlcohol,
|
||||
ShopButcher,
|
||||
ShopClothes,
|
||||
ShopConfectionery,
|
||||
ShopConvenience,
|
||||
ShopBeauty,
|
||||
ShopDepartmentStore,
|
||||
ShopGift,
|
||||
ShopGreengrocer,
|
||||
ShopJewelry,
|
||||
ShopSeafood,
|
||||
ShopShoes,
|
||||
ShopSports,
|
||||
ShopSupermarket,
|
||||
ShopToys,
|
||||
ThemePark,
|
||||
Zoo,
|
||||
Pitch,
|
||||
Swimming,
|
||||
Hospital,
|
||||
Clinic,
|
||||
Pharmacy,
|
||||
DrinkingWater,
|
||||
DrinkingWaterNo,
|
||||
BicycleParking,
|
||||
BicycleParkingCovered,
|
||||
BicycleRental,
|
||||
|
||||
NotFound, // Service value used in developer tools.
|
||||
Count
|
||||
};
|
||||
|
||||
using SearchMarkType = SearchMarkPoint::SearchMarkType;
|
||||
|
||||
namespace
|
||||
{
|
||||
df::ColorConstant const kColorConstant = "SearchmarkDefault";
|
||||
|
||||
float constexpr kVisitedSymbolOpacity = 0.7f;
|
||||
float constexpr kOutOfFiltersSymbolOpacity = 0.4f;
|
||||
|
||||
std::array<std::string, SearchMarkType::Count> const kSymbols = {
|
||||
"search-result", // Default.
|
||||
"search-result-hotel", // Hotel.
|
||||
"search-result-hostel", // Hostel.
|
||||
"search-result-chalet", // Chalet.
|
||||
"search-result-apartment", // Apartment.
|
||||
"search-result-campsite", // Campsite.
|
||||
"search-result-caravan-site", // Caravan site.
|
||||
"search-result-cafe", // Cafe.
|
||||
"search-result-bakery", // Bakery.
|
||||
"search-result-bar", // Bar.
|
||||
"search-result-pub", // Pub.
|
||||
"search-result-restaurant", // Restaurant.
|
||||
"search-result-fastfood", // FastFood.
|
||||
"search-result-casino", // Casino.
|
||||
"search-result-lottery", // Lottery.
|
||||
"search-result-cinema", // Cinema.
|
||||
"search-result-theatre", // Theatre.
|
||||
"search-result-stadium", // Stadium.
|
||||
"search-result-museum", // Museum.
|
||||
"search-result-art", // Art.
|
||||
"search-result-attraction", // Attraction.
|
||||
"search-result-viewpoint", // Viewpoint.
|
||||
"search-result-remains", // Remains.
|
||||
"search-result-archaeological-site", // ArchaeologicalSite.
|
||||
"search-result-information", // Information.
|
||||
"search-result-marketplace", // Marketplace.
|
||||
"search-result-nightclub", // Nightclub.
|
||||
"search-result-playground", // Playground.
|
||||
"search-result-bank", // Bank.
|
||||
"search-result-fuel", // Fuel.
|
||||
"search-result-charging_station", // ChargingStation.
|
||||
"search-result-shop-alcohol", // ShopAlcohol.
|
||||
"search-result-shop-butcher", // ShopButcher.
|
||||
"search-result-shop-clothes", // ShopClothes.
|
||||
"search-result-shop-confectionery", // ShopConfectionery.
|
||||
"search-result-shop-convenience", // ShopConvenience.
|
||||
"search-result-shop-beauty", // ShopBeauty.
|
||||
"search-result-shop-department_store", // ShopDepartmentStore.
|
||||
"search-result-shop-gift", // ShopGift.
|
||||
"search-result-shop-greengrocer", // ShopGreengrocer.
|
||||
"search-result-shop-jewelry", // ShopJewelry.
|
||||
"search-result-shop-seafood", // ShopSeafood.
|
||||
"search-result-shop-shoes", // ShopShoes.
|
||||
"search-result-shop-sports", // ShopSports.
|
||||
"search-result-shop-supermarket", // ShopSupermarket.
|
||||
"search-result-shop-toys", // ShopToys.
|
||||
"search-result-theme-park", // ThemePark.
|
||||
"search-result-zoo", // Zoo.
|
||||
"search-result-pitch", // Pitch.
|
||||
"search-result-swimming", // Swimming.
|
||||
"search-result-hospital", // Hospital.
|
||||
"search-result-clinic", // Clinic.
|
||||
"search-result-pharmacy", // Pharmacy.
|
||||
"search-result-drinking-water", // DrinkingWater.
|
||||
"search-result-drinking-water-no", // DrinkingWaterNo.
|
||||
"search-result-bicycle_parking", // BicycleParking.
|
||||
"search-result-bicycle_parking-covered", // BicycleParkingCovered.
|
||||
"search-result-bicycle_rental", // BicycleRental.
|
||||
|
||||
"search-result-non-found", // NotFound.
|
||||
};
|
||||
|
||||
std::string const & GetSymbol(SearchMarkType searchMarkType)
|
||||
{
|
||||
ASSERT_LESS(searchMarkType, kSymbols.size(), ());
|
||||
return kSymbols[searchMarkType];
|
||||
}
|
||||
|
||||
class SearchMarkTypeChecker
|
||||
{
|
||||
public:
|
||||
static SearchMarkTypeChecker & Instance()
|
||||
{
|
||||
static SearchMarkTypeChecker checker;
|
||||
return checker;
|
||||
}
|
||||
|
||||
SearchMarkType GetSearchMarkType(uint32_t type) const
|
||||
{
|
||||
auto const it = std::partition_point(m_searchMarkTypes.cbegin(), m_searchMarkTypes.cend(),
|
||||
[type](auto && t) { return t.first < type; });
|
||||
if (it == m_searchMarkTypes.cend() || it->first != type)
|
||||
return SearchMarkType::Default;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
using Type = std::pair<uint32_t, SearchMarkType>;
|
||||
|
||||
SearchMarkTypeChecker()
|
||||
{
|
||||
auto const & c = classif();
|
||||
std::pair<std::vector<std::string_view>, SearchMarkType> const table[] = {
|
||||
{{"amenity", "cafe"}, SearchMarkType::Cafe},
|
||||
{{"shop", "bakery"}, SearchMarkType::Bakery},
|
||||
{{"shop", "pastry"}, SearchMarkType::Bakery},
|
||||
{{"amenity", "bar"}, SearchMarkType::Bar},
|
||||
{{"amenity", "pub"}, SearchMarkType::Pub},
|
||||
{{"amenity", "biergarten"}, SearchMarkType::Pub},
|
||||
{{"amenity", "restaurant"}, SearchMarkType::Restaurant},
|
||||
{{"amenity", "food_court"}, SearchMarkType::Restaurant},
|
||||
{{"amenity", "fast_food"}, SearchMarkType::FastFood},
|
||||
{{"amenity", "casino"}, SearchMarkType::Casino},
|
||||
{{"shop", "bookmaker"}, SearchMarkType::Lottery},
|
||||
{{"shop", "lottery"}, SearchMarkType::Lottery},
|
||||
{{"amenity", "cinema"}, SearchMarkType::Cinema},
|
||||
{{"amenity", "theatre"}, SearchMarkType::Theatre},
|
||||
{{"leisure", "stadium"}, SearchMarkType::Stadium},
|
||||
{{"tourism", "museum"}, SearchMarkType::Museum},
|
||||
{{"amenity", "arts_centre"}, SearchMarkType::Art},
|
||||
{{"tourism", "gallery"}, SearchMarkType::Art},
|
||||
{{"tourism", "attraction"}, SearchMarkType::Attraction},
|
||||
{{"tourism", "viewpoint"}, SearchMarkType::Viewpoint},
|
||||
{{"historic", "fort"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "castrum"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "fortified_church"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "fortress"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "hillfort"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "kremlin"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "manor"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "palace"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "shiro"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "defensive"}, SearchMarkType::Remains},
|
||||
{{"historic", "castle", "stately"}, SearchMarkType::Remains},
|
||||
{{"historic", "ruins"}, SearchMarkType::Remains},
|
||||
{{"historic", "city_gate"}, SearchMarkType::Remains},
|
||||
{{"historic", "archaeological_site"}, SearchMarkType::ArchaeologicalSite},
|
||||
{{"tourism", "information"}, SearchMarkType::Information},
|
||||
{{"tourism", "information", "office"}, SearchMarkType::Information},
|
||||
{{"tourism", "information", "visitor_centre"}, SearchMarkType::Information},
|
||||
{{"amenity", "marketplace"}, SearchMarkType::Marketplace},
|
||||
{{"amenity", "nightclub"}, SearchMarkType::Nightclub},
|
||||
{{"leisure", "playground"}, SearchMarkType::Playground},
|
||||
{{"amenity", "bank"}, SearchMarkType::Bank},
|
||||
{{"shop", "money_lender"}, SearchMarkType::Bank},
|
||||
{{"amenity", "fuel"}, SearchMarkType::Fuel},
|
||||
{{"amenity", "charging_station"}, SearchMarkType::ChargingStation},
|
||||
{{"amenity", "charging_station", "bicycle"}, SearchMarkType::ChargingStation},
|
||||
{{"amenity", "charging_station", "motorcar"}, SearchMarkType::ChargingStation},
|
||||
{{"shop", "alcohol"}, SearchMarkType::ShopAlcohol},
|
||||
{{"shop", "beverages"}, SearchMarkType::ShopAlcohol},
|
||||
{{"shop", "wine"}, SearchMarkType::ShopAlcohol},
|
||||
{{"shop", "butcher"}, SearchMarkType::ShopButcher},
|
||||
{{"shop", "clothes"}, SearchMarkType::ShopClothes},
|
||||
{{"shop", "confectionery"}, SearchMarkType::ShopConfectionery},
|
||||
{{"shop", "chocolate"}, SearchMarkType::ShopConfectionery},
|
||||
{{"craft", "confectionery"}, SearchMarkType::ShopConfectionery},
|
||||
{{"shop", "convenience"}, SearchMarkType::ShopConvenience},
|
||||
{{"shop", "grocery"}, SearchMarkType::ShopConvenience},
|
||||
{{"shop", "deli"}, SearchMarkType::ShopConvenience},
|
||||
{{"shop", "farm"}, SearchMarkType::ShopConvenience},
|
||||
{{"shop", "health_food"}, SearchMarkType::ShopConvenience},
|
||||
{{"shop", "beauty"}, SearchMarkType::ShopBeauty},
|
||||
{{"shop", "cosmetics"}, SearchMarkType::ShopBeauty},
|
||||
{{"shop", "department_store"}, SearchMarkType::ShopDepartmentStore},
|
||||
{{"shop", "gift"}, SearchMarkType::ShopGift},
|
||||
{{"shop", "greengrocer"}, SearchMarkType::ShopGreengrocer},
|
||||
{{"shop", "jewelry"}, SearchMarkType::ShopJewelry},
|
||||
{{"shop", "seafood"}, SearchMarkType::ShopSeafood},
|
||||
{{"shop", "shoes"}, SearchMarkType::ShopShoes},
|
||||
{{"craft", "shoemaker"}, SearchMarkType::ShopShoes},
|
||||
{{"shop", "sports"}, SearchMarkType::ShopSports},
|
||||
{{"shop", "supermarket"}, SearchMarkType::ShopSupermarket},
|
||||
{{"shop", "toys"}, SearchMarkType::ShopToys},
|
||||
{{"tourism", "theme_park"}, SearchMarkType::ThemePark},
|
||||
{{"tourism", "zoo"}, SearchMarkType::Zoo},
|
||||
{{"tourism", "chalet"}, SearchMarkType::Chalet},
|
||||
{{"tourism", "alpine_hut"}, SearchMarkType::Chalet},
|
||||
{{"tourism", "wilderness_hut"}, SearchMarkType::Chalet},
|
||||
{{"tourism", "hotel"}, SearchMarkType::Hotel},
|
||||
{{"tourism", "motel"}, SearchMarkType::Hotel},
|
||||
{{"leisure", "resort"}, SearchMarkType::Hotel},
|
||||
{{"tourism", "hostel"}, SearchMarkType::Hostel},
|
||||
{{"tourism", "apartment"}, SearchMarkType::Apartment},
|
||||
{{"tourism", "guest_house"}, SearchMarkType::Apartment},
|
||||
{{"tourism", "camp_site"}, SearchMarkType::Campsite},
|
||||
{{"tourism", "caravan_site"}, SearchMarkType::CaravanSite},
|
||||
{{"amenity", "hospital"}, SearchMarkType::Hospital},
|
||||
{{"amenity", "clinic"}, SearchMarkType::Clinic},
|
||||
{{"amenity", "doctors"}, SearchMarkType::Clinic},
|
||||
{{"amenity", "pharmacy"}, SearchMarkType::Pharmacy},
|
||||
{{"leisure", "pitch"}, SearchMarkType::Pitch},
|
||||
{{"leisure", "ice_rink"}, SearchMarkType::Pitch},
|
||||
{{"leisure", "sports_centre"}, SearchMarkType::Pitch},
|
||||
{{"leisure", "sports_hall"}, SearchMarkType::Pitch},
|
||||
{{"leisure", "swimming_pool"}, SearchMarkType::Swimming},
|
||||
{{"leisure", "water_park"}, SearchMarkType::Swimming},
|
||||
{{"amenity", "drinking_water"}, SearchMarkType::DrinkingWater},
|
||||
{{"amenity", "water_point"}, SearchMarkType::DrinkingWater},
|
||||
{{"man_made", "water_tap"}, SearchMarkType::DrinkingWater},
|
||||
{{"man_made", "water_well"}, SearchMarkType::DrinkingWater},
|
||||
{{"natural", "spring"}, SearchMarkType::DrinkingWater},
|
||||
{{"natural", "hot_spring"}, SearchMarkType::DrinkingWater},
|
||||
{{"amenity", "water_point", "drinking_water_no"}, SearchMarkType::DrinkingWaterNo},
|
||||
{{"man_made", "water_tap", "drinking_water_no"}, SearchMarkType::DrinkingWaterNo},
|
||||
{{"man_made", "water_well", "drinking_water_no"}, SearchMarkType::DrinkingWaterNo},
|
||||
{{"natural", "spring", "drinking_water_no"}, SearchMarkType::DrinkingWaterNo},
|
||||
{{"amenity", "bicycle_parking"}, SearchMarkType::BicycleParking},
|
||||
{{"amenity", "bicycle_parking", "covered"}, SearchMarkType::BicycleParkingCovered},
|
||||
{{"amenity", "bicycle_rental"}, SearchMarkType::BicycleRental},
|
||||
};
|
||||
|
||||
m_searchMarkTypes.reserve(std::size(table));
|
||||
for (auto const & p : table)
|
||||
m_searchMarkTypes.push_back({c.GetTypeByPath(p.first), p.second});
|
||||
|
||||
std::sort(m_searchMarkTypes.begin(), m_searchMarkTypes.end());
|
||||
}
|
||||
|
||||
std::vector<Type> m_searchMarkTypes;
|
||||
};
|
||||
|
||||
SearchMarkType GetSearchMarkType(uint32_t type)
|
||||
{
|
||||
auto const & checker = SearchMarkTypeChecker::Instance();
|
||||
return checker.GetSearchMarkType(type);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SearchMarkPoint::SearchMarkPoint(m2::PointD const & ptOrg)
|
||||
: UserMark(ptOrg, UserMark::Type::SEARCH)
|
||||
, m_type(SearchMarkType::Default)
|
||||
, m_isPreparing(false)
|
||||
, m_hasSale(false)
|
||||
, m_isSelected(false)
|
||||
, m_isVisited(false)
|
||||
, m_isAvailable(true)
|
||||
{}
|
||||
|
||||
m2::PointD SearchMarkPoint::GetPixelOffset() const
|
||||
{
|
||||
return {0.0, 6.0};
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> SearchMarkPoint::GetSymbolNames() const
|
||||
{
|
||||
auto const symbolName = GetSymbolName();
|
||||
if (symbolName == nullptr)
|
||||
return nullptr;
|
||||
|
||||
auto symbolZoomInfo = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbolZoomInfo->emplace(1 /*kWorldZoomLevel*/, *symbolName);
|
||||
return symbolZoomInfo;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolOffsets> SearchMarkPoint::GetSymbolOffsets() const
|
||||
{
|
||||
m2::PointF offset(0, 1);
|
||||
return make_unique_dp<SymbolOffsets>(static_cast<size_t>(scales::UPPER_STYLE_SCALE), offset);
|
||||
}
|
||||
|
||||
bool SearchMarkPoint::IsMarkAboveText() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float SearchMarkPoint::GetSymbolOpacity() const
|
||||
{
|
||||
if (!m_isAvailable)
|
||||
return kOutOfFiltersSymbolOpacity;
|
||||
return m_isVisited ? kVisitedSymbolOpacity : 1.0f;
|
||||
}
|
||||
|
||||
df::ColorConstant SearchMarkPoint::GetColorConstant() const
|
||||
{
|
||||
return kColorConstant;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::TitlesInfo> SearchMarkPoint::GetTitleDecl() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
int SearchMarkPoint::GetMinTitleZoom() const
|
||||
{
|
||||
return scales::GetUpperCountryScale();
|
||||
}
|
||||
|
||||
df::DepthLayer SearchMarkPoint::GetDepthLayer() const
|
||||
{
|
||||
return df::DepthLayer::SearchMarkLayer;
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetFoundFeature(FeatureID const & feature)
|
||||
{
|
||||
SetAttributeValue(m_featureID, feature);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetMatchedName(std::string const & name)
|
||||
{
|
||||
SetAttributeValue(m_matchedName, name);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetFromType(uint32_t type)
|
||||
{
|
||||
SetAttributeValue(m_type, GetSearchMarkType(type));
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetNotFoundType()
|
||||
{
|
||||
SetAttributeValue(m_type, SearchMarkType::NotFound);
|
||||
}
|
||||
|
||||
#define SET_BOOL_ATTRIBUTE(dest, src) \
|
||||
if (dest != src) \
|
||||
{ \
|
||||
dest = src; \
|
||||
SetDirty(); \
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetPreparing(bool isPreparing)
|
||||
{
|
||||
SET_BOOL_ATTRIBUTE(m_isPreparing, isPreparing);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetSale(bool hasSale)
|
||||
{
|
||||
SET_BOOL_ATTRIBUTE(m_hasSale, hasSale);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetSelected(bool isSelected)
|
||||
{
|
||||
SET_BOOL_ATTRIBUTE(m_isSelected, isSelected);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetVisited(bool isVisited)
|
||||
{
|
||||
SET_BOOL_ATTRIBUTE(m_isVisited, isVisited);
|
||||
}
|
||||
|
||||
void SearchMarkPoint::SetAvailable(bool isAvailable)
|
||||
{
|
||||
SET_BOOL_ATTRIBUTE(m_isAvailable, isAvailable);
|
||||
}
|
||||
|
||||
#undef SET_BOOL_ATTRIBUTE
|
||||
|
||||
void SearchMarkPoint::SetReason(std::string const & reason)
|
||||
{
|
||||
SetAttributeValue(m_reason, reason);
|
||||
}
|
||||
|
||||
bool SearchMarkPoint::IsSelected() const
|
||||
{
|
||||
return m_isSelected;
|
||||
}
|
||||
|
||||
bool SearchMarkPoint::IsAvailable() const
|
||||
{
|
||||
return m_isAvailable;
|
||||
}
|
||||
|
||||
std::string const & SearchMarkPoint::GetReason() const
|
||||
{
|
||||
return m_reason;
|
||||
}
|
||||
|
||||
bool SearchMarkPoint::HasReason() const
|
||||
{
|
||||
return !m_reason.empty();
|
||||
}
|
||||
|
||||
std::string const * SearchMarkPoint::GetSymbolName() const
|
||||
{
|
||||
std::string const * symbolName = nullptr;
|
||||
|
||||
if (!SearchMarks::HaveSizes())
|
||||
return symbolName;
|
||||
|
||||
if (m_type >= SearchMarkType::Count)
|
||||
{
|
||||
ASSERT(false, (m_type));
|
||||
symbolName = &GetSymbol(SearchMarkType::Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
symbolName = &GetSymbol(m_type);
|
||||
}
|
||||
|
||||
if (!SearchMarks::GetSize(*symbolName))
|
||||
{
|
||||
ASSERT(false, (*symbolName));
|
||||
symbolName = nullptr;
|
||||
}
|
||||
|
||||
return symbolName;
|
||||
}
|
||||
|
||||
// static
|
||||
std::map<std::string, m2::PointF> SearchMarks::s_markSizes;
|
||||
|
||||
SearchMarks::SearchMarks() : m_bmManager(nullptr) {}
|
||||
|
||||
void SearchMarks::SetDrapeEngine(ref_ptr<df::DrapeEngine> engine)
|
||||
{
|
||||
m_drapeEngine.Set(engine);
|
||||
if (engine == nullptr)
|
||||
return;
|
||||
|
||||
std::vector<std::string> symbols;
|
||||
for (uint8_t t = 0; t < SearchMarkType::Count; ++t)
|
||||
symbols.push_back(GetSymbol(static_cast<SearchMarkType>(t)));
|
||||
|
||||
base::SortUnique(symbols);
|
||||
|
||||
m_drapeEngine.SafeCall(&df::DrapeEngine::RequestSymbolsSize, symbols,
|
||||
[this](std::map<std::string, m2::PointF> && sizes)
|
||||
{
|
||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, sizes = std::move(sizes)]() mutable
|
||||
{
|
||||
s_markSizes = std::move(sizes);
|
||||
UpdateMaxDimension();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SearchMarks::SetBookmarkManager(BookmarkManager * bmManager)
|
||||
{
|
||||
m_bmManager = bmManager;
|
||||
}
|
||||
|
||||
m2::PointD SearchMarks::GetMaxDimension(ScreenBase const & modelView) const
|
||||
{
|
||||
return m_maxDimension * modelView.GetScale();
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<m2::PointD> SearchMarks::GetSize(std::string const & symbolName)
|
||||
{
|
||||
auto const it = s_markSizes.find(symbolName);
|
||||
if (it == s_markSizes.end())
|
||||
return {};
|
||||
return m2::PointD(it->second);
|
||||
}
|
||||
|
||||
void SearchMarks::SetPreparingState(std::vector<FeatureID> const & features, bool isPreparing)
|
||||
{
|
||||
if (features.empty())
|
||||
return;
|
||||
|
||||
ProcessMarks([&features, isPreparing](SearchMarkPoint * mark) -> base::ControlFlow
|
||||
{
|
||||
ASSERT(std::is_sorted(features.cbegin(), features.cend()), ());
|
||||
if (std::binary_search(features.cbegin(), features.cend(), mark->GetFeatureID()))
|
||||
mark->SetPreparing(isPreparing);
|
||||
return base::ControlFlow::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void SearchMarks::SetSales(std::vector<FeatureID> const & features, bool hasSale)
|
||||
{
|
||||
if (features.empty())
|
||||
return;
|
||||
|
||||
ProcessMarks([&features, hasSale](SearchMarkPoint * mark) -> base::ControlFlow
|
||||
{
|
||||
ASSERT(std::is_sorted(features.cbegin(), features.cend()), ());
|
||||
if (std::binary_search(features.cbegin(), features.cend(), mark->GetFeatureID()))
|
||||
mark->SetSale(hasSale);
|
||||
return base::ControlFlow::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
bool SearchMarks::IsThereSearchMarkForFeature(FeatureID const & featureId) const
|
||||
{
|
||||
for (auto const markId : m_bmManager->GetUserMarkIds(UserMark::Type::SEARCH))
|
||||
if (m_bmManager->GetUserMark(markId)->GetFeatureID() == featureId)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SearchMarks::OnActivate(FeatureID const & featureId)
|
||||
{
|
||||
m_selectedFeature = featureId;
|
||||
m_visitedSearchMarks.erase(featureId);
|
||||
ProcessMarks([&featureId](SearchMarkPoint * mark) -> base::ControlFlow
|
||||
{
|
||||
if (featureId != mark->GetFeatureID())
|
||||
return base::ControlFlow::Continue;
|
||||
mark->SetVisited(false);
|
||||
mark->SetSelected(true);
|
||||
return base::ControlFlow::Break;
|
||||
});
|
||||
}
|
||||
|
||||
void SearchMarks::OnDeactivate(FeatureID const & featureId)
|
||||
{
|
||||
m_selectedFeature = {};
|
||||
m_visitedSearchMarks.insert(featureId);
|
||||
ProcessMarks([&featureId](SearchMarkPoint * mark) -> base::ControlFlow
|
||||
{
|
||||
if (featureId != mark->GetFeatureID())
|
||||
return base::ControlFlow::Continue;
|
||||
mark->SetVisited(true);
|
||||
mark->SetSelected(false);
|
||||
return base::ControlFlow::Break;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
void SearchMarks::SetUnavailable(SearchMarkPoint & mark, std::string const & reasonKey)
|
||||
{
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(m_lock);
|
||||
m_unavailable.insert_or_assign(mark.GetFeatureID(), reasonKey);
|
||||
}
|
||||
mark.SetAvailable(false);
|
||||
mark.SetReason(platform::GetLocalizedString(reasonKey));
|
||||
}
|
||||
|
||||
void SearchMarks::SetUnavailable(std::vector<FeatureID> const & features,
|
||||
std::string const & reasonKey)
|
||||
{
|
||||
if (features.empty())
|
||||
return;
|
||||
|
||||
ProcessMarks([this, &features, &reasonKey](SearchMarkPoint * mark) -> base::ControlFlow
|
||||
{
|
||||
ASSERT(std::is_sorted(features.cbegin(), features.cend()), ());
|
||||
if (std::binary_search(features.cbegin(), features.cend(), mark->GetFeatureID()))
|
||||
SetUnavailable(*mark, reasonKey);
|
||||
return base::ControlFlow::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
bool SearchMarks::IsUnavailable(FeatureID const & id) const
|
||||
{
|
||||
std::scoped_lock<std::mutex> lock(m_lock);
|
||||
return m_unavailable.find(id) != m_unavailable.cend();
|
||||
}
|
||||
*/
|
||||
|
||||
void SearchMarks::SetVisited(FeatureID const & id)
|
||||
{
|
||||
m_visitedSearchMarks.insert(id);
|
||||
}
|
||||
|
||||
bool SearchMarks::IsVisited(FeatureID const & id) const
|
||||
{
|
||||
return m_visitedSearchMarks.find(id) != m_visitedSearchMarks.cend();
|
||||
}
|
||||
|
||||
void SearchMarks::SetSelected(FeatureID const & id)
|
||||
{
|
||||
m_selectedFeature = id;
|
||||
}
|
||||
|
||||
bool SearchMarks::IsSelected(FeatureID const & id) const
|
||||
{
|
||||
return id == m_selectedFeature;
|
||||
}
|
||||
|
||||
void SearchMarks::ClearTrackedProperties()
|
||||
{
|
||||
// {
|
||||
// std::scoped_lock<std::mutex> lock(m_lock);
|
||||
// m_unavailable.clear();
|
||||
// }
|
||||
m_selectedFeature = {};
|
||||
}
|
||||
|
||||
void SearchMarks::ProcessMarks(std::function<base::ControlFlow(SearchMarkPoint *)> && processor) const
|
||||
{
|
||||
if (m_bmManager == nullptr || processor == nullptr)
|
||||
return;
|
||||
|
||||
auto editSession = m_bmManager->GetEditSession();
|
||||
for (auto markId : m_bmManager->GetUserMarkIds(UserMark::Type::SEARCH))
|
||||
{
|
||||
auto * mark = editSession.GetMarkForEdit<SearchMarkPoint>(markId);
|
||||
if (processor(mark) == base::ControlFlow::Break)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SearchMarks::UpdateMaxDimension()
|
||||
{
|
||||
// Use only generic search mark as 'filter' dimension. Users claim about missing results.
|
||||
// https://github.com/organicmaps/organicmaps/issues/2070
|
||||
auto const it = s_markSizes.find(kSymbols[SearchMarkType::Default]);
|
||||
if (it != s_markSizes.end())
|
||||
m_maxDimension = it->second;
|
||||
}
|
||||
144
libs/map/search_mark.hpp
Normal file
144
libs/map/search_mark.hpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/user_mark.hpp"
|
||||
|
||||
#include "drape_frontend/drape_engine_safe_ptr.hpp"
|
||||
|
||||
#include "indexer/feature_decl.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
|
||||
#include "base/control_flow.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class BookmarkManager;
|
||||
|
||||
class SearchMarkPoint : public UserMark
|
||||
{
|
||||
public:
|
||||
enum SearchMarkType : uint8_t;
|
||||
|
||||
explicit SearchMarkPoint(m2::PointD const & ptOrg);
|
||||
|
||||
m2::PointD GetPixelOffset() const override;
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
df::ColorConstant GetColorConstant() const override;
|
||||
drape_ptr<TitlesInfo> GetTitleDecl() const override;
|
||||
int GetMinTitleZoom() const override;
|
||||
df::DepthLayer GetDepthLayer() const override;
|
||||
drape_ptr<SymbolOffsets> GetSymbolOffsets() const override;
|
||||
bool GetDepthTestEnabled() const override { return false; }
|
||||
bool IsMarkAboveText() const override;
|
||||
float GetSymbolOpacity() const override;
|
||||
bool IsSymbolSelectable() const override { return true; }
|
||||
bool IsNonDisplaceable() const override { return true; }
|
||||
|
||||
FeatureID GetFeatureID() const override { return m_featureID; }
|
||||
void SetFoundFeature(FeatureID const & feature);
|
||||
|
||||
std::string const & GetMatchedName() const { return m_matchedName; }
|
||||
void SetMatchedName(std::string const & name);
|
||||
|
||||
void SetFromType(uint32_t type);
|
||||
void SetNotFoundType();
|
||||
|
||||
void SetPreparing(bool isPreparing);
|
||||
void SetSale(bool hasSale);
|
||||
void SetSelected(bool isSelected);
|
||||
void SetVisited(bool isVisited);
|
||||
void SetAvailable(bool isAvailable);
|
||||
void SetReason(std::string const & reason);
|
||||
|
||||
bool IsSelected() const;
|
||||
bool IsAvailable() const;
|
||||
std::string const & GetReason() const;
|
||||
|
||||
protected:
|
||||
template <typename T, typename U>
|
||||
void SetAttributeValue(T & dst, U && src)
|
||||
{
|
||||
if (dst == src)
|
||||
return;
|
||||
|
||||
SetDirty();
|
||||
dst = std::forward<U>(src);
|
||||
}
|
||||
|
||||
bool HasReason() const;
|
||||
|
||||
std::string const * GetSymbolName() const;
|
||||
|
||||
// Used to pass exact search result matched string into a place page.
|
||||
std::string m_matchedName;
|
||||
std::string m_reason;
|
||||
|
||||
FeatureID m_featureID;
|
||||
SearchMarkType m_type;
|
||||
|
||||
bool m_isPreparing : 1;
|
||||
bool m_hasSale : 1;
|
||||
bool m_isSelected : 1;
|
||||
bool m_isVisited : 1;
|
||||
bool m_isAvailable : 1;
|
||||
};
|
||||
|
||||
class SearchMarks
|
||||
{
|
||||
public:
|
||||
SearchMarks();
|
||||
|
||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
||||
void SetBookmarkManager(BookmarkManager * bmManager);
|
||||
|
||||
m2::PointD GetMaxDimension(ScreenBase const & modelView) const;
|
||||
|
||||
// NOTE: Vector of features must be sorted.
|
||||
void SetPreparingState(std::vector<FeatureID> const & features, bool isPreparing);
|
||||
|
||||
// NOTE: Vector of features must be sorted.
|
||||
void SetSales(std::vector<FeatureID> const & features, bool hasSale);
|
||||
|
||||
bool IsThereSearchMarkForFeature(FeatureID const & featureId) const;
|
||||
void OnActivate(FeatureID const & featureId);
|
||||
void OnDeactivate(FeatureID const & featureId);
|
||||
|
||||
// void SetUnavailable(SearchMarkPoint & mark, std::string const & reasonKey);
|
||||
// void SetUnavailable(std::vector<FeatureID> const & features, std::string const & reasonKey);
|
||||
// bool IsUnavailable(FeatureID const & id) const;
|
||||
|
||||
void SetVisited(FeatureID const & id);
|
||||
bool IsVisited(FeatureID const & id) const;
|
||||
|
||||
void SetSelected(FeatureID const & id);
|
||||
bool IsSelected(FeatureID const & id) const;
|
||||
|
||||
void ClearTrackedProperties();
|
||||
|
||||
static bool HaveSizes() { return !s_markSizes.empty(); }
|
||||
static std::optional<m2::PointD> GetSize(std::string const & symbolName);
|
||||
|
||||
private:
|
||||
void ProcessMarks(std::function<base::ControlFlow(SearchMarkPoint *)> && processor) const;
|
||||
void UpdateMaxDimension();
|
||||
|
||||
BookmarkManager * m_bmManager;
|
||||
df::DrapeEngineSafePtr m_drapeEngine;
|
||||
|
||||
static std::map<std::string, m2::PointF> s_markSizes;
|
||||
|
||||
m2::PointD m_maxDimension{0, 0};
|
||||
|
||||
std::set<FeatureID> m_visitedSearchMarks;
|
||||
FeatureID m_selectedFeature;
|
||||
|
||||
// mutable std::mutex m_lock;
|
||||
// std::map<FeatureID, std::string /* SearchMarkPoint::m_reason */> m_unavailable;
|
||||
};
|
||||
8
libs/map/search_product_info.hpp
Normal file
8
libs/map/search_product_info.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
namespace search
|
||||
{
|
||||
/// Here was an additional info like UGC, advertising stuff, etc.
|
||||
struct ProductInfo
|
||||
{};
|
||||
} // namespace search
|
||||
13
libs/map/style_tests/CMakeLists.txt
Normal file
13
libs/map/style_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
project(style_tests)
|
||||
|
||||
set(SRC
|
||||
classificator_tests.cpp
|
||||
dashes_test.cpp
|
||||
helpers.hpp
|
||||
style_symbols_consistency_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} indexer)
|
||||
|
||||
402
libs/map/style_tests/classificator_tests.cpp
Normal file
402
libs/map/style_tests/classificator_tests.cpp
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
#include "helpers.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_visibility.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
namespace classificator_tests
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
class DoCheckConsistency
|
||||
{
|
||||
Classificator const & m_c;
|
||||
|
||||
public:
|
||||
explicit DoCheckConsistency(Classificator const & c) : m_c(c) {}
|
||||
void operator()(ClassifObject const * p, uint32_t type) const
|
||||
{
|
||||
if (p->IsDrawableAny() && !m_c.IsTypeValid(type))
|
||||
TEST(false, ("Inconsistency type", type, m_c.GetFullObjectName(type)));
|
||||
}
|
||||
};
|
||||
|
||||
UNIT_TEST(Classificator_CheckConsistency)
|
||||
{
|
||||
styles::RunForEveryMapStyle([](MapStyle)
|
||||
{
|
||||
Classificator const & c = classif();
|
||||
|
||||
DoCheckConsistency doCheck(c);
|
||||
c.ForEachTree(doCheck);
|
||||
});
|
||||
}
|
||||
|
||||
using namespace feature;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class DoCheckStyles
|
||||
{
|
||||
Classificator const & m_c;
|
||||
GeomType m_geomType;
|
||||
int m_rules;
|
||||
|
||||
public:
|
||||
DoCheckStyles(Classificator const & c, GeomType geomType, int rules) : m_c(c), m_geomType(geomType), m_rules(rules) {}
|
||||
|
||||
void operator()(ClassifObject const * p, uint32_t type) const
|
||||
{
|
||||
// We can't put TEST here, should check output manually. Or rewrite test in more sophisticated way.
|
||||
// - place=county/region are non-drawable
|
||||
// - historic=citywalls is not a Point
|
||||
// - waterway=* [tunnel] are non-drawable
|
||||
// - some highway,waterway are Point
|
||||
|
||||
if (p->IsDrawableAny())
|
||||
{
|
||||
TypesHolder holder(m_geomType);
|
||||
holder.Add(type);
|
||||
|
||||
pair<int, int> const range = GetDrawableScaleRangeForRules(holder, m_rules);
|
||||
if (range.first == -1 || range.second == -1)
|
||||
LOG(LWARNING, ("No styles:", m_c.GetFullObjectName(type)));
|
||||
}
|
||||
else if (ftype::GetLevel(type) > 1)
|
||||
LOG(LWARNING, ("Type without any rules:", m_c.GetFullObjectName(type)));
|
||||
}
|
||||
};
|
||||
|
||||
void ForEachObject(Classificator const & c, vector<string_view> const & path, GeomType geomType, int rules)
|
||||
{
|
||||
uint32_t const type = c.GetTypeByPath(path);
|
||||
ClassifObject const * pObj = c.GetObject(type);
|
||||
|
||||
DoCheckStyles doCheck(c, geomType, rules);
|
||||
doCheck(pObj, type);
|
||||
pObj->ForEachObjectInTree(doCheck, type);
|
||||
}
|
||||
|
||||
void ForEachObject(Classificator const & c, string const & name, GeomType geomType, int rules)
|
||||
{
|
||||
ForEachObject(c, strings::Tokenize(name, "-"), geomType, rules);
|
||||
}
|
||||
|
||||
void CheckPointStyles(Classificator const & c, string const & name)
|
||||
{
|
||||
ForEachObject(c, name, GeomType::Point, RULE_CAPTION | RULE_SYMBOL);
|
||||
}
|
||||
|
||||
void CheckLineStyles(Classificator const & c, string const & name)
|
||||
{
|
||||
ForEachObject(c, name, GeomType::Line, RULE_PATH_TEXT);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Classificator_DrawingRules)
|
||||
{
|
||||
styles::RunForEveryMapStyle([](MapStyle style)
|
||||
{
|
||||
if (style != MapStyle::MapStyleDefaultLight && style != MapStyle::MapStyleDefaultDark)
|
||||
return;
|
||||
|
||||
Classificator const & c = classif();
|
||||
|
||||
LOG(LINFO, ("--------------- Point styles ---------------"));
|
||||
CheckPointStyles(c, "amenity");
|
||||
CheckPointStyles(c, "historic");
|
||||
CheckPointStyles(c, "office");
|
||||
CheckPointStyles(c, "place");
|
||||
CheckPointStyles(c, "shop");
|
||||
CheckPointStyles(c, "sport");
|
||||
CheckPointStyles(c, "tourism");
|
||||
CheckPointStyles(c, "highway-bus_stop");
|
||||
CheckPointStyles(c, "highway-motorway_junction");
|
||||
CheckPointStyles(c, "railway-station");
|
||||
CheckPointStyles(c, "railway-tram_stop");
|
||||
CheckPointStyles(c, "railway-halt");
|
||||
|
||||
LOG(LINFO, ("--------------- Linear styles ---------------"));
|
||||
CheckLineStyles(c, "highway");
|
||||
CheckLineStyles(c, "waterway");
|
||||
// CheckLineStyles(c, "railway");
|
||||
});
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
pair<int, int> GetMinMax(int level, vector<uint32_t> const & types, drule::TypeT ruleType)
|
||||
{
|
||||
pair<int, int> res(numeric_limits<int>::max(), numeric_limits<int>::min());
|
||||
|
||||
drule::KeysT keys;
|
||||
feature::GetDrawRule(types, level, feature::GeomType::Area, keys);
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
if (keys[i].m_type != ruleType)
|
||||
continue;
|
||||
|
||||
if (keys[i].m_priority < res.first)
|
||||
res.first = keys[i].m_priority;
|
||||
if (keys[i].m_priority > res.second)
|
||||
res.second = keys[i].m_priority;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
string CombineArrT(base::StringIL const & arrT)
|
||||
{
|
||||
string result;
|
||||
for (auto it = arrT.begin(); it != arrT.end(); ++it)
|
||||
{
|
||||
if (it != arrT.begin())
|
||||
result.append("-");
|
||||
result.append(*it);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheckPriority(vector<base::StringIL> const & arrT, vector<size_t> const & arrI, drule::TypeT ruleType)
|
||||
{
|
||||
Classificator const & c = classif();
|
||||
vector<vector<uint32_t>> types;
|
||||
vector<vector<string>> typesInfo;
|
||||
|
||||
styles::RunForEveryMapStyle([&](MapStyle)
|
||||
{
|
||||
types.clear();
|
||||
typesInfo.clear();
|
||||
|
||||
size_t ind = 0;
|
||||
for (size_t i = 0; i < arrI.size(); ++i)
|
||||
{
|
||||
types.push_back(vector<uint32_t>());
|
||||
types.back().reserve(arrI[i]);
|
||||
typesInfo.push_back(vector<string>());
|
||||
typesInfo.back().reserve(arrI[i]);
|
||||
|
||||
for (size_t j = 0; j < arrI[i]; ++j)
|
||||
{
|
||||
types.back().push_back(c.GetTypeByPath(arrT[ind]));
|
||||
typesInfo.back().push_back(CombineArrT(arrT[ind]));
|
||||
++ind;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_EQUAL(ind, arrT.size(), ());
|
||||
|
||||
for (int level = scales::GetUpperWorldScale() + 1; level <= scales::GetUpperStyleScale(); ++level)
|
||||
{
|
||||
pair<int, int> minmax(numeric_limits<int>::max(), numeric_limits<int>::min());
|
||||
vector<string> minmaxInfo;
|
||||
for (size_t i = 0; i < types.size(); ++i)
|
||||
{
|
||||
pair<int, int> const mm = GetMinMax(level, types[i], ruleType);
|
||||
TEST_LESS(minmax.second, mm.first,
|
||||
("Priority bug on zoom", level, "group", i, ":", minmaxInfo, minmax.first, minmax.second, "vs",
|
||||
typesInfo[i], mm.first, mm.second));
|
||||
minmax = mm;
|
||||
minmaxInfo = typesInfo[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Check area drawing priority according to the types order below (from downmost to upmost).
|
||||
// If someone disagrees with this order, please refer to VNG :)
|
||||
// natural-coastline
|
||||
// place-island = natural-land
|
||||
// natural-scrub,heath,grassland = landuse-grass,farmland,forest
|
||||
// natural-water,lake = landuse-basin
|
||||
|
||||
UNIT_TEST(Classificator_AreaPriority)
|
||||
{
|
||||
CheckPriority(
|
||||
{// 0
|
||||
{"natural", "coastline"},
|
||||
// 1
|
||||
{"place", "island"},
|
||||
{"natural", "land"},
|
||||
// 2
|
||||
{"natural", "scrub"},
|
||||
{"natural", "heath"},
|
||||
{"natural", "grassland"},
|
||||
{"landuse", "grass"},
|
||||
{"landuse", "farmland"},
|
||||
{"landuse", "forest"},
|
||||
// ?
|
||||
//{"leisure", "park"}, {"leisure", "garden"}, - maybe next time (too tricky to do it now)
|
||||
// 3
|
||||
{"natural", "water"},
|
||||
{"natural", "water", "lake"},
|
||||
{"landuse", "basin"}},
|
||||
{1, 2, 6, 3}, drule::area);
|
||||
|
||||
CheckPriority(
|
||||
{
|
||||
// ? - linear waterways @todo: add ability to compare different drule types (areas vs lines)
|
||||
//{"waterway", "river"}, {"waterway", "stream"}, {"natural", "strait"}, {"waterway", "ditch"},
|
||||
// 0 - water areas
|
||||
{"natural", "water"},
|
||||
{"landuse", "reservoir"},
|
||||
{"natural", "water", "river"},
|
||||
{"waterway", "dock"},
|
||||
// ? - hatching fills @todo: absent in vehicle style, need to test main style only
|
||||
//{"leisure", "nature_reserve"}, {"boundary", "national_park"}, {"landuse", "military"},
|
||||
// 1 - above-water features
|
||||
{"man_made", "pier"},
|
||||
{"man_made", "breakwater"},
|
||||
{"waterway", "dam"},
|
||||
},
|
||||
{4, 3}, drule::area);
|
||||
|
||||
CheckPriority(
|
||||
{
|
||||
// 0
|
||||
{"leisure", "park"},
|
||||
// 1
|
||||
{"leisure", "pitch"},
|
||||
{"leisure", "playground"},
|
||||
{"sport", "multi"},
|
||||
},
|
||||
{1, 3}, drule::area);
|
||||
}
|
||||
|
||||
UNIT_TEST(Classificator_PoiPriority)
|
||||
{
|
||||
{
|
||||
CheckPriority(
|
||||
{
|
||||
// 1
|
||||
{"amenity", "drinking_water"},
|
||||
// 2
|
||||
{"tourism", "camp_site"},
|
||||
// 3
|
||||
{"tourism", "wilderness_hut"},
|
||||
// 4
|
||||
{"tourism", "alpine_hut"},
|
||||
},
|
||||
{1, 1, 1, 1}, drule::symbol);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Classificator_MultipleTypesPoiPriority)
|
||||
{
|
||||
{
|
||||
CheckPriority(
|
||||
{// 1
|
||||
{"amenity", "atm"},
|
||||
// 2
|
||||
{"amenity", "bank"}},
|
||||
{1, 1}, drule::symbol);
|
||||
}
|
||||
|
||||
{
|
||||
CheckPriority(
|
||||
{
|
||||
// 1
|
||||
{"amenity", "bench"},
|
||||
{"amenity", "shelter"},
|
||||
// 2
|
||||
{"highway", "bus_stop"},
|
||||
{"amenity", "bus_station"},
|
||||
{"railway", "station"},
|
||||
{"railway", "halt"},
|
||||
{"railway", "tram_stop"},
|
||||
},
|
||||
{2, 5}, drule::symbol);
|
||||
}
|
||||
|
||||
/// @todo Check that all of sport=* icons priority is bigger than all of pitch, sports_center, recreation_ground.
|
||||
|
||||
{
|
||||
CheckPriority(
|
||||
{// 1
|
||||
{"leisure", "pitch"},
|
||||
// 2
|
||||
{"sport", "yoga"}},
|
||||
{1, 1}, drule::symbol);
|
||||
}
|
||||
|
||||
{
|
||||
CheckPriority(
|
||||
{// 1
|
||||
{"leisure", "sports_centre"},
|
||||
// 2
|
||||
{"sport", "shooting"}},
|
||||
{1, 1}, drule::symbol);
|
||||
}
|
||||
|
||||
{
|
||||
CheckPriority(
|
||||
{// 1
|
||||
{"landuse", "recreation_ground"},
|
||||
// 2
|
||||
{"sport", "multi"}},
|
||||
{1, 1}, drule::symbol);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct RangeEntry
|
||||
{
|
||||
uint32_t m_type;
|
||||
std::pair<int, int> m_range;
|
||||
|
||||
// From C++ 20.
|
||||
// auto operator<=>(RangeEntry const &) const = default;
|
||||
bool operator!=(RangeEntry const & rhs) const { return m_type != rhs.m_type || m_range != rhs.m_range; }
|
||||
|
||||
friend std::string DebugPrint(RangeEntry const & e)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << classif().GetReadableObjectName(e.m_type) << "; (" << e.m_range.first << "," << e.m_range.second << ")";
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Classificator_HighwayZoom_AcrossStyles)
|
||||
{
|
||||
std::array<std::vector<RangeEntry>, MapStyleCount> scales;
|
||||
|
||||
styles::RunForEveryMapStyle([&scales](MapStyle style)
|
||||
{
|
||||
auto const & cl = classif();
|
||||
uint32_t const type = cl.GetTypeByPath({"highway"});
|
||||
ClassifObject const * pObj = cl.GetObject(type);
|
||||
|
||||
pObj->ForEachObjectInTree([&scales, style](ClassifObject const *, uint32_t type)
|
||||
{
|
||||
TypesHolder holder(GeomType::Line);
|
||||
holder.Add(type);
|
||||
scales[style].push_back({type, GetDrawableScaleRangeForRules(holder, RULE_LINE)});
|
||||
}, type);
|
||||
});
|
||||
|
||||
for (size_t iStyle = 1; iStyle < MapStyleCount; ++iStyle)
|
||||
{
|
||||
if (iStyle == MapStyleMerged)
|
||||
continue;
|
||||
|
||||
// Don't put TEST, only diagnostic logs. In general, Clear and Vehical visibility styles are different
|
||||
// for highways like: footway, path, steps, cycleway, bridleway, track, service.
|
||||
TEST_EQUAL(scales[0].size(), scales[iStyle].size(), (iStyle));
|
||||
for (size_t j = 0; j < scales[0].size(); ++j)
|
||||
if (scales[0][j] != scales[iStyle][j])
|
||||
LOG(LWARNING, (scales[0][j], scales[iStyle][j]));
|
||||
}
|
||||
}
|
||||
} // namespace classificator_tests
|
||||
38
libs/map/style_tests/dashes_test.cpp
Normal file
38
libs/map/style_tests/dashes_test.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include "helpers.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "drape/stipple_pen_resource.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/drawing_rules.hpp"
|
||||
#include "indexer/drules_include.hpp"
|
||||
|
||||
UNIT_TEST(Test_Dashes)
|
||||
{
|
||||
styles::RunForEveryMapStyle([](MapStyle)
|
||||
{
|
||||
drule::rules().ForEachRule([](drule::BaseRule const * rule)
|
||||
{
|
||||
LineRuleProto const * const line = rule->GetLine();
|
||||
if (nullptr == line || !line->has_dashdot())
|
||||
return;
|
||||
|
||||
DashDotProto const & dd = line->dashdot();
|
||||
|
||||
int const n = dd.dd_size();
|
||||
if (n > 0)
|
||||
{
|
||||
TEST_GREATER_OR_EQUAL(n, 2, ());
|
||||
TEST_LESS_OR_EQUAL(n, 4, ());
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
double const value = dd.dd(i);
|
||||
TEST_GREATER_OR_EQUAL(value, 0.0, ());
|
||||
}
|
||||
|
||||
double const patternLength = (dd.dd(0) + dd.dd(1)) * df::kMaxVisualScale;
|
||||
TEST_LESS_OR_EQUAL(patternLength, dp::kMaxStipplePenLength, (dd.dd(0), dd.dd(1)));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
31
libs/map/style_tests/helpers.hpp
Normal file
31
libs/map/style_tests/helpers.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/map_style.hpp"
|
||||
#include "indexer/map_style_reader.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
namespace styles
|
||||
{
|
||||
template <class TFn>
|
||||
void RunForEveryMapStyle(TFn && fn)
|
||||
{
|
||||
auto & reader = GetStyleReader();
|
||||
for (size_t s = 0; s < MapStyleCount; ++s)
|
||||
{
|
||||
MapStyle const mapStyle = static_cast<MapStyle>(s);
|
||||
if (mapStyle != MapStyle::MapStyleMerged)
|
||||
{
|
||||
reader.SetCurrentStyle(mapStyle);
|
||||
classificator::Load();
|
||||
LOG(LINFO, ("Test with map style", mapStyle));
|
||||
fn(mapStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore default style.
|
||||
reader.SetCurrentStyle(kDefaultMapStyle);
|
||||
classificator::Load();
|
||||
}
|
||||
} // namespace styles
|
||||
97
libs/map/style_tests/style_symbols_consistency_test.cpp
Normal file
97
libs/map/style_tests/style_symbols_consistency_test.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "map/style_tests/helpers.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/drawing_rules.hpp"
|
||||
#include "indexer/drules_include.hpp"
|
||||
#include "indexer/map_style_reader.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "coding/parse_xml.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
|
||||
#include <cstring> // std::strcmp
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace style_symbols_consistency_tests
|
||||
{
|
||||
typedef std::set<std::string> StringSet;
|
||||
|
||||
class SdfParsingDispatcher
|
||||
{
|
||||
public:
|
||||
explicit SdfParsingDispatcher(StringSet & symbols) : m_symbols(symbols) {}
|
||||
|
||||
bool Push(char const *) { return true; }
|
||||
void Pop(char const *) {}
|
||||
void CharData(std::string const &) {}
|
||||
void AddAttr(char const * attribute, char const * value)
|
||||
{
|
||||
if (0 == std::strcmp(attribute, "name"))
|
||||
m_symbols.insert(value);
|
||||
}
|
||||
|
||||
private:
|
||||
StringSet & m_symbols;
|
||||
};
|
||||
|
||||
StringSet GetSymbolsSetFromDrawingRule()
|
||||
{
|
||||
StringSet symbols;
|
||||
drule::rules().ForEachRule([&symbols](drule::BaseRule const * rule)
|
||||
{
|
||||
SymbolRuleProto const * symbol = rule->GetSymbol();
|
||||
if (symbol && !symbol->name().empty())
|
||||
symbols.insert(symbol->name());
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
StringSet GetSymbolsSetFromResourcesFile(std::string_view density)
|
||||
{
|
||||
StringSet symbols;
|
||||
SdfParsingDispatcher dispatcher(symbols);
|
||||
ReaderPtr<Reader> reader = GetStyleReader().GetResourceReader("symbols.sdf", density);
|
||||
ReaderSource<ReaderPtr<Reader>> source(reader);
|
||||
ParseXML(source, dispatcher);
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// Tests that all symbols specified in drawing rules have corresponding symbols in resources
|
||||
UNIT_TEST(Test_SymbolsConsistency)
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
std::string_view constexpr densities[] = {"mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi", "6plus"};
|
||||
|
||||
styles::RunForEveryMapStyle([&](MapStyle mapStyle)
|
||||
{
|
||||
StringSet const drawingRuleSymbols = GetSymbolsSetFromDrawingRule();
|
||||
|
||||
for (std::string_view density : densities)
|
||||
{
|
||||
StringSet const resourceStyles = GetSymbolsSetFromResourcesFile(density);
|
||||
|
||||
std::vector<std::string> missed;
|
||||
std::set_difference(drawingRuleSymbols.begin(), drawingRuleSymbols.end(), resourceStyles.begin(),
|
||||
resourceStyles.end(), back_inserter(missed));
|
||||
|
||||
if (!missed.empty())
|
||||
{
|
||||
// We are interested in all set of bugs, therefore we do not stop test here but
|
||||
// continue it just keeping in res that test failed.
|
||||
LOG(LINFO, ("Symbols mismatch: style", mapStyle, ", density", density, ", missed", missed));
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TEST(res, ());
|
||||
}
|
||||
} // namespace style_symbols_consistency_tests
|
||||
230
libs/map/track.cpp
Normal file
230
libs/map/track.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#include "map/track.hpp"
|
||||
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
#include "map/user_mark_id_storage.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/rect_intersect.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
Track::Track(kml::TrackData && data)
|
||||
: Base(data.m_id == kml::kInvalidTrackId ? UserMarkIdStorage::Instance().GetNextTrackId() : data.m_id)
|
||||
, m_data(std::move(data))
|
||||
{
|
||||
m_data.m_id = GetId();
|
||||
CHECK(m_data.m_geometry.IsValid(), ());
|
||||
}
|
||||
|
||||
void Track::CacheDataForInteraction() const
|
||||
{
|
||||
m_interactionData = InteractionData();
|
||||
m_interactionData->m_lengths = GetLengthsImpl();
|
||||
m_interactionData->m_limitRect = GetLimitRectImpl();
|
||||
}
|
||||
|
||||
std::vector<Track::Lengths> Track::GetLengthsImpl() const
|
||||
{
|
||||
double distance = 0;
|
||||
std::vector<Lengths> lengths;
|
||||
for (auto const & line : m_data.m_geometry.m_lines)
|
||||
{
|
||||
Lengths lineLengths;
|
||||
lineLengths.emplace_back(distance);
|
||||
for (size_t j = 1; j < line.size(); ++j)
|
||||
{
|
||||
auto const & pt1 = line[j - 1].GetPoint();
|
||||
auto const & pt2 = line[j].GetPoint();
|
||||
distance += mercator::DistanceOnEarth(pt1, pt2);
|
||||
lineLengths.emplace_back(distance);
|
||||
}
|
||||
lengths.emplace_back(std::move(lineLengths));
|
||||
}
|
||||
return lengths;
|
||||
}
|
||||
|
||||
m2::RectD Track::GetLimitRectImpl() const
|
||||
{
|
||||
m2::RectD limitRect;
|
||||
for (auto const & line : m_data.m_geometry.m_lines)
|
||||
for (auto const & pt : line)
|
||||
limitRect.Add(pt.GetPoint());
|
||||
return limitRect;
|
||||
}
|
||||
|
||||
bool Track::HasAltitudes() const
|
||||
{
|
||||
bool hasNonDefaultAltitude = false;
|
||||
|
||||
for (auto const & line : m_data.m_geometry.m_lines)
|
||||
{
|
||||
for (auto const & pt : line)
|
||||
{
|
||||
if (pt.GetAltitude() == geometry::kInvalidAltitude)
|
||||
return false;
|
||||
if (!hasNonDefaultAltitude && pt.GetAltitude() != geometry::kDefaultAltitudeMeters)
|
||||
hasNonDefaultAltitude = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasNonDefaultAltitude;
|
||||
}
|
||||
|
||||
std::string Track::GetName() const
|
||||
{
|
||||
return GetPreferredBookmarkStr(m_data.m_name);
|
||||
}
|
||||
|
||||
void Track::SetName(std::string const & name)
|
||||
{
|
||||
kml::SetDefaultStr(m_data.m_name, name);
|
||||
}
|
||||
|
||||
std::string Track::GetDescription() const
|
||||
{
|
||||
return GetPreferredBookmarkStr(m_data.m_description);
|
||||
}
|
||||
|
||||
void Track::setData(kml::TrackData const & data)
|
||||
{
|
||||
m_isDirty = true;
|
||||
m_data = data;
|
||||
}
|
||||
|
||||
m2::RectD Track::GetLimitRect() const
|
||||
{
|
||||
if (m_interactionData)
|
||||
return m_interactionData->m_limitRect;
|
||||
return GetLimitRectImpl();
|
||||
}
|
||||
|
||||
double Track::GetLengthMeters() const
|
||||
{
|
||||
return GetStatistics().m_length;
|
||||
}
|
||||
|
||||
double Track::GetLengthMetersImpl(size_t lineIndex, size_t ptIndex) const
|
||||
{
|
||||
if (!m_interactionData)
|
||||
CacheDataForInteraction();
|
||||
auto const & lineLengths = m_interactionData->m_lengths[lineIndex];
|
||||
return lineLengths[ptIndex];
|
||||
}
|
||||
|
||||
void Track::UpdateSelectionInfo(m2::RectD const & touchRect, TrackSelectionInfo & info) const
|
||||
{
|
||||
if (m_interactionData && !m_interactionData->m_limitRect.IsIntersect(touchRect))
|
||||
return;
|
||||
|
||||
for (size_t lineIndex = 0; lineIndex < m_data.m_geometry.m_lines.size(); ++lineIndex)
|
||||
{
|
||||
auto const & line = m_data.m_geometry.m_lines[lineIndex];
|
||||
for (size_t ptIndex = 0; ptIndex + 1 < line.size(); ++ptIndex)
|
||||
{
|
||||
auto pt1 = line[ptIndex].GetPoint();
|
||||
auto pt2 = line[ptIndex + 1].GetPoint();
|
||||
if (!m2::Intersect(touchRect, pt1, pt2))
|
||||
continue;
|
||||
|
||||
m2::ParametrizedSegment<m2::PointD> seg(pt1, pt2);
|
||||
auto const closestPoint = seg.ClosestPointTo(touchRect.Center());
|
||||
auto const squaredDist = closestPoint.SquaredLength(touchRect.Center());
|
||||
if (squaredDist >= info.m_squareDist)
|
||||
continue;
|
||||
|
||||
info.m_squareDist = squaredDist;
|
||||
info.m_trackId = m_data.m_id;
|
||||
info.m_trackPoint = closestPoint;
|
||||
|
||||
auto const segDistInMeters = mercator::DistanceOnEarth(line[ptIndex].GetPoint(), closestPoint);
|
||||
info.m_distFromBegM = segDistInMeters + GetLengthMetersImpl(lineIndex, ptIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
df::DepthLayer Track::GetDepthLayer() const
|
||||
{
|
||||
return df::DepthLayer::UserLineLayer;
|
||||
}
|
||||
|
||||
size_t Track::GetLayerCount() const
|
||||
{
|
||||
return m_data.m_layers.size();
|
||||
}
|
||||
|
||||
dp::Color Track::GetColor(size_t layerIndex) const
|
||||
{
|
||||
CHECK_LESS(layerIndex, m_data.m_layers.size(), ());
|
||||
return dp::Color(m_data.m_layers[layerIndex].m_color.m_rgba);
|
||||
}
|
||||
|
||||
void Track::SetColor(dp::Color color)
|
||||
{
|
||||
m_isDirty = true;
|
||||
m_data.m_layers[0].m_color.m_rgba = color.GetRGBA();
|
||||
}
|
||||
|
||||
float Track::GetWidth(size_t layerIndex) const
|
||||
{
|
||||
CHECK_LESS(layerIndex, m_data.m_layers.size(), ());
|
||||
return static_cast<float>(m_data.m_layers[layerIndex].m_lineWidth);
|
||||
}
|
||||
|
||||
float Track::GetDepth(size_t layerIndex) const
|
||||
{
|
||||
return layerIndex * 10;
|
||||
}
|
||||
|
||||
void Track::ForEachGeometry(GeometryFnT && fn) const
|
||||
{
|
||||
for (auto const & line : m_data.m_geometry.m_lines)
|
||||
{
|
||||
std::vector<m2::PointD> points;
|
||||
points.reserve(line.size());
|
||||
for (auto const & pt : line)
|
||||
points.push_back(pt.GetPoint());
|
||||
|
||||
fn(std::move(points));
|
||||
}
|
||||
}
|
||||
|
||||
void Track::Attach(kml::MarkGroupId groupId)
|
||||
{
|
||||
ASSERT_EQUAL(m_groupID, kml::kInvalidMarkGroupId, ());
|
||||
m_groupID = groupId;
|
||||
}
|
||||
|
||||
void Track::Detach()
|
||||
{
|
||||
m_groupID = kml::kInvalidMarkGroupId;
|
||||
}
|
||||
|
||||
kml::MultiGeometry::LineT Track::GetGeometry() const
|
||||
{
|
||||
kml::MultiGeometry::LineT geometry;
|
||||
for (auto const & line : m_data.m_geometry.m_lines)
|
||||
for (size_t i = 0; i < line.size(); ++i)
|
||||
geometry.emplace_back(line[i]);
|
||||
return geometry;
|
||||
}
|
||||
|
||||
TrackStatistics Track::GetStatistics() const
|
||||
{
|
||||
if (!m_trackStatistics.has_value())
|
||||
m_trackStatistics = TrackStatistics(m_data.m_geometry);
|
||||
return m_trackStatistics.value();
|
||||
}
|
||||
|
||||
std::optional<ElevationInfo> Track::GetElevationInfo() const
|
||||
{
|
||||
if (!HasAltitudes())
|
||||
return std::nullopt;
|
||||
if (!m_elevationInfo)
|
||||
m_elevationInfo = ElevationInfo(GetData().m_geometry.m_lines);
|
||||
return m_elevationInfo;
|
||||
}
|
||||
|
||||
double Track::GetDurationInSeconds() const
|
||||
{
|
||||
return GetStatistics().m_duration;
|
||||
}
|
||||
96
libs/map/track.hpp
Normal file
96
libs/map/track.hpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
|
||||
#include "kml/types.hpp"
|
||||
|
||||
#include "map/elevation_info.hpp"
|
||||
#include "map/track_statistics.hpp"
|
||||
|
||||
#include "drape_frontend/user_marks_provider.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
class Track : public df::UserLineMark
|
||||
{
|
||||
using Base = df::UserLineMark;
|
||||
using Lengths = std::vector<double>;
|
||||
|
||||
public:
|
||||
Track(kml::TrackData && data);
|
||||
|
||||
kml::MarkGroupId GetGroupId() const override { return m_groupID; }
|
||||
|
||||
bool IsDirty() const override { return m_isDirty; }
|
||||
void ResetChanges() const override { m_isDirty = false; }
|
||||
|
||||
kml::TrackData const & GetData() const { return m_data; }
|
||||
void setData(kml::TrackData const & data);
|
||||
|
||||
std::string GetName() const;
|
||||
void SetName(std::string const & name);
|
||||
std::string GetDescription() const;
|
||||
|
||||
m2::RectD GetLimitRect() const;
|
||||
double GetLengthMeters() const;
|
||||
double GetDurationInSeconds() const;
|
||||
TrackStatistics GetStatistics() const;
|
||||
std::optional<ElevationInfo> GetElevationInfo() const;
|
||||
std::pair<m2::PointD, double> GetCenterPoint() const;
|
||||
|
||||
struct TrackSelectionInfo
|
||||
{
|
||||
TrackSelectionInfo() = default;
|
||||
TrackSelectionInfo(kml::TrackId trackId, m2::PointD const & trackPoint, double distFromBegM)
|
||||
: m_trackId(trackId)
|
||||
, m_trackPoint(trackPoint)
|
||||
, m_distFromBegM(distFromBegM)
|
||||
{}
|
||||
|
||||
kml::TrackId m_trackId = kml::kInvalidTrackId;
|
||||
m2::PointD m_trackPoint;
|
||||
// Distance in meters from the beginning to m_trackPoint.
|
||||
double m_distFromBegM;
|
||||
// Mercator square distance, used to select nearest track.
|
||||
double m_squareDist = std::numeric_limits<double>::max();
|
||||
};
|
||||
|
||||
void UpdateSelectionInfo(m2::RectD const & touchRect, TrackSelectionInfo & info) const;
|
||||
|
||||
int GetMinZoom() const override { return 1; }
|
||||
df::DepthLayer GetDepthLayer() const override;
|
||||
size_t GetLayerCount() const override;
|
||||
dp::Color GetColor(size_t layerIndex) const override;
|
||||
void SetColor(dp::Color color);
|
||||
float GetWidth(size_t layerIndex) const override;
|
||||
float GetDepth(size_t layerIndex) const override;
|
||||
void ForEachGeometry(GeometryFnT && fn) const override;
|
||||
|
||||
void Attach(kml::MarkGroupId groupId);
|
||||
void Detach();
|
||||
|
||||
bool GetPoint(double distanceInMeters, m2::PointD & pt) const;
|
||||
|
||||
kml::MultiGeometry::LineT GetGeometry() const;
|
||||
bool HasAltitudes() const;
|
||||
|
||||
private:
|
||||
std::vector<Lengths> GetLengthsImpl() const;
|
||||
m2::RectD GetLimitRectImpl() const;
|
||||
|
||||
void CacheDataForInteraction() const;
|
||||
|
||||
double GetLengthMetersImpl(size_t lineIndex, size_t ptIdx) const;
|
||||
|
||||
kml::TrackData m_data;
|
||||
kml::MarkGroupId m_groupID = kml::kInvalidMarkGroupId;
|
||||
mutable std::optional<TrackStatistics> m_trackStatistics;
|
||||
mutable std::optional<ElevationInfo> m_elevationInfo;
|
||||
|
||||
struct InteractionData
|
||||
{
|
||||
std::vector<Lengths> m_lengths;
|
||||
m2::RectD m_limitRect;
|
||||
};
|
||||
mutable std::optional<InteractionData> m_interactionData;
|
||||
|
||||
mutable bool m_isDirty = true;
|
||||
};
|
||||
96
libs/map/track_mark.cpp
Normal file
96
libs/map/track_mark.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "map/track_mark.hpp"
|
||||
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kTrackDeselectedSymbolName = "track_marker_deselected";
|
||||
std::string const kInfoSignSymbolName = "infosign";
|
||||
int constexpr kMinInfoVisibleZoom = 1;
|
||||
} // namespace
|
||||
|
||||
TrackInfoMark::TrackInfoMark(m2::PointD const & ptOrg) : UserMark(ptOrg, UserMark::TRACK_INFO) {}
|
||||
|
||||
void TrackInfoMark::SetOffset(m2::PointF const & offset)
|
||||
{
|
||||
SetDirty();
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
void TrackInfoMark::SetPosition(m2::PointD const & ptOrg)
|
||||
{
|
||||
SetDirty();
|
||||
m_ptOrg = ptOrg;
|
||||
}
|
||||
|
||||
void TrackInfoMark::SetIsVisible(bool isVisible)
|
||||
{
|
||||
SetDirty();
|
||||
m_isVisible = isVisible;
|
||||
}
|
||||
|
||||
void TrackInfoMark::SetTrackId(kml::TrackId trackId)
|
||||
{
|
||||
m_trackId = trackId;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> TrackInfoMark::GetSymbolNames() const
|
||||
{
|
||||
auto symbol = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbol->insert(std::make_pair(kMinInfoVisibleZoom, kInfoSignSymbolName));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolOffsets> TrackInfoMark::GetSymbolOffsets() const
|
||||
{
|
||||
SymbolOffsets offsets(scales::UPPER_STYLE_SCALE, m_offset);
|
||||
return make_unique_dp<SymbolOffsets>(offsets);
|
||||
}
|
||||
|
||||
TrackSelectionMark::TrackSelectionMark(m2::PointD const & ptOrg) : UserMark(ptOrg, UserMark::TRACK_SELECTION) {}
|
||||
|
||||
void TrackSelectionMark::SetPosition(m2::PointD const & ptOrg)
|
||||
{
|
||||
SetDirty();
|
||||
m_ptOrg = ptOrg;
|
||||
}
|
||||
|
||||
void TrackSelectionMark::SetIsVisible(bool isVisible)
|
||||
{
|
||||
SetDirty();
|
||||
m_isVisible = isVisible;
|
||||
}
|
||||
|
||||
void TrackSelectionMark::SetMinVisibleZoom(int zoom)
|
||||
{
|
||||
SetDirty();
|
||||
m_minVisibleZoom = zoom;
|
||||
}
|
||||
|
||||
void TrackSelectionMark::SetTrackId(kml::TrackId trackId)
|
||||
{
|
||||
m_trackId = trackId;
|
||||
}
|
||||
|
||||
void TrackSelectionMark::SetDistance(double distance)
|
||||
{
|
||||
m_distance = distance;
|
||||
}
|
||||
|
||||
void TrackSelectionMark::SetMyPositionDistance(double distance)
|
||||
{
|
||||
m_myPositionDistance = distance;
|
||||
}
|
||||
|
||||
drape_ptr<df::UserPointMark::SymbolNameZoomInfo> TrackSelectionMark::GetSymbolNames() const
|
||||
{
|
||||
auto symbol = make_unique_dp<SymbolNameZoomInfo>();
|
||||
symbol->insert(std::make_pair(m_minVisibleZoom, kTrackDeselectedSymbolName));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
// static
|
||||
std::string TrackSelectionMark::GetInitialSymbolName()
|
||||
{
|
||||
return kTrackDeselectedSymbolName;
|
||||
}
|
||||
67
libs/map/track_mark.hpp
Normal file
67
libs/map/track_mark.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "map/user_mark.hpp"
|
||||
|
||||
class TrackInfoMark : public UserMark
|
||||
{
|
||||
public:
|
||||
explicit TrackInfoMark(m2::PointD const & ptOrg);
|
||||
|
||||
void SetOffset(m2::PointF const & offset);
|
||||
void SetPosition(m2::PointD const & ptOrg);
|
||||
void SetIsVisible(bool isVisible);
|
||||
|
||||
void SetTrackId(kml::TrackId trackId);
|
||||
kml::TrackId GetTrackId() const { return m_trackId; }
|
||||
|
||||
// df::UserPointMark overrides.
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
drape_ptr<SymbolOffsets> GetSymbolOffsets() const override;
|
||||
dp::Anchor GetAnchor() const override { return dp::Bottom; }
|
||||
bool IsVisible() const override { return m_isVisible; }
|
||||
bool SymbolIsPOI() const override { return true; }
|
||||
|
||||
df::SpecialDisplacement GetDisplacement() const override { return df::SpecialDisplacement::SpecialModeUserMark; }
|
||||
|
||||
df::DepthLayer GetDepthLayer() const override { return df::DepthLayer::RoutingMarkLayer; }
|
||||
|
||||
private:
|
||||
m2::PointF m_offset;
|
||||
bool m_isVisible = false;
|
||||
kml::TrackId m_trackId = kml::kInvalidTrackId;
|
||||
};
|
||||
|
||||
class TrackSelectionMark : public UserMark
|
||||
{
|
||||
public:
|
||||
static double constexpr kInvalidDistance = -1.0;
|
||||
|
||||
explicit TrackSelectionMark(m2::PointD const & ptOrg);
|
||||
|
||||
void SetPosition(m2::PointD const & ptOrg);
|
||||
void SetIsVisible(bool isVisible);
|
||||
void SetMinVisibleZoom(int zoom);
|
||||
|
||||
void SetTrackId(kml::TrackId trackId);
|
||||
kml::TrackId GetTrackId() const { return m_trackId; }
|
||||
|
||||
void SetDistance(double distance);
|
||||
double GetDistance() const { return m_distance; }
|
||||
|
||||
void SetMyPositionDistance(double distance);
|
||||
double GetMyPositionDistance() const { return m_myPositionDistance; }
|
||||
|
||||
// df::UserPointMark overrides.
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
int GetMinZoom() const override { return m_minVisibleZoom; }
|
||||
bool IsVisible() const override { return m_isVisible; }
|
||||
|
||||
static std::string GetInitialSymbolName();
|
||||
|
||||
private:
|
||||
int m_minVisibleZoom = 1;
|
||||
double m_distance = 0.0;
|
||||
double m_myPositionDistance = kInvalidDistance;
|
||||
kml::TrackId m_trackId = kml::kInvalidTrackId;
|
||||
bool m_isVisible = false;
|
||||
};
|
||||
142
libs/map/track_statistics.cpp
Normal file
142
libs/map/track_statistics.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#include "map/track_statistics.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "platform/distance.hpp"
|
||||
#include "platform/duration.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kInvalidTimestamp = std::numeric_limits<double>::min();
|
||||
geometry::PointWithAltitude const kInvalidPoint = {m2::PointD::Zero(), geometry::kInvalidAltitude};
|
||||
} // namespace
|
||||
|
||||
TrackStatistics::TrackStatistics()
|
||||
: m_length(0)
|
||||
, m_duration(0)
|
||||
, m_ascent(0)
|
||||
, m_descent(0)
|
||||
, m_minElevation(geometry::kDefaultAltitudeMeters)
|
||||
, m_maxElevation(geometry::kDefaultAltitudeMeters)
|
||||
, m_previousPoint(kInvalidPoint)
|
||||
, m_previousTimestamp(kInvalidTimestamp)
|
||||
{}
|
||||
|
||||
TrackStatistics::TrackStatistics(kml::MultiGeometry const & geometry) : TrackStatistics()
|
||||
{
|
||||
for (auto const & line : geometry.m_lines)
|
||||
AddPoints(line);
|
||||
if (geometry.HasTimestamps())
|
||||
{
|
||||
for (size_t i = 0; i < geometry.m_timestamps.size(); ++i)
|
||||
{
|
||||
ASSERT(geometry.HasTimestampsFor(i), ());
|
||||
AddTimestamps(geometry.m_timestamps[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackStatistics::AddGpsInfoPoint(location::GpsInfo const & point)
|
||||
{
|
||||
auto const pointWithAltitude =
|
||||
geometry::PointWithAltitude(mercator::FromLatLon(point.m_latitude, point.m_longitude), point.m_altitude);
|
||||
auto const altitude = geometry::Altitude(point.m_altitude);
|
||||
if (HasNoPoints())
|
||||
{
|
||||
m_minElevation = altitude;
|
||||
m_maxElevation = altitude;
|
||||
m_previousPoint = pointWithAltitude;
|
||||
m_previousTimestamp = point.m_timestamp;
|
||||
return;
|
||||
}
|
||||
|
||||
m_minElevation = std::min(m_minElevation, altitude);
|
||||
m_maxElevation = std::max(m_maxElevation, altitude);
|
||||
|
||||
auto const deltaAltitude = altitude - m_previousPoint.GetAltitude();
|
||||
if (deltaAltitude > 0)
|
||||
m_ascent += deltaAltitude;
|
||||
else
|
||||
m_descent -= deltaAltitude;
|
||||
m_length += mercator::DistanceOnEarth(m_previousPoint.GetPoint(), pointWithAltitude.GetPoint());
|
||||
m_duration += point.m_timestamp - m_previousTimestamp;
|
||||
|
||||
m_previousPoint = pointWithAltitude;
|
||||
m_previousTimestamp = point.m_timestamp;
|
||||
}
|
||||
|
||||
void TrackStatistics::AddPoints(Points const & points)
|
||||
{
|
||||
if (points.empty())
|
||||
return;
|
||||
|
||||
bool const hasNoPoints = HasNoPoints();
|
||||
auto const & firstPoint = points[0];
|
||||
auto const altitude = firstPoint.GetAltitude();
|
||||
|
||||
m_minElevation = hasNoPoints ? altitude : std::min(m_minElevation, altitude);
|
||||
m_maxElevation = hasNoPoints ? altitude : std::max(m_maxElevation, altitude);
|
||||
m_previousPoint = firstPoint;
|
||||
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
auto const & point = points[i];
|
||||
auto const pointAltitude = point.GetAltitude();
|
||||
|
||||
m_minElevation = std::min(m_minElevation, pointAltitude);
|
||||
m_maxElevation = std::max(m_maxElevation, pointAltitude);
|
||||
|
||||
auto const deltaAltitude = pointAltitude - m_previousPoint.GetAltitude();
|
||||
if (deltaAltitude > 0)
|
||||
m_ascent += deltaAltitude;
|
||||
else
|
||||
m_descent -= deltaAltitude;
|
||||
m_length += mercator::DistanceOnEarth(m_previousPoint.GetPoint(), point.GetPoint());
|
||||
|
||||
m_previousPoint = point;
|
||||
}
|
||||
}
|
||||
|
||||
void TrackStatistics::AddTimestamps(Timestamps const & timestamps)
|
||||
{
|
||||
if (timestamps.empty())
|
||||
return;
|
||||
m_duration += timestamps.back() - timestamps.front();
|
||||
m_previousTimestamp = timestamps.back();
|
||||
}
|
||||
|
||||
bool TrackStatistics::HasNoPoints() const
|
||||
{
|
||||
return m_previousPoint == kInvalidPoint;
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedLength() const
|
||||
{
|
||||
return platform::Distance::CreateFormatted(m_length).ToString();
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedDuration() const
|
||||
{
|
||||
return platform::Duration(static_cast<int>(m_duration)).GetPlatformLocalizedString();
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedAscent() const
|
||||
{
|
||||
return platform::Distance::FormatAltitude(m_ascent);
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedDescent() const
|
||||
{
|
||||
return platform::Distance::FormatAltitude(m_descent);
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedMinElevation() const
|
||||
{
|
||||
return platform::Distance::FormatAltitude(m_minElevation);
|
||||
}
|
||||
|
||||
std::string TrackStatistics::GetFormattedMaxElevation() const
|
||||
{
|
||||
return platform::Distance::FormatAltitude(m_maxElevation);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue