Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

109
libs/map/CMakeLists.txt Normal file
View 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()

View 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;
}

View 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;
};

View 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
)

View 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

View 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

View 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

View 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;
}

View 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

View file

@ -0,0 +1,8 @@
#pragma once
class Framework;
namespace benchmark
{
void RunGraphicsBenchmark(Framework * framework);
} // namespace benchmark

403
libs/map/bookmark.cpp Normal file
View 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
View 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;
};

View 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);
}

View 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);

File diff suppressed because it is too large Load diff

View 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);
};

View 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

View 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

View 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

View 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);
}
}

View 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);
};

View 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

View 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

View 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

View 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

View 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

View 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
)

View 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;
}

View 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);
}

View 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

File diff suppressed because it is too large Load diff

772
libs/map/framework.hpp Normal file
View 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;
};

View 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
View 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
View 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;
};

View 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;
}

View 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;
};

View 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;
}

View 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;
};

View 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;
}
}

View 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
View 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
View 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;
};

View 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();
}

View 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);

View 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
)

View 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

View 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
)

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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()));
});
}

View 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

View 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

View 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);
}
}

View 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

View 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

View 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

View 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));
}
}

View 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
*/

View 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

View 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

View 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

View 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

View 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");
}

View 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)

View 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

View 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

View 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()), ());
}

View 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
View 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
View 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
View 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

View 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

View 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

View 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;
};

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};

View file

@ -0,0 +1,8 @@
#pragma once
namespace search
{
/// Here was an additional info like UGC, advertising stuff, etc.
struct ProductInfo
{};
} // namespace search

View 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)

View 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

View 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)));
}
});
});
}

View 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

View 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
View 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
View 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
View 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
View 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;
};

View 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