From 0fd7f8d5738d5fa663c4e065461e3efc3f478633 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 23 Jul 2025 10:50:00 +0700 Subject: [PATCH 001/252] [fdroid] Release version 2025.07.23-2 Signed-off-by: Konstantin Pastbin --- android/app/src/fdroid/play/version.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/fdroid/play/version.yaml b/android/app/src/fdroid/play/version.yaml index ef749110e..c66597b6c 100644 --- a/android/app/src/fdroid/play/version.yaml +++ b/android/app/src/fdroid/play/version.yaml @@ -1 +1 @@ -version: 2025.03.02-7-FDroid+25030207 +version: 2025.07.23-2-FDroid+25072302 From be3e3d773b56b69b1d4d2bdcf3fdc82cb3553704 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 23 Jul 2025 16:31:02 +0700 Subject: [PATCH 002/252] [android] Fix Panoramax links not working Signed-off-by: Konstantin Pastbin --- .../widget/placepage/sections/PlacePageLinksFragment.java | 8 +++++--- indexer/validate_and_format_contacts.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageLinksFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageLinksFragment.java index bc7eb5b1d..796e0f3ee 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageLinksFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageLinksFragment.java @@ -85,7 +85,7 @@ private String getLink(@NonNull Metadata.MetadataType type) case FMD_WEBSITE_MENU -> mMapObject.getWebsiteUrl(false /* strip */, Metadata.MetadataType.FMD_WEBSITE_MENU); case FMD_CONTACT_FACEBOOK, FMD_CONTACT_INSTAGRAM, FMD_CONTACT_TWITTER, - FMD_CONTACT_FEDIVERSE, FMD_CONTACT_BLUESKY, FMD_CONTACT_VK, FMD_CONTACT_LINE -> + FMD_CONTACT_FEDIVERSE, FMD_CONTACT_BLUESKY, FMD_CONTACT_VK, FMD_CONTACT_LINE, FMD_PANORAMAX -> { if (TextUtils.isEmpty(mMapObject.getMetadata(type))) yield ""; @@ -171,7 +171,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mPanoramax = mFrame.findViewById(R.id.ll__place_panoramax); mTvPanoramax = mFrame.findViewById(R.id.tv__place_panoramax); mPanoramax.setOnClickListener((v) -> openUrl(Metadata.MetadataType.FMD_PANORAMAX)); - mTvPanoramax.setOnLongClickListener((v) -> copyUrl(mPanoramax, Metadata.MetadataType.FMD_PANORAMAX)); + mPanoramax.setOnLongClickListener((v) -> copyUrl(mPanoramax, Metadata.MetadataType.FMD_PANORAMAX)); } private void openUrl(Metadata.MetadataType type) @@ -192,6 +192,7 @@ private boolean copyUrl(View view, Metadata.MetadataType type) final String title = switch (type){ case FMD_WEBSITE -> mMapObject.getWebsiteUrl(false /* strip */, Metadata.MetadataType.FMD_WEBSITE); case FMD_WEBSITE_MENU -> mMapObject.getWebsiteUrl(false /* strip */, Metadata.MetadataType.FMD_WEBSITE_MENU); + case FMD_PANORAMAX -> null; // Don't add raw ID to list, as it's useless for users. default -> mMapObject.getMetadata(type); }; // Add user names for social media if available @@ -237,7 +238,8 @@ private void refreshLinks() refreshMetadataOrHide(line, mLinePage, mTvLinePage); final String panoramax = mMapObject.getMetadata(Metadata.MetadataType.FMD_PANORAMAX); - refreshMetadataOrHide(panoramax, mPanoramax, mTvPanoramax); + final String panoramaxTitle = TextUtils.isEmpty(panoramax) ? "" : getResources().getString(R.string.panoramax); + refreshMetadataOrHide(panoramaxTitle, mPanoramax, mTvPanoramax); } @Override diff --git a/indexer/validate_and_format_contacts.cpp b/indexer/validate_and_format_contacts.cpp index cb40e5f3f..96a4609a2 100644 --- a/indexer/validate_and_format_contacts.cpp +++ b/indexer/validate_and_format_contacts.cpp @@ -26,6 +26,7 @@ constexpr string_view kVk{"contact:vk"}; constexpr string_view kLine{"contact:line"}; constexpr string_view kFediverse{"contact:mastodon"}; constexpr string_view kBluesky{"contact:bluesky"}; +constexpr string_view kPanoramax{"panoramax"}; constexpr string_view kProfilePhp{"profile.php"}; @@ -608,7 +609,7 @@ bool ValidateBlueskyPage(string const & page) bool isSocialContactTag(string_view tag) { - return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine || tag == kFediverse || tag == kBluesky; + return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine || tag == kFediverse || tag == kBluesky || tag == kPanoramax; } bool isSocialContactTag(MapObject::MetadataID const metaID) @@ -619,7 +620,8 @@ bool isSocialContactTag(MapObject::MetadataID const metaID) metaID == MapObject::MetadataID::FMD_CONTACT_VK || metaID == MapObject::MetadataID::FMD_CONTACT_LINE || metaID == MapObject::MetadataID::FMD_CONTACT_FEDIVERSE || - metaID == MapObject::MetadataID ::FMD_CONTACT_BLUESKY; + metaID == MapObject::MetadataID::FMD_CONTACT_BLUESKY || + metaID == MapObject::MetadataID::FMD_PANORAMAX; } // Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name @@ -647,6 +649,8 @@ string socialContactToURL(string_view tag, string_view value) else // 'value' is an URL. return string{kHttps}.append(value); } + if (tag == kPanoramax) + return string{kUrlPanoramax}.append(value); return string{value}; } From 20c9fc5f45b37b62d58d45e1f179093c1b9a966a Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 23 Jul 2025 21:00:52 +0700 Subject: [PATCH 003/252] [fdroid] Release version 2025.07.23-4 Signed-off-by: Konstantin Pastbin --- android/app/src/fdroid/play/version.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/fdroid/play/version.yaml b/android/app/src/fdroid/play/version.yaml index c66597b6c..5d9cfbd0f 100644 --- a/android/app/src/fdroid/play/version.yaml +++ b/android/app/src/fdroid/play/version.yaml @@ -1 +1 @@ -version: 2025.07.23-2-FDroid+25072302 +version: 2025.07.23-4-FDroid+25072304 From 6f2f61b30a0dc312f50de35c814909f902d8075b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Apr 2025 23:44:02 +0300 Subject: [PATCH 004/252] Revert "[qt] Remove defunct Traffic layer button" This reverts commit df2541e1bf12abca329becdac8de7c92f0893b03. --- 3party/CMake-MetalShaderSupport | 2 +- 3party/gflags | 2 +- 3party/jansson/jansson | 2 +- qt/mainwindow.cpp | 17 ++++++++--------- qt/mainwindow.hpp | 5 ++--- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/3party/CMake-MetalShaderSupport b/3party/CMake-MetalShaderSupport index 989857d2e..84209c32e 160000 --- a/3party/CMake-MetalShaderSupport +++ b/3party/CMake-MetalShaderSupport @@ -1 +1 @@ -Subproject commit 989857d2e5e54869c35ad06fb21a67d12a2dbc67 +Subproject commit 84209c32e50966ee4dca66f7b7eebacd934c87fb diff --git a/3party/gflags b/3party/gflags index a738fdf93..52e94563e 160000 --- a/3party/gflags +++ b/3party/gflags @@ -1 +1 @@ -Subproject commit a738fdf9338412f83ab3f26f31ac11ed3f3ec4bd +Subproject commit 52e94563eba1968783864942fedf6e87e3c611f4 diff --git a/3party/jansson/jansson b/3party/jansson/jansson index 61fc3d0e2..96d160df9 160000 --- a/3party/jansson/jansson +++ b/3party/jansson/jansson @@ -1 +1 @@ -Subproject commit 61fc3d0e28e1a35410af42e329cd977095ec32d2 +Subproject commit 96d160df90016066d04d493d1d69639474ba4f20 diff --git a/qt/mainwindow.cpp b/qt/mainwindow.cpp index 02e31905b..d3684b2eb 100644 --- a/qt/mainwindow.cpp +++ b/qt/mainwindow.cpp @@ -282,10 +282,9 @@ void MainWindow::CreateNavigationBar() m_layers = new PopupMenuHolder(this); - /// @todo Uncomment when we will integrate a traffic provider. - // m_layers->addAction(QIcon(":/navig64/traffic.png"), tr("Traffic"), - // std::bind(&MainWindow::OnLayerEnabled, this, LayerType::TRAFFIC), true); - // m_layers->setChecked(LayerType::TRAFFIC, m_pDrawWidget->GetFramework().LoadTrafficEnabled()); + m_layers->addAction(QIcon(":/navig64/traffic.png"), tr("Traffic"), + std::bind(&MainWindow::OnLayerEnabled, this, LayerType::TRAFFIC), true); + m_layers->setChecked(LayerType::TRAFFIC, m_pDrawWidget->GetFramework().LoadTrafficEnabled()); m_layers->addAction(QIcon(":/navig64/subway.png"), tr("Public transport"), std::bind(&MainWindow::OnLayerEnabled, this, LayerType::TRANSIT), true); @@ -876,11 +875,11 @@ void MainWindow::SetLayerEnabled(LayerType type, bool enable) auto & frm = m_pDrawWidget->GetFramework(); switch (type) { - // @todo Uncomment when we will integrate a traffic provider. - // case LayerType::TRAFFIC: - // frm.GetTrafficManager().SetEnabled(enable); - // frm.SaveTrafficEnabled(enable); - // break; + case LayerType::TRAFFIC: + /// @todo Uncomment when we will integrate a traffic provider. + // frm.GetTrafficManager().SetEnabled(enable); + // frm.SaveTrafficEnabled(enable); + break; case LayerType::TRANSIT: frm.GetTransitManager().EnableTransitSchemeMode(enable); frm.SaveTransitSchemeEnabled(enable); diff --git a/qt/mainwindow.hpp b/qt/mainwindow.hpp index 6ea00a517..e3073e9e9 100644 --- a/qt/mainwindow.hpp +++ b/qt/mainwindow.hpp @@ -50,9 +50,8 @@ class MainWindow : public QMainWindow, location::LocationObserver enum LayerType : uint8_t { - /// @todo Uncomment when we will integrate a traffic provider. - // TRAFFIC = 0, - TRANSIT = 0, // Metro scheme + TRAFFIC = 0, + TRANSIT, // Metro scheme ISOLINES, OUTDOORS, }; From 932dda65522e339c1fcfeaeeae707ae3fbf96dae Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Apr 2025 23:45:55 +0300 Subject: [PATCH 005/252] Revert "[desktop] Disable traffic switch and TrafficManager initialization." This reverts commit 16ad61f4c8ebd22bdc282496122db49a5243f02f. --- map/framework.cpp | 14 ++++++++++---- qt/mainwindow.cpp | 5 ++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 8a176dd13..7c42d9ff3 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -7,7 +7,9 @@ #include "ge0/url_generator.hpp" +#include "routing/index_router.hpp" #include "routing/route.hpp" +#include "routing/routing_helpers.hpp" #include "routing/speed_camera_prohibition.hpp" #include "routing_common/num_mwm_id.hpp" @@ -18,12 +20,15 @@ #include "storage/storage.hpp" #include "storage/country_info_getter.hpp" +#include "storage/routing_helpers.hpp" #include "storage/storage_helpers.hpp" #include "drape_frontend/color_constants.hpp" #include "drape_frontend/gps_track_point.hpp" #include "drape_frontend/visual_params.hpp" +#include "editor/editable_data_source.hpp" + #include "descriptions/loader.hpp" #include "indexer/categories_holder.hpp" @@ -39,6 +44,7 @@ #include "indexer/scales.hpp" #include "indexer/transliteration_loader.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/localization.hpp" #include "platform/measurement_utils.hpp" #include "platform/mwm_version.hpp" @@ -61,6 +67,7 @@ #include "base/logging.hpp" #include "base/math.hpp" +#include "base/stl_helpers.hpp" #include "base/string_utils.hpp" #include "std/target_os.hpp" @@ -364,10 +371,9 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) editor.SetDelegate(make_unique(m_featuresFetcher.GetDataSource())); editor.SetInvalidateFn([this](){ InvalidateRect(GetCurrentViewport()); }); - /// @todo Uncomment when we will integrate a traffic provider. - // m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); - // m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors()); - // m_trafficManager.SetEnabled(LoadTrafficEnabled()); + m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); + m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors()); + m_trafficManager.SetEnabled(LoadTrafficEnabled()); m_isolinesManager.SetEnabled(LoadIsolinesEnabled()); diff --git a/qt/mainwindow.cpp b/qt/mainwindow.cpp index d3684b2eb..4f9233d8a 100644 --- a/qt/mainwindow.cpp +++ b/qt/mainwindow.cpp @@ -876,9 +876,8 @@ void MainWindow::SetLayerEnabled(LayerType type, bool enable) switch (type) { case LayerType::TRAFFIC: - /// @todo Uncomment when we will integrate a traffic provider. - // frm.GetTrafficManager().SetEnabled(enable); - // frm.SaveTrafficEnabled(enable); + frm.GetTrafficManager().SetEnabled(enable); + frm.SaveTrafficEnabled(enable); break; case LayerType::TRANSIT: frm.GetTransitManager().EnableTransitSchemeMode(enable); From 1574b5b7cbcdfec78c41e9cb9a80e3c4217a1e01 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Apr 2025 23:46:09 +0300 Subject: [PATCH 006/252] Revert "[routing] Do not create TrafficStash instance." This reverts commit 3c12ba5134f2aa9d19c0c9a54af89d368f389eb4. --- routing/index_router.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/routing/index_router.cpp b/routing/index_router.cpp index 3de70fffa..d87ed514a 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -130,10 +130,13 @@ unique_ptr CreateDirectionsEngine(VehicleType vehicleType, UNREACHABLE(); } -shared_ptr CreateTrafficStash(VehicleType, shared_ptr, traffic::TrafficCache const &) +shared_ptr CreateTrafficStash(VehicleType vehicleType, shared_ptr numMwmIds, + traffic::TrafficCache const & trafficCache) { - return nullptr; - //return (vehicleType == VehicleType::Car ? make_shared(trafficCache, numMwmIds) : nullptr); + if (vehicleType != VehicleType::Car) + return nullptr; + + return make_shared(trafficCache, numMwmIds); } void PushPassedSubroutes(Checkpoints const & checkpoints, vector & subroutes) From 9c93f421ac5169f815568d67a2b726efeae58a7a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 8 Apr 2025 20:28:20 +0300 Subject: [PATCH 007/252] [traffic] Add documentation Signed-off-by: mvglasow --- map/traffic_manager.hpp | 18 +++++++++ traffic/speed_groups.hpp | 50 +++++++++++++++-------- traffic/traffic_info.hpp | 86 +++++++++++++++++++++++++++++++--------- 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index f2a2d28b1..f3169ccc9 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -68,9 +68,27 @@ class TrafficManager final void SetStateListener(TrafficStateChangedFn const & onStateChangedFn); void SetDrapeEngine(ref_ptr engine); + /** + * @brief Sets the version of the MWM used locally. + */ void SetCurrentDataVersion(int64_t dataVersion); + /** + * @brief Enables or disables the traffic manager. + * + * This sets the internal state and notifies the drape engine. Enabling the traffic manager will + * invalidate its data, disabling it will notify the observer that traffic data has been cleared. + * + * Calling this function with `enabled` identical to the current state is a no-op. + * + * @param enabled True to enable, false to disable + */ void SetEnabled(bool enabled); + /** + * @brief Whether the traffic manager is enabled. + * + * @return True if enabled, false if not + */ bool IsEnabled() const; void UpdateViewport(ScreenBase const & screen); diff --git a/traffic/speed_groups.hpp b/traffic/speed_groups.hpp index 385e68707..23c0da9ec 100644 --- a/traffic/speed_groups.hpp +++ b/traffic/speed_groups.hpp @@ -5,6 +5,15 @@ namespace traffic { +/** + * A bucket for the ratio of the speed of moving traffic to the posted speed limit. + * + * Let Vmax be the posted speed limit and Vreal the speed at which traffic is currently flowing + * or expected to flow. The possible ratios (Vreal/Vmax) are grouped into buckets and, from then + * on, only the bucket number is used. + * + * The threshold ratios for the individual values are defined in `kSpeedGroupThresholdPercentage`. + */ enum class SpeedGroup : uint8_t { G0 = 0, @@ -20,24 +29,33 @@ enum class SpeedGroup : uint8_t static_assert(static_cast(SpeedGroup::Count) <= 8, ""); -// Let M be the maximal speed that is possible on a free road -// and let V be the maximal speed that is possible on this road when -// taking the traffic data into account. -// We group all possible ratios (V/M) into a small number of -// buckets and only use the number of a bucket everywhere. -// That is, we forget the specific values of V when transmitting and -// displaying traffic information. The value M of a road is known at the -// stage of building the mwm containing this road. -// -// kSpeedGroupThresholdPercentage[g] denotes the maximal value of (V/M) -// that is possible for group |g|. Values falling on a border of two groups -// may belong to either group. -// -// The threshold percentage is defined to be 100 for the -// special groups where V is unknown or not defined. +/** + * Threshold ratios for the individual values of `SpeedGroup`. + * + * Let Vmax be the posted speed limit and Vreal the speed at which traffic is currently flowing + * or expected to flow. The possible ratios (Vreal/Vmax) are grouped into buckets and, from then + * on, only the bucket number is used. + * + * `kSpeedGroupThresholdPercentage[g]` is the maximum percentage of Vreal/Vmax for group g. Values + * falling on the border of two groups may belong to either group. + * + * For special groups, where Vreal/Vmax is unknown or undefined, the threshold is 100%. + */ extern uint32_t const kSpeedGroupThresholdPercentage[static_cast(SpeedGroup::Count)]; -/// \note This method is used in traffic jam generation. +/** + * Converts the ratio between speed of flowing traffic and the posted limit to a `SpeedGroup`. + * + * This method is used in traffic jam generation: Let Vmax be the posted speed limit and Vreal the + * speed at which traffic is currently flowing or expected to flow. The possible ratios + * (Vreal/Vmax) are grouped into buckets and, from then on, only the bucket number is used. + * + * This method performs the conversion from the ratio to a `SpeedGroup` bucket. + * + * @param p Vreal / Vmax * 100% (ratio expressed in percent) + * + * @return the `SpeedGroup` value which corresponds to `p` + */ SpeedGroup GetSpeedGroupByPercentage(double p); std::string DebugPrint(SpeedGroup const & group); diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index c92378fc1..60e789986 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -15,8 +15,10 @@ class HttpClient; namespace traffic { -// This class is responsible for providing the real-time -// information about road traffic for one mwm file. +/** + * @brief The `TrafficInfo` class is responsible for providing the real-time information about road + * traffic for one MWM. + */ class TrafficInfo { public: @@ -32,6 +34,16 @@ class TrafficInfo Unknown }; + /** + * @brief The RoadSegmentId struct models a segment of a road. + * + * A road segment is the link between two consecutive points of an OSM way. The way must be + * tagged with a valid `highway` tag. A segment refers to a single direction. + * + * Therefore, an OSM way with `n` points has `n - 1` segments if tagged as one-way, `2 (n - 1)` + * otherwise (as each pair of adjacent points is connected by two segments, one in each + * direction.) + */ struct RoadSegmentId { // m_dir can be kForwardDirection or kReverseDirection. @@ -71,6 +83,9 @@ class TrafficInfo uint8_t m_dir : 1; }; + /** + * @brief Mapping from feature segments to speed groups (see `speed_groups.hpp`), for one MWM. + */ // todo(@m) unordered_map? using Coloring = std::map; @@ -78,30 +93,62 @@ class TrafficInfo TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion); + /** + * @brief Returns a `TrafficInfo` instance with pre-populated traffic information. + * @param coloring The traffic information (road segments and their speed group) + * @return The new `TrafficInfo` instance + */ static TrafficInfo BuildForTesting(Coloring && coloring); void SetTrafficKeysForTesting(std::vector const & keys); - // Fetches the latest traffic data from the server and updates the coloring and ETag. - // Construct the url by passing an MwmId. - // The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. - // It is one of several mechanisms that HTTP provides for web cache validation, - // which allows a client to make conditional requests. - // *NOTE* This method must not be called on the UI thread. + /** + * @brief Fetches the latest traffic data from the server and updates the coloring and ETag. + * + * The url is constructed using the `mwmId` specified in the constructor. + * + * The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. + * It is one of several mechanisms that HTTP provides for web cache validation, + * which allows a client to make conditional requests. + * + * NOTE: This method must not be called on the UI thread. + * + * @param etag The entity tag + * @return True on success, false on failure. + */ bool ReceiveTrafficData(std::string & etag); - // Returns the latest known speed group by a feature segment's id - // or SpeedGroup::Unknown if there is no information about the segment. + /** + * @brief Returns the latest known speed group by a feature segment's ID. + * @param id The road segment ID. + * @return The speed group, or `SpeedGroup::Unknown` if no information is available. + */ SpeedGroup GetSpeedGroup(RoadSegmentId const & id) const; MwmSet::MwmId const & GetMwmId() const { return m_mwmId; } Coloring const & GetColoring() const { return m_coloring; } Availability GetAvailability() const { return m_availability; } - // Extracts RoadSegmentIds from mwm and stores them in a sorted order. + /** + * @brief Extracts RoadSegmentIds from an MWM and stores them in a sorted order. + * @param mwmPath Path to the MWM file + */ static void ExtractTrafficKeys(std::string const & mwmPath, std::vector & result); - // Adds the unknown values to the partially known coloring map |knownColors| - // so that the keys of the resulting map are exactly |keys|. + /** + * @brief Adds unknown values to a partially known coloring map. + * + * After this method returns, the keys of `result` will be exactly `keys`. The speed group + * associated with each key will be the same as in `knownColors`, or `SpeedGroup::Unknown` for + * keys which are not found in `knownColors`. + * + * Keys in `knownColors` which are not in `keys` will be ignored. + * + * If `result` contains mappings prior to this method being called, they will be deleted. + * + * @param keys The keys for the result map. + * @param knownColors The map containing the updates. + * @param result The map to be updated. + */ static void CombineColorings(std::vector const & keys, TrafficInfo::Coloring const & knownColors, TrafficInfo::Coloring & result); @@ -143,13 +190,16 @@ class TrafficInfo ServerDataStatus ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion); - // The mapping from feature segments to speed groups (see speed_groups.hpp). + /** + * @brief The mapping from feature segments to speed groups (see speed_groups.hpp). + */ Coloring m_coloring; - // The keys of the coloring map. The values are downloaded periodically - // and combined with the keys to form m_coloring. - // *NOTE* The values must be received in the exact same order that the - // keys are saved in. + /** + * @brief The keys of the coloring map. The values are downloaded periodically + * and combined with the keys to form `m_coloring`. + * *NOTE* The values must be received in the exact same order that the keys are saved in. + */ std::vector m_keys; MwmSet::MwmId m_mwmId; From 737d7b564378985ab1e6352ed745be57a5da4451 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Apr 2025 23:57:13 +0300 Subject: [PATCH 008/252] [traffic] Initialize TrafficManager with CountryParentNameGetterFn Signed-off-by: mvglasow # Conflicts: # map/framework.cpp # map/traffic_manager.cpp # map/traffic_manager.hpp --- map/framework.cpp | 3 ++- map/traffic_manager.cpp | 6 ++++-- map/traffic_manager.hpp | 8 +++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 7c42d9ff3..0f2a65187 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -295,7 +295,8 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) [this]() -> StringsBundle const & { return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }), static_cast(*this)) - , m_trafficManager(bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), + , m_trafficManager([this](string const & id) -> string { return m_storage.GetParentIdFor(id); }, + bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes, m_routingManager.RoutingSession()) , m_lastReportedCountry(kInvalidCountryId) , m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index b3c434ddb..1836079dd 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -41,9 +41,11 @@ TrafficManager::CacheEntry::CacheEntry(time_point const & requestT , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) {} -TrafficManager::TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, +TrafficManager::TrafficManager(const CountryParentNameGetterFn &countryParentNameGetter, + GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer) - : m_getMwmsByRectFn(getMwmsByRectFn) + : m_countryParentNameGetterFn(countryParentNameGetter) + , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) , m_state(TrafficState::Disabled) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index f3169ccc9..e8bcb9956 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -9,6 +9,8 @@ #include "indexer/mwm_set.hpp" +#include "openlr/openlr_decoder.hpp" + #include "geometry/point2d.hpp" #include "geometry/polyline2d.hpp" #include "geometry/screenbase.hpp" @@ -31,6 +33,8 @@ class TrafficManager final { public: + using CountryParentNameGetterFn = std::function; + enum class TrafficState { Disabled, @@ -58,7 +62,8 @@ class TrafficManager final using TrafficStateChangedFn = std::function; using GetMwmsByRectFn = std::function(m2::RectD const &)>; - TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, + TrafficManager(CountryParentNameGetterFn const & countryParentNameGetter, + GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer); ~TrafficManager(); @@ -165,6 +170,7 @@ class TrafficManager final std::for_each(activeMwms.begin(), activeMwms.end(), std::forward(f)); } + CountryParentNameGetterFn m_countryParentNameGetterFn; GetMwmsByRectFn m_getMwmsByRectFn; traffic::TrafficObserver & m_observer; From 7b420def1713f9471ee2714cf57c2359cbd20e89 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 13 Apr 2025 19:09:19 +0300 Subject: [PATCH 009/252] [qt] Link against openlr library Signed-off-by: mvglasow --- qt/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index f1fb4c66b..0900e465d 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -70,6 +70,7 @@ target_link_libraries(${PROJECT_NAME} qt_common map gflags::gflags + openlr ) if (BUILD_DESIGNER) From 6e8d400611a66b6cd24ee86c25ddfa21b18b6bdb Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 12 Apr 2025 19:46:31 +0300 Subject: [PATCH 010/252] [traffic] Include OpenLR headers in traffic_manager.cpp Signed-off-by: mvglasow --- map/traffic_manager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 1836079dd..c89065841 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -10,6 +10,10 @@ #include "geometry/mercator.hpp" +#include "openlr/decoded_path.hpp" +#include "openlr/openlr_decoder.hpp" +#include "openlr/openlr_model.hpp" + #include "platform/platform.hpp" using namespace std::chrono; From bb410fc3bc01ba8d8c76f1c5b530e6d20ed72d35 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 21 Apr 2025 13:14:06 +0300 Subject: [PATCH 011/252] [traffxml] Add module Signed-off-by: mvglasow --- CMakeLists.txt | 1 + .../openlr_assessment_tool/CMakeLists.txt | 1 + qt/CMakeLists.txt | 1 + traffxml/CMakeLists.txt | 16 + traffxml/traff_decoder.cpp | 7 + traffxml/traff_decoder.hpp | 7 + traffxml/traff_model.cpp | 248 +++++++ traffxml/traff_model.hpp | 203 ++++++ traffxml/traff_model_xml.cpp | 677 ++++++++++++++++++ traffxml/traff_model_xml.hpp | 14 + 10 files changed, 1175 insertions(+) create mode 100644 traffxml/CMakeLists.txt create mode 100644 traffxml/traff_decoder.cpp create mode 100644 traffxml/traff_decoder.hpp create mode 100644 traffxml/traff_model.cpp create mode 100644 traffxml/traff_model.hpp create mode 100644 traffxml/traff_model_xml.cpp create mode 100644 traffxml/traff_model_xml.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 40a056580..7eef8681c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,7 @@ add_subdirectory(shaders) add_subdirectory(storage) add_subdirectory(tracking) add_subdirectory(traffic) +add_subdirectory(traffxml) add_subdirectory(transit) if (PLATFORM_DESKTOP) diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt b/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt index 5aa14f8e9..23ad94182 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt +++ b/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt @@ -28,4 +28,5 @@ target_link_libraries(${PROJECT_NAME} qt_common map gflags::gflags + traffxml ) diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index 0900e465d..e78813c1c 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -71,6 +71,7 @@ target_link_libraries(${PROJECT_NAME} map gflags::gflags openlr + traffxml ) if (BUILD_DESIGNER) diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt new file mode 100644 index 000000000..418c7e15f --- /dev/null +++ b/traffxml/CMakeLists.txt @@ -0,0 +1,16 @@ +project(traffxml) + +set(SRC + traff_decoder.cpp + traff_decoder.hpp + traff_model.cpp + traff_model.hpp + traff_model_xml.cpp + traff_model_xml.hpp +) + +omim_add_library(${PROJECT_NAME} ${SRC}) + +target_link_libraries(${PROJECT_NAME} + pugixml +) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp new file mode 100644 index 000000000..0a30f26a5 --- /dev/null +++ b/traffxml/traff_decoder.cpp @@ -0,0 +1,7 @@ +#include "traffxml/traff_decoder.hpp" + +//#include "traffxml/traff_foo.hpp" + +namespace traffxml +{ +} // namespace traffxml diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp new file mode 100644 index 000000000..075c20ab1 --- /dev/null +++ b/traffxml/traff_decoder.hpp @@ -0,0 +1,7 @@ +#pragma once + +//#include "traffxml/traff_foo.hpp" + +namespace traffxml +{ +} // namespace traffxml diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp new file mode 100644 index 000000000..8bf9fff8f --- /dev/null +++ b/traffxml/traff_model.cpp @@ -0,0 +1,248 @@ +#include "traffxml/traff_model.hpp" + +using namespace std; + +namespace traffxml +{ +/* +string DebugPrint(LinearSegmentSource source) +{ + switch (source) + { + case LinearSegmentSource::NotValid: return "NotValid"; + case LinearSegmentSource::FromLocationReferenceTag: return "FromLocationReferenceTag"; + case LinearSegmentSource::FromCoordinatesTag: return "FromCoordinatesTag"; + } + UNREACHABLE(); +} + */ +std::string DebugPrint(IsoTime time) +{ + std::ostringstream os; + os << std::put_time(&time, "%Y-%m-%d %H:%M:%S %z"); + return os.str(); +} + +std::string DebugPrint(Directionality directionality) +{ + switch (directionality) + { + case Directionality::OneDirection: return "OneDirection"; + case Directionality::BothDirections: return "BothDirections"; + } + UNREACHABLE(); +} + +std::string DebugPrint(Ramps ramps) +{ + switch (ramps) + { + case Ramps::All: return "All"; + case Ramps::Entry: return "Entry"; + case Ramps::Exit: return "Exit"; + case Ramps::None: return "None"; + } + UNREACHABLE(); +} + +std::string DebugPrint(RoadClass roadClass) +{ + switch (roadClass) + { + case RoadClass::Motorway: return "Motorway"; + case RoadClass::Trunk: return "Trunk"; + case RoadClass::Primary: return "Primary"; + case RoadClass::Secondary: return "Secondary"; + case RoadClass::Tertiary: return "Tertiary"; + case RoadClass::Other: return "Other"; + } + UNREACHABLE(); +} + +std::string DebugPrint(EventClass eventClass) +{ + switch (eventClass) + { + case EventClass::Invalid: return "Invalid"; + case EventClass::Activity: return "Activity"; + case EventClass::Authority: return "Authority"; + case EventClass::Carpool: return "Carpool"; + case EventClass::Congestion: return "Congestion"; + case EventClass::Construction: return "Construction"; + case EventClass::Delay: return "Delay"; + case EventClass::Environment: return "Environment"; + case EventClass::EquipmentStatus: return "EquipmentStatus"; + case EventClass::Hazard: return "Hazard"; + case EventClass::Incident: return "Incident"; + case EventClass::Restriction: return "Restriction"; + case EventClass::Security: return "Security"; + case EventClass::Transport: return "Transport"; + case EventClass::Weather: return "Weather"; + } + UNREACHABLE(); +} + +std::string DebugPrint(EventType eventType) +{ + switch (eventType) + { + case EventType::Invalid: return "Invalid"; + // TODO Activity*, Authority*, Carpool* (not in enum yet) + case EventType::CongestionCleared: return "CongestionCleared"; + case EventType::CongestionForecastWithdrawn: return "CongestionForecastWithdrawn"; + case EventType::CongestionHeavyTraffic: return "CongestionHeavyTraffic"; + case EventType::CongestionLongQueue: return "CongestionLongQueue"; + case EventType::CongestionNone: return "CongestionNone"; + case EventType::CongestionNormalTraffic: return "CongestionNormalTraffic"; + case EventType::CongestionQueue: return "CongestionQueue"; + case EventType::CongestionQueueLikely: return "CongestionQueueLikely"; + case EventType::CongestionSlowTraffic: return "CongestionSlowTraffic"; + case EventType::CongestionStationaryTraffic: return "CongestionStationaryTraffic"; + case EventType::CongestionStationaryTrafficLikely: return "CongestionStationaryTrafficLikely"; + case EventType::CongestionTrafficBuildingUp: return "CongestionTrafficBuildingUp"; + case EventType::CongestionTrafficCongestion: return "CongestionTrafficCongestion"; + case EventType::CongestionTrafficEasing: return "CongestionTrafficEasing"; + case EventType::CongestionTrafficFlowingFreely: return "CongestionTrafficFlowingFreely"; + case EventType::CongestionTrafficHeavierThanNormal: return "CongestionTrafficHeavierThanNormal"; + case EventType::CongestionTrafficLighterThanNormal: return "CongestionTrafficLighterThanNormal"; + case EventType::CongestionTrafficMuchHeavierThanNormal: return "CongestionTrafficMuchHeavierThanNormal"; + case EventType::CongestionTrafficProblem: return "CongestionTrafficProblem"; + // TODO Construction* (not in enum yet) + case EventType::DelayClearance: return "DelayClearance"; + case EventType::DelayDelay: return "DelayDelay"; + case EventType::DelayDelayPossible: return "DelayDelayPossible"; + case EventType::DelayForecastWithdrawn: return "DelayForecastWithdrawn"; + case EventType::DelayLongDelay: return "DelayLongDelay"; + case EventType::DelaySeveralHours: return "DelaySeveralHours"; + case EventType::DelayUncertainDuration: return "DelayUncertainDuration"; + case EventType::DelayVeryLongDelay: return "DelayVeryLongDelay"; + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) + // TODO complete Restriction* (not in enum yet) + case EventType::RestrictionBlocked: return "RestrictionBlocked"; + case EventType::RestrictionBlockedAhead: return "RestrictionBlockedAhead"; + case EventType::RestrictionCarriagewayBlocked: return "RestrictionCarriagewayBlocked"; + case EventType::RestrictionCarriagewayClosed: return "RestrictionCarriagewayClosed"; + case EventType::RestrictionClosed: return "RestrictionClosed"; + case EventType::RestrictionClosedAhead: return "RestrictionClosedAhead"; + case EventType::RestrictionEntryBlocked: return "RestrictionEntryBlocked"; + case EventType::RestrictionEntryReopened: return "RestrictionEntryReopened"; + case EventType::RestrictionExitBlocked: return "RestrictionExitBlocked"; + case EventType::RestrictionExitReopened: return "RestrictionExitReopened"; + case EventType::RestrictionOpen: return "RestrictionOpen"; + case EventType::RestrictionRampBlocked: return "RestrictionRampBlocked"; + case EventType::RestrictionRampClosed: return "RestrictionRampClosed"; + case EventType::RestrictionRampReopened: return "RestrictionRampReopened"; + case EventType::RestrictionReopened: return "RestrictionReopened"; + case EventType::RestrictionSpeedLimit: return "RestrictionSpeedLimit"; + case EventType::RestrictionSpeedLimitLifted: return "RestrictionSpeedLimitLifted"; + // TODO Security*, Transport*, Weather* (not in enum yet) + } + UNREACHABLE(); +} + +std::string DebugPrint(Point point) +{ + std::ostringstream os; + os << "Point { "; + os << "coordinates: " << DebugPrint(point.m_coordinates) << ", "; + // TODO optional float m_distance; (not in struct yet) + os << "junctionName: " << point.m_junctionName.value_or("nullopt") << ", "; + os << "junctionRef: " << point.m_junctionRef.value_or("nullopt"); + os << " }"; + return os.str(); +} + +std::string DebugPrint(TraffLocation location) +{ + std::ostringstream os; + os << "TraffLocation { "; + os << "from: " << (location.m_from ? DebugPrint(location.m_from.value()) : "nullopt") << ", "; + os << "at: " << (location.m_at ? DebugPrint(location.m_at.value()) : "nullopt") << ", "; + os << "via: " << (location.m_via ? DebugPrint(location.m_via.value()) : "nullopt") << ", "; + os << "to: " << (location.m_to ? DebugPrint(location.m_to.value()) : "nullopt") << ", "; + os << "notVia: " << (location.m_notVia ? DebugPrint(location.m_notVia.value()) : "nullopt") << ", "; + // TODO fuzziness (not yet implemented in struct) + os << "country: " << location.m_country.value_or("nullopt") << ", "; + os << "territory: " << location.m_territory.value_or("nullopt") << ", "; + os << "town: " << location.m_town.value_or("nullopt") << ", "; + os << "roadClass: " << (location.m_roadClass ? DebugPrint(location.m_roadClass.value()) : "nullopt") << ", "; + os << "roadRef: " << location.m_roadRef.value_or("nullopt") << ", "; + os << "roadName: " << location.m_roadName.value_or("nullopt") << ", "; + os << "origin: " << location.m_origin.value_or("nullopt") << ", "; + os << "destination: " << location.m_destination.value_or("nullopt") << ", "; + os << "direction: " << location.m_direction.value_or("nullopt") << ", "; + os << "directionality: " << DebugPrint(location.m_directionality) << ", "; + os << "ramps: " << (location.m_ramps ? DebugPrint(location.m_ramps.value()) : "nullopt"); + os << " }"; + return os.str(); +} + +std::string DebugPrint(TraffEvent event) +{ + std::ostringstream os; + os << "TraffEvent { "; + os << "class: " << DebugPrint(event.m_Class) << ", "; + os << "type: " << DebugPrint(event.m_Type) << ", "; + os << "length: " << (event.m_length ? std::to_string(event.m_length.value()) : "nullopt") << ", "; + os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", "; + // TODO optional quantifier + os << "speed: " << (event.m_speed ? std::to_string(event.m_speed.value()) : "nullopt"); + // TODO supplementary information + os << " }"; + return os.str(); +} + +std::string DebugPrint(TraffMessage message) +{ + std::string sep; + std::ostringstream os; + os << "TraffMessage { "; + os << "id: " << message.m_id << ", "; + + os << "replaces: ["; + sep = " "; + for (auto const & replacedId : message.m_replaces) + { + os << sep << replacedId; + sep = ", "; + } + os << " ], "; + + os << "receiveTime: " << DebugPrint(message.m_receiveTime) << ", "; + os << "updateTime: " << DebugPrint(message.m_updateTime) << ", "; + os << "expirationTime: " << DebugPrint(message.m_expirationTime) << ", "; + os << "startTime: " << (message.m_startTime ? DebugPrint(message.m_startTime.value()) : "nullopt") << ", "; + os << "endTime: " << (message.m_endTime ? DebugPrint(message.m_endTime.value()) : "nullopt") << ", "; + os << "cancellation: " << message.m_cancellation << ", "; + os << "forecast: " << message.m_forecast << ", "; + // TODO std::optional m_urgency; (not in struct yet) + os << "location: " << (message.m_location ? DebugPrint(message.m_location.value()) : "nullopt") << ", "; + + os << "events: ["; + sep = " "; + for (auto const & event : message.m_events) + { + os << sep << DebugPrint(event); + sep = ", "; + } + os << " ]"; + + os << " }"; + return os.str(); +} + +std::string DebugPrint(TraffFeed feed) +{ + std::string sep; + std::ostringstream os; + os << "[ "; + sep = ""; + for (auto const & message : feed) + { + os << sep << DebugPrint(message); + sep = ", "; + } + os << " ]"; + return os.str(); +} +} // namespace traffxml diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp new file mode 100644 index 000000000..bc1f521c3 --- /dev/null +++ b/traffxml/traff_model.hpp @@ -0,0 +1,203 @@ +#pragma once + +//#include "traffxml/traff_foo.hpp" + +#include "geometry/latlon.hpp" +#include "geometry/point2d.hpp" + +#include +#include + +namespace traffxml +{ +/** + * @brief Date and time decoded from ISO 8601. + * + * `IsoTime` is an opaque type. It is only guaranteed to be capable of holding a timestamp + * converted from ISO 8601 which refers to the same UTC time as its ISO 8601 representation. + * Time zone information is not guaranteed to be preserved: `13:37+01:00` may be returned e.g. as + * `12:37Z` or `06:37-06:00`. + * + * Code using `IsoTime` must not rely on it being identical to any other type, as this is not + * guaranteed to be stable. + */ +/* + * Where no time zone is indicated, the timestamp shall always be interpreted as UTC. + * `IsoTime` currently maps to `std::tm`, but this is not guaranteed. + */ +using IsoTime = std::tm; + +// TODO enum urgency + +enum class Directionality +{ + OneDirection, + BothDirections +}; + +// TODO enum fuzziness + +enum class Ramps +{ + None, + All, + Entry, + Exit +}; + +enum class RoadClass +{ + Motorway, + Trunk, + Primary, + Secondary, + Tertiary, + Other +}; + +enum class EventClass +{ + Invalid, + Activity, + Authority, + Carpool, + Congestion, + Construction, + Delay, + Environment, + EquipmentStatus, + Hazard, + Incident, + Restriction, + Security, + Transport, + Weather +}; + +enum class EventType +{ + Invalid, + // TODO Activity*, Authority*, Carpool* + CongestionCleared, + CongestionForecastWithdrawn, + CongestionHeavyTraffic, + CongestionLongQueue, + CongestionNone, + CongestionNormalTraffic, + CongestionQueue, + CongestionQueueLikely, + CongestionSlowTraffic, + CongestionStationaryTraffic, + CongestionStationaryTrafficLikely, + CongestionTrafficBuildingUp, + CongestionTrafficCongestion, + CongestionTrafficEasing, + CongestionTrafficFlowingFreely, + CongestionTrafficHeavierThanNormal, + CongestionTrafficLighterThanNormal, + CongestionTrafficMuchHeavierThanNormal, + CongestionTrafficProblem, + // TODO Construction* + DelayClearance, + DelayDelay, + DelayDelayPossible, + DelayForecastWithdrawn, + DelayLongDelay, + DelaySeveralHours, + DelayUncertainDuration, + DelayVeryLongDelay, + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* + // TODO complete Restriction* + RestrictionBlocked, + RestrictionBlockedAhead, + RestrictionCarriagewayBlocked, + RestrictionCarriagewayClosed, + RestrictionClosed, + RestrictionClosedAhead, + RestrictionEntryBlocked, + RestrictionEntryReopened, + RestrictionExitBlocked, + RestrictionExitReopened, + RestrictionOpen, + RestrictionRampBlocked, + RestrictionRampClosed, + RestrictionRampReopened, + RestrictionReopened, + RestrictionSpeedLimit, + RestrictionSpeedLimitLifted, + // TODO Security*, Transport*, Weather* +}; + +struct Point +{ + // TODO role? + ms::LatLon m_coordinates = ms::LatLon::Zero(); + // TODO optional float m_distance; + std::optional m_junctionName; + std::optional m_junctionRef; +}; + +struct TraffLocation +{ + std::optional m_country; + std::optional m_destination; + std::optional m_direction; + Directionality m_directionality = Directionality::BothDirections; + // TODO std::optional m_fuzziness; + std::optional m_origin; + std::optional m_ramps; + std::optional m_roadClass; + // disabled for now, optional behaves weird and we don't really need it + //std::optional m_roadIsUrban; + std::optional m_roadRef; + std::optional m_roadName; + std::optional m_territory; + std::optional m_town; + std::optional m_from; + std::optional m_to; + std::optional m_at; + std::optional m_via; + std::optional m_notVia; +}; + +struct TraffEvent +{ + EventClass m_Class = EventClass::Invalid; + EventType m_Type = EventType::Invalid; + std::optional m_length; + std::optional m_probability; + // TODO optional quantifier + std::optional m_speed; + // TODO supplementary information +}; + +struct TraffMessage +{ + std::string m_id; + IsoTime m_receiveTime = {}; + IsoTime m_updateTime = {}; + IsoTime m_expirationTime = {}; + std::optional m_startTime = {}; + std::optional m_endTime = {}; + bool m_cancellation = false; + bool m_forecast = false; + // TODO std::optional m_urgency; + std::optional m_location; + std::vector m_events; + std::vector m_replaces; +}; + +using TraffFeed = std::vector; + +std::string DebugPrint(IsoTime time); +std::string DebugPrint(Directionality directionality); +std::string DebugPrint(Ramps ramps); +std::string DebugPrint(RoadClass roadClass); +std::string DebugPrint(EventClass eventClass); +std::string DebugPrint(EventType eventType); +std::string DebugPrint(Point point); +std::string DebugPrint(TraffLocation location); +std::string DebugPrint(TraffEvent event); +std::string DebugPrint(TraffMessage message); +std::string DebugPrint(TraffFeed feed); +} // namespace traffxml diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp new file mode 100644 index 000000000..4ad50f25a --- /dev/null +++ b/traffxml/traff_model_xml.cpp @@ -0,0 +1,677 @@ +#include "traffxml/traff_model_xml.hpp" +#include "traffxml/traff_model.hpp" + +#include "base/logging.hpp" + +#include +#include +#include +#include +#include + +#include + +using namespace std; + +namespace traffxml +{ +const std::map kDirectionalityMap{ + {"ONE_DIRECTION", Directionality::OneDirection}, + {"BOTH_DIRECTIONS", Directionality::BothDirections} +}; + +const std::map kRampsMap{ + {"ALL_RAMPS", Ramps::All}, + {"ENTRY_RAMP", Ramps::Entry}, + {"EXIT_RAMP", Ramps::Exit}, + {"NONE", Ramps::None} +}; + +const std::map kRoadClassMap{ + {"MOTORWAY", RoadClass::Motorway}, + {"TRUNK", RoadClass::Trunk}, + {"PRIMARY", RoadClass::Primary}, + {"SECONDARY", RoadClass::Secondary}, + {"TERTIARY", RoadClass::Tertiary}, + {"OTHER", RoadClass::Other} +}; + +const std::map kEventClassMap{ + {"INVALID", EventClass::Invalid}, + {"ACTIVITY", EventClass::Activity}, + {"AUTHORITY", EventClass::Authority}, + {"CARPOOL", EventClass::Carpool}, + {"CONGESTION", EventClass::Congestion}, + {"CONSTRUCTION", EventClass::Construction}, + {"DELAY", EventClass::Delay}, + {"ENVIRONMENT", EventClass::Environment}, + {"EQUIPMENT_STATUS", EventClass::EquipmentStatus}, + {"HAZARD", EventClass::Hazard}, + {"INCIDENT", EventClass::Incident}, + {"RESTRICTION", EventClass::Restriction}, + {"SECURITY", EventClass::Security}, + {"TRANSPORT", EventClass::Transport}, + {"WEATHER", EventClass::Weather} +}; + +const std::map kEventTypeMap{ + {"INVALID", EventType::Invalid}, + // TODO Activity*, Authority*, Carpool* (not in enum yet) + {"CONGESTION_CLEARED", EventType::CongestionCleared}, + {"CONGESTION_FORECAST_WITHDRAWN", EventType::CongestionForecastWithdrawn}, + {"CONGESTION_HEAVY_TRAFFIC", EventType::CongestionHeavyTraffic}, + {"CONGESTION_LONG_QUEUE", EventType::CongestionLongQueue}, + {"CONGESTION_NONE", EventType::CongestionNone}, + {"CONGESTION_NORMAL_TRAFFIC", EventType::CongestionNormalTraffic}, + {"CONGESTION_QUEUE", EventType::CongestionQueue}, + {"CONGESTION_QUEUE_LIKELY", EventType::CongestionQueueLikely}, + {"CONGESTION_SLOW_TRAFFIC", EventType::CongestionSlowTraffic}, + {"CONGESTION_STATIONARY_TRAFFIC", EventType::CongestionStationaryTraffic}, + {"CONGESTION_STATIONARY_TRAFFIC_LIKELY", EventType::CongestionStationaryTrafficLikely}, + {"CONGESTION_TRAFFIC_BUILDING_UP", EventType::CongestionTrafficBuildingUp}, + {"CONGESTION_TRAFFIC_CONGESTION", EventType::CongestionTrafficCongestion}, + {"CONGESTION_TRAFFIC_EASING", EventType::CongestionTrafficEasing}, + {"CONGESTION_TRAFFIC_FLOWING_FREELY", EventType::CongestionTrafficFlowingFreely}, + {"CONGESTION_TRAFFIC_HEAVIER_THAN_NORMAL", EventType::CongestionTrafficHeavierThanNormal}, + {"CONGESTION_TRAFFIC_LIGHTER_THAN_NORMAL", EventType::CongestionTrafficLighterThanNormal}, + {"CONGESTION_TRAFFIC_MUCH_HEAVIER_THAN_NORMAL", EventType::CongestionTrafficMuchHeavierThanNormal}, + {"CONGESTION_TRAFFIC_PROBLEM", EventType::CongestionTrafficProblem}, + // TODO Construction* (not in enum yet) + {"DELAY_CLEARANCE", EventType::DelayClearance}, + {"DELAY_DELAY", EventType::DelayDelay}, + {"DELAY_DELAY_POSSIBLE", EventType::DelayDelayPossible}, + {"DELAY_FORECAST_WITHDRAWN", EventType::DelayForecastWithdrawn}, + {"DELAY_LONG_DELAY", EventType::DelayLongDelay}, + {"DELAY_SEVERAL_HOURS", EventType::DelaySeveralHours}, + {"DELAY_UNCERTAIN_DURATION", EventType::DelayUncertainDuration}, + {"DELAY_VERY_LONG_DELAY", EventType::DelayVeryLongDelay}, + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) + // TODO complete Restriction* (not in enum yet) + {"RESTRICTION_BLOCKED", EventType::RestrictionBlocked}, + {"RESTRICTION_BLOCKED_AHEAD", EventType::RestrictionBlockedAhead}, + {"RESTRICTION_CARRIAGEWAY_BLOCKED", EventType::RestrictionCarriagewayBlocked}, + {"RESTRICTION_CARRIAGEWAY_CLOSED", EventType::RestrictionCarriagewayClosed}, + {"RESTRICTION_CLOSED", EventType::RestrictionClosed}, + {"RESTRICTION_CLOSED_AHEAD", EventType::RestrictionClosedAhead}, + {"RESTRICTION_ENTRY_BLOCKED", EventType::RestrictionEntryBlocked}, + {"RESTRICTION_ENTRY_REOPENED", EventType::RestrictionEntryReopened}, + {"RESTRICTION_EXIT_BLOCKED", EventType::RestrictionExitBlocked}, + {"RESTRICTION_EXIT_REOPENED", EventType::RestrictionExitReopened}, + {"RESTRICTION_OPEN", EventType::RestrictionOpen}, + {"RESTRICTION_RAMP_BLOCKED", EventType::RestrictionRampBlocked}, + {"RESTRICTION_RAMP_CLOSED", EventType::RestrictionRampClosed}, + {"RESTRICTION_RAMP_REOPENED", EventType::RestrictionRampReopened}, + {"RESTRICTION_REOPENED", EventType::RestrictionReopened}, + {"RESTRICTION_SPEED_LIMIT", EventType::RestrictionSpeedLimit}, + {"RESTRICTION_SPEED_LIMIT_LIFTED", EventType::RestrictionSpeedLimitLifted}, + // TODO Security*, Transport*, Weather* (not in enum yet) +}; + +/** + * @brief Retrieves an integer value from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @return `true` on success, `false` if the attribute is not set or does not contain an integer value. + */ +std::optional OptionalIntegerFromXml(pugi::xml_attribute attribute) +{ + if (attribute.empty()) + return std::nullopt; + try + { + uint8_t result = std::stoi(attribute.as_string()); + return result; + } + catch (std::invalid_argument const& ex) + { + return std::nullopt; + } +} + +/** + * @brief Retrieves a string from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @param string Receives the string retrieved. + * @return `true` on success, `false` if the attribute is not set or set to an empty string. + */ +bool StringFromXml(pugi::xml_attribute attribute, std::string & string) +{ + if (attribute.empty()) + return false; + string = attribute.as_string(); + return true; +} + +/** + * @brief Retrieves a string from an XML element. + * + * @param node The XML element to retrieve. + * @param string Receives the string retrieved. + * @return `true` on success, `false` if the node does not exist. + */ +bool StringFromXml(pugi::xml_node node, std::string & string) +{ + if (!node) + return false; + string = node.text().as_string(); + return true; +} + +/** + * @brief Retrieves a string from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @return The string, or `std::nullopt` if the attribute is not set or set to an empty string. + */ +std::optional OptionalStringFromXml(pugi::xml_attribute attribute) +{ + std::string result; + if (!StringFromXml(attribute, result)) + return std::nullopt; + return result; +} + +/** + * @brief Parses time in ISO 8601 format from a time attribute and stores it in an `IsoTime`. + * + * ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC + * offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which + * is 10:45:42 UTC. + * + * A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed + * if no UTC offset is specified. + * + * The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`. + * + * Seconds can be specified as integer or float, but will be rounded to the nearest integer. For + * example, 42.645 seconds will be rounded to 43 seconds. + * + * @param attribute The XML attribute from which to receive time. + * @param tm Receives the parsed time. + * @return `true` on success, `false` if the attribute is not set or does not contain a timestamp. + */ +bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) +{ + std::string timeString; + if (!StringFromXml(attribute, timeString)) + return false; + /* + * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher + * will contain the following items: + * + * 0: 2019-11-01T11:55:42+01:00 (entire expression) + * 1: 2019 (year) + * 2: 11 (month) + * 3: 01 (day) + * 4: 11 (hour, local) + * 5: 55 (minute, local) + * 6: 42.445 (second, local, float) + * 7: .445 (fractional seconds) + * 8: +01:00 (complete UTC offset, or Z; blank if not specified) + * 9: +01:00 (complete UTC offset, blank for Z or of not specified) + * 10: +01 (UTC offset, hours with sign; blank for Z or if not specified) + * 11: :00 (UTC offset, minutes, prefixed with separator) + * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) + */ + std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); + + std::smatch iso8601Matcher; + if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) + { + int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; + int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; + if (offset_h < 0) + offset_m *= -1; + + tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; + tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; + tm.tm_mday = std::stoi(iso8601Matcher[3]); + tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; + tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; + tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; + // Call timegm once to normalize tm; return value can be discarded + timegm(&tm); + return true; + } + else + { + LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString)); + return false; + } +} + +/** + * @brief Parses time in ISO 8601 format from a time attribute and stores it in an `IsoTime`. + * + * ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC + * offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which + * is 10:45:42 UTC. + * + * A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed + * if no UTC offset is specified. + * + * The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`. + * + * Seconds can be specified as integer or float, but will be rounded to the nearest integer. For + * example, 42.645 seconds will be rounded to 43 seconds. + * + * @param attribute The XML attribute from which to receive time. + * @return The parsed time, or `std::nullopt` if the attribute is not set or does not contain a timestamp. + */ +std::optional OptionalTimeFromXml(pugi::xml_attribute attribute) +{ + IsoTime result = {}; + if (!TimeFromXml(attribute, result)) + return std::nullopt; + return result; +} + +/** + * @brief Retrieves a boolean value from an attribute. + * @param attribute The XML attribute to retrieve. + * @param defaultValue The default value to return. + * @return The value of the attribute, or `defaultValue` if the attribute is not set. + */ +bool BoolFromXml(pugi::xml_attribute attribute, bool defaultValue) +{ + if (attribute.empty()) + return defaultValue; + return attribute.as_bool(); +} + +/** + * @brief Retrieves a boolean value from an attribute. + * @param attribute The XML attribute to retrieve. + * @return The value of the attribute, or `std::nullopt` if the attribute is not set. + */ +std::optional OptionalBoolFromXml(pugi::xml_attribute attribute) +{ + if (attribute.empty()) + return std::nullopt; + return attribute.as_bool(); +} + +/** + * @brief Retrieves an enum value from an attribute. + * + * Enum values are retrieved in two steps: first, a string is retrieved, which is then decoded to + * an enum value. The enum type is determined by the type of `value` and the value type of `map`, + * both of which must match. The mapping between strings and their corresponding enum values is + * determined by the entries in `map`. + * + * @param attribute The XML attribute to retrieve. + * @param Value Receives the enum value to retrieve. + * @param map A map from strings to their respective enum values. + * @return `true` on success, `false` if the attribute is not set or its value is not found in `map`. + */ +template +bool EnumFromXml(pugi::xml_attribute attribute, Value & value, std::map map) +{ + std::string string; + if (StringFromXml(attribute, string)) + { + auto it = map.find(string); + if (it != map.end()) + { + value = it->second; + return true; + } + else + LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)")); + } + return false; +} + +/** + * @brief Retrieves an enum value from an attribute. + * + * Enum values are retrieved in two steps: first, a string is retrieved, which is then decoded to + * an enum value. The enum type is determined by the value type of `map`. The mapping between + * strings and their corresponding enum values is determined by the entries in `map`. + * + * @param attribute The XML attribute to retrieve. + * @param map A map from strings to their respective enum values. + * @return The enum value, or `std::nullopt` if the attribute is not set or its value is not found in `map`. + */ +template +std::optional OptionalEnumFromXml(pugi::xml_attribute attribute, std::map map) +{ + std::string string; + if (StringFromXml(attribute, string)) + { + auto it = map.find(string); + if (it != map.end()) + return it->second; + else + LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)")); + } + return std::nullopt; +} + +/** + * @brief Retrieves the IDs of replaced messages from an XML element. + * @param node The XML element to retrieve (`merge`). + * @param replacedIds Receives the replaced IDs. + * @return `true` on success (including if the node contains no replaced IDs), `false` if the node does not exist or does not contain valid data. + */ +bool ReplacedMessageIdsFromXml(pugi::xml_node node, std::vector & replacedIds) +{ + if (!node) + return false; + + bool result = false; + auto const replacedIdNodes = node.select_nodes("./replaces"); + + if (replacedIdNodes.empty()) + return true; + + for (auto const & xpathNode : replacedIdNodes) + { + auto const replacedIdNode = xpathNode.node(); + std::string replacedId; + if (StringFromXml(replacedIdNode.attribute("id"), replacedId)) + { + replacedIds.push_back(replacedId); + result = true; + } + else + LOG(LWARNING, ("Could not parse merge element, skipping")); + } + return result; +} + +/** + * @brief Retrieves a latitude/longitude pair from an XML element. + * + * Coordinates must be given as latitude, followed by as space, then longitude. Latitude and + * longitude are given as floating-point numbers, optionally with a sign (plus is assumed if no + * sign is given). Coordinates are interpreted as degrees in WGS84 format. + * + * @param node The XML element to retrieve. + * @param latLon Receives the latitude/longitude pair. + * @return `true` on success, `false` if the node does not exist or does not contain valid coordinates. + */ +bool LatLonFromXml(pugi::xml_node node, ms::LatLon & latLon) +{ + if (!node) + return false; + std::string string; + if (StringFromXml(node, string)) + { + std::regex latLonRegex("([+-]?[0-9]*\\.?[0-9]*)\\s+([+-]?[0-9]*\\.?[0-9]*)"); + std::smatch latLonMatcher; + if (std::regex_search(string, latLonMatcher, latLonRegex) && latLonMatcher[1].matched && latLonMatcher[2].matched) + { + try + { + latLon.m_lat = std::stod(latLonMatcher[1]); + latLon.m_lon = std::stod(latLonMatcher[2]); + return true; + } + catch (std::invalid_argument const& ex) + { + LOG(LWARNING, ("Not a valid coordinate pair:", string)); + } + } + else + LOG(LWARNING, ("Not a valid coordinate pair:", string)); + } + return false; +} + +/** + * @brief Retrieves a Traff `Point` from an XML element. + * @param node The XML element to retrieve (any child of `location`). + * @return The point, or `std::nullopt` if the node does not exist or does not contain valid point data, + */ +std::optional OptionalPointFromXml(pugi::xml_node node) +{ + if (!node) + return std::nullopt; + Point result; + + if (!LatLonFromXml(node, result.m_coordinates)) + { + LOG(LWARNING, (node.name(), "has no coordinates, ignoring")); + return std::nullopt; + } + + // TODO optional float m_distance (not yet implemented in struct) + + result.m_junctionName = OptionalStringFromXml(node.attribute("junction_name")); + result.m_junctionRef = OptionalStringFromXml(node.attribute("junction_ref")); + + return result; +} + +/** + * @brief Retrieves a `TraffLocation` from an XML element. + * @param node The XML element to retrieve (`location`). + * @param location Receives the location. + * @return `true` on success, `false` if the node does not exist or does not contain valid location data. + */ +bool LocationFromXml(pugi::xml_node node, TraffLocation & location) +{ + if (!node) + return false; + + location.m_from = OptionalPointFromXml(node.child("from")); + location.m_to = OptionalPointFromXml(node.child("to")); + location.m_at = OptionalPointFromXml(node.child("at")); + location.m_via = OptionalPointFromXml(node.child("via")); + location.m_notVia = OptionalPointFromXml(node.child("not_via")); + + if (!location.m_from && !location.m_to && !location.m_at) + { + LOG(LWARNING, ("Neither from, to nor at point is specified, ignoring location")); + return false; + } + + location.m_country = OptionalStringFromXml(node.attribute("country")); + location.m_destination = OptionalStringFromXml(node.attribute("destination")); + location.m_direction = OptionalStringFromXml(node.attribute("direction")); + + EnumFromXml(node.attribute("directionality"), location.m_directionality, kDirectionalityMap); + + // TODO fuzziness (not yet implemented in struct) + + location.m_origin = OptionalStringFromXml(node.attribute("origin")); + location.m_ramps = OptionalEnumFromXml(node.attribute("ramps"), kRampsMap); + location.m_roadClass = OptionalEnumFromXml(node.attribute("road_class"), kRoadClassMap); + // disabled for now + //location.m_roadIsUrban = OptionalBoolFromXml(node.attribute(("road_is_urban"))); + location.m_roadRef = OptionalStringFromXml(node.attribute("road_ref")); + location.m_roadName = OptionalStringFromXml(node.attribute("road_name")); + location.m_territory = OptionalStringFromXml(node.attribute("territory")); + location.m_town = OptionalStringFromXml(node.attribute("town")); + + return true; +} + +/** + * @brief Retrieves a `TraffEvent` from an XML element. + * @param node The XML element to retrieve (`event`). + * @param event Receives the event. + * @return `true` on success, `false` if the node does not exist or does not contain valid event data. + */ +bool EventFromXml(pugi::xml_node node, TraffEvent & event) +{ + std::string eventClass; + if (!StringFromXml(node.attribute("class"), eventClass)) + { + LOG(LWARNING, ("No event class specified, ignoring")); + return false; + } + if (!EnumFromXml(node.attribute("class"), event.m_Class, kEventClassMap)) + return false; + + std::string eventType; + if (!StringFromXml(node.attribute("type"), eventType)) + { + LOG(LWARNING, ("No event type specified, ignoring")); + return false; + } + if (!eventType.starts_with(eventClass + "_")) + { + LOG(LWARNING, ("Event type", eventType, "does not match event class", eventClass, "(ignoring)")); + return false; + } + if (!EnumFromXml(node.attribute("type"), event.m_Type, kEventTypeMap)) + return false; + + event.m_length = OptionalIntegerFromXml(node.attribute("length")); + event.m_probability = OptionalIntegerFromXml(node.attribute("probability")); + + // TODO optional quantifier (not yet implemented in struct) + + event.m_speed = OptionalIntegerFromXml(node.attribute("speed")); + + // TODO supplementary information (not yet implemented in struct) + return true; +} + +/** + * @brief Retrieves the TraFF events associsted with a message from an XML element. + * @param node The XML element to retrieve (`events`). + * @param events Receives the events. + * @return `true` on success, `false` if the node does not exist or does not contain valid event data (including if the node contains no events). + */ +bool EventsFromXml(pugi::xml_node node, std::vector & events) +{ + if (!node) + return false; + + bool result = false; + auto const eventNodes = node.select_nodes("./event"); + + if (eventNodes.empty()) + return false; + + for (auto const & xpathNode : eventNodes) + { + auto const eventNode = xpathNode.node(); + TraffEvent event; + if (EventFromXml(eventNode, event)) + { + events.push_back(event); + result = true; + } + else + LOG(LWARNING, ("Could not parse event, skipping")); + } + return result; +} + +/** + * @brief Retrieves a TraFF message from an XML element. + * @param node The XML element to retrieve (`message`). + * @param message Receives the message. + * @return `true` on success, `false` if the node does not exist or does not contain valid message data. + */ +bool MessageFromXml(pugi::xml_node node, TraffMessage & message) +{ + if (!StringFromXml(node.attribute("id"), message.m_id)) + { + LOG(LWARNING, ("Message has no id")); + return false; + } + + if (!TimeFromXml(node.attribute("receive_time"), message.m_receiveTime)) + { + LOG(LWARNING, ("Message", message.m_id, "has no receive_time")); + return false; + } + + if (!TimeFromXml(node.attribute("update_time"), message.m_updateTime)) + { + LOG(LWARNING, ("Message", message.m_id, "has no update_time")); + return false; + } + + if (!TimeFromXml(node.attribute("expiration_time"), message.m_expirationTime)) + { + LOG(LWARNING, ("Message", message.m_id, "has no expiration_time")); + return false; + } + + message.m_startTime = OptionalTimeFromXml(node.attribute("start_time")); + message.m_endTime = OptionalTimeFromXml(node.attribute("end_time")); + + message.m_cancellation = BoolFromXml(node.attribute("cancellation"), false); + message.m_forecast = BoolFromXml(node.attribute("forecast"), false); + + // TODO urgency (not yet implemented in struct) + + ReplacedMessageIdsFromXml(node.child("merge"), message.m_replaces); + + if (!message.m_cancellation) + { + message.m_location.emplace(); + if (!LocationFromXml(node.child("location"), message.m_location.value())) + { + message.m_location.reset(); + LOG(LWARNING, ("Message", message.m_id, "has no location but is not a cancellation message")); + return false; + } + + if (!EventsFromXml(node.child("events"), message.m_events)) + { + LOG(LWARNING, ("Message", message.m_id, "has no events but is not a cancellation message")); + return false; + } + } + return true; +} + +/** + * @brief Retrieves a TraFF feed from an XML document. + * + * The document must conform loosely to the TraFF specification (currently version 0.8). + * + * The name of the root element is not verified, but the `message` elements must be its immediate + * children. + * + * Custom elements and attributes which are not part of the TraFF specification are ignored. + * + * Values which cannot be parsed correctly are skipped. + * + * Events whose event type does not match their event class are skipped. + * + * Messages, events, locations or points which lack mandatory information are skipped. + * + * If children are skipped but the parent remains valid, parsing it will report success. + * + * Parsing the feed will report failure if all its messages fail to parse, but not if it has no + * messages. + * + * @param document The XML document from which to retrieve the messages. + * @param feed Receives the TraFF feed. + * @return `true` on success, `false` on failure. + */ +bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) +{ + bool result = false; + + // Select all messages elements that are direct children of the root. + auto const messages = document.document_element().select_nodes("./message"); + + if (messages.empty()) + return true; + + // TODO try block? + for (auto const & xpathNode : messages) + { + auto const messageNode = xpathNode.node(); + TraffMessage message; + if (MessageFromXml(messageNode, message)) + { + feed.push_back(message); + result = true; + } + else + LOG(LWARNING, ("Could not parse message, skipping")); + } + return result; +} +} // namespace openlr diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp new file mode 100644 index 000000000..ed090f7ce --- /dev/null +++ b/traffxml/traff_model_xml.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "traffxml/traff_model.hpp" + +namespace pugi +{ +class xml_document; +class xml_node; +} // namespace pugi + +namespace traffxml +{ +bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed); +} // namespace traffxml From 2017907b1fe614e06182a80c067e8bdc09f8f34f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 30 Apr 2025 22:15:54 +0300 Subject: [PATCH 012/252] [traffic] Documentation Signed-off-by: mvglasow --- map/traffic_manager.hpp | 179 +++++++++++++++++++++++++++++++++++++-- traffic/traffic_info.hpp | 21 +++++ 2 files changed, 195 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index e8bcb9956..2f7690dde 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -35,15 +35,26 @@ class TrafficManager final public: using CountryParentNameGetterFn = std::function; + /** + * @brief Global state of traffic information. + */ enum class TrafficState { + /** Traffic is disabled, no traffic data will be retrieved or considered for routing. */ Disabled, + /** Traffic is enabled and working normally (the first request may not have been scheduled yet). */ Enabled, + /** At least one request is currently pending. */ WaitingData, + /** At least one MWM has stale traffic data. */ Outdated, + /** Traffic data for at least one MWM was invalid or not found on the server. */ NoData, + /** At least one request failed or timed out. */ NetworkError, + /** Traffic data could not be retrieved because the map data is outdated. */ ExpiredData, + /** Traffic data could not be retrieved because the app version is outdated. */ ExpiredApp }; @@ -112,46 +123,172 @@ class TrafficManager final bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; } private: + /** + * @brief Holds information about pending or previous traffic requests pertaining to an MWM. + */ struct CacheEntry { CacheEntry(); explicit CacheEntry(std::chrono::time_point const & requestTime); + /** + * @brief Whether we have traffic data for this MWM. + */ bool m_isLoaded; + + /** + * @brief The amount of memory occupied by the coloring for this MWM. + */ size_t m_dataSize; + /** + * @brief When the last update request occurred, not including forced updates. + * + * This timestamp is the basis for eliminating the oldest entries from the cache. + */ std::chrono::time_point m_lastActiveTime; + + /** + * @brief When the last update request occurred, including forced updates. + * + * This timestamp is the basis for determining whether an update is needed. + */ std::chrono::time_point m_lastRequestTime; + + /** + * @brief When the last response was received. + * + * This timestamp is the basis for determining whether a network request timed out, or if data is outdated. + */ std::chrono::time_point m_lastResponseTime; + /** + * @brief The number of failed traffic requests for this MWM. + * + * Reset when the MWM becomes inactive. + */ int m_retriesCount; + + /** + * @brief Whether a request is currently pending for this MWM. + * + * Set to `true` when a request is scheduled, reverted to `false` when a response is received or the request fails. + */ bool m_isWaitingForResponse; traffic::TrafficInfo::Availability m_lastAvailability; }; + /** + * @brief Event loop for the traffic worker thread. + * + * This method runs an event loop, which blocks until woken up or a timeout equivalent to the + * update interval elapses. It cycles through the list of MWMs for which updates have been + * scheduled, triggering a network request for each and processing the result. + */ void ThreadRoutine(); + + /** + * @brief Blocks until a request for traffic data is received or a timeout expires. + * + * This method acts as the loop condition for `ThreadRoutine()`. It blocks until woken up or the + * update interval expires. In the latter case, it calls `RequestTrafficData()` to insert all + * currently active MWMs into the list of MWMs to update; otherwise, it leaves the list as it is. + * In either case, it populates `mwms` with the list and returns. + * + * @param mwms Receives a list of MWMs for which to update traffic data. + * @return `true` during normal operation, `false` during teardown (signaling the event loop to exit). + */ bool WaitForRequest(std::vector & mwms); void OnTrafficDataResponse(traffic::TrafficInfo && info); + /** + * @brief Processes a failed traffic request. + * + * This method gets called when a traffic request has failed. + * + * It updates the `m_isWaitingForResponse` and `m_lastAvailability` of `info. + * + * If the MWM is no longer active, this method returns immediately after that. + * + * If the retry limit has not been reached, the MWM is re-inserted into the list by calling + * `RequestTrafficData(MwmSet::MwmId, bool)` with `force` set to true. Otherwise, the retry count + * is reset and the state updated accordingly. + * + * @param info + */ void OnTrafficRequestFailed(traffic::TrafficInfo && info); - /// \brief Updates |activeMwms| and request traffic data. - /// \param rect is a rectangle covering a new active mwm set. - /// \note |lastMwmsByRect|/|activeMwms| may be either |m_lastDrapeMwmsByRect/|m_activeDrapeMwms| - /// or |m_lastRoutingMwmsByRect|/|m_activeRoutingMwms|. - /// \note |m_mutex| is locked inside the method. So the method should be called without |m_mutex|. + /** + * @brief Updates `activeMwms` and requests traffic data. + * + * The old and new list of active MWMs may refer either to those used by the rendering engine + * (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those used by the routing engine + * (`m_lastRoutingMwmsByRect`/`m_activeRoutingMwms`). + * + * The method first determines the list of MWMs overlapping with `rect`. If it is identical to + * `lastMwmsByRect`, the method returns immediately. Otherwise, it stores the new set in + * `lastMwmsByRect` and populates `activeMwms` with the elements. + * + * This method locks `m_mutex` while populating `activeMwms`. There is no need for the caller to + * do that. + * + * @param rect Rectangle covering the new active MWM set. + * @param lastMwmsByRect Set of active MWMs, see description. + * @param activeMwms Vector of active MWMs, see description. + */ void UpdateActiveMwms(m2::RectD const & rect, std::vector & lastMwmsByRect, std::set & activeMwms); // This is a group of methods that haven't their own synchronization inside. + + /** + * @brief Requests a refresh of traffic data for all currently active MWMs. + * + * This method is the entry point for periodic traffic data refresh operations. It cycles through + * all active MWMs and calls `RequestTrafficData(MwmSet::MwmId, bool)` on each `MwmId`, + * scheduling a refresh if needed. The actual network operation is performed asynchronously on a + * separate thread. + * + * The method does nothing if the `TrafficManager` instance is disabled, paused, in an invalid + * state (`NetworkError`) or if neither the rendering engine nor the routing engine have any + * active MWMs. + * + * This method is unsynchronized; the caller must lock `m_mutex` prior to calling it. + */ void RequestTrafficData(); + + /** + * @brief Requests a refresh of traffic data for a single MWM. + * + * This method first checks if traffic data for the given MWM needs to be refreshed, which is the + * case if no traffic data has ever been fetched for the given MWM, the update interval has + * expired or `force` is true. In that case, the method inserts the `mwmId` into the list of MWMs + * for which to update traffic and wakes up the worker thread. + * + * This method is unsynchronized; the caller must lock `m_mutex` prior to calling it. + * + * @param mwmId Describes the MWM for which traffic data is to be refreshed. + * @param force If true, a refresh is requested even if the update interval has not expired. + */ void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force); void Clear(); void ClearCache(MwmSet::MwmId const & mwmId); void ShrinkCacheToAllowableSize(); + /** + * @brief Updates the state of the traffic manager based on the state of all MWMs used by the renderer. + * + * This method cycles through the state of all MWMs used by the renderer (MWMs used by the + * routing engine but not by the rendering engine are not considered), examines their traffic + * state and sets the global state accordingly. + * + * For a description of states, see `TrafficState`. The order of states is as follows, the first + * state whose conditions are fulfilled becomes the new state: `TrafficState::NetworkError`, + * `TrafficState::WaitingData`, `TrafficState::ExpiredApp`, `TrafficState::ExpiredData`, + * `TrafficState::NoData`, `TrafficState::Outdated`, `TrafficState::Enabled`. + */ void UpdateState(); void ChangeState(TrafficState newState); @@ -194,6 +331,31 @@ class TrafficManager final bool m_isRunning; std::condition_variable m_condition; + /* + * To determine for which MWMs we need traffic data, we need to keep track of two groups of MWMs: + * those used by the renderer (i.e. in or just around the viewport) and those used by the routing + * engine (i.e. those within a certain area around the route endpoints). + * + * Each group is stored twice: as a set and as a vector. The set always holds the MWMs which were + * last seen in use. Both get updated together when active MWMs are added or removed. However, + * the vector is used as a reference to detect changes. It may get cleared when the set is not, + * which is used to invalidate the set without destroying its contents. + * + * Methods which use only the set: + * + * * RequestTrafficData(), exits if empty, otherwise cycles through the set. + * * OnTrafficRequestFailed(), determines if an MWM is still active and the request should be retried. + * * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficData() or to shrink the cache). + * * UpdateState(), cycles through the set to determine the state of traffic requests (renderer only). + * + * Methods which use both, but in a different way: + * + * * ClearCache(), removes the requested MWM from the set but clears the vector completely. + * * Invalidate(), clears the vector but not the set. + * * UpdateActiveMwms(), uses the vector to detect changes. If so, it updates both vector and set. + * + * Clear() clears both the set and the vector. + */ std::vector m_lastDrapeMwmsByRect; std::set m_activeDrapeMwms; std::vector m_lastRoutingMwmsByRect; @@ -206,8 +368,15 @@ class TrafficManager final std::atomic m_isPaused; + /** + * @brief MWMs for which to retrieve traffic data. + */ std::vector m_requestedMwms; std::mutex m_mutex; + + /** + * @brief Worker thread which fetches traffic updates. + */ threads::SimpleThread m_thread; }; diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index 60e789986..a0a6ed147 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -25,12 +25,26 @@ class TrafficInfo static uint8_t const kLatestKeysVersion; static uint8_t const kLatestValuesVersion; + /** + * @brief Whether traffic data is available in this `TrafficInfo` instance. + */ + /* + * TODO A global traffic update would require some 2–3 states: + * * IsAvailable + * * Data available but not yet decoded + * * (possibly) No traffic reports for this MWM + */ enum class Availability { + /** This `TrafficInfo` instance has data available. */ IsAvailable, + /** No traffic data is available (file not found on the server, or server returned invalid data). */ NoData, + /** Traffic data could not be retrieved because the map data is outdated. */ ExpiredData, + /** Traffic data could not be retrieved because the app version is outdated. */ ExpiredApp, + /** No traffic data is available because the server responded with an error (other than “not found”), or no request was made yet. */ Unknown }; @@ -166,11 +180,18 @@ class TrafficInfo static void DeserializeTrafficValues(std::vector const & data, std::vector & result); private: + /** + * @brief Result of the last request to the server. + */ enum class ServerDataStatus { + /** New data was returned. */ New, + /** Data has not changed since the last request. */ NotChanged, + /** The URL was not found on the server. */ NotFound, + /** An error prevented data from being requested, or the server responded with an error. */ Error, }; From f0f847b2148833d89d829092018fd1f91bfbeb68 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 3 May 2025 22:57:33 +0300 Subject: [PATCH 013/252] [openlr] Debug output for FunctionalRoadClass Signed-off-by: mvglasow --- openlr/openlr_model.cpp | 17 +++++++++++++++++ openlr/openlr_model.hpp | 1 + 2 files changed, 18 insertions(+) diff --git a/openlr/openlr_model.cpp b/openlr/openlr_model.cpp index 44b47f350..dc9d90dad 100644 --- a/openlr/openlr_model.cpp +++ b/openlr/openlr_model.cpp @@ -38,4 +38,21 @@ string DebugPrint(LinearSegmentSource source) } UNREACHABLE(); } + +string DebugPrint(FunctionalRoadClass frc) +{ + switch (frc) + { + case FunctionalRoadClass::FRC0: return "FRC0"; + case FunctionalRoadClass::FRC1: return "FRC1"; + case FunctionalRoadClass::FRC2: return "FRC2"; + case FunctionalRoadClass::FRC3: return "FRC3"; + case FunctionalRoadClass::FRC4: return "FRC4"; + case FunctionalRoadClass::FRC5: return "FRC5"; + case FunctionalRoadClass::FRC6: return "FRC6"; + case FunctionalRoadClass::FRC7: return "FRC7"; + case FunctionalRoadClass::NotAValue: return "NotAValue"; + } + UNREACHABLE(); +} } // namespace openlr diff --git a/openlr/openlr_model.hpp b/openlr/openlr_model.hpp index a5f4b8435..a33a6ce97 100644 --- a/openlr/openlr_model.hpp +++ b/openlr/openlr_model.hpp @@ -121,4 +121,5 @@ struct LinearSegment }; std::string DebugPrint(LinearSegmentSource source); +std::string DebugPrint(FunctionalRoadClass frc); } // namespace openlr From 7be0b8a25650919730dd4e68d8adfc98ec8c396e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 4 May 2025 16:49:40 +0300 Subject: [PATCH 014/252] [traffxml] Adhere to naming convention for member names Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 4 ++-- traffxml/traff_model.hpp | 4 ++-- traffxml/traff_model_xml.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 8bf9fff8f..cd29d0dd9 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -181,8 +181,8 @@ std::string DebugPrint(TraffEvent event) { std::ostringstream os; os << "TraffEvent { "; - os << "class: " << DebugPrint(event.m_Class) << ", "; - os << "type: " << DebugPrint(event.m_Type) << ", "; + os << "class: " << DebugPrint(event.m_class) << ", "; + os << "type: " << DebugPrint(event.m_type) << ", "; os << "length: " << (event.m_length ? std::to_string(event.m_length.value()) : "nullopt") << ", "; os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", "; // TODO optional quantifier diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index bc1f521c3..bb76f74f7 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -162,8 +162,8 @@ struct TraffLocation struct TraffEvent { - EventClass m_Class = EventClass::Invalid; - EventType m_Type = EventType::Invalid; + EventClass m_class = EventClass::Invalid; + EventType m_type = EventType::Invalid; std::optional m_length; std::optional m_probability; // TODO optional quantifier diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 4ad50f25a..58532712d 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -503,7 +503,7 @@ bool EventFromXml(pugi::xml_node node, TraffEvent & event) LOG(LWARNING, ("No event class specified, ignoring")); return false; } - if (!EnumFromXml(node.attribute("class"), event.m_Class, kEventClassMap)) + if (!EnumFromXml(node.attribute("class"), event.m_class, kEventClassMap)) return false; std::string eventType; @@ -517,7 +517,7 @@ bool EventFromXml(pugi::xml_node node, TraffEvent & event) LOG(LWARNING, ("Event type", eventType, "does not match event class", eventClass, "(ignoring)")); return false; } - if (!EnumFromXml(node.attribute("type"), event.m_Type, kEventTypeMap)) + if (!EnumFromXml(node.attribute("type"), event.m_type, kEventTypeMap)) return false; event.m_length = OptionalIntegerFromXml(node.attribute("length")); From 8827ec3c09d51aabd5c196f2611b0405b208acd2 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 4 May 2025 20:23:13 +0300 Subject: [PATCH 015/252] [indexer] Documentation Signed-off-by: mvglasow --- indexer/data_source.hpp | 47 ++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/indexer/data_source.hpp b/indexer/data_source.hpp index 7e429f282..5df8bffd6 100644 --- a/indexer/data_source.hpp +++ b/indexer/data_source.hpp @@ -19,20 +19,31 @@ class DataSource : public MwmSet /// Registers a new map. std::pair RegisterMap(platform::LocalCountryFile const & localFile); - /// Deregisters a map from internal records. - /// - /// \param countryFile A countryFile denoting a map to be deregistered. - /// \return True if the map was successfully deregistered. If map is locked - /// now, returns false. + /** + * @brief Deregisters a map from internal records. + * @param countryFile A `CountryFile` denoting a map to be deregistered. + * @return True if the map was successfully deregistered, false if the map is locked now. + */ bool DeregisterMap(platform::CountryFile const & countryFile); void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale, covering::CoveringMode mode = covering::ViewportWithLowLevels) const; void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const; - // Calls |f| for features closest to |center| until |stopCallback| returns true or distance - // |sizeM| from has been reached. Then for EditableDataSource calls |f| for each edited feature - // inside square with center |center| and side |2 * sizeM|. Edited features are not in the same - // hierarchy and there is no fast way to merge frozen and edited features. + + /** + * @brief Iterates over features within a given distance of a center point. + * + * Calls `f` for features closest to `center` until `stopCallback` returns true or distance + * `sizeM` from has been reached. Then for EditableDataSource calls `f` for each edited feature + * inside square with center `center` and side `2 * sizeM`. Edited features are not in the same + * hierarchy and there is no fast way to merge frozen and edited features. + * + * @brief f Callback function that is called on each feature. + * @brief stopCallback Callback function which decides whether to continue searching or stop. + * @brief center The center of the search area. + * @brief sizeM The size of the search area, as a distance from the center point. + * @brief scale + */ void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center, double sizeM, int scale) const; void ForEachInScale(FeatureCallback const & f, int scale) const; @@ -70,18 +81,24 @@ class DataSource : public MwmSet std::unique_ptr m_factory; }; -// DataSource which operates with features from mwm file and does not support features creation -// deletion or modification. +/** + * @brief A `DataSource` which operates with features from an MWM file and does not support + * creation, deletion or modification of features. + */ class FrozenDataSource : public DataSource { public: FrozenDataSource() : DataSource(std::make_unique()) {} }; -/// Guard for loading features from particular MWM by demand. -/// @note If you need to work with FeatureType from different threads you need to use -/// a unique FeaturesLoaderGuard instance for every thread. -/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest. +/** + * @brief Guard for loading features from particular MWM by demand. + * + * @note If you need to work with `FeatureType` from different threads, you need to use + * a unique `FeaturesLoaderGuard` instance for every thread. + * For an example of concurrent extracting feature details please see `ConcurrentFeatureParsingTest` + * in `routing/routing_integration_tests`. + */ class FeaturesLoaderGuard { public: From 16cb70a9524a7abc570f94ac9e51c17b161cacc3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 5 May 2025 23:48:30 +0300 Subject: [PATCH 016/252] [traffic] Add TrafficInfo constructor with Coloring Signed-off-by: mvglasow --- traffic/traffic_info.cpp | 7 +++++++ traffic/traffic_info.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/traffic/traffic_info.cpp b/traffic/traffic_info.cpp index 526a4e4b3..29f845fa4 100644 --- a/traffic/traffic_info.cpp +++ b/traffic/traffic_info.cpp @@ -133,6 +133,13 @@ TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion } } +TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring) + : m_mwmId(mwmId) + , m_coloring(std::move(coloring)) +{ + m_availability = Availability::IsAvailable; +} + // static TrafficInfo TrafficInfo::BuildForTesting(Coloring && coloring) { diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index a0a6ed147..fc71c8340 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -107,6 +107,7 @@ class TrafficInfo TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion); + TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring); /** * @brief Returns a `TrafficInfo` instance with pre-populated traffic information. * @param coloring The traffic information (road segments and their speed group) From 24d65bd37fb6b7d367ede36b4cd2f491a44fd0a5 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 6 May 2025 00:04:09 +0300 Subject: [PATCH 017/252] WIP: [traffic] Implement basic TraFF parsing, currently from hardcoded path Not feature complete, produces incorrect results for some test cases Some parts of the implementation are not very elegant yet Inefficient as the whole set of messages is parsed on update Lots of verbose debug logging Lots of dead code from old traffic module (#ifdef traffic_dead_code) Signed-off-by: mvglasow --- map/traffic_manager.cpp | 373 +++++++++++++++++++++++++++++++++++ map/traffic_manager.hpp | 133 +++++++++++++ traffic/traffic_info.cpp | 18 ++ traffic/traffic_info.hpp | 16 ++ traffxml/traff_model.cpp | 248 +++++++++++++++++++++++ traffxml/traff_model.hpp | 129 ++++++++++++ traffxml/traff_model_xml.cpp | 9 +- 7 files changed, 924 insertions(+), 2 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index c89065841..15dac614f 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -16,6 +16,8 @@ #include "platform/platform.hpp" +#include "traffxml/traff_model_xml.hpp" + using namespace std::chrono; namespace @@ -25,6 +27,10 @@ auto constexpr kOutdatedDataTimeout = minutes(5) + kUpdateInterval; auto constexpr kNetworkErrorTimeout = minutes(20); auto constexpr kMaxRetriesCount = 5; + +// Number of identical data sources to create for the OpenLR decoder, one source per worker thread. +// TODO how to determine the best number of worker threads? +auto constexpr kNumDecoderThreads = 1; } // namespace TrafficManager::CacheEntry::CacheEntry() @@ -53,7 +59,10 @@ TrafficManager::TrafficManager(const CountryParentNameGetterFn &countryParentNam , m_observer(observer) , m_currentDataVersion(0) , m_state(TrafficState::Disabled) +// TODO no longer needed +#ifdef traffic_dead_code , m_maxCacheSizeBytes(maxCacheSizeBytes) +#endif , m_isRunning(true) , m_isPaused(false) , m_thread(&TrafficManager::ThreadRoutine, this) @@ -113,7 +122,10 @@ void TrafficManager::SetEnabled(bool enabled) void TrafficManager::Clear() { +// TODO no longer needed +#ifdef traffic_dead_code m_currentCacheSizeBytes = 0; +#endif m_mwmCache.clear(); m_lastDrapeMwmsByRect.clear(); m_lastRoutingMwmsByRect.clear(); @@ -190,6 +202,7 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, { std::lock_guard lock(m_mutex); + m_activeMwmsChanged = true; activeMwms.clear(); for (auto const & mwm : mwms) { @@ -232,11 +245,309 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) UpdateActiveMwms(screen.ClipRect(), m_lastDrapeMwmsByRect, m_activeDrapeMwms); } +// TODO make this work with multiple sources (e.g. Android) +bool TrafficManager::Subscribe(std::set & mwms) +{ + // TODO what if we’re subscribed already? + // TODO + LOG(LINFO, ("Would subscribe to", mwms)); + m_subscriptionId = "placeholder_subscription_id"; + m_isPollNeeded = true; // would be false if we got a feed here + return true; +} + +// TODO make this work with multiple sources (e.g. Android) +bool TrafficManager::ChangeSubscription(std::set & mwms) +{ + // TODO what if we’re not subscribed yet? + // TODO + LOG(LINFO, ("Would change subscription", m_subscriptionId, "to", mwms)); + m_isPollNeeded = true; // would be false if we got a feed here + return true; +} + +bool TrafficManager::SetSubscriptionArea() +{ + std::set activeMwms; + if (!IsSubscribed()) + { + { + std::lock_guard lock(m_mutex); + m_activeMwmsChanged = false; + UniteActiveMwms(activeMwms); + } + if (!Subscribe(activeMwms)) + return false; + } + else if (m_activeMwmsChanged) + { + { + std::lock_guard lock(m_mutex); + m_activeMwmsChanged = false; + UniteActiveMwms(activeMwms); + } + if (!ChangeSubscription(activeMwms)) + return false; + } + return true; +} + +// TODO make this work with multiple sources (e.g. Android) +void TrafficManager::Unsubscribe() +{ + if (!IsSubscribed()) + return; + // TODO + LOG(LINFO, ("Would unsubscribe from", m_subscriptionId)); + m_subscriptionId.clear(); +} + +bool TrafficManager::IsSubscribed() +{ + return !m_subscriptionId.empty(); +} + +// TODO make this work with multiple sources (e.g. Android) +// TODO deal with subscriptions rejected by the server (delete, resubscribe) +bool TrafficManager::Poll() +{ + // TODO + //std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany.xml"); + std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml"); + //std::string path("/home/michael/src/organicmaps/data/test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml"); + pugi::xml_document document; + auto const load_result = document.load_file(path.data()); + if (!load_result) + { + LOG(LERROR, ("Can't load file", path, ":", load_result.description())); + return false; + } + + std::setlocale(LC_ALL, "en_US.UTF-8"); + traffxml::TraffFeed feed; + if (traffxml::ParseTraff(document, feed)) + { + { + std::lock_guard lock(m_mutex); + m_feeds.push_back(feed); + } + return true; + } + else + { + LOG(LWARNING, ("An error occurred parsing the TraFF feed")); + return false; + } +} + +void TrafficManager::Push(traffxml::TraffFeed feed) +{ + std::lock_guard lock(m_mutex); + m_feeds.push_back(feed); +} + +void TrafficManager::UpdateMessageCache(std::map & cache) +{ + traffxml::TraffFeed feed; + // Thread-safe iteration over m_feeds, releasing the mutex during the loop + while (true) + { + { + std::lock_guard lock(m_mutex); + if (!m_feeds.empty()) + { + feed = m_feeds.front(); + m_feeds.erase(m_feeds.begin()); + } + else + break; + } + + for (auto message : feed) + { + LOG(LINFO, (" message:", message)); + auto it = cache.find(message.m_id); + bool process = (it == cache.end()); + if (!process) + process = (timegm(&(it->second.m_updateTime)) < timegm(&(message.m_updateTime))); + if (process) + cache.insert_or_assign(message.m_id, message); + } + } +} + +void TrafficManager::InitializeDataSources(std::vector & dataSources) +{ + /* + * TODO can we include all available MWMs in the list (including non-active ones)? + * Then we could initialize the decoder once and for all. + */ + ForEachActiveMwm([this, &dataSources](MwmSet::MwmId const & mwmId) { + ASSERT(mwmId.IsAlive(), ()); + // TODO do we need .SyncWithDisk() for the file? + for (size_t i = 0; i < dataSources.size(); i++) + dataSources[i].RegisterMap(mwmId.GetInfo()->GetLocalFile()); + }); +} + +/* + * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). + * Decoding message by message kind of defeats the purpose. + * But after decoding the location, we need to examine the map features we got in order to + * determine the speed groups, thus we may need to decode one by one (TBD). + * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path + * structures to accept a TraFF message ID (string) rather than an integer. + */ +void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, + traffxml::TraffMessage & message, std::map & trafficCache) +{ + if (message.m_location) + { + // Decode events into consolidated traffic impact + std::optional impact = message.GetTrafficImpact(); + + LOG(LINFO, (" Impact: ", impact)); + + // Skip further processing if there is no impact + if (!impact) + return; + + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments + = message.m_location.value().ToOpenLrSegments(message.m_id); + + for (auto segment : segments) + { + LOG(LINFO, (" Segment:", segment.m_segmentId)); + for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) + { + LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); + if (i < segment.m_locationReference.m_points.size() - 1) + { + LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); + LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); + } + } + } + + // Decode the location into a path on the map. + // One path per segment + std::vector paths(segments.size()); + decoder.DecodeV3(segments, kNumDecoderThreads, paths); + + for (size_t i = 0; i < paths.size(); i++) + { + LOG(LINFO, (" Path", i)); + LOG(LINFO, (" Partner segment ID:", paths[i].m_segmentId)); + LOG(LINFO, (" Edges:", paths[i].m_path.size())); + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + LOG(LINFO, (" ", paths[i].m_path[j])); + } + } + + // TODO store maxspeed in edges + // store decoded paths and speed groups in trafficCache + if (impact) + { + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ + for (size_t i = 0; i < paths.size(); i++) + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + std::string countryName = paths[i].m_path[j].GetFeatureId().m_mwmId.GetInfo()->GetCountryName(); + auto fid = paths[i].m_path[j].GetFeatureId().m_index; + auto segment = paths[i].m_path[j].GetSegId(); + uint8_t direction = paths[i].m_path[j].IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + // TODO process all TrafficImpact fields and determine the speed group based on that + trafficCache[countryName][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = impact.value().m_speedGroup; + } + } + } +} + void TrafficManager::ThreadRoutine() { std::vector mwms; while (WaitForRequest(mwms)) { + // TODO clean out expired messages + + // poll is always needed, unless a new subscription or a subscription change returns a feed + m_isPollNeeded = true; + + if (!SetSubscriptionArea()) + { + LOG(LWARNING, ("SetSubscriptionArea failed.")); + if (!IsSubscribed()) + // do not skip out of the loop, we may need to process pushed feeds + LOG(LWARNING, ("No subscription, no traffic data will be retrieved.")); + } + + // fetch traffic data if subscribed, unless this has already happened in the previous step + if (m_isPollNeeded && IsSubscribed()) + { + if (!Poll()) + { + LOG(LWARNING, ("Poll failed.")); + // TODO set failed status somewhere and retry + } + } + LOG(LINFO, (m_feeds.size(), "feed(s) in queue")); + + /* + * TODO call on a temp struct, then unite with m_messageCache, processing only messages with changes + * (adding segments for new messages, removing segments for deleted messages, replacing segments + * for updated messages) and leaving all other segments untouched + */ + UpdateMessageCache(m_messageCache); + LOG(LINFO, (m_messageCache.size(), "message(s) in cache")); + + // initialize the decoder + /* + * Access to `DataSource` is not thread-safe. The main app, which works with + * `EditableDataSource` (as the map can be edited), wraps map operations into a + * `FeaturesLoaderGuard`. The OpenLR decoder expects one `FrozenDataSource` (a read-only + * subclass) per worker thread – which works as long as the map is not modified. + * Edits are not relevant to the OpenLR decoder. However, if the edits modify MWM files (rather + * than being stored separately), this might confuse the `FrozenDataSource`. In this case, we + * would need to rewrite the OpenLR decoder to work with a `FeaturesLoaderGuard` (which is + * probably the more elegant way to do this anyway). + */ + std::vector dataSources(kNumDecoderThreads); + // TODO test with data source from framework + InitializeDataSources(dataSources); + openlr::OpenLRDecoder decoder(dataSources, m_countryParentNameGetterFn); + + /* + * Map between country names and their colorings. + * TODO use MwmId as map keys: + * As long as we don’t/can‘t use the framework’s `DataSource` instance for the OpenLR decoder, + * `MwmId` instances from the decoder will not match those from the framework because of the + * way the identity operator is currently implemented (comparing `MwmInfo` instances rather than + * their contents). The ultimate goal is to do matching based on `MwmId`s, but that requires + * either running the OpenLR decoder off the shared `DataSource` or changing the way `MwmInfo` + * comparison works, eitehr of which may come with regressions and needs to be tested. + */ + std::map allMwmColoring; + for (auto [id, message] : m_messageCache) + { + LOG(LINFO, (" ", id, ":", message)); + DecodeMessage(decoder, message, allMwmColoring); + } + + // set new coloring for MWMs + OnTrafficDataUpdate(allMwmColoring); + +// TODO no longer needed +#ifdef traffic_dead_code for (auto const & mwm : mwms) { if (!mwm.IsAlive()) @@ -265,8 +576,11 @@ void TrafficManager::ThreadRoutine() m_trafficETags[mwm] = tag; } } +#endif mwms.clear(); } + // Calling Unsubscribe() form the worker thread on exit makes thread synchronization easier + Unsubscribe(); } bool TrafficManager::WaitForRequest(std::vector & mwms) @@ -335,6 +649,8 @@ void TrafficManager::RequestTrafficData() UpdateState(); } +// TODO no longer needed +#ifdef traffic_dead_code void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) { std::lock_guard lock(m_mutex); @@ -364,7 +680,57 @@ void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) UpdateState(); } +#endif +void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) +{ + /* + * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: + * + * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData(). + * trafficCache lookup is original code. + * TrafficInfo construction is taken fron TheadRoutine(), with modifications (different constructor). + * Updating m_mwmCache is from RequestTrafficData(MwmSet::MwmId const &, bool), with modifications. + * The remainder of the loop is from OnTrafficDataResponse(traffic::TrafficInfo &&), with minor modifications + */ + ForEachActiveMwm([this, trafficCache](MwmSet::MwmId const & mwmId) { + ASSERT(mwmId.IsAlive(), ()); + auto tcit = trafficCache.find(mwmId.GetInfo()->GetCountryName()); + if (tcit != trafficCache.end()) + { + std::lock_guard lock(m_mutex); + + traffic::TrafficInfo::Coloring coloring = tcit->second; + LOG(LINFO, ("Setting new coloring for", mwmId, "with", coloring.size(), "entries")); + traffic::TrafficInfo info(mwmId, std::move(coloring)); + + m_mwmCache.try_emplace(mwmId, CacheEntry(steady_clock::now())); + + auto it = m_mwmCache.find(mwmId); + if (it != m_mwmCache.end()) + { + it->second.m_isLoaded = true; + it->second.m_lastResponseTime = steady_clock::now(); + it->second.m_isWaitingForResponse = false; + it->second.m_lastAvailability = info.GetAvailability(); + + UpdateState(); + + if (!info.GetColoring().empty()) + { + m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, + static_cast(info)); + + // Update traffic colors for routing. + m_observer.OnTrafficInfoAdded(std::move(info)); + } + } + } + }); +} + +// TODO no longer needed +#ifdef traffic_dead_code void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info) { { @@ -401,6 +767,7 @@ void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info) m_observer.OnTrafficInfoAdded(std::move(info)); } } +#endif void TrafficManager::UniteActiveMwms(std::set & activeMwms) const { @@ -408,6 +775,8 @@ void TrafficManager::UniteActiveMwms(std::set & activeMwms) const activeMwms.insert(m_activeRoutingMwms.cbegin(), m_activeRoutingMwms.cend()); } +// TODO no longer needed +#ifdef traffic_dead_code void TrafficManager::ShrinkCacheToAllowableSize() { // Calculating number of different active mwms. @@ -429,6 +798,7 @@ void TrafficManager::ShrinkCacheToAllowableSize() } } } +#endif void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId) { @@ -438,8 +808,11 @@ void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId) if (it->second.m_isLoaded) { +// TODO no longer needed +#ifdef traffic_dead_code ASSERT_GREATER_OR_EQUAL(m_currentCacheSizeBytes, it->second.m_dataSize, ()); m_currentCacheSizeBytes -= it->second.m_dataSize; +#endif m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, mwmId); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 2f7690dde..9f6f9a2e2 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -11,6 +11,8 @@ #include "openlr/openlr_decoder.hpp" +#include "traffxml/traff_model.hpp" + #include "geometry/point2d.hpp" #include "geometry/polyline2d.hpp" #include "geometry/screenbase.hpp" @@ -179,6 +181,85 @@ class TrafficManager final traffic::TrafficInfo::Availability m_lastAvailability; }; + /** + * @brief Subscribes to a traffic service. + * + * @param mwms The MWMs for which data is needed. + * @return true on success, false on failure. + */ + bool Subscribe(std::set & mwms); + + /** + * @brief Changes an existing traffic subscription. + * + * @param mwms The new set of MWMs for which data is needed. + * @return true on success, false on failure. + */ + bool ChangeSubscription(std::set & mwms); + + /** + * @brief Ensures we have a subscription covering all currently active MWMs. + * + * This method subscribes to a traffic service if not already subscribed, or changes the existing + * subscription otherwise. + * + * @return true on success, false on failure. + */ + bool SetSubscriptionArea(); + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + */ + void Unsubscribe(); + + /** + * @brief Whether we are currently subscribed to a traffic service. + * @return + */ + bool IsSubscribed(); + + /** + * @brief Polls the traffic service for updates. + * + * @return true on success, false on failure. + */ + bool Poll(); + + /** + * @brief Processes a traffic feed received through a push operation. + * + * Push operations are not supported on all platforms. + * + * @param feed The traffic feed. + */ + void Push(traffxml::TraffFeed feed); + + /** + * @brief Merges new messages from `m_feeds` into a message cache. + * + * Existing messages in `cache` will be overwritten by newer messages with the same ID in `m_feeds`. + * + * @param cache The message cache. + */ + void UpdateMessageCache(std::map & cache); + + /** + * @brief Initializes the data sources for an OpenLR decoder. + * + * @param dataSources Receives the data sources for the decoder (one per worker thread). + */ + void InitializeDataSources(std::vector &dataSources); + + /** + * @brief Decodes a single message to its segments and their speed groups. + * + * @param decoder The OpenLR decoder instance. + * @param message The message to decode. + * @param trafficCache The cache in which all decoded paths with their speed groups will be stored. + */ + void DecodeMessage(openlr::OpenLRDecoder &decoder, traffxml::TraffMessage & message, + std::map & trafficCache); + /** * @brief Event loop for the traffic worker thread. * @@ -199,8 +280,18 @@ class TrafficManager final * @param mwms Receives a list of MWMs for which to update traffic data. * @return `true` during normal operation, `false` during teardown (signaling the event loop to exit). */ + // TODO mwms argument is no longer needed bool WaitForRequest(std::vector & mwms); + /** + * @brief Processes new traffic data. + * + * @param trafficCache The new per-MWM colorings (preprocessed traffic information). + */ + void OnTrafficDataUpdate(std::map & trafficCache); + +// TODO no longer needed +#ifdef traffic_dead_code void OnTrafficDataResponse(traffic::TrafficInfo && info); /** * @brief Processes a failed traffic request. @@ -218,6 +309,7 @@ class TrafficManager final * @param info */ void OnTrafficRequestFailed(traffic::TrafficInfo && info); +#endif /** * @brief Updates `activeMwms` and requests traffic data. @@ -275,7 +367,10 @@ class TrafficManager final void Clear(); void ClearCache(MwmSet::MwmId const & mwmId); +// TODO no longer needed +#ifdef traffic_dead_code void ShrinkCacheToAllowableSize(); +#endif /** * @brief Updates the state of the traffic manager based on the state of all MWMs used by the renderer. @@ -323,8 +418,11 @@ class TrafficManager final bool m_hasSimplifiedColorScheme = true; +// TODO no longer needed +#ifdef traffic_dead_code size_t m_maxCacheSizeBytes; size_t m_currentCacheSizeBytes = 0; +#endif std::map m_mwmCache; @@ -378,6 +476,41 @@ class TrafficManager final * @brief Worker thread which fetches traffic updates. */ threads::SimpleThread m_thread; + + /** + * @brief Whether active MWMs have changed since the last request. + */ + bool m_activeMwmsChanged = false; + + /** + * @brief The subscription ID received from the traffic server. + * + * An empty subscription ID means no subscription. + */ + std::string m_subscriptionId; + + /** + * @brief Whether a poll operation is needed. + * + * Used in the worker thread. A poll operation is needed unless a subscription (or subscription + * change) operation was performed before and a feed was received a part of it. + */ + bool m_isPollNeeded; + + /** + * @brief Queue of feeds waiting to be processed. + * + * Threads must lock `m_mutex` before accessing `m_feeds`, as some platforms may receive feeds + * on multiple threads. + */ + std::vector m_feeds; + + /** + * @brief Cache of all currently active TraFF messages. + * + * Keys are message IDs, values are messages. + */ + std::map m_messageCache; }; extern std::string DebugPrint(TrafficManager::TrafficState state); diff --git a/traffic/traffic_info.cpp b/traffic/traffic_info.cpp index 29f845fa4..b7b646898 100644 --- a/traffic/traffic_info.cpp +++ b/traffic/traffic_info.cpp @@ -61,6 +61,8 @@ bool ReadRemoteFile(string const & url, vector & contents, int & errorC return true; } +// TODO no longer needed +#ifdef traffic_dead_code string MakeRemoteURL(string const & name, uint64_t version) { if (string(TRAFFIC_DATA_BASE_URL).empty()) @@ -73,6 +75,7 @@ string MakeRemoteURL(string const & name, uint64_t version) ss << url::UrlEncode(name) << TRAFFIC_FILE_EXTENSION; return ss.str(); } +#endif char const kETag[] = "etag"; } // namespace @@ -91,6 +94,8 @@ TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t di uint8_t const TrafficInfo::kLatestKeysVersion = 0; uint8_t const TrafficInfo::kLatestValuesVersion = 0; +// TODO no longer needed +#ifdef traffic_dead_code TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion) : m_mwmId(mwmId) , m_currentDataVersion(currentDataVersion) @@ -132,6 +137,7 @@ TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion LOG(LWARNING, ("Could not initialize traffic keys")); } } +#endif TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring) : m_mwmId(mwmId) @@ -154,6 +160,8 @@ void TrafficInfo::SetTrafficKeysForTesting(vector const & keys) m_availability = Availability::IsAvailable; } +// TODO no longer needed +#ifdef traffic_dead_code bool TrafficInfo::ReceiveTrafficData(string & etag) { vector values; @@ -169,6 +177,7 @@ bool TrafficInfo::ReceiveTrafficData(string & etag) } return false; } +#endif SpeedGroup TrafficInfo::GetSpeedGroup(RoadSegmentId const & id) const { @@ -399,6 +408,8 @@ void TrafficInfo::DeserializeTrafficValues(vector const & data, ASSERT_EQUAL(src.Size(), 0, ()); } +// TODO no longer needed +#ifdef traffic_dead_code // todo(@m) This is a temporary method. Do not refactor it. bool TrafficInfo::ReceiveTrafficKeys() { @@ -437,7 +448,10 @@ bool TrafficInfo::ReceiveTrafficKeys() m_keys.swap(keys); return true; } +#endif +// TODO no longer needed +#ifdef traffic_dead_code TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, vector & values) { if (!m_mwmId.IsAlive()) @@ -481,6 +495,7 @@ TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, v m_availability = Availability::IsAvailable; return ServerDataStatus::New; } +#endif bool TrafficInfo::UpdateTrafficData(vector const & values) { @@ -504,6 +519,8 @@ bool TrafficInfo::UpdateTrafficData(vector const & values) return true; } +// TODO no longer needed +#ifdef traffic_dead_code TrafficInfo::ServerDataStatus TrafficInfo::ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion) { switch (request.ErrorCode()) @@ -532,6 +549,7 @@ TrafficInfo::ServerDataStatus TrafficInfo::ProcessFailure(platform::HttpClient c return ServerDataStatus::Error; } +#endif string DebugPrint(TrafficInfo::RoadSegmentId const & id) { diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index fc71c8340..61a4e3474 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -105,9 +105,13 @@ class TrafficInfo TrafficInfo() = default; +// TODO no longer needed +#ifdef traffic_dead_code TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion); +#endif TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring); + /** * @brief Returns a `TrafficInfo` instance with pre-populated traffic information. * @param coloring The traffic information (road segments and their speed group) @@ -116,6 +120,8 @@ class TrafficInfo static TrafficInfo BuildForTesting(Coloring && coloring); void SetTrafficKeysForTesting(std::vector const & keys); +// TODO no longer needed +#ifdef traffic_dead_code /** * @brief Fetches the latest traffic data from the server and updates the coloring and ETag. * @@ -131,6 +137,7 @@ class TrafficInfo * @return True on success, false on failure. */ bool ReceiveTrafficData(std::string & etag); +#endif /** * @brief Returns the latest known speed group by a feature segment's ID. @@ -198,19 +205,28 @@ class TrafficInfo friend void UnitTest_TrafficInfo_UpdateTrafficData(); +// TODO no longer needed +#ifdef traffic_dead_code // todo(@m) A temporary method. Remove it once the keys are added // to the generator and the data is regenerated. bool ReceiveTrafficKeys(); +#endif +// TODO no longer needed +#ifdef traffic_dead_code // Tries to read the values of the Coloring map from server into |values|. // Returns result of communicating with server as ServerDataStatus. // Otherwise, returns false and does not change m_coloring. ServerDataStatus ReceiveTrafficValues(std::string & etag, std::vector & values); +#endif // Updates the coloring and changes the availability status if needed. bool UpdateTrafficData(std::vector const & values); +// TODO no longer needed +#ifdef traffic_dead_code ServerDataStatus ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion); +#endif /** * @brief The mapping from feature segments to speed groups (see speed_groups.hpp). diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index cd29d0dd9..14b469473 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -1,9 +1,246 @@ #include "traffxml/traff_model.hpp" +#include "base/logging.hpp" + +#include "geometry/mercator.hpp" + using namespace std; namespace traffxml { +const std::map kEventSpeedGroupMap{ + // TODO Activity*, Authority*, Carpool* (not in enum yet) + {EventType::CongestionHeavyTraffic, traffic::SpeedGroup::G4}, + {EventType::CongestionLongQueue, traffic::SpeedGroup::G0}, + {EventType::CongestionNone, traffic::SpeedGroup::G5}, + {EventType::CongestionNormalTraffic, traffic::SpeedGroup::G5}, + {EventType::CongestionQueue, traffic::SpeedGroup::G2}, + {EventType::CongestionQueueLikely, traffic::SpeedGroup::G3}, + {EventType::CongestionSlowTraffic, traffic::SpeedGroup::G3}, + {EventType::CongestionStationaryTraffic, traffic::SpeedGroup::G1}, + {EventType::CongestionStationaryTrafficLikely, traffic::SpeedGroup::G2}, + {EventType::CongestionTrafficBuildingUp, traffic::SpeedGroup::G4}, + {EventType::CongestionTrafficCongestion, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal + {EventType::CongestionTrafficFlowingFreely, traffic::SpeedGroup::G5}, + {EventType::CongestionTrafficHeavierThanNormal, traffic::SpeedGroup::G4}, + {EventType::CongestionTrafficLighterThanNormal, traffic::SpeedGroup::G5}, + {EventType::CongestionTrafficMuchHeavierThanNormal, traffic::SpeedGroup::G3}, + {EventType::CongestionTrafficProblem, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal + // TODO Construction* (not in enum yet) + /* + * Some delay types have a duration which depends on the route. This is better expressed as a + * speed group, although the mapping may be somewhat arbitrary and may need to be corrected. + */ + {EventType::DelayDelay, traffic::SpeedGroup::G2}, + {EventType::DelayDelayPossible, traffic::SpeedGroup::G3}, + {EventType::DelayLongDelay, traffic::SpeedGroup::G1}, + {EventType::DelayVeryLongDelay, traffic::SpeedGroup::G0}, + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) + // TODO complete Restriction* (not in enum yet) + {EventType::RestrictionBlocked, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionBlockedAhead, traffic::SpeedGroup::TempBlock}, + //{EventType::RestrictionCarriagewayBlocked, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open + //{EventType::RestrictionCarriagewayClosed, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open + {EventType::RestrictionClosed, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionClosedAhead, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionEntryBlocked, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionExitBlocked, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionRampBlocked, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionRampClosed, traffic::SpeedGroup::TempBlock}, + {EventType::RestrictionSpeedLimit, traffic::SpeedGroup::G4}, + // TODO Security*, Transport*, Weather* (not in enum yet) +}; + +// none of the currently define events imply an explicit maxspeed +#if 0 +const std::map kEventMaxspeedMap{ + // TODO Activity*, Authority*, Carpool* (not in enum yet) + // TODO Construction* (not in enum yet) + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) + // TODO complete Restriction* (not in enum yet) + // TODO Security*, Transport*, Weather* (not in enum yet) +}; +#endif + +const std::map kEventDelayMap{ + // TODO Activity*, Authority*, Carpool* (not in enum yet) + // TODO Construction* (not in enum yet) + //{EventType::DelayDelay, }, // mapped to speed group + //{EventType::DelayDelayPossible, }, // mapped to speed group + //{EventType::DelayLongDelay, }, // mapped to speed group + {EventType::DelaySeveralHours, 150}, // assumption: 2.5 hours + {EventType::DelayUncertainDuration, 60}, // assumption: 1 hour + //{EventType::DelayVeryLongDelay, }, // mapped to speed group + // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) + // TODO complete Restriction* (not in enum yet) + // TODO Security*, Transport*, Weather* (not in enum yet) +}; + +openlr::LocationReferencePoint Point::ToLrp() +{ + openlr::LocationReferencePoint result; + result.m_latLon = ms::LatLon(this->m_coordinates.m_lat, this->m_coordinates.m_lon); + return result; +} + +openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool backwards) +{ + openlr::LinearLocationReference locationReference; + locationReference.m_points.clear(); + std::vector points; + if (m_from) + points.push_back(m_from.value()); + if (m_at) + points.push_back(m_at.value()); + else if (m_via) + points.push_back(m_via.value()); + if (m_to) + points.push_back(m_to.value()); + if (backwards) + std::reverse(points.begin(), points.end()); + // m_notVia is ignored as OpenLR does not support this functionality. + // TODO do we ensure a minimum of two reference points (from/to/at) when building the location? + CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); + for (auto point : points) + { + openlr::LocationReferencePoint lrp = point.ToLrp(); + if (!locationReference.m_points.empty()) + { + locationReference.m_points.back().m_distanceToNextPoint + = GuessDnp(locationReference.m_points.back(), lrp); + locationReference.m_points.back().m_functionalRoadClass = GetFrc(); + } + locationReference.m_points.push_back(lrp); + } + return locationReference; +} + +// TODO make segment ID in OpenLR a string value, and store messageId +std::vector TraffLocation::ToOpenLrSegments(std::string & messageId) +{ + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments; + int dirs = (m_directionality == Directionality::BothDirections) ? 2 : 1; + for (int dir = 0; dir < dirs; dir++) + { + openlr::LinearSegment segment; + // TODO make this a reference to the TraFF message ID + segment.m_segmentId = 42; + /* + * Segments generated from coordinates can have any number of points. Each point, except for + * the last point, must indicate the distance to the next point. Line properties (functional + * road class (FRC), form of way, bearing) or path properties other than distance to next point + * (lowest FRC to next point, againstDrivingDirection) are ignored. + * Segment length is never evaluated. + * TODO update OpenLR decoder to make all line and path properties optional. + */ + segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag; + segment.m_locationReference = this->ToLinearLocationReference(dir == 0 ? false : true); + + segments.push_back(segment); + } + return segments; +} + +openlr::FunctionalRoadClass TraffLocation::GetFrc() +{ + if (!m_roadClass) + return openlr::FunctionalRoadClass::NotAValue; + switch (m_roadClass.value()) + { + case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1; + case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2; + case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; + /* + * TODO Revisit FRC for Other. + * Other corresponds to FRC4–7. + * FRC4 matches secondary/tertiary (zero score) and anything below (full score). + * FRC5–7 match anything below tertiary (full score); secondary/tertiary never match. + * Primary and above never matches any of these FRCs. + */ + case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4; + } + UNREACHABLE(); +} + +std::optional TraffMessage::GetTrafficImpact() +{ + // no events, no impact + if (m_events.empty()) + return std::nullopt; + + // examine events + std::vector impacts; + for (auto event : m_events) + { + TrafficImpact impact; + + if (auto it = kEventSpeedGroupMap.find(event.m_type); it != kEventSpeedGroupMap.end()) + impact.m_speedGroup = it->second; + + if (event.m_speed) + impact.m_maxspeed = event.m_speed.value(); + // TODO if no explicit speed given, look up in kEventMaxspeedMap (once we have entries) + + // TODO if event is in delay class and has an explicit duration quantifier, use that and skip the map lookup. + if (auto it = kEventDelayMap.find(event.m_type); it != kEventDelayMap.end()) + impact.m_delayMins = it->second; + + // TempBlock overrules everything else, return immediately + if (impact.m_speedGroup == traffic::SpeedGroup::TempBlock) + return impact; + // if there is no actual impact, discard + if ((impact.m_maxspeed < kMaxspeedNone) + || (impact.m_delayMins > 0) + || (impact.m_speedGroup != traffic::SpeedGroup::Unknown)) + impacts.push_back(impact); + } + + if (impacts.empty()) + return std::nullopt; + + TrafficImpact result; + for (auto impact : impacts) + { + ASSERT(impact.m_speedGroup != traffic::SpeedGroup::TempBlock, ("Got SpeedGroup::TempBlock, which should not happen at this stage")); + if (result.m_speedGroup == traffic::SpeedGroup::Unknown) + result.m_speedGroup = impact.m_speedGroup; + // TempBlock cannot occur here, so we can do just a simple comparison + else if ((impact.m_speedGroup != traffic::SpeedGroup::Unknown) && (impact.m_speedGroup < result.m_speedGroup)) + result.m_speedGroup = impact.m_speedGroup; + + if (impact.m_maxspeed < result.m_maxspeed) + result.m_maxspeed = impact.m_maxspeed; + + if (impact.m_delayMins > result.m_delayMins) + result.m_delayMins = impact.m_delayMins; + } + if ((result.m_maxspeed < kMaxspeedNone) + || (result.m_delayMins > 0) + || (result.m_speedGroup != traffic::SpeedGroup::Unknown)) + return result; + else + // should never happen, unless we have a bug somewhere + return std::nullopt; +} + +uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2) +{ + double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_latLon), + mercator::FromLatLon(p2.m_latLon)); + /* + * The tolerance factor is currently 1/0.6, or ~1.67, so that direct distance is just at the + * lower boundary for `openlr::LinearSegmentSource::FromLocationReferenceTag`. Since we use + * `openlr::LinearSegmentSource::FromCoordinatesTag`, different tolerance values apply, + * so direct distance is well within the lower boundary, where the upper boundary is ~6.67 times + * the direct distance. This should work even in mountain areas, where the shortest route from + * one valley to the next, using long-distance roads, can be up to ~3 times the direct distance. + */ + return doe / 0.6f + 0.5f; +} + /* string DebugPrint(LinearSegmentSource source) { @@ -140,6 +377,17 @@ std::string DebugPrint(EventType eventType) UNREACHABLE(); } +std::string DebugPrint(TrafficImpact impact) +{ + std::ostringstream os; + os << "TrafficImpact { "; + os << "speedGroup: " << DebugPrint(impact.m_speedGroup) << ", "; + os << "maxspeed: " << (impact.m_maxspeed == kMaxspeedNone ? "none" : std::to_string(impact.m_maxspeed)) << ", "; + os << "delayMins: " << impact.m_delayMins; + os << " }"; + return os.str(); +} + std::string DebugPrint(Point point) { std::ostringstream os; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index bb76f74f7..3e8fd539f 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -5,11 +5,17 @@ #include "geometry/latlon.hpp" #include "geometry/point2d.hpp" +#include "openlr/openlr_model.hpp" + +#include "traffic/speed_groups.hpp" + #include #include namespace traffxml { +constexpr uint8_t kMaxspeedNone = 255; + /** * @brief Date and time decoded from ISO 8601. * @@ -55,6 +61,13 @@ enum class RoadClass Other }; +/* + * When adding a new event class to this enum, be sure to do the following: + * + * * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventClassMap` + * * in `traff_model.cpp`, extend `DebugPrint(EventClass)` to correctly process the new event classes + * * in this file, add event types for this class to `EventType` + */ enum class EventClass { Invalid, @@ -74,6 +87,16 @@ enum class EventClass Weather }; +/* + * When adding a new event type to this enum, be sure to do the following: + * + * * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventTypeMap` + * * in `traff_model.cpp`: + * * add speed group mappings in `kEventSpeedGroupMap`, if any + * * add maxspeed mappings in `kEventMaxspeedMap`, if any (uncomment if needed) + * * add delay mappings in `kEventDelayMap`, if any + * * extend `DebugPrint(TraffEvent)` to correctly process the new events + */ enum class EventType { Invalid, @@ -128,8 +151,57 @@ enum class EventType // TODO Security*, Transport*, Weather* }; +/** + * @brief Represents the impact of one or more traffic events. + * + * Impact can be expressed in three ways: + * + * Traffic may flow at a certain percentage of the posted limit, often divided in bins. This is + * used by some traffic services which report e.g. “slow traffic”, “stationary traffic” or + * “queues”, and maps to speed groups in a straightforward way. + * + * Traffic may flow at, or be restricted to, a given speed. This is common with traffic flow + * measurement data, or with temporary speed limits. Converting this to a speed group requires + * knowledge of the regular speed limit. + * + * There may be a fixed delay, expressed as a duration in time. This may happen at checkpoints, + * at sections where traffic flow is limited or where there is single alternate-lane traffic. + * As the routing data model does not provide for explicit delays, they have to be converted into + * speed groups. Again, this requires knowledge of the regular travel time along the route, as well + * as its length. + * + * Closures can be expressed by setting `m_speedGroup` to `traffic::SpeedGroup::TempBlock`. If that + * is the case, the other struct members are to be ignored. + */ +struct TrafficImpact +{ + /** + * @brief The speed group for the affected segments, or `traffic::SpeedGroup::Unknown` if unknown. + */ + traffic::SpeedGroup m_speedGroup = traffic::SpeedGroup::Unknown; + + /** + * @brief The speed limit, or speed of flowing traffic; `kMaxspeedNone` if none or unknown. + */ + uint8_t m_maxspeed = kMaxspeedNone; + + /** + * @brief The delay in minutes; 0 if none or unknown. + */ + uint16_t m_delayMins = 0; +}; + struct Point { + /** + * @brief Converts the point to an OpenLR location reference point. + * + * Only coordinates are populated. + * + * @return An OpenLR LRP with the coordinates of the point. + */ + openlr::LocationReferencePoint ToLrp(); + // TODO role? ms::LatLon m_coordinates = ms::LatLon::Zero(); // TODO optional float m_distance; @@ -139,6 +211,34 @@ struct Point struct TraffLocation { + /** + * @brief Converts the location to an OpenLR linear location reference. + * + * @param backwards If true, gnerates a linear location reference for the backwards direction, + * with the order of points reversed. + * @return An OpenLR linear location reference which corresponds to the location. + */ + openlr::LinearLocationReference ToLinearLocationReference(bool backwards); + + /** + * @brief Converts the location to a vector of OpenLR segments. + * + * Depending on the directionality, the resulting vector will hold one or two elements: one for + * the forward direction, and for bidirectional locations, a second one for the backward + * direction. + * + * @param messageId The message ID + * @return A vector holding the resulting OpenLR segments. + */ + std::vector ToOpenLrSegments(std::string & messageId); + + /** + * @brief Returns the OpenLR functional road class (FRC) matching `m_roadClass`. + * + * @return The FRC. + */ + openlr::FunctionalRoadClass GetFrc(); + std::optional m_country; std::optional m_destination; std::optional m_direction; @@ -173,6 +273,20 @@ struct TraffEvent struct TraffMessage { + /** + * @brief Retrieves the traffic impact of all events. + * + * If the message has multiple events, the traffic impact is determined separately for each + * event and then aggregated. Aggregation takes the most restrictive value in each category + * (speed group, maxspeed, delay). + * + * If the aggregated traffic impact includes `SpeedGroup::TempBlock`, its other members are to + * be considered invalid. + * + * @return The aggregated traffic impact, or `std::nullopt` if the message has no events with traffic impact. + */ + std::optional GetTrafficImpact(); + std::string m_id; IsoTime m_receiveTime = {}; IsoTime m_updateTime = {}; @@ -189,12 +303,27 @@ struct TraffMessage using TraffFeed = std::vector; +/** + * @brief Guess the distance to the next point. + * + * This is calculated as direct distance, multiplied with a tolerance factor to account for the + * fact that the road is not always a straight line. + * + * The result can be used to provide some semi-valid DNP values. + * + * @param p1 The first point. + * @param p2 The second point. + * @return The approximate distance on the ground, in meters. + */ +uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2); + std::string DebugPrint(IsoTime time); std::string DebugPrint(Directionality directionality); std::string DebugPrint(Ramps ramps); std::string DebugPrint(RoadClass roadClass); std::string DebugPrint(EventClass eventClass); std::string DebugPrint(EventType eventType); +std::string DebugPrint(TrafficImpact impact); std::string DebugPrint(Point point); std::string DebugPrint(TraffLocation location); std::string DebugPrint(TraffEvent event); diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 58532712d..ae7cb4f6d 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -462,9 +462,14 @@ bool LocationFromXml(pugi::xml_node node, TraffLocation & location) location.m_via = OptionalPointFromXml(node.child("via")); location.m_notVia = OptionalPointFromXml(node.child("not_via")); - if (!location.m_from && !location.m_to && !location.m_at) + int numPoints = 0; + for (std::optional point : {location.m_from, location.m_to, location.m_at}) + if (point) + numPoints++; + // single-point locations are not supported, locations without points are not valid + if (numPoints < 2) { - LOG(LWARNING, ("Neither from, to nor at point is specified, ignoring location")); + LOG(LWARNING, ("Only", numPoints, "points of from/to/at specified, ignoring location")); return false; } From dbf253c9d147193b6d510cc1fb515de07320029e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 8 May 2025 22:39:47 +0300 Subject: [PATCH 018/252] [openlr] Always evaluate FRC and FOW, regardless of LinearSegment source Signed-off-by: mvglasow --- openlr/helpers.cpp | 2 +- openlr/score_candidate_paths_getter.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openlr/helpers.cpp b/openlr/helpers.cpp index 40a8150de..6c1c479d3 100644 --- a/openlr/helpers.cpp +++ b/openlr/helpers.cpp @@ -41,7 +41,7 @@ optional GetFrcScore(Graph::Edge const & e, FunctionalRoadClass functiona Score constexpr kMaxScoreForFrc = 25; if (functionalRoadClass == FunctionalRoadClass::NotAValue) - return nullopt; + return optional(0); auto const hwClass = infoGetter.Get(e.GetFeatureId()).m_hwClass; diff --git a/openlr/score_candidate_paths_getter.cpp b/openlr/score_candidate_paths_getter.cpp index 92985a341..368d8c39a 100644 --- a/openlr/score_candidate_paths_getter.cpp +++ b/openlr/score_candidate_paths_getter.cpp @@ -147,8 +147,7 @@ void ScoreCandidatePathsGetter::GetAllSuitablePaths(ScoreEdgeVec const & startLi for (auto const & e : startLines) { Score roadScore = 0; // Score based on functional road class and form of way. - if (source == LinearSegmentSource::FromLocationReferenceTag && - !PassesRestrictionV3(e.m_edge, functionalRoadClass, formOfWay, m_infoGetter, roadScore)) + if (!PassesRestrictionV3(e.m_edge, functionalRoadClass, formOfWay, m_infoGetter, roadScore)) { continue; } @@ -203,8 +202,7 @@ void ScoreCandidatePathsGetter::GetAllSuitablePaths(ScoreEdgeVec const & startLi CHECK(currentEdge.HasRealPart(), ()); Score roadScore = 0; - if (source == LinearSegmentSource::FromLocationReferenceTag && - !PassesRestrictionV3(e, functionalRoadClass, formOfWay, m_infoGetter, roadScore)) + if (!PassesRestrictionV3(e, functionalRoadClass, formOfWay, m_infoGetter, roadScore)) { continue; } From f041f910e75774e5c02dde90f5661ee2da325067 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 8 May 2025 23:16:37 +0300 Subject: [PATCH 019/252] [openlr] Documentation Signed-off-by: mvglasow --- openlr/graph.hpp | 22 +++-- openlr/helpers.hpp | 22 +++-- openlr/openlr_decoder.hpp | 9 +- openlr/openlr_model.hpp | 104 ++++++++++++++++------- openlr/router.hpp | 72 +++++++++++----- openlr/score_candidate_paths_getter.hpp | 15 ++-- openlr/score_candidate_points_getter.hpp | 4 +- openlr/score_paths_connector.hpp | 9 +- openlr/stats.hpp | 4 +- 9 files changed, 181 insertions(+), 80 deletions(-) diff --git a/openlr/graph.hpp b/openlr/graph.hpp index 4caa9f1fa..0b5476056 100644 --- a/openlr/graph.hpp +++ b/openlr/graph.hpp @@ -30,16 +30,26 @@ class Graph Graph(DataSource & dataSource, std::shared_ptr carModelFactory); - // Appends edges such as that edge.GetStartJunction() == junction to the |edges|. + /** + * Appends edges to `edges` such as that `edge.GetStartJunction() == junction`. + */ void GetOutgoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges); - // Appends edges such as that edge.GetEndJunction() == junction to the |edges|. + + /** + * Appends edges to `edges` such as that `edge.GetEndJunction() == junction`. + */ void GetIngoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges); - // Appends edges such as that edge.GetStartJunction() == junction and edge.IsFake() == false - // to the |edges|. + /** + * Appends edges to `edges` such as that `edge.GetStartJunction() == junction` and + * `edge.IsFake() == false`. + */ void GetRegularOutgoingEdges(Junction const & junction, EdgeListT & edges); - // Appends edges such as that edge.GetEndJunction() == junction and edge.IsFale() == false - // to the |edges|. + + /** + * Appends edges to `edges` such as that `edge.GetEndJunction() == junction` and + * `edge.IsFake() == false`. + */ void GetRegularIngoingEdges(Junction const & junction, EdgeListT & edges); void FindClosestEdges(m2::PointD const & point, uint32_t const count, diff --git a/openlr/helpers.hpp b/openlr/helpers.hpp index 11d78aea6..b9e7105af 100644 --- a/openlr/helpers.hpp +++ b/openlr/helpers.hpp @@ -47,14 +47,18 @@ std::common_type_t AbsDifference(T const a, U const b) bool PassesRestriction(Graph::Edge const & e, FunctionalRoadClass restriction, FormOfWay formOfWay, int frcThreshold, RoadInfoGetter & infoGetter); -/// \returns true if |e| conforms |functionalRoadClass| and |formOfWay| and false otherwise. -/// \note If the method returns true |score| should be considered next. +/** + * @return true if `e` conforms `functionalRoadClass` and `formOfWay` and false otherwise. + * @note If the method returns true `score` should be considered next. + */ bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay, RoadInfoGetter & infoGetter, Score & score); -/// \returns true if edge |e| conforms Lowest Functional Road Class to Next Point. -/// \note frc means Functional Road Class. Please see openlr documentation for details: -/// http://www.openlr.org/data/docs/whitepaper/1_0/OpenLR-Whitepaper_v1.0.pdf +/** + * @return true if edge `e` conforms Lowest Functional Road Class to Next Point. + * @note frc means Functional Road Class. Please see openlr documentation for details: + * http://www.openlr.org/data/docs/whitepaper/1_0/OpenLR-Whitepaper_v1.0.pdf + */ bool ConformLfrcnp(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, int frcThreshold, RoadInfoGetter & infoGetter); bool ConformLfrcnpV3(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, @@ -64,9 +68,11 @@ size_t IntersectionLen(Graph::EdgeVector a, Graph::EdgeVector b); bool SuffixEqualsPrefix(Graph::EdgeVector const & a, Graph::EdgeVector const & b, size_t len); -// Returns a length of the longest suffix of |a| that matches any prefix of |b|. -// Neither |a| nor |b| can contain several repetitions of any edge. -// Returns -1 if |a| intersection |b| is not equal to some suffix of |a| and some prefix of |b|. +/** + * Returns a length of the longest suffix of `a` that matches any prefix of `b`. + * Neither `a` nor `b` can contain several repetitions of any edge. + * Returns -1 if `a` intersection `b` is not equal to some suffix of `a` and some prefix of `b`. + */ int32_t PathOverlappingLen(Graph::EdgeVector const & a, Graph::EdgeVector const & b); m2::PointD PointAtSegmentM(m2::PointD const & p1, m2::PointD const & p2, double const distanceM); diff --git a/openlr/openlr_decoder.hpp b/openlr/openlr_decoder.hpp index 67a190220..9e612a29d 100644 --- a/openlr/openlr_decoder.hpp +++ b/openlr/openlr_decoder.hpp @@ -43,10 +43,17 @@ class OpenLRDecoder OpenLRDecoder(std::vector & dataSources, CountryParentNameGetter const & countryParentNameGetter); - // Maps partner segments to mwm paths. |segments| should be sorted by partner id. + /** + * Maps partner segments to mwm paths. + * + * `segments` should be sorted by partner id. + */ void DecodeV2(std::vector const & segments, uint32_t const numThreads, std::vector & paths); + /** + * Maps partner segments to mwm paths. + */ void DecodeV3(std::vector const & segments, uint32_t numThreads, std::vector & paths); diff --git a/openlr/openlr_model.hpp b/openlr/openlr_model.hpp index a33a6ce97..eb0d0cc4a 100644 --- a/openlr/openlr_model.hpp +++ b/openlr/openlr_model.hpp @@ -10,42 +10,70 @@ namespace openlr { -// The form of way (FOW) describes the physical road type of a line. +/** + * The form of way (FOW) describes the physical road type of a line. + */ enum class FormOfWay { - // The physical road type is unknown. + /** + * The physical road type is unknown. + */ Undefined, - // A road permitted for motorized vehicles only in combination with a prescribed minimum speed. - // It has two or more physically separated carriageways and no single level-crossings. + /** + * A road permitted for motorized vehicles only in combination with a prescribed minimum speed. + * It has two or more physically separated carriageways and no single level-crossings. + */ Motorway, - // A road with physically separated carriageways regardless of the number of lanes. - // If a road is also a motorway, it should be coded as such and not as a multiple carriageway. + /** + * A road with physically separated carriageways regardless of the number of lanes. + * If a road is also a motorway, it should be coded as such and not as a multiple carriageway. + */ MultipleCarriageway, - // All roads without separate carriageways are considered as roads with a single carriageway. + /** + * All roads without separate carriageways are considered as roads with a single carriageway. + */ SingleCarriageway, - // A road which forms a ring on which traffic traveling in only one direction is allowed. + /** + * A road which forms a ring on which traffic traveling in only one direction is allowed. + */ Roundabout, - // An open area (partly) enclosed by roads which is used for non-traffic purposes - // and which is not a Roundabout. + /** + * An open area (partly) enclosed by roads which is used for non-traffic purposes + * and which is not a Roundabout. + */ Trafficsquare, - // A road especially designed to enter or leave a line. + /** + * A road especially designed to enter or leave a line. + */ Sliproad, - // The physical road type is known, but does not fit into one of the other categories. + /** + * The physical road type is known, but does not fit into one of the other categories. + */ Other, - // A path only allowed for bikes. + /** + * A path only allowed for bikes. + */ BikePath, - // A path only allowed for pedestrians. + /** + * A path only allowed for pedestrians. + */ Footpath, NotAValue }; -// The functional road class (FRC) of a line is a road classification based on the importance -// of the road represented by the line. +/** + * The functional road class (FRC) of a line is a road classification based on the importance + * of the road represented by the line. + */ enum class FunctionalRoadClass { - // Main road, highest importance. + /** + * Main road, highest importance. + */ FRC0, - // First class road. + /** + * First class road. + */ FRC1, // Other road classes. @@ -70,27 +98,37 @@ enum class LinearSegmentSource struct LocationReferencePoint { - // Coordinates of the point of interest. + /** + * Coordinates of the point of interest. + */ ms::LatLon m_latLon = ms::LatLon::Zero(); - // The bearing (BEAR) describes the angle between the true North and a line which is defined - // by the coordinate of the LocationReferencePoint and a coordinate which is BEARDIST along - // the line defined by the LocationReference-point attributes. - // For more information see OpenLR-Whitepaper `Bearing' section. + + /** + * The bearing (BEAR) describes the angle between the true North and a line which is defined + * by the coordinate of the LocationReferencePoint and a coordinate which is BEARDIST along + * the line defined by the LocationReference-point attributes. + * For more information see OpenLR-Whitepaper ‘Bearing’ section. + */ uint8_t m_bearing = 0; FunctionalRoadClass m_functionalRoadClass = FunctionalRoadClass::NotAValue; FormOfWay m_formOfWay = FormOfWay::NotAValue; - // The distance to next point field describes the distance to the next LocationReferencePoint - // in the topological connection of the LocationReferencePoints. The distance is measured in meters - // and is calculated along the location reference path between two subsequent LR-points. - // The last LRP has the distance value 0. - // Should not be used in the last point of a segment. + /** + * The distance to next point field describes the distance to the next LocationReferencePoint + * in the topological connection of the LocationReferencePoints. The distance is measured in meters + * and is calculated along the location reference path between two subsequent LR-points. + * The last LRP has the distance value 0. + * Should not be used in the last point of a segment. + */ uint32_t m_distanceToNextPoint = 0; - // The lowest FunctionalRoadClass to the next point (LFRCNP) is the lowest FunctionalRoadClass - // value which appears in the location reference path between two consecutive LR-points. - // This information could be used to limit the number of road classes which need to be - // scanned during the decoding. - // Should not be used in the last point of a segment. + + /** + * The lowest FunctionalRoadClass to the next point (LFRCNP) is the lowest FunctionalRoadClass + * value which appears in the location reference path between two consecutive LR-points. + * This information could be used to limit the number of road classes which need to be + * scanned during the decoding. + * Should not be used in the last point of a segment. + */ FunctionalRoadClass m_lfrcnp = FunctionalRoadClass::NotAValue; bool m_againstDrivingDirection = false; }; diff --git a/openlr/router.hpp b/openlr/router.hpp index 118443402..c93207e48 100644 --- a/openlr/router.hpp +++ b/openlr/router.hpp @@ -34,20 +34,30 @@ class Router final class Score final { public: - // A weight for total length of true fake edges. + /** + * A weight for total length of true fake edges. + */ static const int kTrueFakeCoeff = 10; - // A weight for total length of fake edges that are parts of some - // real edges. + /** + * A weight for total length of fake edges that are parts of some + * real edges. + */ static constexpr double kFakeCoeff = 0.001; - // A weight for passing too far from pivot points. + /** + * A weight for passing too far from pivot points. + */ static const int kIntermediateErrorCoeff = 3; - // A weight for excess of distance limit. + /** + * A weight for excess of distance limit. + */ static const int kDistanceErrorCoeff = 3; - // A weight for deviation from bearing. + /** + * A weight for deviation from bearing. + */ static const int kBearingErrorCoeff = 5; void AddDistance(double p) { m_distance += p; } @@ -66,7 +76,9 @@ class Router final bool operator!=(Score const & rhs) const { return !(*this == rhs); } private: - // Reduced length of path in meters. + /** + * Reduced length of path in meters. + */ double m_distance = 0.0; double m_penalty = 0.0; @@ -143,21 +155,31 @@ class Router final bool Init(std::vector const & points, double positiveOffsetM, double negativeOffsetM); bool FindPath(Path & path); - // Returns true if the bearing should be checked for |u|, if the - // real passed distance from the source vertex is |distanceM|. + /** + * Returns true if the bearing should be checked for `u`, if the + * real passed distance from the source vertex is `distanceM`. + */ bool NeedToCheckBearing(Vertex const & u, double distanceM) const; double GetPotential(Vertex const & u) const; - // Returns true if |u| is located near portal to the next stage. - // |pi| is the potential of |u|. + /** + * Returns true if `u` is located near portal to the next stage. + * + * @param pi potential of `u`. + */ bool NearNextStage(Vertex const & u, double pi) const; - // Returns true if it's possible to move to the next stage from |u|. - // |pi| is the potential of |u|. + /** + * Returns true if it's possible to move to the next stage from `u`. + * + * @param pi potential of `u`. + */ bool MayMoveToNextStage(Vertex const & u, double pi) const; - // Returns true if |u| is a final vertex and the router may stop now. + /** + * Returns true if `u` is a final vertex and the router may stop now. + */ bool IsFinalVertex(Vertex const & u) const { return u.m_stage == m_pivots.size(); } double GetWeight(routing::Edge const & e) const @@ -194,20 +216,26 @@ class Router final template size_t FindPrefixLengthToConsume(It b, It const e, double lengthM); - // Finds all edges that are on (u, v) and have the same direction as - // (u, v). Then, computes the fraction of the union of these edges - // to the total length of (u, v). + /** + * Finds all edges that are on (u, v) and have the same direction as + * (u, v). Then, computes the fraction of the union of these edges + * to the total length of (u, v). + */ template double GetCoverage(m2::PointD const & u, m2::PointD const & v, It b, It e); - // Finds the longest prefix of [b, e) that covers edge (u, v). - // Returns the fraction of the coverage to the length of the (u, v). + /** + * Finds the longest prefix of [b, e) that covers edge (u, v). + * Returns the fraction of the coverage to the length of the (u, v). + */ template double GetMatchingScore(m2::PointD const & u, m2::PointD const & v, It b, It e); - // Finds the longest prefix of fake edges of [b, e) that have the - // same stage as |stage|. If the prefix exists, passes its bounding - // iterator to |fn|. + /** + * Finds the longest prefix of fake edges of [b, e) that have the + * same stage as `stage`. If the prefix exists, passes its bounding + * iterator to `fn`. + */ template void ForStagePrefix(It b, It e, size_t stage, Fn && fn); diff --git a/openlr/score_candidate_paths_getter.hpp b/openlr/score_candidate_paths_getter.hpp index c8fc14335..99203948d 100644 --- a/openlr/score_candidate_paths_getter.hpp +++ b/openlr/score_candidate_paths_getter.hpp @@ -55,8 +55,10 @@ class ScoreCandidatePathsGetter Graph::Edge const m_edge; double const m_distanceM; Score const m_pointScore; - // Minimum score of segments of the path going along |m_parent| based on functional road class - // and form of way. + /** + * Minimum score of segments of the path going along `m_parent` based on functional road class + * and form of way. + */ Score const m_minRoadScore; }; @@ -94,9 +96,12 @@ class ScoreCandidatePathsGetter // distance-to-next point is taken from point 3. You can learn more in // TomTom OpenLR spec. - /// \brief Fills |allPaths| with paths near start or finish point starting from |startLines|. - /// To extract a path from |allPaths| a item from |allPaths| should be taken, - /// then should be taken the member |m_parent| of the item and so on till the beginning. + /** + * @brief Fills `allPaths` with paths near start or finish point starting from `startLines`. + * + * To extract a path from `allPaths` a item from `allPaths` should be taken, + * then should be taken the member `m_parent` of the item and so on till the beginning. + */ void GetAllSuitablePaths(ScoreEdgeVec const & startLines, LinearSegmentSource source, bool isLastPoint, double bearDistM, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay, diff --git a/openlr/score_candidate_points_getter.hpp b/openlr/score_candidate_points_getter.hpp index 3c8c964ec..660ca470c 100644 --- a/openlr/score_candidate_points_getter.hpp +++ b/openlr/score_candidate_points_getter.hpp @@ -37,7 +37,9 @@ class ScoreCandidatePointsGetter ScoreEdgeVec & edgeCandidates); void EnrichWithProjectionPoints(m2::PointD const & p, ScoreEdgeVec & edgeCandidates); - /// \returns true if |p| is a junction and false otherwise. + /** + * @return true if `p` is a junction and false otherwise. + */ bool IsJunction(m2::PointD const & p); Score GetScoreByDistance(m2::PointD const & point, m2::PointD const & candidate); diff --git a/openlr/score_paths_connector.hpp b/openlr/score_paths_connector.hpp index 5561ceab9..1945e345b 100644 --- a/openlr/score_paths_connector.hpp +++ b/openlr/score_paths_connector.hpp @@ -17,9 +17,12 @@ class ScorePathsConnector public: ScorePathsConnector(Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat); - /// \brief Connects |lineCandidates| and fills |resultPath| with the path with maximum score - /// if there's a good enough. - /// \returns true if the best path is found and false otherwise. + /** + * @brief Connects `lineCandidates` and fills `resultPath` with the path with maximum score + * if there's a good enough one. + * + * @return true if the best path is found and false otherwise. + */ bool FindBestPath(std::vector const & points, std::vector> const & lineCandidates, LinearSegmentSource source, diff --git a/openlr/stats.hpp b/openlr/stats.hpp index 4f42a1116..01da5fb76 100644 --- a/openlr/stats.hpp +++ b/openlr/stats.hpp @@ -41,7 +41,9 @@ struct alignas(kCacheLineSize) Stats uint32_t m_noShortestPathFound = 0; uint32_t m_notEnoughScore = 0; uint32_t m_wrongOffsets = 0; - // Number of zeroed distance-to-next point values in the input. + /** + * Number of zeroed distance-to-next point values in the input. + */ uint32_t m_zeroDistToNextPointCount = 0; }; } // namespace V2 From 6e65e60c3d396fc1ebfc0c4d87ed5e9a3500ddbe Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 9 May 2025 17:56:55 +0300 Subject: [PATCH 020/252] [traffxml] Set FRC on all OpenLR location reference points Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 14b469473..9ba8d1926 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -104,11 +104,11 @@ openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool ba for (auto point : points) { openlr::LocationReferencePoint lrp = point.ToLrp(); + lrp.m_functionalRoadClass = GetFrc(); if (!locationReference.m_points.empty()) { locationReference.m_points.back().m_distanceToNextPoint = GuessDnp(locationReference.m_points.back(), lrp); - locationReference.m_points.back().m_functionalRoadClass = GetFrc(); } locationReference.m_points.push_back(lrp); } From 80a7ed503e65ebac962386509e1922cfb559ed8d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 9 May 2025 18:03:34 +0300 Subject: [PATCH 021/252] [traffxml] Tweak GuessDnp Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 9ba8d1926..0e54fa813 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -107,6 +107,7 @@ openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool ba lrp.m_functionalRoadClass = GetFrc(); if (!locationReference.m_points.empty()) { + // TODO use `distance` from TraFF reference point, if available and consistent with direct distance locationReference.m_points.back().m_distanceToNextPoint = GuessDnp(locationReference.m_points.back(), lrp); } @@ -226,19 +227,37 @@ std::optional TraffMessage::GetTrafficImpact() return std::nullopt; } +// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2) { double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_latLon), mercator::FromLatLon(p2.m_latLon)); /* - * The tolerance factor is currently 1/0.6, or ~1.67, so that direct distance is just at the - * lower boundary for `openlr::LinearSegmentSource::FromLocationReferenceTag`. Since we use - * `openlr::LinearSegmentSource::FromCoordinatesTag`, different tolerance values apply, - * so direct distance is well within the lower boundary, where the upper boundary is ~6.67 times - * the direct distance. This should work even in mountain areas, where the shortest route from - * one valley to the next, using long-distance roads, can be up to ~3 times the direct distance. + * Acceptance boundaries for candidate paths are currently: + * + * for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times + * the direct distance, + * + * for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance. + * + * A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all + * cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may + * cause the actual distance to be overestimated and an incorrect route chosen as a result, as + * path candidates are scored based on the match between DNP and their length. + * Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are + * much wider than that. + * In practice, the shortest route from one valley to the next in a mountain area is seldom more + * than 3 times the direct distance, based on a brief examination. This would be even within the + * limits of direct distance, hence we do not need a large correction factor for this scenario. + * + * Candidate values: + * 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits + * 1.41 (2^0.5) – ratio between two sides of a square and its diagonal + * 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66) + * 1.19 – close to the square root of 1.41 + * 1 – direct distance unmodified */ - return doe / 0.6f + 0.5f; + return doe * 1.19f + 0.5f; } /* From 2f6a8564cb67c2d9ed786986353b82b772856cf9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 9 May 2025 20:16:18 +0300 Subject: [PATCH 022/252] [openlr] Evaluate FOW for Sliproad/*_link Signed-off-by: mvglasow --- openlr/helpers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openlr/helpers.cpp b/openlr/helpers.cpp index 6c1c479d3..1d1fd6fee 100644 --- a/openlr/helpers.cpp +++ b/openlr/helpers.cpp @@ -175,7 +175,8 @@ bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRo score = *frcScore; Score constexpr kScoreForFormOfWay = 25; - if (formOfWay == FormOfWay::Roundabout && infoGetter.Get(e.GetFeatureId()).m_isRoundabout) + if ((formOfWay == FormOfWay::Roundabout && infoGetter.Get(e.GetFeatureId()).m_isRoundabout) + || (formOfWay == FormOfWay::Sliproad && infoGetter.Get(e.GetFeatureId()).m_link)) score += kScoreForFormOfWay; return true; From 5cdf14386d3e9c9e848324a4d20f4ca2d9a0e958 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 9 May 2025 20:30:26 +0300 Subject: [PATCH 023/252] [traffxml] Set OpenLR FOW for ramps Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 0e54fa813..3ece1a95f 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -105,6 +105,8 @@ openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool ba { openlr::LocationReferencePoint lrp = point.ToLrp(); lrp.m_functionalRoadClass = GetFrc(); + if (m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) + lrp.m_formOfWay = openlr::FormOfWay::Sliproad; if (!locationReference.m_points.empty()) { // TODO use `distance` from TraFF reference point, if available and consistent with direct distance From 73d61ff655985f69c7af3e0ce6e166b2089587ae Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 9 May 2025 22:04:40 +0300 Subject: [PATCH 024/252] [traffic] Store TraFF message ID with decoded path Signed-off-by: mvglasow --- map/traffic_manager.cpp | 1 + openlr/decoded_path.hpp | 1 + openlr/openlr_decoder.cpp | 2 ++ openlr/openlr_model.hpp | 1 + traffxml/traff_model.cpp | 10 ++++++++-- 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 15dac614f..4495468fe 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -440,6 +440,7 @@ void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, { LOG(LINFO, (" Path", i)); LOG(LINFO, (" Partner segment ID:", paths[i].m_segmentId)); + LOG(LINFO, (" Message ID:", paths[i].m_messageId)); LOG(LINFO, (" Edges:", paths[i].m_path.size())); for (size_t j = 0; j < paths[i].m_path.size(); j++) { diff --git a/openlr/decoded_path.hpp b/openlr/decoded_path.hpp index fe4923768..fdfc9a289 100644 --- a/openlr/decoded_path.hpp +++ b/openlr/decoded_path.hpp @@ -28,6 +28,7 @@ DECLARE_EXCEPTION(DecodedPathSaveError, RootException); struct DecodedPath { PartnerSegmentId m_segmentId; + std::string m_messageId; Path m_path; }; diff --git a/openlr/openlr_decoder.cpp b/openlr/openlr_decoder.cpp index 65dcd59ac..7a9df268d 100644 --- a/openlr/openlr_decoder.cpp +++ b/openlr/openlr_decoder.cpp @@ -234,6 +234,7 @@ class SegmentsDecoderV2 m_graph.ResetFakes(); path.m_segmentId.Set(segment.m_segmentId); + path.m_messageId = segment.m_messageId; auto const & points = segment.GetLRPs(); CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points")); @@ -322,6 +323,7 @@ class SegmentsDecoderV3 uint32_t constexpr kMaxProjectionCandidates = 5; path.m_segmentId.Set(segment.m_segmentId); + path.m_messageId = segment.m_messageId; auto const & points = segment.GetLRPs(); CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points")); diff --git a/openlr/openlr_model.hpp b/openlr/openlr_model.hpp index eb0d0cc4a..8acf7167e 100644 --- a/openlr/openlr_model.hpp +++ b/openlr/openlr_model.hpp @@ -151,6 +151,7 @@ struct LinearSegment LinearSegmentSource m_source = LinearSegmentSource::NotValid; // TODO(mgsergio): Think of using openlr::PartnerSegmentId uint32_t m_segmentId = kInvalidSegmentId; + std::string m_messageId = ""; // TODO(mgsergio): Make sure that one segment cannot contain // more than one location reference. LinearLocationReference m_locationReference; diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 3ece1a95f..f6e4715b9 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -127,8 +127,14 @@ std::vector TraffLocation::ToOpenLrSegments(std::string & for (int dir = 0; dir < dirs; dir++) { openlr::LinearSegment segment; - // TODO make this a reference to the TraFF message ID - segment.m_segmentId = 42; + /* + * Segment IDs are used internally by the decoder but nowhere else. + * Since we decode TraFF locations one at a time, there are at most two segments in a single + * decoder instance (one segment per direction). Therefore, a segment ID derived from the + * direction is unique within the decoder instance. + */ + segment.m_segmentId = dir; + segment.m_messageId = messageId; /* * Segments generated from coordinates can have any number of points. Each point, except for * the last point, must indicate the distance to the next point. Line properties (functional From a9ceec399583df39a463041329345baada099a23 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 10 May 2025 18:29:58 +0300 Subject: [PATCH 025/252] [traffic] Initialize TrafficManager with a DataSource Signed-off-by: mvglasow --- map/framework.cpp | 3 ++- map/traffic_manager.cpp | 6 ++++-- map/traffic_manager.hpp | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 0f2a65187..06b83b3c8 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -295,7 +295,8 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) [this]() -> StringsBundle const & { return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }), static_cast(*this)) - , m_trafficManager([this](string const & id) -> string { return m_storage.GetParentIdFor(id); }, + , m_trafficManager(m_featuresFetcher.GetDataSource(), + [this](string const & id) -> string { return m_storage.GetParentIdFor(id); }, bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes, m_routingManager.RoutingSession()) , m_lastReportedCountry(kInvalidCountryId) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 4495468fe..00dae2ec4 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -51,10 +51,12 @@ TrafficManager::CacheEntry::CacheEntry(time_point const & requestT , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) {} -TrafficManager::TrafficManager(const CountryParentNameGetterFn &countryParentNameGetter, +TrafficManager::TrafficManager(DataSource & dataSource, + const CountryParentNameGetterFn &countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer) - : m_countryParentNameGetterFn(countryParentNameGetter) + : m_dataSource(dataSource) + , m_countryParentNameGetterFn(countryParentNameGetter) , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 9f6f9a2e2..c52f1172d 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -7,6 +7,7 @@ #include "drape/pointers.hpp" +#include "indexer/data_source.hpp" #include "indexer/mwm_set.hpp" #include "openlr/openlr_decoder.hpp" @@ -75,7 +76,8 @@ class TrafficManager final using TrafficStateChangedFn = std::function; using GetMwmsByRectFn = std::function(m2::RectD const &)>; - TrafficManager(CountryParentNameGetterFn const & countryParentNameGetter, + TrafficManager(DataSource & dataSource, + CountryParentNameGetterFn const & countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer); ~TrafficManager(); @@ -402,6 +404,7 @@ class TrafficManager final std::for_each(activeMwms.begin(), activeMwms.end(), std::forward(f)); } + DataSource & m_dataSource; CountryParentNameGetterFn m_countryParentNameGetterFn; GetMwmsByRectFn m_getMwmsByRectFn; traffic::TrafficObserver & m_observer; From d0a9c564e45ee5bbbb81691ed3ff4034fefd85f6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 10 May 2025 22:03:15 +0300 Subject: [PATCH 026/252] [traffic] Process TrafficImpact::m_maxspeed Signed-off-by: mvglasow --- map/traffic_manager.cpp | 48 +++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 00dae2ec4..d53bd152a 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -1,7 +1,10 @@ #include "map/traffic_manager.hpp" +#include "routing/maxspeeds.hpp" #include "routing/routing_helpers.hpp" +#include "routing_common/maxspeed_conversion.hpp" + #include "drape_frontend/drape_engine.hpp" #include "drape_frontend/visual_params.hpp" @@ -454,12 +457,6 @@ void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, // store decoded paths and speed groups in trafficCache if (impact) { - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ for (size_t i = 0; i < paths.size(); i++) for (size_t j = 0; j < paths[i].m_path.size(); j++) { @@ -469,8 +466,45 @@ void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, uint8_t direction = paths[i].m_path[j].IsForward() ? traffic::TrafficInfo::RoadSegmentId::kForwardDirection : traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + + /* + * Consolidate TrafficImpact into a single SpeedGroup per segment. + * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate + * the rest. + */ + traffic::SpeedGroup sg = impact.value().m_speedGroup; + /* + * TODO also process m_delayMins if greater than zero. + * This would require a separate pass over all edges, calculating length, + * total (normal) travel time (length / maxspeed), then a speed group based on + * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between + * reduced and normal speed. That would give us a third potential speed group. + */ + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.value().m_maxspeed != traffxml::kMaxspeedNone)) + { + auto const handle = m_dataSource.GetMwmHandleById(paths[i].m_path[j].GetFeatureId().m_mwmId); + auto const speeds = routing::LoadMaxspeeds(handle); + if (speeds) + { + traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; + auto const speed = speeds->GetMaxspeed(paths[i].m_path[j].GetFeatureId().m_index); + auto const speedKmPH = speed.GetSpeedKmPH(paths[i].m_path[j].IsForward()); + if (speedKmPH != routing::kInvalidSpeed) + { + fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.value().m_maxspeed * 100.0f / speedKmPH); + if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) + sg = fromMaxspeed; + } + } + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ + } // TODO process all TrafficImpact fields and determine the speed group based on that - trafficCache[countryName][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = impact.value().m_speedGroup; + trafficCache[countryName][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = sg; } } } From 382e46af63f56229caea955a77f9aea55050fffc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 11 May 2025 11:50:11 +0300 Subject: [PATCH 027/252] [routing] Documentation Signed-off-by: mvglasow --- routing_common/maxspeed_conversion.hpp | 50 +++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/routing_common/maxspeed_conversion.hpp b/routing_common/maxspeed_conversion.hpp index 7f5fe1dff..293bdc1d0 100644 --- a/routing_common/maxspeed_conversion.hpp +++ b/routing_common/maxspeed_conversion.hpp @@ -218,19 +218,53 @@ class Maxspeed MaxspeedType GetForward() const { return m_forward; } MaxspeedType GetBackward() const { return m_backward; } + /** + * @brief Whether the maxspeed is valid for the forward direction. + * + * Valid maxspeeds include numeric values as well as `kNoneMaxSpeed` and `kWalkMaxSpeed`. + * + * Only the forward direction is evaluated, maxspeed for the backward direction may still be invalid. + * To check for valid maxspeed in both directions, use `IsBidirectional()`. + * + * @return true if valid, false if not. + */ bool IsValid() const { return m_forward != kInvalidSpeed; } - /// \returns true if Maxspeed is considered as Bidirectional(). It means different - /// speed is set for forward and backward direction. Otherwise returns false. It means - /// |m_forward| speed should be used for the both directions. + + /** + * @brief Whether the maxspeed is valid for both directions. + * + * @return true if valid for both directions, false if not. + * + * @todo The documentation previously stated: + * “[Returns] true if Maxspeed is considered as Bidirectional(). It means different speed is set + * for forward and backward direction. Otherwise returns false. It means `m_forward` speed should + * be used for the both directions.” However, this is at odds with the actual code, which just + * checks for validity, not identity. + */ bool IsBidirectional() const { return IsValid() && m_backward != kInvalidSpeed; } - /// \brief returns speed according to |m_units|. |kInvalidSpeed|, |kNoneMaxSpeed| or - /// |kWalkMaxSpeed| may be returned. + /** + * @brief Returns maxspeed in native units. + * + * Native units are the units returned by `GetUnits()`. + * + * @param forward Whether to return maxspeed for the forward or backward direction. + * + * @return Speed in native units, or `kInvalidSpeed`, `kNoneMaxSpeed` or `kWalkMaxSpeed`. + */ MaxspeedType GetSpeedInUnits(bool forward) const; - /// \brief returns speed in km per hour. If it's not valid |kInvalidSpeed| is - /// returned. Otherwise forward or backward speed in km per hour is returned. |kNoneMaxSpeed| and - /// |kWalkMaxSpeed| are converted to some numbers. + /** + * @brief Returns maxspeed in km per hour. + * + * If the maxspeed is not valid `kInvalidSpeed` is returned. Otherwise forward or backward speed + * in km per hour is returned. `kNoneMaxSpeed` and `kWalkMaxSpeed` are converted to actual speeds + * which can be used directly. + * + * @param forward Whether to return maxspeed for the forward or backward direction. + * + * @return Speed in km/h, or `kInvalidSpeed`. + */ MaxspeedType GetSpeedKmPH(bool forward) const; private: From d7facd5732d06478a314ccf406939ae051af2186 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 11 May 2025 19:03:10 +0300 Subject: [PATCH 028/252] [openlr] Initialize OpenLR decoder with a single DataSource Signed-off-by: mvglasow --- map/traffic_manager.cpp | 24 ++++-------------------- map/traffic_manager.hpp | 9 ++++++++- openlr/openlr_decoder.cpp | 8 ++++---- openlr/openlr_decoder.hpp | 4 ++-- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index d53bd152a..bbeb1222d 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -60,6 +60,7 @@ TrafficManager::TrafficManager(DataSource & dataSource, traffic::TrafficObserver & observer) : m_dataSource(dataSource) , m_countryParentNameGetterFn(countryParentNameGetter) + , m_openLrDecoder(m_dataSource, countryParentNameGetter) , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) @@ -403,8 +404,7 @@ void TrafficManager::InitializeDataSources(std::vector & dataS * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path * structures to accept a TraFF message ID (string) rather than an integer. */ -void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, - traffxml::TraffMessage & message, std::map & trafficCache) { if (message.m_location) @@ -439,7 +439,7 @@ void TrafficManager::DecodeMessage(openlr::OpenLRDecoder & decoder, // Decode the location into a path on the map. // One path per segment std::vector paths(segments.size()); - decoder.DecodeV3(segments, kNumDecoderThreads, paths); + m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); for (size_t i = 0; i < paths.size(); i++) { @@ -547,22 +547,6 @@ void TrafficManager::ThreadRoutine() UpdateMessageCache(m_messageCache); LOG(LINFO, (m_messageCache.size(), "message(s) in cache")); - // initialize the decoder - /* - * Access to `DataSource` is not thread-safe. The main app, which works with - * `EditableDataSource` (as the map can be edited), wraps map operations into a - * `FeaturesLoaderGuard`. The OpenLR decoder expects one `FrozenDataSource` (a read-only - * subclass) per worker thread – which works as long as the map is not modified. - * Edits are not relevant to the OpenLR decoder. However, if the edits modify MWM files (rather - * than being stored separately), this might confuse the `FrozenDataSource`. In this case, we - * would need to rewrite the OpenLR decoder to work with a `FeaturesLoaderGuard` (which is - * probably the more elegant way to do this anyway). - */ - std::vector dataSources(kNumDecoderThreads); - // TODO test with data source from framework - InitializeDataSources(dataSources); - openlr::OpenLRDecoder decoder(dataSources, m_countryParentNameGetterFn); - /* * Map between country names and their colorings. * TODO use MwmId as map keys: @@ -577,7 +561,7 @@ void TrafficManager::ThreadRoutine() for (auto [id, message] : m_messageCache) { LOG(LINFO, (" ", id, ":", message)); - DecodeMessage(decoder, message, allMwmColoring); + DecodeMessage(message, allMwmColoring); } // set new coloring for MWMs diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c52f1172d..c354e00fc 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -259,7 +259,7 @@ class TrafficManager final * @param message The message to decode. * @param trafficCache The cache in which all decoded paths with their speed groups will be stored. */ - void DecodeMessage(openlr::OpenLRDecoder &decoder, traffxml::TraffMessage & message, + void DecodeMessage(traffxml::TraffMessage & message, std::map & trafficCache); /** @@ -514,6 +514,13 @@ class TrafficManager final * Keys are message IDs, values are messages. */ std::map m_messageCache; + + /** + * @brief The OpenLR decoder instance. + * + * Used to decode TraFF locations into road segments on the map. + */ + openlr::OpenLRDecoder m_openLrDecoder; }; extern std::string DebugPrint(TrafficManager::TrafficState state); diff --git a/openlr/openlr_decoder.cpp b/openlr/openlr_decoder.cpp index 7a9df268d..aba779f8c 100644 --- a/openlr/openlr_decoder.cpp +++ b/openlr/openlr_decoder.cpp @@ -437,9 +437,9 @@ bool OpenLRDecoder::SegmentsFilter::Matches(LinearSegment const & segment) const } // OpenLRDecoder ----------------------------------------------------------------------------- -OpenLRDecoder::OpenLRDecoder(vector & dataSources, +OpenLRDecoder::OpenLRDecoder(DataSource & dataSource, CountryParentNameGetter const & countryParentNameGetter) - : m_dataSources(dataSources), m_countryParentNameGetter(countryParentNameGetter) + : m_dataSource(dataSource), m_countryParentNameGetter(countryParentNameGetter) { } @@ -490,9 +490,9 @@ void OpenLRDecoder::Decode(vector const & segments, vector stats(numThreads); vector workers; for (size_t i = 1; i < numThreads; ++i) - workers.emplace_back(worker, i, ref(m_dataSources[i]), ref(stats[i])); + workers.emplace_back(worker, i, ref(m_dataSource), ref(stats[i])); - worker(0 /* threadNum */, m_dataSources[0], stats[0]); + worker(0 /* threadNum */, m_dataSource, stats[0]); for (auto & worker : workers) worker.join(); diff --git a/openlr/openlr_decoder.hpp b/openlr/openlr_decoder.hpp index 9e612a29d..a7ee9e952 100644 --- a/openlr/openlr_decoder.hpp +++ b/openlr/openlr_decoder.hpp @@ -40,7 +40,7 @@ class OpenLRDecoder bool const m_multipointsOnly; }; - OpenLRDecoder(std::vector & dataSources, + OpenLRDecoder(DataSource & dataSource, CountryParentNameGetter const & countryParentNameGetter); /** @@ -62,7 +62,7 @@ class OpenLRDecoder void Decode(std::vector const & segments, uint32_t const numThreads, std::vector & paths); - std::vector & m_dataSources; + DataSource & m_dataSource; CountryParentNameGetter m_countryParentNameGetter; }; } // namespace openlr From fafec070c96a471ce1b1608f35d947b0cfd19506 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 13 May 2025 00:49:53 +0300 Subject: [PATCH 029/252] [traffic] Use MWM ID for Coloring map, now that we have a single DataSource Signed-off-by: mvglasow --- map/traffic_manager.cpp | 22 ++++++++-------------- map/traffic_manager.hpp | 4 ++-- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index bbeb1222d..eabbdfbbc 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -404,7 +404,7 @@ void TrafficManager::InitializeDataSources(std::vector & dataS * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path * structures to accept a TraFF message ID (string) rather than an integer. */ -void TrafficManager::DecodeMessage(traffxml::TraffMessage & message, std::map & trafficCache) { if (message.m_location) @@ -460,7 +460,6 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message, std::mapGetCountryName(); auto fid = paths[i].m_path[j].GetFeatureId().m_index; auto segment = paths[i].m_path[j].GetSegId(); uint8_t direction = paths[i].m_path[j].IsForward() ? @@ -504,7 +503,7 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message, std::map allMwmColoring; + //TODO should we use std::map> ? + // TODO store mwm/segment/speed group map with each message, build allMwmColoring on the fly + std::map allMwmColoring; for (auto [id, message] : m_messageCache) { LOG(LINFO, (" ", id, ":", message)); @@ -703,7 +697,7 @@ void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) } #endif -void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) +void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) { /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: @@ -716,7 +710,7 @@ void TrafficManager::OnTrafficDataUpdate(std::mapGetCountryName()); + auto tcit = trafficCache.find(mwmId); if (tcit != trafficCache.end()) { std::lock_guard lock(m_mutex); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c354e00fc..4838381f1 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -260,7 +260,7 @@ class TrafficManager final * @param trafficCache The cache in which all decoded paths with their speed groups will be stored. */ void DecodeMessage(traffxml::TraffMessage & message, - std::map & trafficCache); + std::map &trafficCache); /** * @brief Event loop for the traffic worker thread. @@ -290,7 +290,7 @@ class TrafficManager final * * @param trafficCache The new per-MWM colorings (preprocessed traffic information). */ - void OnTrafficDataUpdate(std::map & trafficCache); + void OnTrafficDataUpdate(std::map &trafficCache); // TODO no longer needed #ifdef traffic_dead_code From 7107314e2f1403c11dc41978fad5389637c9c67c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 14 May 2025 19:22:50 +0300 Subject: [PATCH 030/252] [traffic] Store colorings with message and build global coloring from that Signed-off-by: mvglasow --- map/traffic_manager.cpp | 10 ++++++---- traffxml/CMakeLists.txt | 1 + traffxml/traff_model.cpp | 24 ++++++++++++++++++++++++ traffxml/traff_model.hpp | 25 +++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index eabbdfbbc..d6ba95c05 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -404,8 +404,7 @@ void TrafficManager::InitializeDataSources(std::vector & dataS * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path * structures to accept a TraFF message ID (string) rather than an integer. */ -void TrafficManager::DecodeMessage(traffxml::TraffMessage & message, std::map & trafficCache) +void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) { if (message.m_location) { @@ -503,7 +502,7 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message, std::mapsecond.find(rsid) ; c_it != target_it->second.end()) + { + // if delta overrules target (target is Unknown, delta is TempBlock or delta is slower than target) + if ((sg == traffic::SpeedGroup::TempBlock) + || (c_it->second == traffic::SpeedGroup::Unknown) || (sg < c_it->second)) + target_it->second[rsid] = sg; + } + else + // if target[mwm] does not contain segment, add speed group + target_it->second[rsid] = sg; + else + // if target does not contain mwm, add coloring + target[mwmId] = coloring; +} + /* string DebugPrint(LinearSegmentSource source) { diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 3e8fd539f..436f99966 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -5,10 +5,14 @@ #include "geometry/latlon.hpp" #include "geometry/point2d.hpp" +#include "indexer/mwm_set.hpp" + #include "openlr/openlr_model.hpp" #include "traffic/speed_groups.hpp" +#include "traffic/traffic_info.hpp" +#include #include #include @@ -271,6 +275,11 @@ struct TraffEvent // TODO supplementary information }; +/** + * @brief Global mapping from feature segments to speed groups, across all MWMs. + */ +using MultiMwmColoring = std::map>; + struct TraffMessage { /** @@ -299,6 +308,7 @@ struct TraffMessage std::optional m_location; std::vector m_events; std::vector m_replaces; + MultiMwmColoring m_decoded; }; using TraffFeed = std::vector; @@ -317,6 +327,21 @@ using TraffFeed = std::vector; */ uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2); +/** + * @brief Merges the contents of one `MultiMwmColoring` into another. + * + * After this function returns, `target` will hold the union of the entries it had prior to the + * function call and the entries from `delta`. + * + * In case of conflict, the more restrictive speed group wins. That is, `TempBlock` overrides + * everything else, `Unknown` never overrides anything else, and among `G0` to `G5`, the lowest + * group wins. + * + * @param delta Contains the entries to be added. + * @param target Receives the added entries. + */ +void MergeMultiMwmColoring(MultiMwmColoring &delta, MultiMwmColoring & target); + std::string DebugPrint(IsoTime time); std::string DebugPrint(Directionality directionality); std::string DebugPrint(Ramps ramps); From 53e80b9283b82a1937965ca8847fb583e42b7312 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 14 May 2025 20:23:14 +0300 Subject: [PATCH 031/252] [traffic] Refactor m_feeds to m_feedQueue Signed-off-by: mvglasow --- map/traffic_manager.cpp | 14 +++++++------- map/traffic_manager.hpp | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index d6ba95c05..527ffdd2f 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -335,7 +335,7 @@ bool TrafficManager::Poll() { { std::lock_guard lock(m_mutex); - m_feeds.push_back(feed); + m_feedQueue.push_back(feed); } return true; } @@ -349,21 +349,21 @@ bool TrafficManager::Poll() void TrafficManager::Push(traffxml::TraffFeed feed) { std::lock_guard lock(m_mutex); - m_feeds.push_back(feed); + m_feedQueue.push_back(feed); } void TrafficManager::UpdateMessageCache(std::map & cache) { traffxml::TraffFeed feed; - // Thread-safe iteration over m_feeds, releasing the mutex during the loop + // Thread-safe iteration over m_feedQueue, releasing the mutex during the loop while (true) { { std::lock_guard lock(m_mutex); - if (!m_feeds.empty()) + if (!m_feedQueue.empty()) { - feed = m_feeds.front(); - m_feeds.erase(m_feeds.begin()); + feed = m_feedQueue.front(); + m_feedQueue.erase(m_feedQueue.begin()); } else break; @@ -535,7 +535,7 @@ void TrafficManager::ThreadRoutine() // TODO set failed status somewhere and retry } } - LOG(LINFO, (m_feeds.size(), "feed(s) in queue")); + LOG(LINFO, (m_feedQueue.size(), "feed(s) in queue")); /* * TODO call on a temp struct, then unite with m_messageCache, processing only messages with changes diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 4838381f1..33977e271 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -237,10 +237,10 @@ class TrafficManager final void Push(traffxml::TraffFeed feed); /** - * @brief Merges new messages from `m_feeds` into a message cache. * - * Existing messages in `cache` will be overwritten by newer messages with the same ID in `m_feeds`. + * @brief Merges new messages from `m_feedQueue` into a message cache. * + * Existing messages in `cache` will be overwritten by newer messages with the same ID in `m_feedQueue`. * @param cache The message cache. */ void UpdateMessageCache(std::map & cache); @@ -503,10 +503,10 @@ class TrafficManager final /** * @brief Queue of feeds waiting to be processed. * - * Threads must lock `m_mutex` before accessing `m_feeds`, as some platforms may receive feeds + * Threads must lock `m_mutex` before accessing `m_feedQueue`, as some platforms may receive feeds * on multiple threads. */ - std::vector m_feeds; + std::vector m_feedQueue; /** * @brief Cache of all currently active TraFF messages. From edb1b7e784cfe244bbb1dbf61127dddee3bf497d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 14 May 2025 21:03:54 +0300 Subject: [PATCH 032/252] [traffic] Consolidate feed queue before decoding messages Signed-off-by: mvglasow --- map/traffic_manager.cpp | 53 ++++++++++++++++++++++++++++++++++++++++ map/traffic_manager.hpp | 9 +++++++ traffxml/traff_model.cpp | 14 +++++++++++ traffxml/traff_model.hpp | 4 +++ 4 files changed, 80 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 527ffdd2f..7e2e6182c 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -352,6 +352,56 @@ void TrafficManager::Push(traffxml::TraffFeed feed) m_feedQueue.push_back(feed); } +void TrafficManager::ConsolidateFeedQueue() +{ + std::lock_guard lock(m_mutex); + if (m_feedQueue.empty()) + return; + for (size_t i = m_feedQueue.size() - 1; i <= 0; i--) + for (size_t j = m_feedQueue.size() - 1; j <= 0; j--) + { + if (i == j) + continue; + for (auto it_i = m_feedQueue[i].begin(); it_i != m_feedQueue[i].end(); ) + for (auto it_j = m_feedQueue[j].end(); it_j != m_feedQueue[j].end(); ) + if (it_i->m_id == it_j->m_id) + { + // dupe, remove older + if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + { + // standard case: i has the newer one + ++it_i; + it_j = m_feedQueue[j].erase(it_j); + } + else if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + { + // j has the newer one + it_i = m_feedQueue[i].erase(it_i); + ++it_j; + } + else if (i > j) + { + // same time, but feed i was received after j, keep i + ++it_i; + it_j = m_feedQueue[j].erase(it_j); + } + else + { + // same time, but feed j was received after i, keep j + ASSERT(i != j, ()); + it_i = m_feedQueue[i].erase(it_i); + ++it_j; + } + } + } + // remove empty feeds + for (auto it = m_feedQueue.begin(); it != m_feedQueue.end(); ) + if (it->empty()) + it = m_feedQueue.erase(it); + else + ++it; +} + void TrafficManager::UpdateMessageCache(std::map & cache) { traffxml::TraffFeed feed; @@ -537,6 +587,9 @@ void TrafficManager::ThreadRoutine() } LOG(LINFO, (m_feedQueue.size(), "feed(s) in queue")); + // consolidate feed queue (remove older messages in favor of newer ones) + ConsolidateFeedQueue(); + /* * TODO call on a temp struct, then unite with m_messageCache, processing only messages with changes * (adding segments for new messages, removing segments for deleted messages, replacing segments diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 33977e271..613ad3bd0 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -237,7 +237,16 @@ class TrafficManager final void Push(traffxml::TraffFeed feed); /** + * @brief Consolidates the feed queue. * + * If multiple feeds in the queue have the same message ID, only the message with the newest + * update time is kept (if two messages have the same ID and update time, the one in the feed + * with the higher index is kept); other messages with the same ID are discarded. Empty feeds + * are discarded. + */ + void ConsolidateFeedQueue(); + + /** * @brief Merges new messages from `m_feedQueue` into a message cache. * * Existing messages in `cache` will be overwritten by newer messages with the same ID in `m_feedQueue`. diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index f61b07adf..c68ed461d 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -76,6 +76,20 @@ const std::map kEventDelayMap{ // TODO Security*, Transport*, Weather* (not in enum yet) }; +bool operator< (IsoTime lhs, IsoTime rhs) +{ + std::time_t t_lhs = std::mktime(&lhs); + std::time_t t_rhs = std::mktime(&rhs); + return t_lhs < t_rhs; +} + +bool operator> (IsoTime lhs, IsoTime rhs) +{ + std::time_t t_lhs = std::mktime(&lhs); + std::time_t t_rhs = std::mktime(&rhs); + return t_lhs > t_rhs; +} + openlr::LocationReferencePoint Point::ToLrp() { openlr::LocationReferencePoint result; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 436f99966..faa8135ab 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -35,8 +35,12 @@ constexpr uint8_t kMaxspeedNone = 255; * Where no time zone is indicated, the timestamp shall always be interpreted as UTC. * `IsoTime` currently maps to `std::tm`, but this is not guaranteed. */ +// TODO make this a class with a private std::tm member using IsoTime = std::tm; +bool operator< (IsoTime lhs, IsoTime rhs); +bool operator> (IsoTime lhs, IsoTime rhs); + // TODO enum urgency enum class Directionality From 3a713c477a4a2be94c79c19b7f927f7a2ceeaa86 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 14 May 2025 21:47:16 +0300 Subject: [PATCH 033/252] [traffic] Refactor IsoTime into a class Signed-off-by: mvglasow --- map/traffic_manager.cpp | 6 +-- traffxml/traff_model.cpp | 73 +++++++++++++++++++++++++++++++++--- traffxml/traff_model.hpp | 48 ++++++++++++++++++++---- traffxml/traff_model_xml.cpp | 50 ++++-------------------- 4 files changed, 119 insertions(+), 58 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 7e2e6182c..9e33135f2 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -367,13 +367,13 @@ void TrafficManager::ConsolidateFeedQueue() if (it_i->m_id == it_j->m_id) { // dupe, remove older - if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + if (it_i->m_updateTime < it_j->m_updateTime) { // standard case: i has the newer one ++it_i; it_j = m_feedQueue[j].erase(it_j); } - else if (traffxml::operator<(it_i->m_updateTime, it_j->m_updateTime)) + else if (it_i->m_updateTime < it_j->m_updateTime) { // j has the newer one it_i = m_feedQueue[i].erase(it_i); @@ -425,7 +425,7 @@ void TrafficManager::UpdateMessageCache(std::mapsecond.m_updateTime)) < timegm(&(message.m_updateTime))); + process = (it->second.m_updateTime < message.m_updateTime); if (process) cache.insert_or_assign(message.m_id, message); } diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index c68ed461d..7479f713f 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -4,6 +4,8 @@ #include "geometry/mercator.hpp" +#include + using namespace std; namespace traffxml @@ -76,17 +78,78 @@ const std::map kEventDelayMap{ // TODO Security*, Transport*, Weather* (not in enum yet) }; +std::optional IsoTime::ParseIsoTime(std::string timeString) +{ + /* + * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher + * will contain the following items: + * + * 0: 2019-11-01T11:55:42+01:00 (entire expression) + * 1: 2019 (year) + * 2: 11 (month) + * 3: 01 (day) + * 4: 11 (hour, local) + * 5: 55 (minute, local) + * 6: 42.445 (second, local, float) + * 7: .445 (fractional seconds) + * 8: +01:00 (complete UTC offset, or Z; blank if not specified) + * 9: +01:00 (complete UTC offset, blank for Z or of not specified) + * 10: +01 (UTC offset, hours with sign; blank for Z or if not specified) + * 11: :00 (UTC offset, minutes, prefixed with separator) + * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) + */ + std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); + + std::smatch iso8601Matcher; + if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) + { + int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; + int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; + if (offset_h < 0) + offset_m *= -1; + + std::tm tm = {}; + tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; + tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; + tm.tm_mday = std::stoi(iso8601Matcher[3]); + tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; + tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; + tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; + // Call timegm once to normalize tm; return value can be discarded + timegm(&tm); + IsoTime result(tm); + return result; + } + else + { + LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString)); + return std::nullopt; + } +} + +IsoTime IsoTime::Now() +{ + std::time_t t = std::time(nullptr); + std::tm* tm = std::gmtime(&t); + return IsoTime(*tm); +} + +IsoTime::IsoTime(std::tm tm) + : m_tm(tm) +{} + + bool operator< (IsoTime lhs, IsoTime rhs) { - std::time_t t_lhs = std::mktime(&lhs); - std::time_t t_rhs = std::mktime(&rhs); + std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs < t_rhs; } bool operator> (IsoTime lhs, IsoTime rhs) { - std::time_t t_lhs = std::mktime(&lhs); - std::time_t t_rhs = std::mktime(&rhs); + std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs > t_rhs; } @@ -321,7 +384,7 @@ string DebugPrint(LinearSegmentSource source) std::string DebugPrint(IsoTime time) { std::ostringstream os; - os << std::put_time(&time, "%Y-%m-%d %H:%M:%S %z"); + os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z"); return os.str(); } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index faa8135ab..221b0c500 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -35,11 +35,45 @@ constexpr uint8_t kMaxspeedNone = 255; * Where no time zone is indicated, the timestamp shall always be interpreted as UTC. * `IsoTime` currently maps to `std::tm`, but this is not guaranteed. */ -// TODO make this a class with a private std::tm member -using IsoTime = std::tm; +class IsoTime +{ +public: + /** + * @brief Parses time in ISO 8601 format from a string and stores it in an `IsoTime`. + * + * ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC + * offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which + * is 10:45:42 UTC. + * + * A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed + * if no UTC offset is specified. + * + * The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`. + * + * Seconds can be specified as integer or float, but will be rounded to the nearest integer. For + * example, 42.645 seconds will be rounded to 43 seconds. + * + * @param timeString Time in ISO8601 format + * @return An `IsoTime` instance corresponding to `timeString`, or `std::nullopt` if `timeString` is not a valid ISO8601 time string. + */ + static std::optional ParseIsoTime(std::string timeString); -bool operator< (IsoTime lhs, IsoTime rhs); -bool operator> (IsoTime lhs, IsoTime rhs); + /** + * @brief Returns an `IsoTime` corresponding to current wall clock time. + * + * @return An `IsoTime` corresponding to current wall clock time. + */ + static IsoTime Now(); + + friend bool operator< (IsoTime lhs, IsoTime rhs); + friend bool operator> (IsoTime lhs, IsoTime rhs); +private: + friend std::string DebugPrint(IsoTime time); + + IsoTime(std::tm tm); + + std::tm m_tm; +}; // TODO enum urgency @@ -301,9 +335,9 @@ struct TraffMessage std::optional GetTrafficImpact(); std::string m_id; - IsoTime m_receiveTime = {}; - IsoTime m_updateTime = {}; - IsoTime m_expirationTime = {}; + IsoTime m_receiveTime = IsoTime::Now(); + IsoTime m_updateTime = IsoTime::Now(); + IsoTime m_expirationTime = IsoTime::Now(); std::optional m_startTime = {}; std::optional m_endTime = {}; bool m_cancellation = false; diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index ae7cb4f6d..1bc634648 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -196,49 +196,13 @@ bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) std::string timeString; if (!StringFromXml(attribute, timeString)) return false; - /* - * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher - * will contain the following items: - * - * 0: 2019-11-01T11:55:42+01:00 (entire expression) - * 1: 2019 (year) - * 2: 11 (month) - * 3: 01 (day) - * 4: 11 (hour, local) - * 5: 55 (minute, local) - * 6: 42.445 (second, local, float) - * 7: .445 (fractional seconds) - * 8: +01:00 (complete UTC offset, or Z; blank if not specified) - * 9: +01:00 (complete UTC offset, blank for Z or of not specified) - * 10: +01 (UTC offset, hours with sign; blank for Z or if not specified) - * 11: :00 (UTC offset, minutes, prefixed with separator) - * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) - */ - std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); - - std::smatch iso8601Matcher; - if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) - { - int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; - int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; - if (offset_h < 0) - offset_m *= -1; - - tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; - tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; - tm.tm_mday = std::stoi(iso8601Matcher[3]); - tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; - tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; - tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; - // Call timegm once to normalize tm; return value can be discarded - timegm(&tm); - return true; - } - else - { - LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString)); + + std::optional result = IsoTime::ParseIsoTime(timeString); + if (!result) return false; - } + + tm = result.value(); + return true; } /** @@ -261,7 +225,7 @@ bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) */ std::optional OptionalTimeFromXml(pugi::xml_attribute attribute) { - IsoTime result = {}; + IsoTime result = IsoTime::Now(); if (!TimeFromXml(attribute, result)) return std::nullopt; return result; From cf57942a0b0fd19ebb23d2c6b5ff831b70fc682f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 15 May 2025 23:54:21 +0300 Subject: [PATCH 034/252] [traffic] Allow decoding to be interrupted after each message Message deduplication currently disabled Signed-off-by: mvglasow --- map/traffic_manager.cpp | 157 ++++++++++++++++++++++++---------------- map/traffic_manager.hpp | 23 ++++-- 2 files changed, 110 insertions(+), 70 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 9e33135f2..d8a016a90 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -25,6 +25,9 @@ using namespace std::chrono; namespace { +/** + * Poll interval for traffic data + */ auto constexpr kUpdateInterval = minutes(1); auto constexpr kOutdatedDataTimeout = minutes(5) + kUpdateInterval; auto constexpr kNetworkErrorTimeout = minutes(20); @@ -337,11 +340,15 @@ bool TrafficManager::Poll() std::lock_guard lock(m_mutex); m_feedQueue.push_back(feed); } + m_lastResponseTime = steady_clock::now(); + m_isPollNeeded = false; return true; } else { LOG(LWARNING, ("An error occurred parsing the TraFF feed")); + // TODO should we really reset m_isPollNeeded here? + m_isPollNeeded = false; return false; } } @@ -350,6 +357,7 @@ void TrafficManager::Push(traffxml::TraffFeed feed) { std::lock_guard lock(m_mutex); m_feedQueue.push_back(feed); + // TODO should we update m_lastResponseTime? } void TrafficManager::ConsolidateFeedQueue() @@ -402,36 +410,6 @@ void TrafficManager::ConsolidateFeedQueue() ++it; } -void TrafficManager::UpdateMessageCache(std::map & cache) -{ - traffxml::TraffFeed feed; - // Thread-safe iteration over m_feedQueue, releasing the mutex during the loop - while (true) - { - { - std::lock_guard lock(m_mutex); - if (!m_feedQueue.empty()) - { - feed = m_feedQueue.front(); - m_feedQueue.erase(m_feedQueue.begin()); - } - else - break; - } - - for (auto message : feed) - { - LOG(LINFO, (" message:", message)); - auto it = cache.find(message.m_id); - bool process = (it == cache.end()); - if (!process) - process = (it->second.m_updateTime < message.m_updateTime); - if (process) - cache.insert_or_assign(message.m_id, message); - } - } -} - void TrafficManager::InitializeDataSources(std::vector & dataSources) { /* @@ -558,6 +536,46 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) } } +void TrafficManager::DecodeFirstMessage() +{ + traffxml::TraffMessage message; + { + // Lock the mutex while iterating over the feed queue + std::lock_guard lock(m_mutex); + // remove empty feeds from the beginning of the queue + while (!m_feedQueue.empty() && m_feedQueue.front().empty()) + m_feedQueue.erase(m_feedQueue.begin()); + // if we have no more feeds, return (nothing to do) + if (m_feedQueue.empty()) + return; + // retrieve the first message from the first feed, remove it from the feed + std::swap(message, m_feedQueue.front().front()); + m_feedQueue.front().erase(m_feedQueue.front().begin()); + // if the feed has no more messages, erase it (eager erase, as an empty queue is used as a condition later) + if (m_feedQueue.front().empty()) + m_feedQueue.erase(m_feedQueue.begin()); + } + + // check if message is actually newer + auto it = m_messageCache.find(message.m_id); + bool process = (it == m_messageCache.end()); + if (!process) + process = (it->second.m_updateTime < message.m_updateTime); + if (!process) + { + LOG(LINFO, ("message", message.m_id, "is already in cache, skipping")); + return; + } + + LOG(LINFO, (" ", message.m_id, ":", message)); + DecodeMessage(message); + // store message in cache + //m_messageCache.insert_or_assign(message.m_id, message); + // store message coloring in AllMwmColoring + // TODO trigger full cache processing if segments were removed or traffic has eased + traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); +} + void TrafficManager::ThreadRoutine() { std::vector mwms; @@ -565,9 +583,9 @@ void TrafficManager::ThreadRoutine() { // TODO clean out expired messages - // poll is always needed, unless a new subscription or a subscription change returns a feed - m_isPollNeeded = true; + LOG(LINFO, ("start loop, active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); + // this is a no-op if active MWMs have not changed if (!SetSubscriptionArea()) { LOG(LWARNING, ("SetSubscriptionArea failed.")); @@ -576,7 +594,10 @@ void TrafficManager::ThreadRoutine() LOG(LWARNING, ("No subscription, no traffic data will be retrieved.")); } - // fetch traffic data if subscribed, unless this has already happened in the previous step + /* + * Fetch traffic data if needed and we have a subscription. + * m_isPollNeeded may be set by WaitForRequest() and set/unset by SetSubscriptionArea(). + */ if (m_isPollNeeded && IsSubscribed()) { if (!Poll()) @@ -590,31 +611,11 @@ void TrafficManager::ThreadRoutine() // consolidate feed queue (remove older messages in favor of newer ones) ConsolidateFeedQueue(); - /* - * TODO call on a temp struct, then unite with m_messageCache, processing only messages with changes - * (adding segments for new messages, removing segments for deleted messages, replacing segments - * for updated messages) and leaving all other segments untouched - */ - UpdateMessageCache(m_messageCache); - LOG(LINFO, (m_messageCache.size(), "message(s) in cache")); - - /* - * Map between MWM IDs and their colorings. - */ - //TODO should we use std::map> ? - // TODO store mwm/segment/speed group map with each message, build allMwmColoring on the fly - std::map allMwmColoring; - for (auto [id, message] : m_messageCache) - { - LOG(LINFO, (" ", id, ":", message)); - DecodeMessage(message); - // store message coloring in AllMwmColoring - // TODO do this in a later pass...? - traffxml::MergeMultiMwmColoring(message.m_decoded, allMwmColoring); - } + // decode one message and add it to the cache + DecodeFirstMessage(); // set new coloring for MWMs - OnTrafficDataUpdate(allMwmColoring); + OnTrafficDataUpdate(m_allMwmColoring); // TODO no longer needed #ifdef traffic_dead_code @@ -657,20 +658,44 @@ bool TrafficManager::WaitForRequest(std::vector & mwms) { std::unique_lock lock(m_mutex); + /* + * if we got terminated, return false immediately + * (don’t wait until sleep, we might not get much sleep if we’re busy processing a long feed) + */ + if (!m_isRunning) + return false; + + // if we have feeds in the queue, return immediately + if (!m_feedQueue.empty()) + { + LOG(LINFO, ("feed queue not empty, returning immediately")); + return true; + } + + // if update interval has elapsed, return immediately + auto const currentTime = steady_clock::now(); + auto const passedSeconds = currentTime - m_lastResponseTime; + if (passedSeconds >= kUpdateInterval) + { + LOG(LINFO, ("last response was", passedSeconds, "ago, returning immediately")); + m_isPollNeeded = true; + return true; + } + + LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { - return !m_isRunning || !m_requestedMwms.empty(); + return !m_isRunning || m_activeMwmsChanged; }); + // check again if we got terminated while waiting (or woken up because we got terminated) if (!m_isRunning) return false; - if (timeout) - RequestTrafficData(); - - if (!m_requestedMwms.empty()) - mwms.swap(m_requestedMwms); + // this works as long as wait timeout is at least equal to the poll interval + m_isPollNeeded |= timeout; + LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged)); return true; } @@ -754,6 +779,14 @@ void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) { + /* + * TODO do not update coloring on every single pass (i.e. after every single message). + * Coloring needs to be updated when the queue is empty (i.e. we have processed all messages for now) + * and should be updated periodically while larger numbers of messages are being processed. + * The interval is TBD and we may want to choose different intervals for the graphics engine + * and the routing engine: 10–20 seconds seems reasonable for graphics, for the routing engine the + * interval should not be shorter than the time needed for route recalculation. + */ /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 613ad3bd0..3975044e0 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -246,14 +246,6 @@ class TrafficManager final */ void ConsolidateFeedQueue(); - /** - * @brief Merges new messages from `m_feedQueue` into a message cache. - * - * Existing messages in `cache` will be overwritten by newer messages with the same ID in `m_feedQueue`. - * @param cache The message cache. - */ - void UpdateMessageCache(std::map & cache); - /** * @brief Initializes the data sources for an OpenLR decoder. * @@ -261,6 +253,11 @@ class TrafficManager final */ void InitializeDataSources(std::vector &dataSources); + /** + * @brief Removes the first message from the first feed and decodes it. + */ + void DecodeFirstMessage(); + /** * @brief Decodes a single message to its segments and their speed groups. * @@ -489,6 +486,11 @@ class TrafficManager final */ threads::SimpleThread m_thread; + /** + * @brief When the last response was received. + */ + std::chrono::time_point m_lastResponseTime; + /** * @brief Whether active MWMs have changed since the last request. */ @@ -530,6 +532,11 @@ class TrafficManager final * Used to decode TraFF locations into road segments on the map. */ openlr::OpenLRDecoder m_openLrDecoder; + + /** + * @brief Map between MWM IDs and their colorings. + */ + std::map m_allMwmColoring; }; extern std::string DebugPrint(TrafficManager::TrafficState state); From 3455050876748e71e201a8ef96364148bd6d90a4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 15 May 2025 23:55:43 +0300 Subject: [PATCH 035/252] [traffic] Forgotten hunk of 9f39d3bc (store coloring with message) Signed-off-by: mvglasow --- map/traffic_manager.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 3975044e0..14b882d93 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -265,8 +265,7 @@ class TrafficManager final * @param message The message to decode. * @param trafficCache The cache in which all decoded paths with their speed groups will be stored. */ - void DecodeMessage(traffxml::TraffMessage & message, - std::map &trafficCache); + void DecodeMessage(traffxml::TraffMessage & message); /** * @brief Event loop for the traffic worker thread. From 2ba3030366c57c108bedaa3f41c432e9afb21a09 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 15 May 2025 23:57:52 +0300 Subject: [PATCH 036/252] [traffic] Remove forgotten InitializeDataSources() method Obsolete since we started using a single data source in 5a031c55 Signed-off-by: mvglasow --- map/traffic_manager.cpp | 14 -------------- map/traffic_manager.hpp | 7 ------- 2 files changed, 21 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index d8a016a90..1ac54f318 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -410,20 +410,6 @@ void TrafficManager::ConsolidateFeedQueue() ++it; } -void TrafficManager::InitializeDataSources(std::vector & dataSources) -{ - /* - * TODO can we include all available MWMs in the list (including non-active ones)? - * Then we could initialize the decoder once and for all. - */ - ForEachActiveMwm([this, &dataSources](MwmSet::MwmId const & mwmId) { - ASSERT(mwmId.IsAlive(), ()); - // TODO do we need .SyncWithDisk() for the file? - for (size_t i = 0; i < dataSources.size(); i++) - dataSources[i].RegisterMap(mwmId.GetInfo()->GetLocalFile()); - }); -} - /* * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). * Decoding message by message kind of defeats the purpose. diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 14b882d93..e384dbea4 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -246,13 +246,6 @@ class TrafficManager final */ void ConsolidateFeedQueue(); - /** - * @brief Initializes the data sources for an OpenLR decoder. - * - * @param dataSources Receives the data sources for the decoder (one per worker thread). - */ - void InitializeDataSources(std::vector &dataSources); - /** * @brief Removes the first message from the first feed and decodes it. */ From e94c23d538d2ef27525a204bcbcc321151b42cf5 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 15 May 2025 23:58:58 +0300 Subject: [PATCH 037/252] [traffic] Insert mew messages into cache but skip deduplication for now Signed-off-by: mvglasow --- map/traffic_manager.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 1ac54f318..62a7148ed 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -542,6 +542,14 @@ void TrafficManager::DecodeFirstMessage() m_feedQueue.erase(m_feedQueue.begin()); } +/* + * TODO this breaks things in the current test setup. + * When TrafficManager starts up and processes the first feed, maps are not loaded yet and messages + * cannot be decoded, so they are added to the cache without segments. + * The next feed (being a static file) returns the same data, so this check causes the message to + * get ignored as it has not changed. + */ +#if 0 // check if message is actually newer auto it = m_messageCache.find(message.m_id); bool process = (it == m_messageCache.end()); @@ -552,11 +560,12 @@ void TrafficManager::DecodeFirstMessage() LOG(LINFO, ("message", message.m_id, "is already in cache, skipping")); return; } +#endif LOG(LINFO, (" ", message.m_id, ":", message)); DecodeMessage(message); // store message in cache - //m_messageCache.insert_or_assign(message.m_id, message); + m_messageCache.insert_or_assign(message.m_id, message); // store message coloring in AllMwmColoring // TODO trigger full cache processing if segments were removed or traffic has eased traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); From c8d5a07262755d6b79c8b96e46889ef1e40880f0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 02:01:06 +0300 Subject: [PATCH 038/252] [traffic] Defer TrafficManager startup until MWMs are first updated Signed-off-by: mvglasow --- map/traffic_manager.cpp | 34 ++++++++++++++++++++-------------- map/traffic_manager.hpp | 5 +++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 62a7148ed..b890cc269 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -73,6 +73,7 @@ TrafficManager::TrafficManager(DataSource & dataSource, , m_maxCacheSizeBytes(maxCacheSizeBytes) #endif , m_isRunning(true) + , m_isStarted(false) , m_isPaused(false) , m_thread(&TrafficManager::ThreadRoutine, this) { @@ -211,6 +212,7 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, { std::lock_guard lock(m_mutex); + m_isStarted = true; m_activeMwmsChanged = true; activeMwms.clear(); for (auto const & mwm : mwms) @@ -660,21 +662,24 @@ bool TrafficManager::WaitForRequest(std::vector & mwms) if (!m_isRunning) return false; - // if we have feeds in the queue, return immediately - if (!m_feedQueue.empty()) + if (m_isStarted) { - LOG(LINFO, ("feed queue not empty, returning immediately")); - return true; - } + // if we have feeds in the queue, return immediately + if (!m_feedQueue.empty()) + { + LOG(LINFO, ("feed queue not empty, returning immediately")); + return true; + } - // if update interval has elapsed, return immediately - auto const currentTime = steady_clock::now(); - auto const passedSeconds = currentTime - m_lastResponseTime; - if (passedSeconds >= kUpdateInterval) - { - LOG(LINFO, ("last response was", passedSeconds, "ago, returning immediately")); - m_isPollNeeded = true; - return true; + // if update interval has elapsed, return immediately + auto const currentTime = steady_clock::now(); + auto const passedSeconds = currentTime - m_lastResponseTime; + if (passedSeconds >= kUpdateInterval) + { + LOG(LINFO, ("last response was", passedSeconds, "ago, returning immediately")); + m_isPollNeeded = true; + return true; + } } LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); @@ -688,7 +693,8 @@ bool TrafficManager::WaitForRequest(std::vector & mwms) return false; // this works as long as wait timeout is at least equal to the poll interval - m_isPollNeeded |= timeout; + if (m_isStarted) + m_isPollNeeded |= timeout; LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged)); return true; diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index e384dbea4..64ab8be26 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -465,6 +465,11 @@ class TrafficManager final // which allows a client to make conditional requests. std::map m_trafficETags; + /** + * Whether the traffic manager should begin receiving information. + */ + std::atomic m_isStarted; + std::atomic m_isPaused; /** From 356b051036db6cc68b11ecd064bc1d0e3a993b28 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 02:02:45 +0300 Subject: [PATCH 039/252] [traffic] Re-enable message deduplication between feed queue and cache Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index b890cc269..8eb833442 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -551,7 +551,6 @@ void TrafficManager::DecodeFirstMessage() * The next feed (being a static file) returns the same data, so this check causes the message to * get ignored as it has not changed. */ -#if 0 // check if message is actually newer auto it = m_messageCache.find(message.m_id); bool process = (it == m_messageCache.end()); @@ -562,7 +561,6 @@ void TrafficManager::DecodeFirstMessage() LOG(LINFO, ("message", message.m_id, "is already in cache, skipping")); return; } -#endif LOG(LINFO, (" ", message.m_id, ":", message)); DecodeMessage(message); From 136293c308ac4ee826e247e824dbeb753632091e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 17:48:29 +0300 Subject: [PATCH 040/252] [traffic] Add IsoTime::IsPast() Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 6 ++++++ traffxml/traff_model.hpp | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 7479f713f..ec981e489 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -138,6 +138,12 @@ IsoTime::IsoTime(std::tm tm) : m_tm(tm) {} +bool IsoTime::IsPast() +{ + std::time_t t_now = std::time(nullptr); + std::time_t t_tm = timegm(&m_tm); + return t_tm < t_now; +} bool operator< (IsoTime lhs, IsoTime rhs) { diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 221b0c500..0ab998e94 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -65,6 +65,15 @@ class IsoTime */ static IsoTime Now(); + /** + * @brief Whether the instance refers to a point of time in the past. + * + * Comparison is against system time. + * + * @return true if in the past, false of not. + */ + bool IsPast(); + friend bool operator< (IsoTime lhs, IsoTime rhs); friend bool operator> (IsoTime lhs, IsoTime rhs); private: From a39bdee0d17e73e34b17e2209e2a6ea77fbe25ec Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 18:07:02 +0300 Subject: [PATCH 041/252] [traffic] Refactor IsoTime comparison operators Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 8 ++++---- traffxml/traff_model.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index ec981e489..e7cd9a515 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -145,16 +145,16 @@ bool IsoTime::IsPast() return t_tm < t_now; } -bool operator< (IsoTime lhs, IsoTime rhs) +bool IsoTime::operator< (IsoTime & rhs) { - std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_lhs = std::mktime(&m_tm); std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs < t_rhs; } -bool operator> (IsoTime lhs, IsoTime rhs) +bool IsoTime::operator> (IsoTime & rhs) { - std::time_t t_lhs = std::mktime(&lhs.m_tm); + std::time_t t_lhs = std::mktime(&m_tm); std::time_t t_rhs = std::mktime(&rhs.m_tm); return t_lhs > t_rhs; } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 0ab998e94..b73c28ce5 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -74,8 +74,8 @@ class IsoTime */ bool IsPast(); - friend bool operator< (IsoTime lhs, IsoTime rhs); - friend bool operator> (IsoTime lhs, IsoTime rhs); + bool operator< (IsoTime & rhs); + bool operator> (IsoTime & rhs); private: friend std::string DebugPrint(IsoTime time); From e2aff5329160959e9068a2379f9680e2cee5ffe9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 18:32:47 +0300 Subject: [PATCH 042/252] [traffic] Comparison operators for TrafficImpact, TraffLocation and Point Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 24 ++++++++++++++++++++++++ traffxml/traff_model.hpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index e7cd9a515..7343cdb66 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -159,6 +159,21 @@ bool IsoTime::operator> (IsoTime & rhs) return t_lhs > t_rhs; } +bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs) +{ + if ((lhs.m_speedGroup == traffic::SpeedGroup::TempBlock) + && (rhs.m_speedGroup == traffic::SpeedGroup::TempBlock)) + return true; + return (lhs.m_speedGroup == rhs.m_speedGroup) + && (lhs.m_maxspeed == rhs.m_maxspeed) + && (lhs.m_delayMins == rhs.m_delayMins); +} + +bool operator==(Point const & lhs, Point const & rhs) +{ + return lhs.m_coordinates == rhs.m_coordinates; +} + openlr::LocationReferencePoint Point::ToLrp() { openlr::LocationReferencePoint result; @@ -166,6 +181,15 @@ openlr::LocationReferencePoint Point::ToLrp() return result; } +bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) +{ + return (lhs.m_from == rhs.m_from) + && (lhs.m_at == rhs.m_at) + && (lhs.m_via == rhs.m_via) + && (lhs.m_notVia == rhs.m_notVia) + && (lhs.m_to == rhs.m_to); +} + openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool backwards) { openlr::LinearLocationReference locationReference; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index b73c28ce5..291319c4f 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -226,6 +226,17 @@ enum class EventType */ struct TrafficImpact { + /** + * @brief Whether two `TrafficImpact` instances are equal. + * + * Instances are considered equal if both have a speed group of `TempBlock`, in which case other + * members are not compared. Otherwise, they are equal if, and only if, all three members hold + * identical values between both instances. + */ + // Non-member friend as member operators do not work with std::optional + friend bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs); + friend bool operator!=(TrafficImpact const & lhs, TrafficImpact const & rhs) { return !(lhs == rhs); } + /** * @brief The speed group for the affected segments, or `traffic::SpeedGroup::Unknown` if unknown. */ @@ -244,6 +255,15 @@ struct TrafficImpact struct Point { + /** + * @brief Whether two points are equal. + * + * Two points are equal if, and only if, their coordinates are. Other attributes are not compared. + */ + // Non-member friend as member operators do not work with std::optional + friend bool operator==(Point const & lhs, Point const & rhs); + friend bool operator!=(Point const & lhs, Point const & rhs) { return !(lhs == rhs); } + /** * @brief Converts the point to an OpenLR location reference point. * @@ -262,6 +282,21 @@ struct Point struct TraffLocation { + /** + * @brief Whether two locations are equal. + * + * Two locations are equal if, and only if, they contain the same points in the same roles. + * + * @todo Road class and ramps are not compared, though these values are used by the decoder. Not + * comparing these values could lead to two seemingly equal locations resolving to a different + * path. However, given that comparison only takes place between messages with identical IDs + * (indicating both refer to the same event at the same location), such a situation is highly + * unlikely to occur in practice. + */ + // Non-member friend as member operators do not work with std::optional + friend bool operator==(TraffLocation const & lhs, TraffLocation const & rhs); + friend bool operator!=(TraffLocation const & lhs, TraffLocation const & rhs) { return !(lhs == rhs); } + /** * @brief Converts the location to an OpenLR linear location reference. * From 74d79e5c8e753ec1b71f7f7d88663f3559542142 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 20:55:27 +0300 Subject: [PATCH 043/252] [traffic] Reuse previously decoded segments and coloring, where possible Signed-off-by: mvglasow --- map/traffic_manager.cpp | 138 ++++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 54 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 8eb833442..0bf2cabc5 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -422,17 +422,42 @@ void TrafficManager::ConsolidateFeedQueue() */ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) { - if (message.m_location) + if (!message.m_location) + return; + // Decode events into consolidated traffic impact + std::optional impact = message.GetTrafficImpact(); + + LOG(LINFO, (" Impact: ", impact)); + + // Skip further processing if there is no impact + if (!impact) + return; + + traffxml::MultiMwmColoring decoded; + + auto it = m_messageCache.find(message.m_id); + if ((it != m_messageCache.end()) + && !it->second.m_decoded.empty() + && (it->second.m_location == message.m_location)) { - // Decode events into consolidated traffic impact - std::optional impact = message.GetTrafficImpact(); + // cache already has a message with reusable location - LOG(LINFO, (" Impact: ", impact)); + LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); - // Skip further processing if there is no impact - if (!impact) - return; + std::optional cachedImpact = it->second.GetTrafficImpact(); + if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) + { + LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); + // same impact, m_decoded can be reused altogether + message.m_decoded = it->second.m_decoded; + return; + } + else + decoded = it->second.m_decoded; + } + else + { // Convert the location to a format understood by the OpenLR decoder. std::vector segments = message.m_location.value().ToOpenLrSegments(message.m_id); @@ -456,6 +481,17 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) std::vector paths(segments.size()); m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); + for (size_t i = 0; i < paths.size(); i++) + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + auto fid = paths[i].m_path[j].GetFeatureId().m_index; + auto segment = paths[i].m_path[j].GetSegId(); + uint8_t direction = paths[i].m_path[j].IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; + } + for (size_t i = 0; i < paths.size(); i++) { LOG(LINFO, (" Path", i)); @@ -468,59 +504,53 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) } } - // TODO store maxspeed in edges - // store decoded paths and speed groups in trafficCache - if (impact) - { - for (size_t i = 0; i < paths.size(); i++) - for (size_t j = 0; j < paths[i].m_path.size(); j++) + // TODO store maxspeed in edges + // store decoded paths and speed groups in trafficCache + if (impact) + { + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) + { + /* + * Consolidate TrafficImpact into a single SpeedGroup per segment. + * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate + * the rest. + */ + traffic::SpeedGroup sg = impact.value().m_speedGroup; + /* + * TODO also process m_delayMins if greater than zero. + * This would require a separate pass over all edges, calculating length, + * total (normal) travel time (length / maxspeed), then a speed group based on + * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between + * reduced and normal speed. That would give us a third potential speed group. + */ + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.value().m_maxspeed != traffxml::kMaxspeedNone)) { - auto fid = paths[i].m_path[j].GetFeatureId().m_index; - auto segment = paths[i].m_path[j].GetSegId(); - uint8_t direction = paths[i].m_path[j].IsForward() ? - traffic::TrafficInfo::RoadSegmentId::kForwardDirection : - traffic::TrafficInfo::RoadSegmentId::kReverseDirection; - - /* - * Consolidate TrafficImpact into a single SpeedGroup per segment. - * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate - * the rest. - */ - traffic::SpeedGroup sg = impact.value().m_speedGroup; - /* - * TODO also process m_delayMins if greater than zero. - * This would require a separate pass over all edges, calculating length, - * total (normal) travel time (length / maxspeed), then a speed group based on - * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between - * reduced and normal speed. That would give us a third potential speed group. - */ - if ((sg != traffic::SpeedGroup::TempBlock) && (impact.value().m_maxspeed != traffxml::kMaxspeedNone)) + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + auto const speeds = routing::LoadMaxspeeds(handle); + if (speeds) { - auto const handle = m_dataSource.GetMwmHandleById(paths[i].m_path[j].GetFeatureId().m_mwmId); - auto const speeds = routing::LoadMaxspeeds(handle); - if (speeds) + traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; + auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); + auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); + if (speedKmPH != routing::kInvalidSpeed) { - traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; - auto const speed = speeds->GetMaxspeed(paths[i].m_path[j].GetFeatureId().m_index); - auto const speedKmPH = speed.GetSpeedKmPH(paths[i].m_path[j].IsForward()); - if (speedKmPH != routing::kInvalidSpeed) - { - fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.value().m_maxspeed * 100.0f / speedKmPH); - if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) - sg = fromMaxspeed; - } + fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.value().m_maxspeed * 100.0f / speedKmPH); + if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) + sg = fromMaxspeed; } - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ } - // TODO process all TrafficImpact fields and determine the speed group based on that - message.m_decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = sg; + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ } - } + // TODO process all TrafficImpact fields and determine the speed group based on that + cit->second = sg; + } + std::swap(message.m_decoded, decoded); } } From de03995e7785bd61bbe945c47e36b9cdd42a255f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 16 May 2025 22:57:08 +0300 Subject: [PATCH 044/252] [openlr] Modify openlr_stat to work with single data source Signed-off-by: mvglasow --- openlr/openlr_stat/openlr_stat.cpp | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/openlr/openlr_stat/openlr_stat.cpp b/openlr/openlr_stat/openlr_stat.cpp index 938c4edf6..83c0f7bbe 100644 --- a/openlr/openlr_stat/openlr_stat.cpp +++ b/openlr/openlr_stat/openlr_stat.cpp @@ -56,8 +56,8 @@ int32_t const kMinNumThreads = 1; int32_t const kMaxNumThreads = 128; int32_t const kHandleAllSegments = -1; -void LoadDataSources(std::string const & pathToMWMFolder, - std::vector & dataSources) +void LoadDataSource(std::string const & pathToMWMFolder, + FrozenDataSource & dataSource) { CHECK(Platform::IsDirectory(pathToMWMFolder), (pathToMWMFolder, "must be a directory.")); @@ -66,8 +66,7 @@ void LoadDataSources(std::string const & pathToMWMFolder, CHECK(!files.empty(), (pathToMWMFolder, "Contains no .mwm files.")); - size_t const numDataSources = dataSources.size(); - std::vector numCountries(numDataSources); + uint64_t numCountries; for (auto const & fileName : files) { @@ -81,15 +80,12 @@ void LoadDataSources(std::string const & pathToMWMFolder, try { localFile.SyncWithDisk(); - for (size_t i = 0; i < numDataSources; ++i) - { - auto const result = dataSources[i].RegisterMap(localFile); - CHECK_EQUAL(result.second, MwmSet::RegResult::Success, ("Can't register mwm:", localFile)); - - auto const & info = result.first.GetInfo(); - if (info && info->GetType() == MwmInfo::COUNTRY) - ++numCountries[i]; - } + auto const result = dataSource.RegisterMap(localFile); + CHECK_EQUAL(result.second, MwmSet::RegResult::Success, ("Can't register mwm:", localFile)); + + auto const & info = result.first.GetInfo(); + if (info && info->GetType() == MwmInfo::COUNTRY) + ++numCountries; } catch (RootException const & ex) { @@ -97,11 +93,8 @@ void LoadDataSources(std::string const & pathToMWMFolder, } } - for (size_t i = 0; i < numDataSources; ++i) - { - if (numCountries[i] == 0) - LOG(LWARNING, ("No countries for thread", i)); - } + if (numCountries == 0) + LOG(LWARNING, ("No countries")); } bool ValidateLimit(char const * flagname, int32_t value) @@ -257,11 +250,11 @@ int main(int argc, char * argv[]) auto const numThreads = static_cast(FLAGS_num_threads); - std::vector dataSources(numThreads); + FrozenDataSource dataSource; - LoadDataSources(FLAGS_mwms_path, dataSources); + LoadDataSource(FLAGS_mwms_path, dataSource); - OpenLRDecoder decoder(dataSources, storage::CountryParentGetter(FLAGS_countries_filename, + OpenLRDecoder decoder(dataSource, storage::CountryParentGetter(FLAGS_countries_filename, GetPlatform().ResourcesDir())); pugi::xml_document document; From e3f5dd3ca822a253525af4241263229f8191e000 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 15:27:49 +0300 Subject: [PATCH 045/252] [traffic] Throttle UI/router refresh while messages are being processed Signed-off-by: mvglasow --- map/traffic_manager.cpp | 55 ++++++++++++++++++++++++++++++++--------- map/traffic_manager.hpp | 10 ++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 0bf2cabc5..a268f22a1 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -34,6 +34,16 @@ auto constexpr kNetworkErrorTimeout = minutes(20); auto constexpr kMaxRetriesCount = 5; +/** + * Interval at which the Drape engine gets traffic updates while messages are being processed. + */ +auto constexpr kDrapeUpdateInterval = seconds(10); + +/** + * Interval at which the traffic observer gets traffic updates while messages are being processed. + */ +auto constexpr kObserverUpdateInterval = minutes(1); + // Number of identical data sources to create for the OpenLR decoder, one source per worker thread. // TODO how to determine the best number of worker threads? auto constexpr kNumDecoderThreads = 1; @@ -603,6 +613,10 @@ void TrafficManager::DecodeFirstMessage() void TrafficManager::ThreadRoutine() { + // initially, treat drape and observer as having just been updated + m_lastDrapeUpdate = steady_clock::now(); + m_lastObserverUpdate = steady_clock::now(); + std::vector mwms; while (WaitForRequest(mwms)) { @@ -808,14 +822,26 @@ void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) { - /* - * TODO do not update coloring on every single pass (i.e. after every single message). - * Coloring needs to be updated when the queue is empty (i.e. we have processed all messages for now) - * and should be updated periodically while larger numbers of messages are being processed. - * The interval is TBD and we may want to choose different intervals for the graphics engine - * and the routing engine: 10–20 seconds seems reasonable for graphics, for the routing engine the - * interval should not be shorter than the time needed for route recalculation. - */ + // Whether to notify the Drape engine of the update. + bool notifyDrape = (m_feedQueue.empty()); + + // Whether to notify the observer of the update. + bool notifyObserver = (m_feedQueue.empty()); + + if (!m_feedQueue.empty()) + { + auto const currentTime = steady_clock::now(); + auto const drapeAge = currentTime - m_lastDrapeUpdate; + auto const observerAge = currentTime - m_lastObserverUpdate; + notifyDrape = (drapeAge >= kDrapeUpdateInterval); + notifyObserver = (observerAge >= kObserverUpdateInterval); + } + + if (!notifyDrape && !notifyObserver) + return; + + LOG(LINFO, ("Announcing traffic update, notifyDrape:", notifyDrape, "notifyObserver:", notifyObserver)); + /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * @@ -823,9 +849,10 @@ void TrafficManager::OnTrafficDataUpdate(std::map(info)); + m_lastDrapeUpdate = steady_clock::now(); + } + if (notifyObserver) + { // Update traffic colors for routing. m_observer.OnTrafficInfoAdded(std::move(info)); + m_lastObserverUpdate = steady_clock::now(); } } } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 64ab8be26..678e33de4 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -488,6 +488,16 @@ class TrafficManager final */ std::chrono::time_point m_lastResponseTime; + /** + * @brief When the last update notification to the Drape engine was posted. + */ + std::chrono::time_point m_lastDrapeUpdate; + + /** + * @brief When the last update notification to the traffic observer was posted. + */ + std::chrono::time_point m_lastObserverUpdate; + /** * @brief Whether active MWMs have changed since the last request. */ From a7897e2347b98f961fb728626aa28f50aed4f5a1 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 17:31:21 +0300 Subject: [PATCH 046/252] [traffic] Calculate filter list for active MWMs Signed-off-by: mvglasow --- map/traffic_manager.cpp | 18 +++++++++++++++--- map/traffic_manager.hpp | 8 ++++++++ traffxml/traff_model.hpp | 16 ++++++++++++++++ traffxml/traff_model_xml.cpp | 12 ++++++++++++ traffxml/traff_model_xml.hpp | 21 +++++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index a268f22a1..adfac2118 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -266,12 +266,21 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) UpdateActiveMwms(screen.ClipRect(), m_lastDrapeMwmsByRect, m_activeDrapeMwms); } +std::string TrafficManager::GetMwmFilters(std::set & mwms) +{ + std::vector rects; + for (auto mwmId : mwms) + rects.push_back(mwmId.GetInfo()->m_bordersRect); + return traffxml::FiltersToXml(rects); +} + // TODO make this work with multiple sources (e.g. Android) bool TrafficManager::Subscribe(std::set & mwms) { // TODO what if we’re subscribed already? + std::string filterList = GetMwmFilters(mwms); // TODO - LOG(LINFO, ("Would subscribe to", mwms)); + LOG(LINFO, ("Would subscribe to:\n", filterList)); m_subscriptionId = "placeholder_subscription_id"; m_isPollNeeded = true; // would be false if we got a feed here return true; @@ -280,9 +289,11 @@ bool TrafficManager::Subscribe(std::set & mwms) // TODO make this work with multiple sources (e.g. Android) bool TrafficManager::ChangeSubscription(std::set & mwms) { - // TODO what if we’re not subscribed yet? + if (!IsSubscribed()) + return false; + std::string filterList = GetMwmFilters(mwms); // TODO - LOG(LINFO, ("Would change subscription", m_subscriptionId, "to", mwms)); + LOG(LINFO, ("Would change subscription", m_subscriptionId, "to:\n", filterList)); m_isPollNeeded = true; // would be false if we got a feed here return true; } @@ -290,6 +301,7 @@ bool TrafficManager::ChangeSubscription(std::set & mwms) bool TrafficManager::SetSubscriptionArea() { std::set activeMwms; + if (!IsSubscribed()) { { diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 678e33de4..3cfd5f0db 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -183,6 +183,14 @@ class TrafficManager final traffic::TrafficInfo::Availability m_lastAvailability; }; + /** + * @brief Returns a TraFF filter list for a set of MWMs. + * + * @param mwms The MWMs for which a filter list is to be created. + * @return A `filter_list` in XML format. + */ + std::string GetMwmFilters(std::set & mwms); + /** * @brief Subscribes to a traffic service. * diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 291319c4f..50a08e646 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -395,6 +395,22 @@ struct TraffMessage using TraffFeed = std::vector; +// TODO Capabilities + +/* + * Filter: currently not implemented. + * We only use bbox, for which we have a suitable data type. + * min_road_class is not needed as we do not filter by road class. + */ + +/* + * TraffSubscription: currently not implemented. + * We just store the ID as a string. + * Filters are only by bbox, not by min_road_class. The list is auto-generated from the list of + * active MWMs and changes exactly when the active MWM set changes, eliminating the need to store + * the full filter list. + */ + /** * @brief Guess the distance to the next point. * diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 1bc634648..d0511bb24 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -643,4 +643,16 @@ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) } return result; } + +std::string FiltersToXml(std::vector & bboxRects) +{ + std::ostringstream os; + for (auto rect : bboxRects) + os << std::format("\n", + mercator::YToLat(rect.minY()), + mercator::XToLon(rect.minX()), + mercator::YToLat(rect.maxY()), + mercator::XToLon(rect.maxX())); + return os.str(); +} } // namespace openlr diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index ed090f7ce..b60cadc6f 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -2,6 +2,11 @@ #include "traffxml/traff_model.hpp" +#include "geometry/rect2d.hpp" + +#include +#include + namespace pugi { class xml_document; @@ -11,4 +16,20 @@ class xml_node; namespace traffxml { bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed); + +/** + * @brief Generates a list of XML `filter` elements from a vector of rects representing bboxes. + * + * The resulting string can be placed inside a TraFF XML `filter_list` element or can be passed as + * an extra to an Android intent. + * + * It will have one `filter` element for each element in `bboxRects`, although simplification may + * be applied to reduce the number of rects (currently not implemented). + * + * The `min_road_class` attribute is not used. + * + * @param bboxRects The rectangles, each of which represents a bounding box. + * @return A string of XML `filter` elements. + */ +std::string FiltersToXml(std::vector & bboxRects); } // namespace traffxml From 18f1dfac45268ce887fdef9584b5b70304574942 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 17:33:54 +0300 Subject: [PATCH 047/252] [traffxml] Documentation Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 25 ------------------------- traffxml/traff_model_xml.hpp | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index d0511bb24..a9d612465 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -593,31 +593,6 @@ bool MessageFromXml(pugi::xml_node node, TraffMessage & message) return true; } -/** - * @brief Retrieves a TraFF feed from an XML document. - * - * The document must conform loosely to the TraFF specification (currently version 0.8). - * - * The name of the root element is not verified, but the `message` elements must be its immediate - * children. - * - * Custom elements and attributes which are not part of the TraFF specification are ignored. - * - * Values which cannot be parsed correctly are skipped. - * - * Events whose event type does not match their event class are skipped. - * - * Messages, events, locations or points which lack mandatory information are skipped. - * - * If children are skipped but the parent remains valid, parsing it will report success. - * - * Parsing the feed will report failure if all its messages fail to parse, but not if it has no - * messages. - * - * @param document The XML document from which to retrieve the messages. - * @param feed Receives the TraFF feed. - * @return `true` on success, `false` on failure. - */ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) { bool result = false; diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index b60cadc6f..fa2320982 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -15,6 +15,31 @@ class xml_node; namespace traffxml { +/** + * @brief Retrieves a TraFF feed from an XML document. + * + * The document must conform loosely to the TraFF specification (currently version 0.8). + * + * The name of the root element is not verified, but the `message` elements must be its immediate + * children. + * + * Custom elements and attributes which are not part of the TraFF specification are ignored. + * + * Values which cannot be parsed correctly are skipped. + * + * Events whose event type does not match their event class are skipped. + * + * Messages, events, locations or points which lack mandatory information are skipped. + * + * If children are skipped but the parent remains valid, parsing it will report success. + * + * Parsing the feed will report failure if all its messages fail to parse, but not if it has no + * messages. + * + * @param document The XML document from which to retrieve the messages. + * @param feed Receives the TraFF feed. + * @return `true` on success, `false` on failure. + */ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed); /** From 52a915211e8f7332bb2fbde1e77c3432050331f3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 17:48:07 +0300 Subject: [PATCH 048/252] [traffic] Remove mwms from ThreadRoutine() Signed-off-by: mvglasow --- map/traffic_manager.cpp | 7 +++---- map/traffic_manager.hpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index adfac2118..47a6799f7 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -629,8 +629,7 @@ void TrafficManager::ThreadRoutine() m_lastDrapeUpdate = steady_clock::now(); m_lastObserverUpdate = steady_clock::now(); - std::vector mwms; - while (WaitForRequest(mwms)) + while (WaitForRequest()) { // TODO clean out expired messages @@ -698,14 +697,14 @@ void TrafficManager::ThreadRoutine() m_trafficETags[mwm] = tag; } } -#endif mwms.clear(); +#endif } // Calling Unsubscribe() form the worker thread on exit makes thread synchronization easier Unsubscribe(); } -bool TrafficManager::WaitForRequest(std::vector & mwms) +bool TrafficManager::WaitForRequest() { std::unique_lock lock(m_mutex); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 3cfd5f0db..71b87e945 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -289,7 +289,7 @@ class TrafficManager final * @return `true` during normal operation, `false` during teardown (signaling the event loop to exit). */ // TODO mwms argument is no longer needed - bool WaitForRequest(std::vector & mwms); + bool WaitForRequest(); /** * @brief Processes new traffic data. From 2ed300ca0857bede4316bea9a5bd3e89b4d73c3f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 18:45:07 +0300 Subject: [PATCH 049/252] [traffic] Comment cleanup Signed-off-by: mvglasow --- map/traffic_manager.cpp | 13 +++++++++---- traffxml/traff_model.cpp | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 47a6799f7..a71e6d199 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -44,8 +44,13 @@ auto constexpr kDrapeUpdateInterval = seconds(10); */ auto constexpr kObserverUpdateInterval = minutes(1); -// Number of identical data sources to create for the OpenLR decoder, one source per worker thread. -// TODO how to determine the best number of worker threads? +// Number of worker threads for the OpenLR decoder +/* + * TODO how to determine the best number of worker threads? + * One per direction? Does not seem to help with bidirectional locations (two reference points). + * One per segment (from–via/from–at, via–to/at–to)? Not yet tested. + * Otherwise there is little to be gained, as we decode messages one at a time. + */ auto constexpr kNumDecoderThreads = 1; } // namespace @@ -440,7 +445,8 @@ void TrafficManager::ConsolidateFeedQueue() * But after decoding the location, we need to examine the map features we got in order to * determine the speed groups, thus we may need to decode one by one (TBD). * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path - * structures to accept a TraFF message ID (string) rather than an integer. + * structures to accept a TraFF message ID (string) rather than an integer, or derive + * [partner] segment IDs from TraFF message IDs. */ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) { @@ -886,7 +892,6 @@ void TrafficManager::OnTrafficDataUpdate(std::map Date: Sat, 17 May 2025 19:25:29 +0300 Subject: [PATCH 050/252] [traffxml] Parse and store distance for location points Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 2 +- traffxml/traff_model.hpp | 2 +- traffxml/traff_model_xml.cpp | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index f0178b1d2..886515777 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -550,7 +550,7 @@ std::string DebugPrint(Point point) std::ostringstream os; os << "Point { "; os << "coordinates: " << DebugPrint(point.m_coordinates) << ", "; - // TODO optional float m_distance; (not in struct yet) + os << "distance: " << (point.m_distance ? std::to_string(point.m_distance.value()) : "nullopt") << ", "; os << "junctionName: " << point.m_junctionName.value_or("nullopt") << ", "; os << "junctionRef: " << point.m_junctionRef.value_or("nullopt"); os << " }"; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 50a08e646..b36a36748 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -275,7 +275,7 @@ struct Point // TODO role? ms::LatLon m_coordinates = ms::LatLon::Zero(); - // TODO optional float m_distance; + std::optional m_distance; std::optional m_junctionName; std::optional m_junctionRef; }; diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index a9d612465..5a3606971 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -128,6 +128,27 @@ std::optional OptionalIntegerFromXml(pugi::xml_attribute attribute) } } +/** + * @brief Retrieves a float value from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @return `true` on success, `false` if the attribute is not set or does not contain a float value. + */ +std::optional OptionalFloatFromXml(pugi::xml_attribute attribute) +{ + if (attribute.empty()) + return std::nullopt; + try + { + float result = std::stof(attribute.as_string()); + return result; + } + catch (std::invalid_argument const& ex) + { + return std::nullopt; + } +} + /** * @brief Retrieves a string from an attribute. * @@ -405,6 +426,7 @@ std::optional OptionalPointFromXml(pugi::xml_node node) result.m_junctionName = OptionalStringFromXml(node.attribute("junction_name")); result.m_junctionRef = OptionalStringFromXml(node.attribute("junction_ref")); + result.m_distance = OptionalFloatFromXml(node.attribute("distance")); return result; } From f7adea08a28ad2e246200aa7f33d614c268d92cb Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 19:29:49 +0300 Subject: [PATCH 051/252] [traffic] Documentation Signed-off-by: mvglasow --- map/traffic_manager.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 71b87e945..48d755f73 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -259,12 +259,18 @@ class TrafficManager final */ void DecodeFirstMessage(); + /** + * @brief Decodes a TraFF location. + * + * @param message + * @param decoded + */ + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + /** * @brief Decodes a single message to its segments and their speed groups. * - * @param decoder The OpenLR decoder instance. * @param message The message to decode. - * @param trafficCache The cache in which all decoded paths with their speed groups will be stored. */ void DecodeMessage(traffxml::TraffMessage & message); From 5b67d668bdb28db26686f384a64ac917e0f14191 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 21:11:12 +0300 Subject: [PATCH 052/252] [traffic] Refactor message decoding Signed-off-by: mvglasow --- map/traffic_manager.cpp | 177 ++++++++++++++++++++-------------------- map/traffic_manager.hpp | 14 +++- 2 files changed, 99 insertions(+), 92 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index a71e6d199..c0c1b1812 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -439,6 +439,91 @@ void TrafficManager::ConsolidateFeedQueue() ++it; } +void TrafficManager::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) +{ + decoded.clear(); + + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments + = message.m_location.value().ToOpenLrSegments(message.m_id); + + for (auto segment : segments) + { + LOG(LINFO, (" Segment:", segment.m_segmentId)); + for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) + { + LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); + if (i < segment.m_locationReference.m_points.size() - 1) + { + LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); + LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); + } + } + } + + // Decode the location into a path on the map. + // One path per segment + std::vector paths(segments.size()); + m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); + + for (size_t i = 0; i < paths.size(); i++) + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + auto fid = paths[i].m_path[j].GetFeatureId().m_index; + auto segment = paths[i].m_path[j].GetSegId(); + uint8_t direction = paths[i].m_path[j].IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; + } +} + +void TrafficManager::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) +{ + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) + { + /* + * Consolidate TrafficImpact into a single SpeedGroup per segment. + * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate + * the rest. + */ + traffic::SpeedGroup sg = impact.m_speedGroup; + /* + * TODO also process m_delayMins if greater than zero. + * This would require a separate pass over all edges, calculating length, + * total (normal) travel time (length / maxspeed), then a speed group based on + * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between + * reduced and normal speed. That would give us a third potential speed group. + */ + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) + { + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + auto const speeds = routing::LoadMaxspeeds(handle); + if (speeds) + { + traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; + auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); + auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); + if (speedKmPH != routing::kInvalidSpeed) + { + fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH); + if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) + sg = fromMaxspeed; + } + } + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ + } + // TODO process all TrafficImpact fields and determine the speed group based on that + cit->second = sg; + } +} + /* * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). * Decoding message by message kind of defeats the purpose. @@ -485,99 +570,11 @@ void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) decoded = it->second.m_decoded; } else - { - // Convert the location to a format understood by the OpenLR decoder. - std::vector segments - = message.m_location.value().ToOpenLrSegments(message.m_id); + DecodeLocation(message, decoded); - for (auto segment : segments) - { - LOG(LINFO, (" Segment:", segment.m_segmentId)); - for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) - { - LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); - if (i < segment.m_locationReference.m_points.size() - 1) - { - LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); - LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); - } - } - } - - // Decode the location into a path on the map. - // One path per segment - std::vector paths(segments.size()); - m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); - - for (size_t i = 0; i < paths.size(); i++) - for (size_t j = 0; j < paths[i].m_path.size(); j++) - { - auto fid = paths[i].m_path[j].GetFeatureId().m_index; - auto segment = paths[i].m_path[j].GetSegId(); - uint8_t direction = paths[i].m_path[j].IsForward() ? - traffic::TrafficInfo::RoadSegmentId::kForwardDirection : - traffic::TrafficInfo::RoadSegmentId::kReverseDirection; - decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; - } - - for (size_t i = 0; i < paths.size(); i++) - { - LOG(LINFO, (" Path", i)); - LOG(LINFO, (" Partner segment ID:", paths[i].m_segmentId)); - LOG(LINFO, (" Message ID:", paths[i].m_messageId)); - LOG(LINFO, (" Edges:", paths[i].m_path.size())); - for (size_t j = 0; j < paths[i].m_path.size(); j++) - { - LOG(LINFO, (" ", paths[i].m_path[j])); - } - } - - // TODO store maxspeed in edges - // store decoded paths and speed groups in trafficCache if (impact) { - for (auto dit = decoded.begin(); dit != decoded.end(); dit++) - for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) - { - /* - * Consolidate TrafficImpact into a single SpeedGroup per segment. - * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate - * the rest. - */ - traffic::SpeedGroup sg = impact.value().m_speedGroup; - /* - * TODO also process m_delayMins if greater than zero. - * This would require a separate pass over all edges, calculating length, - * total (normal) travel time (length / maxspeed), then a speed group based on - * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between - * reduced and normal speed. That would give us a third potential speed group. - */ - if ((sg != traffic::SpeedGroup::TempBlock) && (impact.value().m_maxspeed != traffxml::kMaxspeedNone)) - { - auto const handle = m_dataSource.GetMwmHandleById(dit->first); - auto const speeds = routing::LoadMaxspeeds(handle); - if (speeds) - { - traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; - auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); - auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); - if (speedKmPH != routing::kInvalidSpeed) - { - fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.value().m_maxspeed * 100.0f / speedKmPH); - if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) - sg = fromMaxspeed; - } - } - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ - } - // TODO process all TrafficImpact fields and determine the speed group based on that - cit->second = sg; - } + ApplyTrafficImpact(impact.value(), decoded); std::swap(message.m_decoded, decoded); } } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 48d755f73..90f337385 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -262,11 +262,21 @@ class TrafficManager final /** * @brief Decodes a TraFF location. * - * @param message - * @param decoded + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. */ void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + /** + * @brief Applies traffic impact to a decoded TraFF location. + * + * Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten. + * + * @param impact The traffic impact to apply. + * @param decoded The decoded segments. + */ + void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); + /** * @brief Decodes a single message to its segments and their speed groups. * From 63f07991614a1f3e332ea07172982d568918b4f8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 17 May 2025 23:47:23 +0300 Subject: [PATCH 053/252] [traffic] Calculate DNP from nominal distance, if available and plausible Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 44 +++++++++++++++++++++++++++++++--------- traffxml/traff_model.hpp | 2 +- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 886515777..c714d9ef5 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -207,17 +207,16 @@ openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool ba std::reverse(points.begin(), points.end()); // m_notVia is ignored as OpenLR does not support this functionality. CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); - for (auto point : points) + for (size_t i = 0; i < points.size(); i++) { - openlr::LocationReferencePoint lrp = point.ToLrp(); + openlr::LocationReferencePoint lrp = points[i].ToLrp(); lrp.m_functionalRoadClass = GetFrc(); if (m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) lrp.m_formOfWay = openlr::FormOfWay::Sliproad; - if (!locationReference.m_points.empty()) + if (i < points.size() - 1) { - // TODO use `distance` from TraFF reference point, if available and consistent with direct distance - locationReference.m_points.back().m_distanceToNextPoint - = GuessDnp(locationReference.m_points.back(), lrp); + lrp.m_distanceToNextPoint + = GuessDnp(points[i], points[i + 1]); } locationReference.m_points.push_back(lrp); } @@ -342,10 +341,12 @@ std::optional TraffMessage::GetTrafficImpact() } // TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) -uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2) +uint32_t GuessDnp(Point & p1, Point & p2) { - double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_latLon), - mercator::FromLatLon(p2.m_latLon)); + // direct distance + double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates), + mercator::FromLatLon(p2.m_coordinates)); + /* * Acceptance boundaries for candidate paths are currently: * @@ -371,7 +372,30 @@ uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReference * 1.19 – close to the square root of 1.41 * 1 – direct distance unmodified */ - return doe * 1.19f + 0.5f; + double dist = doe * 1.19f; + + // if we have kilometric points, calculate nominal distance as the difference between them + if (p1.m_distance && p2.m_distance) + { + LOG(LINFO, ("Both points have distance, calculating nominal difference")); + float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0; + if (nominalDist < 0) + nominalDist *= -1; + /* + * Plausibility check for nominal distance, as kilometric points along the route may not be + * continuous: discard if shorter than direct distance (geometrically impossible) or if longer + * than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for + * `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of + * distances between two adjacent mountain valleys, which are up to roughly 3 times the direct + * distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct + * distance with a tolerance factor). + */ + if ((nominalDist >= doe) && (nominalDist <= doe * 4)) + dist = nominalDist; + else + LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding")); + } + return dist + 0.5f; } void MergeMultiMwmColoring(MultiMwmColoring & delta, MultiMwmColoring & target) diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index b36a36748..412ea76d6 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -423,7 +423,7 @@ using TraffFeed = std::vector; * @param p2 The second point. * @return The approximate distance on the ground, in meters. */ -uint32_t GuessDnp(openlr::LocationReferencePoint & p1, openlr::LocationReferencePoint & p2); +uint32_t GuessDnp(Point & p1, Point & p2); /** * @brief Merges the contents of one `MultiMwmColoring` into another. From 2894218573cbc71cbb8b45785014d72ba07d790f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 18 May 2025 00:30:52 +0300 Subject: [PATCH 054/252] [traffic] Use LFRCNP, derived from FRC Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index c714d9ef5..fec9aa5eb 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -217,6 +217,12 @@ openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool ba { lrp.m_distanceToNextPoint = GuessDnp(points[i], points[i + 1]); + /* + * Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is + * used or discarded based on whether a score was returned or not (the score itself is not + * used for LFRCNP). However, this means we can use FRC as LFRCNP. + */ + lrp.m_lfrcnp = GetFrc(); } locationReference.m_points.push_back(lrp); } From bd178932c131ab51a06b4556d879a374b6b2753a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 18 May 2025 18:11:39 +0300 Subject: [PATCH 055/252] [traffic] Refactor TraFF decoder into separate class Signed-off-by: mvglasow --- map/traffic_manager.cpp | 160 +----------------- map/traffic_manager.hpp | 33 +--- traffxml/CMakeLists.txt | 1 + traffxml/traff_decoder.cpp | 332 +++++++++++++++++++++++++++++++++++++ traffxml/traff_decoder.hpp | 153 ++++++++++++++++- traffxml/traff_model.cpp | 162 ------------------ traffxml/traff_model.hpp | 54 ------ 7 files changed, 490 insertions(+), 405 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index c0c1b1812..dfcb55898 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -1,10 +1,7 @@ #include "map/traffic_manager.hpp" -#include "routing/maxspeeds.hpp" #include "routing/routing_helpers.hpp" -#include "routing_common/maxspeed_conversion.hpp" - #include "drape_frontend/drape_engine.hpp" #include "drape_frontend/visual_params.hpp" @@ -13,10 +10,6 @@ #include "geometry/mercator.hpp" -#include "openlr/decoded_path.hpp" -#include "openlr/openlr_decoder.hpp" -#include "openlr/openlr_model.hpp" - #include "platform/platform.hpp" #include "traffxml/traff_model_xml.hpp" @@ -43,15 +36,6 @@ auto constexpr kDrapeUpdateInterval = seconds(10); * Interval at which the traffic observer gets traffic updates while messages are being processed. */ auto constexpr kObserverUpdateInterval = minutes(1); - -// Number of worker threads for the OpenLR decoder -/* - * TODO how to determine the best number of worker threads? - * One per direction? Does not seem to help with bidirectional locations (two reference points). - * One per segment (from–via/from–at, via–to/at–to)? Not yet tested. - * Otherwise there is little to be gained, as we decode messages one at a time. - */ -auto constexpr kNumDecoderThreads = 1; } // namespace TrafficManager::CacheEntry::CacheEntry() @@ -78,7 +62,7 @@ TrafficManager::TrafficManager(DataSource & dataSource, traffic::TrafficObserver & observer) : m_dataSource(dataSource) , m_countryParentNameGetterFn(countryParentNameGetter) - , m_openLrDecoder(m_dataSource, countryParentNameGetter) + , m_traffDecoder(dataSource, countryParentNameGetter, m_messageCache) , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) @@ -439,146 +423,6 @@ void TrafficManager::ConsolidateFeedQueue() ++it; } -void TrafficManager::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) -{ - decoded.clear(); - - // Convert the location to a format understood by the OpenLR decoder. - std::vector segments - = message.m_location.value().ToOpenLrSegments(message.m_id); - - for (auto segment : segments) - { - LOG(LINFO, (" Segment:", segment.m_segmentId)); - for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) - { - LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); - if (i < segment.m_locationReference.m_points.size() - 1) - { - LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); - LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); - } - } - } - - // Decode the location into a path on the map. - // One path per segment - std::vector paths(segments.size()); - m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); - - for (size_t i = 0; i < paths.size(); i++) - for (size_t j = 0; j < paths[i].m_path.size(); j++) - { - auto fid = paths[i].m_path[j].GetFeatureId().m_index; - auto segment = paths[i].m_path[j].GetSegId(); - uint8_t direction = paths[i].m_path[j].IsForward() ? - traffic::TrafficInfo::RoadSegmentId::kForwardDirection : - traffic::TrafficInfo::RoadSegmentId::kReverseDirection; - decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; - } -} - -void TrafficManager::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) -{ - for (auto dit = decoded.begin(); dit != decoded.end(); dit++) - for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) - { - /* - * Consolidate TrafficImpact into a single SpeedGroup per segment. - * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate - * the rest. - */ - traffic::SpeedGroup sg = impact.m_speedGroup; - /* - * TODO also process m_delayMins if greater than zero. - * This would require a separate pass over all edges, calculating length, - * total (normal) travel time (length / maxspeed), then a speed group based on - * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between - * reduced and normal speed. That would give us a third potential speed group. - */ - if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) - { - auto const handle = m_dataSource.GetMwmHandleById(dit->first); - auto const speeds = routing::LoadMaxspeeds(handle); - if (speeds) - { - traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; - auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); - auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); - if (speedKmPH != routing::kInvalidSpeed) - { - fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH); - if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) - sg = fromMaxspeed; - } - } - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ - } - // TODO process all TrafficImpact fields and determine the speed group based on that - cit->second = sg; - } -} - -/* - * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). - * Decoding message by message kind of defeats the purpose. - * But after decoding the location, we need to examine the map features we got in order to - * determine the speed groups, thus we may need to decode one by one (TBD). - * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path - * structures to accept a TraFF message ID (string) rather than an integer, or derive - * [partner] segment IDs from TraFF message IDs. - */ -void TrafficManager::DecodeMessage(traffxml::TraffMessage & message) -{ - if (!message.m_location) - return; - // Decode events into consolidated traffic impact - std::optional impact = message.GetTrafficImpact(); - - LOG(LINFO, (" Impact: ", impact)); - - // Skip further processing if there is no impact - if (!impact) - return; - - traffxml::MultiMwmColoring decoded; - - auto it = m_messageCache.find(message.m_id); - if ((it != m_messageCache.end()) - && !it->second.m_decoded.empty() - && (it->second.m_location == message.m_location)) - { - // cache already has a message with reusable location - - LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); - - std::optional cachedImpact = it->second.GetTrafficImpact(); - if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) - { - LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); - - // same impact, m_decoded can be reused altogether - message.m_decoded = it->second.m_decoded; - return; - } - else - decoded = it->second.m_decoded; - } - else - DecodeLocation(message, decoded); - - if (impact) - { - ApplyTrafficImpact(impact.value(), decoded); - std::swap(message.m_decoded, decoded); - } -} - void TrafficManager::DecodeFirstMessage() { traffxml::TraffMessage message; @@ -618,7 +462,7 @@ void TrafficManager::DecodeFirstMessage() } LOG(LINFO, (" ", message.m_id, ":", message)); - DecodeMessage(message); + m_traffDecoder.DecodeMessage(message); // store message in cache m_messageCache.insert_or_assign(message.m_id, message); // store message coloring in AllMwmColoring diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 90f337385..b7f3eca65 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -7,11 +7,9 @@ #include "drape/pointers.hpp" -#include "indexer/data_source.hpp" #include "indexer/mwm_set.hpp" -#include "openlr/openlr_decoder.hpp" - +#include "traffxml/traff_decoder.hpp" #include "traffxml/traff_model.hpp" #include "geometry/point2d.hpp" @@ -259,31 +257,6 @@ class TrafficManager final */ void DecodeFirstMessage(); - /** - * @brief Decodes a TraFF location. - * - * @param message The message to decode. - * @param decoded Receives the decoded segments. The speed group will be `Unknown`. - */ - void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); - - /** - * @brief Applies traffic impact to a decoded TraFF location. - * - * Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten. - * - * @param impact The traffic impact to apply. - * @param decoded The decoded segments. - */ - void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); - - /** - * @brief Decodes a single message to its segments and their speed groups. - * - * @param message The message to decode. - */ - void DecodeMessage(traffxml::TraffMessage & message); - /** * @brief Event loop for the traffic worker thread. * @@ -558,11 +531,11 @@ class TrafficManager final std::map m_messageCache; /** - * @brief The OpenLR decoder instance. + * @brief The TraFF decoder instance. * * Used to decode TraFF locations into road segments on the map. */ - openlr::OpenLRDecoder m_openLrDecoder; + traffxml::DefaultTraffDecoder m_traffDecoder; /** * @brief Map between MWM IDs and their colorings. diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index e8deb4902..029698d24 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -13,5 +13,6 @@ omim_add_library(${PROJECT_NAME} ${SRC}) target_link_libraries(${PROJECT_NAME} pugixml + openlr coding ) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 0a30f26a5..00093b01b 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -2,6 +2,338 @@ //#include "traffxml/traff_foo.hpp" +#include "openlr/decoded_path.hpp" +#include "openlr/openlr_decoder.hpp" +#include "openlr/openlr_model.hpp" + +#include "routing/maxspeeds.hpp" + +#include "routing_common/maxspeed_conversion.hpp" + namespace traffxml { +// Number of worker threads for the OpenLR decoder +/* + * TODO how to determine the best number of worker threads? + * One per direction? Does not seem to help with bidirectional locations (two reference points). + * One per segment (from–via/from–at, via–to/at–to)? Not yet tested. + * Otherwise there is little to be gained, as we decode messages one at a time. + */ +auto constexpr kNumDecoderThreads = 1; + +TraffDecoder::TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache) + : m_dataSource(dataSource) + , m_countryParentNameGetterFn(countryParentNameGetter) + , m_messageCache(messageCache) +{} + +void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) +{ + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) + { + /* + * Consolidate TrafficImpact into a single SpeedGroup per segment. + * Exception: if TrafficImpact already has SpeedGrup::TempBlock, no need to evaluate + * the rest. + */ + traffic::SpeedGroup sg = impact.m_speedGroup; + /* + * TODO also process m_delayMins if greater than zero. + * This would require a separate pass over all edges, calculating length, + * total (normal) travel time (length / maxspeed), then a speed group based on + * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between + * reduced and normal speed. That would give us a third potential speed group. + */ + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) + { + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + auto const speeds = routing::LoadMaxspeeds(handle); + if (speeds) + { + traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; + auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); + auto const speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); + if (speedKmPH != routing::kInvalidSpeed) + { + fromMaxspeed = traffic::GetSpeedGroupByPercentage(impact.m_maxspeed * 100.0f / speedKmPH); + if ((sg == traffic::SpeedGroup::Unknown) || (fromMaxspeed < sg)) + sg = fromMaxspeed; + } + } + /* + * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) + * If no maxspeed or delay is set, just give out speed groups. + * Else, examine segments, length, normal travel time, travel time considering impact, and + * determine the closest matching speed group. + */ + } + // TODO process all TrafficImpact fields and determine the speed group based on that + cit->second = sg; + } +} + +void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) +{ + if (!message.m_location) + return; + // Decode events into consolidated traffic impact + std::optional impact = message.GetTrafficImpact(); + + LOG(LINFO, (" Impact: ", impact)); + + // Skip further processing if there is no impact + if (!impact) + return; + + traffxml::MultiMwmColoring decoded; + + auto it = m_messageCache.find(message.m_id); + if ((it != m_messageCache.end()) + && !it->second.m_decoded.empty() + && (it->second.m_location == message.m_location)) + { + // cache already has a message with reusable location + + LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); + + std::optional cachedImpact = it->second.GetTrafficImpact(); + if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) + { + LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); + + // same impact, m_decoded can be reused altogether + message.m_decoded = it->second.m_decoded; + return; + } + else + decoded = it->second.m_decoded; + } + else + DecodeLocation(message, decoded); + + if (impact) + { + ApplyTrafficImpact(impact.value(), decoded); + std::swap(message.m_decoded, decoded); + } +} + +OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache) + : TraffDecoder(dataSource, countryParentNameGetter, messageCache) + , m_openLrDecoder(dataSource, countryParentNameGetter) +{} + +openlr::FunctionalRoadClass OpenLrV3TraffDecoder::GetRoadClassFrc(std::optional & roadClass) +{ + if (!roadClass) + return openlr::FunctionalRoadClass::NotAValue; + switch (roadClass.value()) + { + case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0; + case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1; + case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2; + case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; + /* + * TODO Revisit FRC for Other. + * Other corresponds to FRC4–7. + * FRC4 matches secondary/tertiary (zero score) and anything below (full score). + * FRC5–7 match anything below tertiary (full score); secondary/tertiary never match. + * Primary and above never matches any of these FRCs. + */ + case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4; + } + UNREACHABLE(); +} + +// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) +uint32_t OpenLrV3TraffDecoder::GuessDnp(Point & p1, Point & p2) +{ + // direct distance + double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates), + mercator::FromLatLon(p2.m_coordinates)); + + /* + * Acceptance boundaries for candidate paths are currently: + * + * for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times + * the direct distance, + * + * for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance. + * + * A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all + * cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may + * cause the actual distance to be overestimated and an incorrect route chosen as a result, as + * path candidates are scored based on the match between DNP and their length. + * Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are + * much wider than that. + * In practice, the shortest route from one valley to the next in a mountain area is seldom more + * than 3 times the direct distance, based on a brief examination. This would be even within the + * limits of direct distance, hence we do not need a large correction factor for this scenario. + * + * Candidate values: + * 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits + * 1.41 (2^0.5) – ratio between two sides of a square and its diagonal + * 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66) + * 1.19 – close to the square root of 1.41 + * 1 – direct distance unmodified + */ + double dist = doe * 1.19f; + + // if we have kilometric points, calculate nominal distance as the difference between them + if (p1.m_distance && p2.m_distance) + { + LOG(LINFO, ("Both points have distance, calculating nominal difference")); + float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0; + if (nominalDist < 0) + nominalDist *= -1; + /* + * Plausibility check for nominal distance, as kilometric points along the route may not be + * continuous: discard if shorter than direct distance (geometrically impossible) or if longer + * than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for + * `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of + * distances between two adjacent mountain valleys, which are up to roughly 3 times the direct + * distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct + * distance with a tolerance factor). + */ + if ((nominalDist >= doe) && (nominalDist <= doe * 4)) + dist = nominalDist; + else + LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding")); + } + return dist + 0.5f; +} + +openlr::LocationReferencePoint OpenLrV3TraffDecoder::PointToLrp(Point & point) +{ + openlr::LocationReferencePoint result; + result.m_latLon = ms::LatLon(point.m_coordinates.m_lat, point.m_coordinates.m_lon); + return result; +} + +openlr::LinearLocationReference OpenLrV3TraffDecoder::TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards) +{ + openlr::LinearLocationReference locationReference; + locationReference.m_points.clear(); + std::vector points; + if (location.m_from) + points.push_back(location.m_from.value()); + if (location.m_at) + points.push_back(location.m_at.value()); + else if (location.m_via) + points.push_back(location.m_via.value()); + if (location.m_to) + points.push_back(location.m_to.value()); + if (backwards) + std::reverse(points.begin(), points.end()); + // m_notVia is ignored as OpenLR does not support this functionality. + CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); + for (size_t i = 0; i < points.size(); i++) + { + openlr::LocationReferencePoint lrp = PointToLrp(points[i]); + lrp.m_functionalRoadClass = GetRoadClassFrc(location.m_roadClass); + if (location.m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) + lrp.m_formOfWay = openlr::FormOfWay::Sliproad; + if (i < points.size() - 1) + { + lrp.m_distanceToNextPoint + = GuessDnp(points[i], points[i + 1]); + /* + * Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is + * used or discarded based on whether a score was returned or not (the score itself is not + * used for LFRCNP). However, this means we can use FRC as LFRCNP. + */ + lrp.m_lfrcnp = GetRoadClassFrc(location.m_roadClass); + } + locationReference.m_points.push_back(lrp); + } + return locationReference; +} + +// TODO make segment ID in OpenLR a string value, and store messageId +std::vector OpenLrV3TraffDecoder::TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId) +{ + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments; + int dirs = (location.m_directionality == Directionality::BothDirections) ? 2 : 1; + for (int dir = 0; dir < dirs; dir++) + { + openlr::LinearSegment segment; + /* + * Segment IDs are used internally by the decoder but nowhere else. + * Since we decode TraFF locations one at a time, there are at most two segments in a single + * decoder instance (one segment per direction). Therefore, a segment ID derived from the + * direction is unique within the decoder instance. + */ + segment.m_segmentId = dir; + segment.m_messageId = messageId; + /* + * Segments generated from coordinates can have any number of points. Each point, except for + * the last point, must indicate the distance to the next point. Line properties (functional + * road class (FRC), form of way, bearing) or path properties other than distance to next point + * (lowest FRC to next point, againstDrivingDirection) are ignored. + * Segment length is never evaluated. + * TODO update OpenLR decoder to make all line and path properties optional. + */ + segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag; + segment.m_locationReference = TraffLocationToLinearLocationReference(location, dir == 0 ? false : true); + + segments.push_back(segment); + } + return segments; +} + +/* + * TODO the OpenLR decoder is designed to handle multiple segments (i.e. locations). + * Decoding message by message kind of defeats the purpose. + * But after decoding the location, we need to examine the map features we got in order to + * determine the speed groups, thus we may need to decode one by one (TBD). + * If we batch-decode segments, we need to fix the [partner] segment IDs in the segment and path + * structures to accept a TraFF message ID (string) rather than an integer, or derive + * [partner] segment IDs from TraFF message IDs. + */ +void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) +{ + ASSERT(message.m_location, ("Message has no location")); + decoded.clear(); + + // Convert the location to a format understood by the OpenLR decoder. + std::vector segments + = TraffLocationToOpenLrSegments(message.m_location.value(), message.m_id); + + for (auto segment : segments) + { + LOG(LINFO, (" Segment:", segment.m_segmentId)); + for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) + { + LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); + if (i < segment.m_locationReference.m_points.size() - 1) + { + LOG(LINFO, (" FRC:", segment.m_locationReference.m_points[i].m_functionalRoadClass)); + LOG(LINFO, (" DNP:", segment.m_locationReference.m_points[i].m_distanceToNextPoint)); + } + } + } + + // Decode the location into a path on the map. + // One path per segment + std::vector paths(segments.size()); + m_openLrDecoder.DecodeV3(segments, kNumDecoderThreads, paths); + + for (size_t i = 0; i < paths.size(); i++) + for (size_t j = 0; j < paths[i].m_path.size(); j++) + { + auto fid = paths[i].m_path[j].GetFeatureId().m_index; + auto segment = paths[i].m_path[j].GetSegId(); + uint8_t direction = paths[i].m_path[j].IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; + } +} } // namespace traffxml diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 075c20ab1..5e46d1ecf 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -1,7 +1,158 @@ #pragma once -//#include "traffxml/traff_foo.hpp" +#include "traffxml/traff_model.hpp" + +#include "indexer/data_source.hpp" + +#include "openlr/openlr_decoder.hpp" +#include "openlr/openlr_model.hpp" namespace traffxml { +/** + * @brief Abstract base class for all TraFF decoder implementations. + */ +class TraffDecoder +{ +public: + using CountryParentNameGetterFn = std::function; + + TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache); + + /** + * @brief Decodes a single message to its segments and their speed groups. + * + * This method may access the message cache which was passed to the constructor. Access to the + * message cache is not thread-safe. Unless it is guaranteed that any operations on the message + * cache will happen on the same thread, calls to this method need to be synchronized with other + * operations on the message cache. + * + * @param message The message to decode. + */ + void DecodeMessage(traffxml::TraffMessage & message); + +protected: + /** + * @brief Decodes a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + */ + virtual void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) = 0; + + /** + * @brief Applies traffic impact to a decoded TraFF location. + * + * Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten. + * + * @param impact The traffic impact to apply. + * @param decoded The decoded segments. + */ + void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); + + DataSource & m_dataSource; + CountryParentNameGetterFn m_countryParentNameGetterFn; + + /** + * @brief Cache of all currently active TraFF messages. + * + * Keys are message IDs, values are messages. + */ + std::map & m_messageCache; + +private: +}; + +/** + * @brief A `TraffDecoder` implementation which internally uses the version 3 OpenLR decoder. + */ +class OpenLrV3TraffDecoder : public TraffDecoder +{ +public: + OpenLrV3TraffDecoder(DataSource & dataSource, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache); + +protected: + /** + * @brief Decodes a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + */ + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + +private: + /** + * @brief Returns the OpenLR functional road class (FRC) matching a TraFF road class. + * + * @param roadClass The TraFF road class. + * @return The FRC. + */ + static openlr::FunctionalRoadClass GetRoadClassFrc(std::optional & roadClass); + + /** + * @brief Guess the distance between two points. + * + * If both `p1` and `p2` have the `distance` attribute set, the difference between these two is + * evaluated. If it is within a certain tolerance margin of the direct distance between the two + * points, this value is returned. Otherwise, the distance is calculated from direct distance, + * multiplied with a tolerance factor to account for the fact that the road is not always a + * straight line. + * + * The result can be used to provide some semi-valid DNP values. + * + * @param p1 The first point. + * @param p2 The second point. + * @return The approximate distance on the ground, in meters. + */ + static uint32_t GuessDnp(Point & p1, Point & p2); + + /** + * @brief Converts a TraFF point to an OpenLR location reference point. + * + * Only coordinates are populated. + * + * @param point The point + * @return An OpenLR LRP with the coordinates of the point. + */ + static openlr::LocationReferencePoint PointToLrp(Point & point); + + /** + * @brief Converts a TraFF location to an OpenLR linear location reference. + * + * @param location The location + * @param backwards If true, gnerates a linear location reference for the backwards direction, + * with the order of points reversed. + * @return An OpenLR linear location reference which corresponds to the location. + */ + static openlr::LinearLocationReference TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards); + + /** + * @brief Converts a TraFF location to a vector of OpenLR segments. + * + * Depending on the directionality, the resulting vector will hold one or two elements: one for + * the forward direction, and for bidirectional locations, a second one for the backward + * direction. + * + * @param location The location + * @param messageId The message ID + * @return A vector holding the resulting OpenLR segments. + */ + static std::vector TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId); + + /** + * @brief The OpenLR decoder instance. + * + * Used to decode TraFF locations into road segments on the map. + */ + openlr::OpenLRDecoder m_openLrDecoder; +}; + +/** + * @brief The default TraFF decoder implementation, recommended for production use. + */ +using DefaultTraffDecoder = OpenLrV3TraffDecoder; } // namespace traffxml diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index fec9aa5eb..05a424e9e 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -2,8 +2,6 @@ #include "base/logging.hpp" -#include "geometry/mercator.hpp" - #include using namespace std; @@ -174,13 +172,6 @@ bool operator==(Point const & lhs, Point const & rhs) return lhs.m_coordinates == rhs.m_coordinates; } -openlr::LocationReferencePoint Point::ToLrp() -{ - openlr::LocationReferencePoint result; - result.m_latLon = ms::LatLon(this->m_coordinates.m_lat, this->m_coordinates.m_lon); - return result; -} - bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) { return (lhs.m_from == rhs.m_from) @@ -190,101 +181,6 @@ bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) && (lhs.m_to == rhs.m_to); } -openlr::LinearLocationReference TraffLocation::ToLinearLocationReference(bool backwards) -{ - openlr::LinearLocationReference locationReference; - locationReference.m_points.clear(); - std::vector points; - if (m_from) - points.push_back(m_from.value()); - if (m_at) - points.push_back(m_at.value()); - else if (m_via) - points.push_back(m_via.value()); - if (m_to) - points.push_back(m_to.value()); - if (backwards) - std::reverse(points.begin(), points.end()); - // m_notVia is ignored as OpenLR does not support this functionality. - CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); - for (size_t i = 0; i < points.size(); i++) - { - openlr::LocationReferencePoint lrp = points[i].ToLrp(); - lrp.m_functionalRoadClass = GetFrc(); - if (m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) - lrp.m_formOfWay = openlr::FormOfWay::Sliproad; - if (i < points.size() - 1) - { - lrp.m_distanceToNextPoint - = GuessDnp(points[i], points[i + 1]); - /* - * Somewhat hackish. LFRCNP is evaluated by the same function as FRC and the candidate is - * used or discarded based on whether a score was returned or not (the score itself is not - * used for LFRCNP). However, this means we can use FRC as LFRCNP. - */ - lrp.m_lfrcnp = GetFrc(); - } - locationReference.m_points.push_back(lrp); - } - return locationReference; -} - -// TODO make segment ID in OpenLR a string value, and store messageId -std::vector TraffLocation::ToOpenLrSegments(std::string & messageId) -{ - // Convert the location to a format understood by the OpenLR decoder. - std::vector segments; - int dirs = (m_directionality == Directionality::BothDirections) ? 2 : 1; - for (int dir = 0; dir < dirs; dir++) - { - openlr::LinearSegment segment; - /* - * Segment IDs are used internally by the decoder but nowhere else. - * Since we decode TraFF locations one at a time, there are at most two segments in a single - * decoder instance (one segment per direction). Therefore, a segment ID derived from the - * direction is unique within the decoder instance. - */ - segment.m_segmentId = dir; - segment.m_messageId = messageId; - /* - * Segments generated from coordinates can have any number of points. Each point, except for - * the last point, must indicate the distance to the next point. Line properties (functional - * road class (FRC), form of way, bearing) or path properties other than distance to next point - * (lowest FRC to next point, againstDrivingDirection) are ignored. - * Segment length is never evaluated. - * TODO update OpenLR decoder to make all line and path properties optional. - */ - segment.m_source = openlr::LinearSegmentSource::FromCoordinatesTag; - segment.m_locationReference = this->ToLinearLocationReference(dir == 0 ? false : true); - - segments.push_back(segment); - } - return segments; -} - -openlr::FunctionalRoadClass TraffLocation::GetFrc() -{ - if (!m_roadClass) - return openlr::FunctionalRoadClass::NotAValue; - switch (m_roadClass.value()) - { - case RoadClass::Motorway: return openlr::FunctionalRoadClass::FRC0; - case RoadClass::Trunk: return openlr::FunctionalRoadClass::FRC0; - case RoadClass::Primary: return openlr::FunctionalRoadClass::FRC1; - case RoadClass::Secondary: return openlr::FunctionalRoadClass::FRC2; - case RoadClass::Tertiary: return openlr::FunctionalRoadClass::FRC3; - /* - * TODO Revisit FRC for Other. - * Other corresponds to FRC4–7. - * FRC4 matches secondary/tertiary (zero score) and anything below (full score). - * FRC5–7 match anything below tertiary (full score); secondary/tertiary never match. - * Primary and above never matches any of these FRCs. - */ - case RoadClass::Other: return openlr::FunctionalRoadClass::FRC4; - } - UNREACHABLE(); -} - std::optional TraffMessage::GetTrafficImpact() { // no events, no impact @@ -346,64 +242,6 @@ std::optional TraffMessage::GetTrafficImpact() return std::nullopt; } -// TODO tweak formula based on FRC, FOW and direct distance (lower FRC roads may have more and sharper turns) -uint32_t GuessDnp(Point & p1, Point & p2) -{ - // direct distance - double doe = mercator::DistanceOnEarth(mercator::FromLatLon(p1.m_coordinates), - mercator::FromLatLon(p2.m_coordinates)); - - /* - * Acceptance boundaries for candidate paths are currently: - * - * for `openlr::LinearSegmentSource::FromLocationReferenceTag`, 0.6 to ~1.67 (i.e. 1/0.6) times - * the direct distance, - * - * for `openlr::LinearSegmentSource::FromCoordinatesTag`, 0.25 to 4 times the direct distance. - * - * A tolerance factor of 1/0.6 is the maximum for which direct distance would be accepted in all - * cases, with an upper boundary of at least ~2.78 times the direct distance. However, this may - * cause the actual distance to be overestimated and an incorrect route chosen as a result, as - * path candidates are scored based on the match between DNP and their length. - * Also, since we use `openlr::LinearSegmentSource::FromCoordinatesTag`, acceptance limits are - * much wider than that. - * In practice, the shortest route from one valley to the next in a mountain area is seldom more - * than 3 times the direct distance, based on a brief examination. This would be even within the - * limits of direct distance, hence we do not need a large correction factor for this scenario. - * - * Candidate values: - * 1.66 (1/0.6) – upper boundary for direct distance to be just within the most stringent limits - * 1.41 (2^0.5) – ratio between two sides of a square and its diagonal - * 1.3 – close to the square root of 1.66 (halfway between 1 and 1.66) - * 1.19 – close to the square root of 1.41 - * 1 – direct distance unmodified - */ - double dist = doe * 1.19f; - - // if we have kilometric points, calculate nominal distance as the difference between them - if (p1.m_distance && p2.m_distance) - { - LOG(LINFO, ("Both points have distance, calculating nominal difference")); - float nominalDist = (p1.m_distance.value() - p2.m_distance.value()) * 1000.0; - if (nominalDist < 0) - nominalDist *= -1; - /* - * Plausibility check for nominal distance, as kilometric points along the route may not be - * continuous: discard if shorter than direct distance (geometrically impossible) or if longer - * than 4 times direct distance (somewhat arbitrary, based on the OpenLR acceptance limit for - * `openlr::LinearSegmentSource::FromCoordinatesTag`, as well as real-world observations of - * distances between two adjacent mountain valleys, which are up to roughly 3 times the direct - * distance). If nominal distance is outside these boundaries, discard it and use `dist` (direct - * distance with a tolerance factor). - */ - if ((nominalDist >= doe) && (nominalDist <= doe * 4)) - dist = nominalDist; - else - LOG(LINFO, ("Nominal distance:", nominalDist, "direct distance:", doe, "– discarding")); - } - return dist + 0.5f; -} - void MergeMultiMwmColoring(MultiMwmColoring & delta, MultiMwmColoring & target) { // for each mwm in delta diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 412ea76d6..2bec230b8 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -3,12 +3,9 @@ //#include "traffxml/traff_foo.hpp" #include "geometry/latlon.hpp" -#include "geometry/point2d.hpp" #include "indexer/mwm_set.hpp" -#include "openlr/openlr_model.hpp" - #include "traffic/speed_groups.hpp" #include "traffic/traffic_info.hpp" @@ -264,15 +261,6 @@ struct Point friend bool operator==(Point const & lhs, Point const & rhs); friend bool operator!=(Point const & lhs, Point const & rhs) { return !(lhs == rhs); } - /** - * @brief Converts the point to an OpenLR location reference point. - * - * Only coordinates are populated. - * - * @return An OpenLR LRP with the coordinates of the point. - */ - openlr::LocationReferencePoint ToLrp(); - // TODO role? ms::LatLon m_coordinates = ms::LatLon::Zero(); std::optional m_distance; @@ -297,34 +285,6 @@ struct TraffLocation friend bool operator==(TraffLocation const & lhs, TraffLocation const & rhs); friend bool operator!=(TraffLocation const & lhs, TraffLocation const & rhs) { return !(lhs == rhs); } - /** - * @brief Converts the location to an OpenLR linear location reference. - * - * @param backwards If true, gnerates a linear location reference for the backwards direction, - * with the order of points reversed. - * @return An OpenLR linear location reference which corresponds to the location. - */ - openlr::LinearLocationReference ToLinearLocationReference(bool backwards); - - /** - * @brief Converts the location to a vector of OpenLR segments. - * - * Depending on the directionality, the resulting vector will hold one or two elements: one for - * the forward direction, and for bidirectional locations, a second one for the backward - * direction. - * - * @param messageId The message ID - * @return A vector holding the resulting OpenLR segments. - */ - std::vector ToOpenLrSegments(std::string & messageId); - - /** - * @brief Returns the OpenLR functional road class (FRC) matching `m_roadClass`. - * - * @return The FRC. - */ - openlr::FunctionalRoadClass GetFrc(); - std::optional m_country; std::optional m_destination; std::optional m_direction; @@ -411,20 +371,6 @@ using TraffFeed = std::vector; * the full filter list. */ -/** - * @brief Guess the distance to the next point. - * - * This is calculated as direct distance, multiplied with a tolerance factor to account for the - * fact that the road is not always a straight line. - * - * The result can be used to provide some semi-valid DNP values. - * - * @param p1 The first point. - * @param p2 The second point. - * @return The approximate distance on the ground, in meters. - */ -uint32_t GuessDnp(Point & p1, Point & p2); - /** * @brief Merges the contents of one `MultiMwmColoring` into another. * From 9afb28aaa14de2ad94df0e326921af496d0be620 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 24 May 2025 21:08:58 +0300 Subject: [PATCH 056/252] [traffxml] Add router-based decoder, still crude, ugly and buggy To use it, redefine DefaultTraffDecoder in traffxml/traff_decoder.hpp Signed-off-by: mvglasow --- map/framework.cpp | 3 + map/traffic_manager.cpp | 14 +- map/traffic_manager.hpp | 27 +++- routing/index_router.cpp | 34 +++++ routing/index_router.hpp | 40 ++++++ traffxml/traff_decoder.cpp | 278 ++++++++++++++++++++++++++++++++++++- traffxml/traff_decoder.hpp | 88 +++++++++++- 7 files changed, 474 insertions(+), 10 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 06b83b3c8..05f92c748 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -296,6 +296,7 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) [this]() -> power_management::PowerManager const & { return m_powerManager; }), static_cast(*this)) , m_trafficManager(m_featuresFetcher.GetDataSource(), + [this]() -> storage::CountryInfoGetter const & { return GetCountryInfoGetter(); }, [this](string const & id) -> string { return m_storage.GetParentIdFor(id); }, bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes, m_routingManager.RoutingSession()) @@ -388,6 +389,8 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) if (loadMaps) LoadMapsSync(); + + m_trafficManager.Start(); } Framework::~Framework() diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index dfcb55898..6d1dde719 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -56,13 +56,13 @@ TrafficManager::CacheEntry::CacheEntry(time_point const & requestT , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) {} -TrafficManager::TrafficManager(DataSource & dataSource, +TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn &countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer) : m_dataSource(dataSource) + , m_countryInfoGetterFn(countryInfoGetter) , m_countryParentNameGetterFn(countryParentNameGetter) - , m_traffDecoder(dataSource, countryParentNameGetter, m_messageCache) , m_getMwmsByRectFn(getMwmsByRectFn) , m_observer(observer) , m_currentDataVersion(0) @@ -129,6 +129,13 @@ void TrafficManager::SetEnabled(bool enabled) m_observer.OnTrafficInfoClear(); } +void TrafficManager::Start() +{ + m_traffDecoder = make_unique(m_dataSource, m_countryInfoGetterFn, + m_countryParentNameGetterFn, m_messageCache); + m_isStarted = true; +} + void TrafficManager::Clear() { // TODO no longer needed @@ -211,7 +218,6 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, { std::lock_guard lock(m_mutex); - m_isStarted = true; m_activeMwmsChanged = true; activeMwms.clear(); for (auto const & mwm : mwms) @@ -462,7 +468,7 @@ void TrafficManager::DecodeFirstMessage() } LOG(LINFO, (" ", message.m_id, ":", message)); - m_traffDecoder.DecodeMessage(message); + m_traffDecoder->DecodeMessage(message); // store message in cache m_messageCache.insert_or_assign(message.m_id, message); // store message coloring in AllMwmColoring diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index b7f3eca65..7625fc2b9 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -9,6 +9,8 @@ #include "indexer/mwm_set.hpp" +#include "storage/country_info_getter.hpp" + #include "traffxml/traff_decoder.hpp" #include "traffxml/traff_model.hpp" @@ -34,6 +36,7 @@ class TrafficManager final { public: + using CountryInfoGetterFn = std::function; using CountryParentNameGetterFn = std::function; /** @@ -75,6 +78,7 @@ class TrafficManager final using GetMwmsByRectFn = std::function(m2::RectD const &)>; TrafficManager(DataSource & dataSource, + CountryInfoGetterFn countryInfoGetter, CountryParentNameGetterFn const & countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, traffic::TrafficObserver & observer); @@ -109,6 +113,26 @@ class TrafficManager final */ bool IsEnabled() const; + /** + * @brief Starts the traffic manager. + * + * After creation, the traffic manager will not poll any sources or process any feeds until it is + * started. Feeds received through `Push()` will be added to the queue before the traffic manager + * is started, but will not be processed any further until the traffic manager is started. + * + * MWMs must be loaded before starting the traffic manager. + * + * @todo Currently, all MWMs must be loaded before calling `Start()`, as MWMs loaded after that + * will not get picked up. We need to extend `TrafficManager` to react to MWMs being added (and + * removed) – note that this affects the data source, not the set of active MWMs. + * + * @todo Start is currently not integrated with state or pause/resume logic (all of which might + * not be fully implemented ATM). If the traffic manager is not started, no message processing + * (other than filling the queue and deduplication) will take place, regardless of state. Starting + * the traffic manager will not change the state it reports. + */ + void Start(); + void UpdateViewport(ScreenBase const & screen); void UpdateMyPosition(MyPosition const & myPosition); @@ -400,6 +424,7 @@ class TrafficManager final } DataSource & m_dataSource; + CountryInfoGetterFn m_countryInfoGetterFn; CountryParentNameGetterFn m_countryParentNameGetterFn; GetMwmsByRectFn m_getMwmsByRectFn; traffic::TrafficObserver & m_observer; @@ -535,7 +560,7 @@ class TrafficManager final * * Used to decode TraFF locations into road segments on the map. */ - traffxml::DefaultTraffDecoder m_traffDecoder; + std::unique_ptr m_traffDecoder; /** * @brief Map between MWM IDs and their colorings. diff --git a/routing/index_router.cpp b/routing/index_router.cpp index d87ed514a..7f30fe0bd 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -276,6 +276,40 @@ IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes, CHECK(m_directionsEngine, ()); } +IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes, + CountryParentNameGetterFn const & countryParentNameGetterFn, + TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn, + shared_ptr numMwmIds, unique_ptr> numMwmTree, + DataSource & dataSource) + : m_vehicleType(vehicleType) + , m_loadAltitudes(loadAltitudes) + , m_name("astar-bidirectional-" + ToString(m_vehicleType)) + , m_dataSource(dataSource, numMwmIds) + , m_vehicleModelFactory(CreateVehicleModelFactory(m_vehicleType, countryParentNameGetterFn)) + , m_countryFileFn(countryFileFn) + , m_countryRectFn(countryRectFn) + , m_numMwmIds(std::move(numMwmIds)) + , m_numMwmTree(std::move(numMwmTree)) + , m_trafficStash(nullptr) + , m_roadGraph(m_dataSource, + vehicleType == VehicleType::Pedestrian || vehicleType == VehicleType::Transit + ? IRoadGraph::Mode::IgnoreOnewayTag + : IRoadGraph::Mode::ObeyOnewayTag, + m_vehicleModelFactory) + , m_estimator(EdgeEstimator::Create( + m_vehicleType, CalcMaxSpeed(*m_numMwmIds, *m_vehicleModelFactory, m_vehicleType), + CalcOffroadSpeed(*m_vehicleModelFactory), m_trafficStash, + &dataSource, m_numMwmIds)) + , m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) + , m_countryParentNameGetterFn(countryParentNameGetterFn) +{ + CHECK(!m_name.empty(), ()); + CHECK(m_numMwmIds, ()); + CHECK(m_numMwmTree, ()); + CHECK(m_estimator, ()); + CHECK(m_directionsEngine, ()); +} + unique_ptr IndexRouter::MakeSingleMwmWorldGraph() { auto worldGraph = MakeWorldGraph(); diff --git a/routing/index_router.hpp b/routing/index_router.hpp index 6590854f9..4d433584f 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -65,6 +65,23 @@ class IndexRouter : public IRouter m2::PointD const m_direction; }; + /** + * @brief Creates a new `IndexRouter` instance. + * + * This is the constructor intended for normal routing. It requires a `TrafficCache` argument, + * from which it may create a traffic stash so the traffic situation can be considered for the + * route, depending on the vehicle type. + * + * @param vehicleType The vehichle type + * @param loadAltitudes Whether to load altitudes + * @param countryParentNameGetterFn Function which converts a country name into the name of its parent country) + * @param countryFileFn Function which converts a pointer to its country name + * @param countryRectFn Function which returns the rect for a country + * @param numMwmIds + * @param numMwmTree + * @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`) + * @param dataSource The MWM data source + */ IndexRouter(VehicleType vehicleType, bool loadAltitudes, CountryParentNameGetterFn const & countryParentNameGetterFn, TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn, @@ -89,6 +106,29 @@ class IndexRouter : public IRouter VehicleType GetVehicleType() const { return m_vehicleType; } +protected: + /** + * @brief Creates a new `IndexRouter` instance. + * + * This constructor is intended for use by the TraFF decoder, not for normal routing. It lacks the + * `TrafficCache` argument and never creates a traffic stash. This creates a router instance which + * ignores the traffic situation, regardless of the vehicle type. + * + * @param vehicleType The vehichle type + * @param loadAltitudes Whether to load altitudes + * @param countryParentNameGetterFn Function which converts a country name into the name of its parent country) + * @param countryFileFn Function which converts a pointer to its country name + * @param countryRectFn Function which returns the rect for a country + * @param numMwmIds + * @param numMwmTree + * @param dataSource The MWM data source + */ + IndexRouter(VehicleType vehicleType, bool loadAltitudes, + CountryParentNameGetterFn const & countryParentNameGetterFn, + TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn, + std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, + DataSource & dataSource); + private: RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, RouterDelegate const & delegate, diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 00093b01b..4b8557217 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -6,10 +6,18 @@ #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" +#include "routing/async_router.hpp" +#include "routing/checkpoints.hpp" #include "routing/maxspeeds.hpp" +#include "routing/route.hpp" +#include "routing/router_delegate.hpp" #include "routing_common/maxspeed_conversion.hpp" +#include "storage/routing_helpers.hpp" + +#include "traffic/traffic_cache.hpp" + namespace traffxml { // Number of worker threads for the OpenLR decoder @@ -21,10 +29,15 @@ namespace traffxml */ auto constexpr kNumDecoderThreads = 1; -TraffDecoder::TraffDecoder(DataSource & dataSource, +// Timeout for the router in seconds, used by RoutingTraffDecoder +// TODO set to a sensible value +auto constexpr kRouterTimeoutSec = 30; + +TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) : m_dataSource(dataSource) + , m_countryInfoGetterFn(countryInfoGetter) , m_countryParentNameGetterFn(countryParentNameGetter) , m_messageCache(messageCache) {} @@ -121,10 +134,10 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) } } -OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, +OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) - : TraffDecoder(dataSource, countryParentNameGetter, messageCache) + : TraffDecoder(dataSource, countryInfoGetter, countryParentNameGetter, messageCache) , m_openLrDecoder(dataSource, countryParentNameGetter) {} @@ -336,4 +349,263 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; } } + +RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn, + routing::TCountryFileFn const & countryFileFn, + routing::CountryRectFn const & countryRectFn, + std::shared_ptr numMwmIds, + std::unique_ptr> numMwmTree, + DataSource & dataSource) + : routing::IndexRouter(routing::VehicleType::Car /* VehicleType vehicleType */, + false /* bool loadAltitudes */, + countryParentNameGetterFn, + countryFileFn, + countryRectFn, + std::move(numMwmIds), + std::move(numMwmTree), + //std::nullopt /* std::optional const & trafficCache */, + dataSource) + /* TODO build our own edge estimator for TraFF decoding purposes + , m_estimator(EdgeEstimator::Create( + VehicleType::Car, CalcMaxSpeed(*m_numMwmIds, *m_vehicleModelFactory, m_vehicleType), + CalcOffroadSpeed(*m_vehicleModelFactory), m_trafficStash, + &dataSource, m_numMwmIds)) + */ + //, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that? +{} + +RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache) + : TraffDecoder(dataSource, countryInfoGetter, countryParentNameGetter, messageCache) +{ + InitRouter(); +} + +bool RoutingTraffDecoder::InitRouter() +{ + if (m_router) + return true; + + // code mostly from RoutingManager::SetRouterImpl(RouterType) + /* + * RoutingManager::SetRouterImpl(RouterType) calls m_delegate.RegisterCountryFilesOnRoute(numMwmIds). + * m_delegate is the framework, and the routine cycles through the countries in storage. + * As we don’t have access to storage, we get our country files from the data source. + */ + std::vector> mwmsInfo; + m_dataSource.GetMwmsInfo(mwmsInfo); + for (auto mwmInfo : mwmsInfo) + m_numMwmIds->RegisterFile(mwmInfo->GetLocalFile().GetCountryFile()); + + if (m_numMwmIds->IsEmpty()) + return false; + + auto const countryFileGetter = [this](m2::PointD const & p) -> std::string { + // TODO (@gorshenin): fix CountryInfoGetter to return CountryFile + // instances instead of plain strings. + return m_countryInfoGetterFn().GetRegionCountryId(p); + }; + + auto const getMwmRectByName = [this](std::string const & countryId) -> m2::RectD { + return m_countryInfoGetterFn().GetLimitRectForLeaf(countryId); + }; + + m_router = + make_unique(m_countryParentNameGetterFn, + countryFileGetter, getMwmRectByName, m_numMwmIds, + routing::MakeNumMwmTree(*m_numMwmIds, m_countryInfoGetterFn()), + m_dataSource); + + return true; +} + +// Copied from AsyncRouter +// static +void RoutingTraffDecoder::LogCode(routing::RouterResultCode code, double const elapsedSec) +{ + switch (code) + { + case routing::RouterResultCode::StartPointNotFound: + LOG(LWARNING, ("Can't find start or end node")); + break; + case routing::RouterResultCode::EndPointNotFound: + LOG(LWARNING, ("Can't find end point node")); + break; + case routing::RouterResultCode::PointsInDifferentMWM: + LOG(LWARNING, ("Points are in different MWMs")); + break; + case routing::RouterResultCode::RouteNotFound: + LOG(LWARNING, ("Route not found")); + break; + case routing::RouterResultCode::RouteFileNotExist: + LOG(LWARNING, ("There is no routing file")); + break; + case routing::RouterResultCode::NeedMoreMaps: + LOG(LINFO, + ("Routing can find a better way with additional maps, elapsed seconds:", elapsedSec)); + break; + case routing::RouterResultCode::Cancelled: + LOG(LINFO, ("Route calculation cancelled, elapsed seconds:", elapsedSec)); + break; + case routing::RouterResultCode::NoError: + LOG(LINFO, ("Route found, elapsed seconds:", elapsedSec)); + break; + case routing::RouterResultCode::NoCurrentPosition: + LOG(LINFO, ("No current position")); + break; + case routing::RouterResultCode::InconsistentMWMandRoute: + LOG(LINFO, ("Inconsistent mwm and route")); + break; + case routing::RouterResultCode::InternalError: + LOG(LINFO, ("Internal error")); + break; + case routing::RouterResultCode::FileTooOld: + LOG(LINFO, ("File too old")); + break; + case routing::RouterResultCode::IntermediatePointNotFound: + LOG(LWARNING, ("Can't find intermediate point node")); + break; + case routing::RouterResultCode::TransitRouteNotFoundNoNetwork: + LOG(LWARNING, ("No transit route is found because there's no transit network in the mwm of " + "the route point")); + break; + case routing::RouterResultCode::TransitRouteNotFoundTooLongPedestrian: + LOG(LWARNING, ("No transit route is found because pedestrian way is too long")); + break; + case routing::RouterResultCode::RouteNotFoundRedressRouteError: + LOG(LWARNING, ("Route not found because of a redress route error")); + break; + case routing::RouterResultCode::HasWarnings: + LOG(LINFO, ("Route has warnings, elapsed seconds:", elapsedSec)); + break; + } +} + +void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & message, + traffxml::MultiMwmColoring & decoded, bool backwards) +{ + bool adjustToPrevRoute = false; // calculate a fresh route, no adjustments to previous one + uint64_t routeId = 0; // used in callbacks to identify the route, we might not need it at all + std::string routerName; // set later + + std::vector points; + if (message.m_location.value().m_from) + points.push_back(mercator::FromLatLon(message.m_location.value().m_from.value().m_coordinates)); + if (message.m_location.value().m_at) + points.push_back(mercator::FromLatLon(message.m_location.value().m_at.value().m_coordinates)); + else if (message.m_location.value().m_via) + points.push_back(mercator::FromLatLon(message.m_location.value().m_via.value().m_coordinates)); + if (message.m_location.value().m_to) + points.push_back(mercator::FromLatLon(message.m_location.value().m_to.value().m_coordinates)); + if (backwards) + std::reverse(points.begin(), points.end()); + // m_notVia is ignored as OpenLR does not support this functionality. + CHECK_GREATER(points.size(), 1, ("At least two reference points must be given")); + + /* + * startDirection is the direction of travel at start. Can be m2::PointD::Zero() to ignore + * direction, or PositionAccumulator::GetDirection(), which basically returns the difference + * between the last position and an earlier one (offset between two points from which the + * direction of travel can be inferred). + * + * For our purposes, points[1] - points[0] would be as close as we could get to a start direction. + * This would be accurate on very straight roads, less accurate on not-so-straight ones. However, + * even on a near-straight road, the standard router (with the default EdgeEstimator) seemed quite + * unimpressed by the direction and insisted on starting off on the carriageway closest to the + * start point and sending us on a huge detour, instead of taking the direct route on the opposite + * carriageway. + */ + m2::PointD startDirection = m2::PointD::Zero(); + + routing::Checkpoints checkpoints(std::move(points)); + + /* + * This code is mostly lifted from: + * - AsyncRouter::CalculateRoute(Checkpoints const &, m2::PointD const &, bool, ReadyCallbackOwnership const &, + * NeedMoreMapsCallback const &, RemoveRouteCallback const &, ProgressCallback, uint32_t) + * - AsyncRouter::CalculateRoute() + */ + + // AsyncRouter::CalculateRoute(with args) + /* + * AsyncRouter::CalculateRoute() has a `DelegateProxy`, which is private. We just need the return + * value of GetDelegate(), which is a `routing::RouterDelegate`, so use that instead. We don’t + * nedd any of the callbacks, therefore we don’t set them. + */ + routing::RouterDelegate delegate; + delegate.SetTimeout(kRouterTimeoutSec); + + // AsyncRouter::CalculateRoute() + if (!m_router && !InitRouter()) + return; + + routerName = m_router->GetName(); + // TODO is that for following a track? If so, can we use that with just 2–3 reference points? + //router->SetGuides(std::move(m_guides)); + //m_guides.clear(); + + auto route = std::make_shared(m_router->GetName(), routeId); + routing::RouterResultCode code; + + base::Timer timer; + double elapsedSec = 0.0; + + try + { + LOG(LINFO, ("Calculating the route of direct length", checkpoints.GetSummaryLengthBetweenPointsMeters(), + "m. checkpoints:", checkpoints, "startDirection:", startDirection, "router name:", m_router->GetName())); + + // Run basic request. + code = m_router->CalculateRoute(checkpoints, startDirection, adjustToPrevRoute, + delegate, *route); + m_router->SetGuides({}); + elapsedSec = timer.ElapsedSeconds(); // routing time + LogCode(code, elapsedSec); + LOG(LINFO, ("ETA:", route->GetTotalTimeSec(), "sec.")); + } + catch (RootException const & e) + { + code = routing::RouterResultCode::InternalError; + LOG(LERROR, ("Exception happened while calculating route:", e.Msg())); + return; + } + + if (code == routing::RouterResultCode::NoError) + { + LOG(LINFO, ("Decoded route:")); + for (auto rsegment : route->GetRouteSegments()) + { + routing::Segment segment = rsegment.GetSegment(); + if (segment.GetMwmId() == routing::kFakeNumMwmId) + { + LOG(LINFO, (" Fake segment:", segment)); + continue; + } + + LOG(LINFO, (" segment:", segment)); + + auto const countryFile = m_numMwmIds->GetFile(segment.GetMwmId()); + MwmSet::MwmId mwmId = m_dataSource.GetMwmIdByCountryFile(countryFile); + + auto const fid = segment.GetFeatureId(); + auto const sid = segment.GetSegmentIdx(); + uint8_t direction = segment.IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + + decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown; + } + } +} + +void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) +{ + ASSERT(message.m_location, ("Message has no location")); + decoded.clear(); + + int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1; + for (int dir = 0; dir < dirs; dir++) + DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */); +} } // namespace traffxml diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 5e46d1ecf..5c9686b1d 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -7,6 +7,14 @@ #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" +#include "routing/index_router.hpp" +#include "routing/regions_decl.hpp" +#include "routing/router.hpp" + +#include "routing_common/num_mwm_id.hpp" + +#include "storage/country_info_getter.hpp" + namespace traffxml { /** @@ -15,9 +23,10 @@ namespace traffxml class TraffDecoder { public: + using CountryInfoGetterFn = std::function; using CountryParentNameGetterFn = std::function; - TraffDecoder(DataSource & dataSource, + TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); @@ -53,6 +62,7 @@ class TraffDecoder void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded); DataSource & m_dataSource; + CountryInfoGetterFn m_countryInfoGetterFn; CountryParentNameGetterFn m_countryParentNameGetterFn; /** @@ -71,7 +81,7 @@ class TraffDecoder class OpenLrV3TraffDecoder : public TraffDecoder { public: - OpenLrV3TraffDecoder(DataSource & dataSource, + OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); @@ -151,6 +161,80 @@ class OpenLrV3TraffDecoder : public TraffDecoder openlr::OpenLRDecoder m_openLrDecoder; }; +/** + * @brief A `TraffDecoder` implementation which internally uses the routing engine. + */ +class RoutingTraffDecoder : public TraffDecoder +{ +public: + class DecoderRouter : public routing::IndexRouter + { + public: + /** + * @brief Creates a new `DecoderRouter` instance. + * + * @param countryParentNameGetterFn Function which converts a country name into the name of its parent country) + * @param countryFileFn Function which converts a pointer to its country name + * @param countryRectFn Function which returns the rect for a country + * @param numMwmIds + * @param numMwmTree + * @param trafficCache Tre traffic cache (used only if `vehicleType` is `VehicleType::Car`) + * @param dataSource The MWM data source + */ + DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn, + routing::TCountryFileFn const & countryFileFn, + routing::CountryRectFn const & countryRectFn, + std::shared_ptr numMwmIds, + std::unique_ptr> numMwmTree, + DataSource & dataSource); + protected: + private: + }; + + RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, + const CountryParentNameGetterFn & countryParentNameGetter, + std::map & messageCache); + +protected: + /** + * @brief Initializes the router. + * + * This is usually done in the constructor but fails if no maps are loaded (attempting to + * construct a router without maps results in a crash, hence we check for maps and exit with an + * error if we have none). It can be repeated any time. + * + * Attempting to initialize a router which has already been succesfully initialized is a no-op. It + * will be reported as success. + * + * @return true if successful, false if not. + */ + bool InitRouter(); + + /** + * @brief Decodes one direction of a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + * @param backwards If true, decode the backward direction, else the forward direction. + */ + void DecodeLocationDirection(traffxml::TraffMessage & message, + traffxml::MultiMwmColoring & decoded, bool backwards); + + /** + * @brief Decodes a TraFF location. + * + * @param message The message to decode. + * @param decoded Receives the decoded segments. The speed group will be `Unknown`. + */ + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + +private: + static void LogCode(routing::RouterResultCode code, double const elapsedSec); + + std::shared_ptr m_numMwmIds = std::make_shared(); + std::unique_ptr m_router; +}; + /** * @brief The default TraFF decoder implementation, recommended for production use. */ From f566f6f0efa2d430eda58d197c831b1e3ef2bed0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 26 May 2025 19:49:36 +0300 Subject: [PATCH 057/252] [traffxml] Use custom EdgeEstimator for decoding Signed-off-by: mvglasow --- routing/index_router.cpp | 7 ++--- routing/index_router.hpp | 15 +++++++--- traffxml/traff_decoder.cpp | 58 +++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/routing/index_router.cpp b/routing/index_router.cpp index 7f30fe0bd..1ece94437 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -280,7 +280,7 @@ IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes, CountryParentNameGetterFn const & countryParentNameGetterFn, TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn, shared_ptr numMwmIds, unique_ptr> numMwmTree, - DataSource & dataSource) + std::shared_ptr estimator, DataSource & dataSource) : m_vehicleType(vehicleType) , m_loadAltitudes(loadAltitudes) , m_name("astar-bidirectional-" + ToString(m_vehicleType)) @@ -296,10 +296,7 @@ IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes, ? IRoadGraph::Mode::IgnoreOnewayTag : IRoadGraph::Mode::ObeyOnewayTag, m_vehicleModelFactory) - , m_estimator(EdgeEstimator::Create( - m_vehicleType, CalcMaxSpeed(*m_numMwmIds, *m_vehicleModelFactory, m_vehicleType), - CalcOffroadSpeed(*m_vehicleModelFactory), m_trafficStash, - &dataSource, m_numMwmIds)) + , m_estimator(estimator) , m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) , m_countryParentNameGetterFn(countryParentNameGetterFn) { diff --git a/routing/index_router.hpp b/routing/index_router.hpp index 4d433584f..dae5a530b 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -110,9 +110,15 @@ class IndexRouter : public IRouter /** * @brief Creates a new `IndexRouter` instance. * - * This constructor is intended for use by the TraFF decoder, not for normal routing. It lacks the - * `TrafficCache` argument and never creates a traffic stash. This creates a router instance which - * ignores the traffic situation, regardless of the vehicle type. + * This constructor is intended for use by the TraFF decoder, not for normal routing. It differs + * from the general-purpose constructor in two ways. + * + * It takes an explicit `EdgeEstimator` argument, instance, which gives the caller fine-grained + * control over the cost calculations used for routing by supplying an `EdgeEstimator` of their + * choice. + * + * It also lacks the `TrafficCache` argument and never creates a traffic stash. This creates a + * router instance which ignores the traffic situation, regardless of the vehicle type. * * @param vehicleType The vehichle type * @param loadAltitudes Whether to load altitudes @@ -121,13 +127,14 @@ class IndexRouter : public IRouter * @param countryRectFn Function which returns the rect for a country * @param numMwmIds * @param numMwmTree + * @param estimator An edge estimator * @param dataSource The MWM data source */ IndexRouter(VehicleType vehicleType, bool loadAltitudes, CountryParentNameGetterFn const & countryParentNameGetterFn, TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn, std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, - DataSource & dataSource); + std::shared_ptr estimator, DataSource & dataSource); private: RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 4b8557217..fa8d1e601 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -8,6 +8,7 @@ #include "routing/async_router.hpp" #include "routing/checkpoints.hpp" +#include "routing/edge_estimator.hpp" #include "routing/maxspeeds.hpp" #include "routing/route.hpp" #include "routing/router_delegate.hpp" @@ -350,6 +351,53 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf } } +class TraffEstimator final : public routing::EdgeEstimator +{ +public: + TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr numMwmIds, + double maxWeightSpeedKMpH, + routing::SpeedKMpH const & offroadSpeedKMpH) + : EdgeEstimator(maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds) + { + } + + // EdgeEstimator overrides: + double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; + double GetUTurnPenalty(Purpose /* purpose */) const override + { + // Adds 2 minutes penalty for U-turn. The value is quite arbitrary + // and needs to be properly selected after a number of real-world + // experiments. + return 2 * 60; // seconds + } + + double GetFerryLandingPenalty(Purpose purpose) const override + { + switch (purpose) + { + case Purpose::Weight: return 20 * 60; // seconds + case Purpose::ETA: return 20 * 60; // seconds + } + UNREACHABLE(); + } +}; + +// TODO only needed temporarily +double GetSpeedMpS(routing::EdgeEstimator::Purpose purpose, routing::Segment const & segment, routing::RoadGeometry const & road) +{ + routing::SpeedKMpH const & speed = road.GetSpeed(segment.IsForward()); + double const speedMpS = measurement_utils::KmphToMps(purpose == routing::EdgeEstimator::Purpose::Weight ? speed.m_weight : speed.m_eta); + ASSERT_GREATER(speedMpS, 0.0, (segment)); + return speedMpS; +} + +double TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const +{ + double result = road.GetDistance(segment.GetSegmentIdx()) / GetSpeedMpS(purpose, segment, road); + + return result; +} + RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn, routing::TCountryFileFn const & countryFileFn, routing::CountryRectFn const & countryRectFn, @@ -361,16 +409,12 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons countryParentNameGetterFn, countryFileFn, countryRectFn, - std::move(numMwmIds), + numMwmIds, std::move(numMwmTree), //std::nullopt /* std::optional const & trafficCache */, + std::make_shared(&dataSource, numMwmIds, 120.0f /* maxWeighSpeedKMpH */, + routing::SpeedKMpH(0.01 /* weight */, routing::kNotUsed /* eta */) /* offroadSpeedKMpH */), dataSource) - /* TODO build our own edge estimator for TraFF decoding purposes - , m_estimator(EdgeEstimator::Create( - VehicleType::Car, CalcMaxSpeed(*m_numMwmIds, *m_vehicleModelFactory, m_vehicleType), - CalcOffroadSpeed(*m_vehicleModelFactory), m_trafficStash, - &dataSource, m_numMwmIds)) - */ //, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that? {} From 4c5fb21c33c2d92afe15c23d03aa345f9eeac8ba Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 26 May 2025 21:58:14 +0300 Subject: [PATCH 058/252] [traffic] Use distances, not travel time, for weight in TraffEstimator Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index fa8d1e601..220184ce6 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -34,6 +34,23 @@ auto constexpr kNumDecoderThreads = 1; // TODO set to a sensible value auto constexpr kRouterTimeoutSec = 30; +/* + * One meter per second. The TraffEstimator works on distance in meters, not travel time. For code + * which works with speeds and assumes cost to be time-based, a speed of 1 m/s means such + * calculations will effectively return distances in meters. + */ +auto constexpr kOneMpSInKmpH = 3.6; + +/* + * Penalty factor for using a fake segment to get to a nearby road. + */ +auto constexpr kOffroadPenalty = 8; + +/* + * Penalty factor for non-matching attributes + */ +auto constexpr kAttributePenalty = 4; + TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) @@ -382,18 +399,11 @@ class TraffEstimator final : public routing::EdgeEstimator } }; -// TODO only needed temporarily -double GetSpeedMpS(routing::EdgeEstimator::Purpose purpose, routing::Segment const & segment, routing::RoadGeometry const & road) -{ - routing::SpeedKMpH const & speed = road.GetSpeed(segment.IsForward()); - double const speedMpS = measurement_utils::KmphToMps(purpose == routing::EdgeEstimator::Purpose::Weight ? speed.m_weight : speed.m_eta); - ASSERT_GREATER(speedMpS, 0.0, (segment)); - return speedMpS; -} - double TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const { - double result = road.GetDistance(segment.GetSegmentIdx()) / GetSpeedMpS(purpose, segment, road); + double result = road.GetDistance(segment.GetSegmentIdx()); + + // TODO evaluate attributes and penalize accordingly return result; } @@ -412,8 +422,9 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons numMwmIds, std::move(numMwmTree), //std::nullopt /* std::optional const & trafficCache */, - std::make_shared(&dataSource, numMwmIds, 120.0f /* maxWeighSpeedKMpH */, - routing::SpeedKMpH(0.01 /* weight */, routing::kNotUsed /* eta */) /* offroadSpeedKMpH */), + std::make_shared(&dataSource, numMwmIds, kOneMpSInKmpH /* maxWeighSpeedKMpH */, + routing::SpeedKMpH(kOneMpSInKmpH / kOffroadPenalty /* weight */, + routing::kNotUsed /* eta */) /* offroadSpeedKMpH */), dataSource) //, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that? {} From c6de2a25aaaf1260d6c2784cc524f2acb69bb53e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 26 May 2025 21:59:25 +0300 Subject: [PATCH 059/252] [traffic] Documentation Signed-off-by: mvglasow --- map/traffic_manager.hpp | 2 + routing/edge_estimator.hpp | 186 +++++++++++++++++++++++++++++++++++-- traffxml/traff_decoder.cpp | 5 +- 3 files changed, 185 insertions(+), 8 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 7625fc2b9..dfea2b0b1 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -260,6 +260,8 @@ class TrafficManager final /** * @brief Processes a traffic feed received through a push operation. * + * Push is safe to call from any thread. + * * Push operations are not supported on all platforms. * * @param feed The traffic feed. diff --git a/routing/edge_estimator.hpp b/routing/edge_estimator.hpp index 5edf81df8..1a9fc83b5 100644 --- a/routing/edge_estimator.hpp +++ b/routing/edge_estimator.hpp @@ -21,41 +21,156 @@ class TrafficStash; class EdgeEstimator { public: + /** + * @brief The purpose for which cost calculations are to be used. + * + * A number of cost estimation functions take `Purpose` as an argument and may return different + * values depending on the value of that argument. + */ enum class Purpose { + /** + * @brief Indicates that cost calculations are for the purpose of choosing the best route. + */ Weight, + /** + * @brief Indicates that cost calculations are for the purpose of calculating the estimated time + * of arrival. + */ ETA }; + /** + * @brief Constructs a new `EdgeEstimator`. + * + * @param maxWeightSpeedKMpH The maximum speed for the vehicle on a road. + * @param offroadSpeedKMpH The maximum speed for the vehicle on an off-road link. + * @param dataSourcePtr + * @param numMwmIds + */ EdgeEstimator(double maxWeightSpeedKMpH, SpeedKMpH const & offroadSpeedKMpH, DataSource * dataSourcePtr = nullptr, std::shared_ptr numMwmIds = nullptr); virtual ~EdgeEstimator() = default; + /** + * @brief Calculates the heuristic for two points. + * + * The heuristic is used by the A* routing algorithm when choosing the next point to examine. It + * must be less than, or equal to, the lowest possible cost of traveling from one point to the + * other. Zero is an admissible heuristic, but effectively downgrades the A* algorithm to behave + * exactly like the Dijkstra algorithm, of which A* is an improved version. A good heuristic is as + * close as possible to the actual cost, without violating the aforementioned requirement. + * + * @param from The start point for the part of the route for which the heuristic is to be calculated. + * @param to The destination point for the part of the route for which the heuristic is to be calculated. + * @return The heuristic, expressed as travel time in seconds. + */ double CalcHeuristic(ms::LatLon const & from, ms::LatLon const & to) const; - // Estimates time in seconds it takes to go from point |from| to point |to| along a leap (fake) - // edge |from|-|to| using real features. - // Note 1. The result of the method should be used if it's necessary to add a leap (fake) edge - // (|from|, |to|) in road graph. - // Note 2. The result of the method should be less or equal to CalcHeuristic(|from|, |to|). - // Note 3. It's assumed here that CalcLeapWeight(p1, p2) == CalcLeapWeight(p2, p1). + + /** + * @brief Estimates travel time between two points along a leap (fake) edge using real features. + * + * Estimates time in seconds it takes to go from point `from` to point `to` along a leap (fake) + * edge `from`-`to` using real features. + * + * Note 1. The result of the method should be used if it is necessary to add a leap (fake) edge + * (`from`, `to`) in road graph. + * + * Note 2. The result of the method should be less or equal to `CalcHeuristic(from, to)`. + * + * Note 3. It is assumed here that `CalcLeapWeight(p1, p2) == CalcLeapWeight(p2, p1)`. + * + * @todo Note 2 looks like a typo, presumably the result of this method should be no less than the + * heuristic (otherwise the heuristic might not satisfy the requirements of A*). + * + * @param from The start point. + * @param to The destination point. + * @param mwmId + * @return Travel time in seconds. + */ double CalcLeapWeight(ms::LatLon const & from, ms::LatLon const & to, NumMwmId mwmId = kFakeNumMwmId); + /** + * @brief Returns the maximum speed this `EdgeEstimator` instance assumes for any road. + * @return The speed in m/s. + */ double GetMaxWeightSpeedMpS() const; - // Estimates time in seconds it takes to go from point |from| to point |to| along direct fake edge. + /** + * @brief Estimates travel time between two points along a direct fake edge. + * + * Estimates time in seconds it takes to go from point `from` to point `to` along direct fake edge. + * + * @param from The start point. + * @param to The destination point. + * @param purpose The purpose for which the result is to be used. + * @return Travel time in seconds. + */ double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const; + /** + * @brief Returns the travel time along a segment. + * + * @param segment The segment. + * @param road The road geometry (speed, restrictions, points) for the road which the segment is a part of. + * @param purpose The purpose for which the result is to be used. + * @return Travel time in seconds. + */ virtual double CalcSegmentWeight(Segment const & segment, RoadGeometry const & road, Purpose purpose) const = 0; + + /** + * @brief Returns the penalty for making a U turn. + * + * The penalty is a fixed amount of time, determined by the implementation. + * + * @param purpose The purpose for which the result is to be used. + * @return The penalty in seconds. + */ virtual double GetUTurnPenalty(Purpose purpose) const = 0; + + /** + * @brief Returns the penalty for using a ferry or rail transit link. + * + * The penalty is a fixed amount of time, determined by the implementation. It applies once per + * link, hence it needs to cover the sum of the time for boarding and unboarding. + * + * @param purpose The purpose for which the result is to be used. + * @return The penalty in seconds. + */ virtual double GetFerryLandingPenalty(Purpose purpose) const = 0; + /** + * @brief Creates an `EdgeEstimator` based on maximum speeds. + * + * @param vehicleType The vehicle type. + * @param maxWeighSpeedKMpH The maximum speed for the vehicle on a road. + * @param offroadSpeedKMpH The maximum speed for the vehicle on an off-road link. + * @param trafficStash The traffic stash (used only for some vehicle types). + * @param dataSourcePtr + * @param numMwmIds + * @return The `EdgeEstimator` instance. + */ static std::shared_ptr Create(VehicleType vehicleType, double maxWeighSpeedKMpH, SpeedKMpH const & offroadSpeedKMpH, std::shared_ptr trafficStash, DataSource * dataSourcePtr, std::shared_ptr numMwmIds); + /** + * @brief Creates an `EdgeEstimator` based on a vehicle model. + * + * This is a convenience wrapper around `Create(VehicleType, double, SpeedKMpH const &, + * std::shared_ptr, DataSource *, std::shared_ptr)`, which takes a + * `VehicleModel` and derives the maximum speeds for the vehicle from that. + * + * @param vehicleType The vehicle type. + * @param vehicleModel + * @param trafficStash The traffic stash (used only for some vehicle types). + * @param dataSourcePtr + * @param numMwmIds + * @return The `EdgeEstimator` instance. + */ static std::shared_ptr Create(VehicleType vehicleType, VehicleModelInterface const & vehicleModel, std::shared_ptr trafficStash, @@ -70,15 +185,72 @@ class EdgeEstimator //std::shared_ptr m_numMwmIds; //std::unordered_map m_leapWeightSpeedMpS; + /** + * @brief Computes the default speed for leap (fake) segments. + * + * The result is used by `GetLeapWeightSpeed()`. + * + * @return Speed in m/s. + */ double ComputeDefaultLeapWeightSpeed() const; + + /** + * @brief Returns the deafult speed for leap (fake) segments for a given MWM. + * @param mwmId + * @return Speed in m/s. + */ double GetLeapWeightSpeed(NumMwmId mwmId); //double LoadLeapWeightSpeed(NumMwmId mwmId); }; +/** + * @brief Calculates the climb penalty for pedestrians. + * + * The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill + * or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed. + * + * The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the + * altitude (allowing for different penalties at greater altitudes). + * + * @param purpose The purpose for which the result is to be used. + * @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent). + * @param altitudeM The altitude in meters. + * @return The climb penalty, as a factor. + */ double GetPedestrianClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM); + +/** + * @brief Calculates the climb penalty for cyclists. + * + * The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill + * or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed. + * + * The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the + * altitude (allowing for different penalties at greater altitudes). + * + * @param purpose The purpose for which the result is to be used. + * @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent). + * @param altitudeM The altitude in meters. + * @return The climb penalty, as a factor. + */ double GetBicycleClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM); + +/** + * @brief Calculates the climb penalty for cars. + * + * The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill + * or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed. + * + * The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the + * altitude (allowing for different penalties at greater altitudes). + * + * @param purpose The purpose for which the result is to be used. + * @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent). + * @param altitudeM The altitude in meters. + * @return The climb penalty, as a factor. + */ double GetCarClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM); diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 220184ce6..df9284885 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -596,7 +596,10 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa return; routerName = m_router->GetName(); - // TODO is that for following a track? If so, can we use that with just 2–3 reference points? + /* + * TODO is that for following a track? If so, can we use that with just 2–3 reference points? + * – Doesn’t look like it, m_guides only seems to get used in test functions + */ //router->SetGuides(std::move(m_guides)); //m_guides.clear(); From 083845a50268a51b4470a864fb59fada61c2c9f1 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 27 May 2025 21:09:35 +0300 Subject: [PATCH 060/252] [traffxml] Fix location matching on dual carriageway roads Signed-off-by: mvglasow --- routing/index_router.cpp | 2 +- routing/index_router.hpp | 18 ++++++++++++++++++ traffxml/traff_decoder.hpp | 25 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/routing/index_router.cpp b/routing/index_router.cpp index 1ece94437..bb538216f 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -1406,7 +1406,7 @@ bool IndexRouter::PointsOnEdgesSnapping::FindBestEdges( } // Removing all candidates which are fenced off by the road graph (|closestRoads|) from |checkpoint|. - return !IsFencedOff(checkpoint, edgeProj, closestRoads); + return !m_router.IsFakeEndingSetSimplified() || !IsFencedOff(checkpoint, edgeProj, closestRoads); }; // Getting closest edges from |closestRoads| if they are correct according to isGood() function. diff --git a/routing/index_router.hpp b/routing/index_router.hpp index dae5a530b..9e6845b3e 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -136,6 +136,24 @@ class IndexRouter : public IRouter std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, std::shared_ptr estimator, DataSource & dataSource); + + /** + * @brief Whether the set of fake endings generated for the check points is restricted. + * + * The return value is used internally when snapping checkpoints to edges. If this function + * returns true, this instructs the `PointsOnEdgesSnapping` instance to consider only edges which + * are not fenced off, i.e. can be reached from the respective checkpoint without crossing any + * other edges. If it returns false, this restriction does not apply, and all nearby edges are + * considered. + * + * Restricting the set of fake endings in this manner decreases the options considered for routing + * and thus processing time, which is desirable for regular routing and has no side effects. + * + * The `IndexRouter` implementation always returns true; subclasses may override this method and + * return different values. + */ + virtual bool IsFakeEndingSetSimplified() { return true; } + private: RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, RouterDelegate const & delegate, diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 5c9686b1d..68e95cf48 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -188,6 +188,31 @@ class RoutingTraffDecoder : public TraffDecoder std::unique_ptr> numMwmTree, DataSource & dataSource); protected: + /** + * @brief Whether the set of fake endings generated for the check points is restricted. + * + * The return value is used internally when snapping checkpoints to edges. If this function + * returns true, this instructs the `PointsOnEdgesSnapping` instance to consider only edges which + * are not fenced off, i.e. can be reached from the respective checkpoint without crossing any + * other edges. If it returns false, this restriction does not apply, and all nearby edges are + * considered. + * + * Restricting the set of fake endings in this manner decreases the options considered for routing + * and thus processing time, which is desirable for regular routing and has no side effects. + * For TraFF location matching, simplification has undesirable side effects: if reference points + * are located on one side of the road, the other carriageway may not be considered. This would + * lead to situations like these: + * + * --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<-- + * -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->-- + * *< <* + * + * (-- carriageway, + junction, < > direction, *< end point, <* start point, == route) + * + * To avoid this, the `DecoderRouter` implementation always returns false. + */ + bool IsFakeEndingSetSimplified() override { return false; } + private: }; From a4106505afe5b5b8447bd5cde5085bfca81698b9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 28 May 2025 19:48:40 +0300 Subject: [PATCH 061/252] [traffxml] Code cleanup Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index df9284885..b6b009673 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -542,7 +542,6 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { bool adjustToPrevRoute = false; // calculate a fresh route, no adjustments to previous one uint64_t routeId = 0; // used in callbacks to identify the route, we might not need it at all - std::string routerName; // set later std::vector points; if (message.m_location.value().m_from) @@ -576,26 +575,20 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa routing::Checkpoints checkpoints(std::move(points)); /* - * This code is mostly lifted from: - * - AsyncRouter::CalculateRoute(Checkpoints const &, m2::PointD const &, bool, ReadyCallbackOwnership const &, - * NeedMoreMapsCallback const &, RemoveRouteCallback const &, ProgressCallback, uint32_t) - * - AsyncRouter::CalculateRoute() + * This code is mostly lifted from AsyncRouter::CalculateRoute(), both with and without arguments. */ - // AsyncRouter::CalculateRoute(with args) /* * AsyncRouter::CalculateRoute() has a `DelegateProxy`, which is private. We just need the return * value of GetDelegate(), which is a `routing::RouterDelegate`, so use that instead. We don’t - * nedd any of the callbacks, therefore we don’t set them. + * need any of the callbacks, therefore we don’t set them. */ routing::RouterDelegate delegate; delegate.SetTimeout(kRouterTimeoutSec); - // AsyncRouter::CalculateRoute() if (!m_router && !InitRouter()) return; - routerName = m_router->GetName(); /* * TODO is that for following a track? If so, can we use that with just 2–3 reference points? * – Doesn’t look like it, m_guides only seems to get used in test functions From 7a5ea64ea0c9e3cb4f7ced8628d839197baa645e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 31 May 2025 00:33:53 +0300 Subject: [PATCH 062/252] [traffxml] Score candidates based on road attributes Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 196 +++++++++++++++++++++++++++++------ traffxml/traff_decoder.hpp | 49 +++++++-- traffxml/traff_model.cpp | 2 +- traffxml/traff_model.hpp | 2 +- traffxml/traff_model_xml.cpp | 2 +- 5 files changed, 208 insertions(+), 43 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index b6b009673..adccb85a2 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -43,14 +43,24 @@ auto constexpr kOneMpSInKmpH = 3.6; /* * Penalty factor for using a fake segment to get to a nearby road. + * Maximum penalty for roads is currently 16 (4 for ramps * 4 for road type), offroad penalty is + * twice the maximum road penalty. We might need to increase that, since offroad penalty applies to + * direct distance whereas road penalty applies to roads, which can be up to around 3 times the + * direct distance (theoretically unlimited). That would imply multiplying maximum road penalty by + * more than 3 (e.g. 4). */ -auto constexpr kOffroadPenalty = 8; +auto constexpr kOffroadPenalty = 32; /* * Penalty factor for non-matching attributes */ auto constexpr kAttributePenalty = 4; +/* + * Penalty factor for partially matching attributes + */ +auto constexpr kReducedAttributePenalty = 2; + TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) @@ -268,7 +278,7 @@ openlr::LinearLocationReference OpenLrV3TraffDecoder::TraffLocationToLinearLocat { openlr::LocationReferencePoint lrp = PointToLrp(points[i]); lrp.m_functionalRoadClass = GetRoadClassFrc(location.m_roadClass); - if (location.m_ramps.value_or(traffxml::Ramps::None) != traffxml::Ramps::None) + if (location.m_ramps != traffxml::Ramps::None) lrp.m_formOfWay = openlr::FormOfWay::Sliproad; if (i < points.size() - 1) { @@ -368,42 +378,51 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf } } -class TraffEstimator final : public routing::EdgeEstimator +double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { -public: - TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr numMwmIds, - double maxWeightSpeedKMpH, - routing::SpeedKMpH const & offroadSpeedKMpH) - : EdgeEstimator(maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds) - { - } - - // EdgeEstimator overrides: - double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; - double GetUTurnPenalty(Purpose /* purpose */) const override - { - // Adds 2 minutes penalty for U-turn. The value is quite arbitrary - // and needs to be properly selected after a number of real-world - // experiments. - return 2 * 60; // seconds - } + // Adds 2 minutes penalty for U-turn. The value is quite arbitrary + // and needs to be properly selected after a number of real-world + // experiments. + return 2 * 60; // seconds +} - double GetFerryLandingPenalty(Purpose purpose) const override +double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose purpose) const +{ + switch (purpose) { - switch (purpose) - { - case Purpose::Weight: return 20 * 60; // seconds - case Purpose::ETA: return 20 * 60; // seconds - } - UNREACHABLE(); + case Purpose::Weight: return 20 * 60; // seconds + case Purpose::ETA: return 20 * 60; // seconds } -}; + UNREACHABLE(); +} -double TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const +double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const { double result = road.GetDistance(segment.GetSegmentIdx()); - // TODO evaluate attributes and penalize accordingly + if (!m_decoder.m_message || !m_decoder.m_message.value().m_location.value().m_roadClass) + return result; + + std::optional highwayType = road.GetHighwayType(); + + if (highwayType) + { + if (IsRamp(highwayType.value()) != (m_decoder.m_message.value().m_location.value().m_ramps != Ramps::None)) + // if one is a ramp and the other is not, treat it as a mismatch + result *= kAttributePenalty; + if (m_decoder.m_message.value().m_location.value().m_roadClass) + // if the message specifies a road class, penalize mismatches + result *= GetRoadClassPenalty(m_decoder.m_message.value().m_location.value().m_roadClass.value(), + GetRoadClass(highwayType.value())); + } + else // road has no highway class + { + // we can’t determine if it is a ramp, penalize for mismatch + result *= kAttributePenalty; + if (m_decoder.m_message.value().m_location.value().m_roadClass) + // we can’t determine if the road matches the required road class, treat it as mismatch + result *= kAttributePenalty; + } return result; } @@ -413,7 +432,7 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons routing::CountryRectFn const & countryRectFn, std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, - DataSource & dataSource) + DataSource & dataSource, RoutingTraffDecoder & decoder) : routing::IndexRouter(routing::VehicleType::Car /* VehicleType vehicleType */, false /* bool loadAltitudes */, countryParentNameGetterFn, @@ -424,7 +443,8 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons //std::nullopt /* std::optional const & trafficCache */, std::make_shared(&dataSource, numMwmIds, kOneMpSInKmpH /* maxWeighSpeedKMpH */, routing::SpeedKMpH(kOneMpSInKmpH / kOffroadPenalty /* weight */, - routing::kNotUsed /* eta */) /* offroadSpeedKMpH */), + routing::kNotUsed /* eta */) /* offroadSpeedKMpH */, + decoder), dataSource) //, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that? {} @@ -450,8 +470,17 @@ bool RoutingTraffDecoder::InitRouter() */ std::vector> mwmsInfo; m_dataSource.GetMwmsInfo(mwmsInfo); + /* TODO this should include all countries (whether we have the MWM or not), except World and WorldCoasts. + * Excluding World and WorldCoasts is important, else the router will return bogus routes. + * Storage uses a string comparison for filtering, we do the same here. + storage.ForEachCountry([&](storage::Country const & country) + { + numMwmIds->RegisterFile(country.GetFile()); + }); + */ for (auto mwmInfo : mwmsInfo) - m_numMwmIds->RegisterFile(mwmInfo->GetLocalFile().GetCountryFile()); + if (!mwmInfo->GetCountryName().starts_with(WORLD_FILE_NAME)) + m_numMwmIds->RegisterFile(mwmInfo->GetLocalFile().GetCountryFile()); if (m_numMwmIds->IsEmpty()) return false; @@ -470,7 +499,7 @@ bool RoutingTraffDecoder::InitRouter() make_unique(m_countryParentNameGetterFn, countryFileGetter, getMwmRectByName, m_numMwmIds, routing::MakeNumMwmTree(*m_numMwmIds, m_countryInfoGetterFn()), - m_dataSource); + m_dataSource, *this); return true; } @@ -655,8 +684,107 @@ void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traff ASSERT(message.m_location, ("Message has no location")); decoded.clear(); + m_message = message; + int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1; for (int dir = 0; dir < dirs; dir++) DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */); + + m_message = std::nullopt; +} + +traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType) +{ + switch(highwayType) + { + /* + * Parallel carriageways may be tagged as link, hence consider them equivalent to the underlying + * highway type. + */ + case routing::HighwayType::HighwayMotorway: + case routing::HighwayType::HighwayMotorwayLink: + return traffxml::RoadClass::Motorway; + case routing::HighwayType::HighwayTrunk: + case routing::HighwayType::HighwayTrunkLink: + return traffxml::RoadClass::Trunk; + case routing::HighwayType::HighwayPrimary: + case routing::HighwayType::HighwayPrimaryLink: + return traffxml::RoadClass::Primary; + case routing::HighwayType::HighwaySecondary: + case routing::HighwayType::HighwaySecondaryLink: + return traffxml::RoadClass::Secondary; + case routing::HighwayType::HighwayTertiary: + case routing::HighwayType::HighwayTertiaryLink: + return traffxml::RoadClass::Tertiary; + default: + return traffxml::RoadClass::Other; + } +} + +/** + * @brief Returns the penalty factor for road class match/mismatch. + * + * If `lhs` and `rhs` are identical, the penalty factor is 1 (no penalty). If they are adjacent road + * classes (e.g. trunk and primary), the penalty factor is `kReducedAttributePenalty`, in all other + * cases it is `kAttributePenalty`. + * + * @param lhs + * @param rhs + * @return The penalty factor, see above. + */ +double GetRoadClassPenalty(traffxml::RoadClass lhs, traffxml::RoadClass rhs) +{ + if (lhs == rhs) + return 1; + switch (lhs) + { + case traffxml::RoadClass::Motorway: + if (rhs == traffxml::RoadClass::Trunk) + return kReducedAttributePenalty; + else + return kAttributePenalty; + case traffxml::RoadClass::Trunk: + if (rhs == traffxml::RoadClass::Motorway || rhs == traffxml::RoadClass::Primary) + return kReducedAttributePenalty; + else + return kAttributePenalty; + case traffxml::RoadClass::Primary: + if (rhs == traffxml::RoadClass::Trunk || rhs == traffxml::RoadClass::Secondary) + return kReducedAttributePenalty; + else + return kAttributePenalty; + case traffxml::RoadClass::Secondary: + if (rhs == traffxml::RoadClass::Primary || rhs == traffxml::RoadClass::Tertiary) + return kReducedAttributePenalty; + else + return kAttributePenalty; + case traffxml::RoadClass::Tertiary: + if (rhs == traffxml::RoadClass::Secondary || rhs == traffxml::RoadClass::Other) + return kReducedAttributePenalty; + else + return kAttributePenalty; + case traffxml::RoadClass::Other: + if (rhs == traffxml::RoadClass::Tertiary) + return kReducedAttributePenalty; + else + return kAttributePenalty; + default: + UNREACHABLE(); + } +} + +bool IsRamp(routing::HighwayType highwayType) +{ + switch(highwayType) + { + case routing::HighwayType::HighwayMotorwayLink: + case routing::HighwayType::HighwayTrunkLink: + case routing::HighwayType::HighwayPrimaryLink: + case routing::HighwayType::HighwaySecondaryLink: + case routing::HighwayType::HighwayTertiaryLink: + return true; + default: + return false; + } } } // namespace traffxml diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 68e95cf48..bcdd67061 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -15,10 +15,17 @@ #include "storage/country_info_getter.hpp" +#include + namespace traffxml { /** * @brief Abstract base class for all TraFF decoder implementations. + * + * At this point, `TraffDecoder` is single-threaded and not guaranteed to be thread-safe. This means + * that all `TraffDecoder` operations should be limited to one thread or use appropriate thread + * synchronization mechanisms. In particular, calling `DecodeMessage()` concurrently from multiple + * threads is not supported. */ class TraffDecoder { @@ -33,10 +40,12 @@ class TraffDecoder /** * @brief Decodes a single message to its segments and their speed groups. * - * This method may access the message cache which was passed to the constructor. Access to the - * message cache is not thread-safe. Unless it is guaranteed that any operations on the message - * cache will happen on the same thread, calls to this method need to be synchronized with other - * operations on the message cache. + * This method is not guaranteed to be thread-safe. All calls to this method should either be + * strictly limited to one designated thread, or be synchronized using an appropriate mechanism. + * + * In addition to the above, this method may access the message cache which was passed to the + * constructor. This is not thread-safe and needs to be synchronized, unless all other operations + * on the message cache are guaranteed to happen on the same thread that called this method. * * @param message The message to decode. */ @@ -180,13 +189,14 @@ class RoutingTraffDecoder : public TraffDecoder * @param numMwmTree * @param trafficCache Tre traffic cache (used only if `vehicleType` is `VehicleType::Car`) * @param dataSource The MWM data source + * @param decoder The `TraffDecoder` instance to which this router instance is coupled */ DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn, routing::TCountryFileFn const & countryFileFn, routing::CountryRectFn const & countryRectFn, std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, - DataSource & dataSource); + DataSource & dataSource, RoutingTraffDecoder & decoder); protected: /** * @brief Whether the set of fake endings generated for the check points is restricted. @@ -216,6 +226,27 @@ class RoutingTraffDecoder : public TraffDecoder private: }; + class TraffEstimator final : public routing::EdgeEstimator + { + public: + TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr numMwmIds, + double maxWeightSpeedKMpH, + routing::SpeedKMpH const & offroadSpeedKMpH, + RoutingTraffDecoder & decoder) + : EdgeEstimator(maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds) + , m_decoder(decoder) + { + } + + // EdgeEstimator overrides: + double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; + double GetUTurnPenalty(Purpose /* purpose */) const override; + double GetFerryLandingPenalty(Purpose purpose) const override; + + private: + RoutingTraffDecoder & m_decoder; + }; + RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); @@ -258,10 +289,16 @@ class RoutingTraffDecoder : public TraffDecoder std::shared_ptr m_numMwmIds = std::make_shared(); std::unique_ptr m_router; + std::optional m_message = std::nullopt; }; /** * @brief The default TraFF decoder implementation, recommended for production use. */ -using DefaultTraffDecoder = OpenLrV3TraffDecoder; +//using DefaultTraffDecoder = OpenLrV3TraffDecoder; +using DefaultTraffDecoder = RoutingTraffDecoder; + +traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType); +double GetRoadClassPenalty(traffxml::RoadClass lhs, traffxml::RoadClass rhs); +bool IsRamp(routing::HighwayType highwayType); } // namespace traffxml diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 05a424e9e..278208a2b 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -445,7 +445,7 @@ std::string DebugPrint(TraffLocation location) os << "destination: " << location.m_destination.value_or("nullopt") << ", "; os << "direction: " << location.m_direction.value_or("nullopt") << ", "; os << "directionality: " << DebugPrint(location.m_directionality) << ", "; - os << "ramps: " << (location.m_ramps ? DebugPrint(location.m_ramps.value()) : "nullopt"); + os << "ramps: " << DebugPrint(location.m_ramps); os << " }"; return os.str(); } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 2bec230b8..43e84e30b 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -291,7 +291,7 @@ struct TraffLocation Directionality m_directionality = Directionality::BothDirections; // TODO std::optional m_fuzziness; std::optional m_origin; - std::optional m_ramps; + Ramps m_ramps = Ramps::None; std::optional m_roadClass; // disabled for now, optional behaves weird and we don't really need it //std::optional m_roadIsUrban; diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 5a3606971..dcc719c9a 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -468,7 +468,7 @@ bool LocationFromXml(pugi::xml_node node, TraffLocation & location) // TODO fuzziness (not yet implemented in struct) location.m_origin = OptionalStringFromXml(node.attribute("origin")); - location.m_ramps = OptionalEnumFromXml(node.attribute("ramps"), kRampsMap); + EnumFromXml(node.attribute("ramps"), location.m_ramps, kRampsMap); location.m_roadClass = OptionalEnumFromXml(node.attribute("road_class"), kRoadClassMap); // disabled for now //location.m_roadIsUrban = OptionalBoolFromXml(node.attribute(("road_is_urban"))); From 185febd8d8cdd598785d8fd7fa8e23846922d5c0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 31 May 2025 21:21:31 +0300 Subject: [PATCH 063/252] [traffic] Documentation and comments Signed-off-by: mvglasow --- map/traffic_manager.cpp | 7 ------- map/traffic_manager.hpp | 43 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 6d1dde719..f6e41767e 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -449,13 +449,6 @@ void TrafficManager::DecodeFirstMessage() m_feedQueue.erase(m_feedQueue.begin()); } -/* - * TODO this breaks things in the current test setup. - * When TrafficManager starts up and processes the first feed, maps are not loaded yet and messages - * cannot be decoded, so they are added to the cache without segments. - * The next feed (being a static file) returns the same data, so this check causes the message to - * get ignored as it has not changed. - */ // check if message is actually newer auto it = m_messageCache.find(message.m_id); bool process = (it == m_messageCache.end()); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index dfea2b0b1..c8f8967bf 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -106,6 +106,7 @@ class TrafficManager final * @param enabled True to enable, false to disable */ void SetEnabled(bool enabled); + /** * @brief Whether the traffic manager is enabled. * @@ -124,7 +125,7 @@ class TrafficManager final * * @todo Currently, all MWMs must be loaded before calling `Start()`, as MWMs loaded after that * will not get picked up. We need to extend `TrafficManager` to react to MWMs being added (and - * removed) – note that this affects the data source, not the set of active MWMs. + * removed) – note that this affects the `DataSource`, not the set of active MWMs. * * @todo Start is currently not integrated with state or pause/resume logic (all of which might * not be fully implemented ATM). If the traffic manager is not started, no message processing @@ -136,6 +137,16 @@ class TrafficManager final void UpdateViewport(ScreenBase const & screen); void UpdateMyPosition(MyPosition const & myPosition); + /** + * @brief Invalidates traffic information. + * + * Invalidating causes traffic data to be re-requested. + * + * This happens when a new MWM file is downloaded, the traffic manager is enabled after being + * disabled or resumed after being paused. + * + * @todo this goes for the old MWM arch, see if this makes sense for TraFF. + */ void Invalidate(); void OnDestroySurface(); @@ -388,7 +399,37 @@ class TrafficManager final */ void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force); + /** + * @brief Clears the entire traffic cache. + * + * This is currently called when the traffic manager is enabled or disabled. + * + * The old MWM traffic architecture was somewhat liberal in clearing its cache and re-fetching + * traffic data. This was possible because data was pre-processed and required no processing + * beyond deserialization, whereas TraFF data is more expensive to recreate. Also, the old + * architecture lacked any explicit notion of expiration; the app decided that data was to be + * considered stale after a certain period of time. TraFF, in contrast, has an explicit expiration + * time for each message, which can be anywhere from a few minutes to several weeks or months. + * Messages that have expired get deleted individually. + * For this reason, the TraFF message cache should not be cleared out under normal conditions + * (the main exception being tests). + * + * @todo Currently not implemented for TraFF; implement it for test purposes but do not call when + * the enabled state changes. + */ void Clear(); + + /** + * @brief Removes traffic data for one specific MWM from the cache. + * + * This would be used when an MWM file gets deregistered and its traffic data is no longer needed. + * With the old MWM traffic architecture (pre-processed sets of segments), this method was also + * used to shrink the cache to stay below a certain size (no longer possible with TraFF, due to + * the data structures being more complex, and also due to re-fetching data being expensive in + * terms of computing time). + * + * @param mwmId The mwmId for which to remove traffic data. + */ void ClearCache(MwmSet::MwmId const & mwmId); // TODO no longer needed #ifdef traffic_dead_code From fa5608d874ef238a47e9341122af06538156c626 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 1 Jun 2025 00:13:37 +0300 Subject: [PATCH 064/252] [traffxml] Decode point locations (at) Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 83 +++++++++++++++++++++++++++++--------- traffxml/traff_decoder.hpp | 8 ++++ 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index adccb85a2..25eb3513b 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -2,6 +2,8 @@ //#include "traffxml/traff_foo.hpp" +#include "geometry/distance_on_sphere.hpp" + #include "openlr/decoded_path.hpp" #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" @@ -566,6 +568,20 @@ void RoutingTraffDecoder::LogCode(routing::RouterResultCode code, double const e } } +void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded, routing::Segment & segment) +{ + auto const countryFile = m_numMwmIds->GetFile(segment.GetMwmId()); + MwmSet::MwmId mwmId = m_dataSource.GetMwmIdByCountryFile(countryFile); + + auto const fid = segment.GetFeatureId(); + auto const sid = segment.GetSegmentIdx(); + uint8_t direction = segment.IsForward() ? + traffic::TrafficInfo::RoadSegmentId::kForwardDirection : + traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + + decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown; +} + void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded, bool backwards) { @@ -653,29 +669,60 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa if (code == routing::RouterResultCode::NoError) { - LOG(LINFO, ("Decoded route:")); - for (auto rsegment : route->GetRouteSegments()) + std::vector rsegments(route->GetRouteSegments()); + + // erase leading and trailing fake segments + while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.erase(rsegments.begin()); + while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.erase(rsegments.end()); + + if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_to) + // from–at in forward direction, add last segment + AddDecodedSegment(decoded, rsegments.back().GetSegment()); + else if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_from) + // at–to in forward direction, add last segment + AddDecodedSegment(decoded, rsegments.front().GetSegment()); + else if (backwards && message.m_location.value().m_at && !message.m_location.value().m_to) + // from–at in backward direction, add first segment + AddDecodedSegment(decoded, rsegments.front().GetSegment()); + else if (backwards && message.m_location.value().m_at && !message.m_location.value().m_from) + // at–to in backward direction, add first segment + AddDecodedSegment(decoded, rsegments.back().GetSegment()); + else if (message.m_location.value().m_at) { - routing::Segment segment = rsegment.GetSegment(); - if (segment.GetMwmId() == routing::kFakeNumMwmId) - { - LOG(LINFO, (" Fake segment:", segment)); - continue; - } + // from–at–to, find closest segment + ms::LatLon at = message.m_location.value().m_at.value().m_coordinates; + routing::RouteSegment & closestRSegment = rsegments.front(); + double closestDist = ms::DistanceOnEarth(at, mercator::ToLatLon(closestRSegment.GetJunction().GetPoint())); - LOG(LINFO, (" segment:", segment)); + for (auto rsegment : rsegments) + { + // If we have more than two checkpoints, fake segments can occur in the middle, skip them. + if (rsegment.GetSegment().GetMwmId() == routing::kFakeNumMwmId) + continue; - auto const countryFile = m_numMwmIds->GetFile(segment.GetMwmId()); - MwmSet::MwmId mwmId = m_dataSource.GetMwmIdByCountryFile(countryFile); + double dist = ms::DistanceOnEarth(at, mercator::ToLatLon(rsegment.GetJunction().GetPoint())); + if (dist < closestDist) + { + closestRSegment = rsegment; + closestDist = dist; + } + } + AddDecodedSegment(decoded, closestRSegment.GetSegment()); + } + else + // from–[via]–to, add all real segments + for (auto rsegment : rsegments) + { + routing::Segment & segment = rsegment.GetSegment(); - auto const fid = segment.GetFeatureId(); - auto const sid = segment.GetSegmentIdx(); - uint8_t direction = segment.IsForward() ? - traffic::TrafficInfo::RoadSegmentId::kForwardDirection : - traffic::TrafficInfo::RoadSegmentId::kReverseDirection; + // If we have more than two checkpoints, fake segments can occur in the middle, skip them. + if (segment.GetMwmId() == routing::kFakeNumMwmId) + continue; - decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown; - } + AddDecodedSegment(decoded, segment); + } } } diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index bcdd67061..30189c43d 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -266,6 +266,14 @@ class RoutingTraffDecoder : public TraffDecoder */ bool InitRouter(); + /** + * @brief Adds a segment to the decoded segments. + * + * @param decoded The decoded segments. + * @param segment The segment to add. + */ + void AddDecodedSegment(traffxml::MultiMwmColoring & decoded, routing::Segment & segment); + /** * @brief Decodes one direction of a TraFF location. * From 7db32a992220077736fe929c5d1b1b5feaecb841 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 1 Jun 2025 14:32:50 +0300 Subject: [PATCH 065/252] [traffxml] Do not request additional maps during TraFF decoding Signed-off-by: mvglasow --- routing/index_router.cpp | 4 ++-- routing/index_router.hpp | 41 ++++++++++++++++++++++++++++---------- traffxml/traff_decoder.hpp | 34 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/routing/index_router.cpp b/routing/index_router.cpp index bb538216f..857a991b3 100644 --- a/routing/index_router.cpp +++ b/routing/index_router.cpp @@ -577,7 +577,7 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints, } } - if (!route.GetAbsentCountries().empty()) + if ((GetMode() == Mode::Navigation) && !route.GetAbsentCountries().empty()) return RouterResultCode::NeedMoreMaps; TrafficStash::Guard guard(m_trafficStash); @@ -1406,7 +1406,7 @@ bool IndexRouter::PointsOnEdgesSnapping::FindBestEdges( } // Removing all candidates which are fenced off by the road graph (|closestRoads|) from |checkpoint|. - return !m_router.IsFakeEndingSetSimplified() || !IsFencedOff(checkpoint, edgeProj, closestRoads); + return (m_router.GetMode() == Mode::Decoding) || !IsFencedOff(checkpoint, edgeProj, closestRoads); }; // Getting closest edges from |closestRoads| if they are correct according to isGood() function. diff --git a/routing/index_router.hpp b/routing/index_router.hpp index 9e6845b3e..ff561c3c5 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -41,6 +41,24 @@ class IndexGraphStarter; class IndexRouter : public IRouter { public: + /** + * @brief Indicates the mode in which the router is operating. + * + * The mode controls some aspects of router behavior, such as asking for additional maps or how + * checkpoints are matched to nearby segments. + */ + enum Mode + { + /** + * Router mode for navigation, i.e. user-initiated route guidance. + */ + Navigation, + /** + * Router mode for location decoding. + */ + Decoding + }; + class BestEdgeComparator final { public: @@ -138,21 +156,22 @@ class IndexRouter : public IRouter /** - * @brief Whether the set of fake endings generated for the check points is restricted. + * @brief Returns the mode in which the router is operating. * - * The return value is used internally when snapping checkpoints to edges. If this function - * returns true, this instructs the `PointsOnEdgesSnapping` instance to consider only edges which - * are not fenced off, i.e. can be reached from the respective checkpoint without crossing any - * other edges. If it returns false, this restriction does not apply, and all nearby edges are - * considered. + * The `IndexRouter` always returns `Mode::Navigation`; subclasses may override this method and + * return different values. * - * Restricting the set of fake endings in this manner decreases the options considered for routing - * and thus processing time, which is desirable for regular routing and has no side effects. + * In navigation mode, the router may exit with `RouterResultCode::NeedMoreMaps` if it determines + * that a better route can be calculated with additional maps. When snapping endpoints to edges, + * it will consider only edges which are not “fenced off” by other edges, i.e. which can be + * reached from the endpoint without crossing other edges. This decreases the number of fake + * endings and thus speeds up routing, without any undesirable side effects for that use case. * - * The `IndexRouter` implementation always returns true; subclasses may override this method and - * return different values. + * In decoding mode, the router will never exit with `RouterResultCode::NeedMoreMaps`: it will try + * to find a route with the existing maps, or exit without finding a route. When snapping + * endpoints to edges, it considers all edges within the given radius, fenced off or not. */ - virtual bool IsFakeEndingSetSimplified() { return true; } + virtual Mode GetMode() { return Mode::Navigation; } private: RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 30189c43d..4207932bc 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -221,7 +221,39 @@ class RoutingTraffDecoder : public TraffDecoder * * To avoid this, the `DecoderRouter` implementation always returns false. */ - bool IsFakeEndingSetSimplified() override { return false; } + /** + * @brief Returns the mode in which the router is operating. + * + * The `DecoderRouter` always returns `Mode::Decoding`. + * + * In navigation mode, the router may exit with `RouterResultCode::NeedMoreMaps` if it determines + * that a better route can be calculated with additional maps. When snapping endpoints to edges, + * it will consider only edges which are not “fenced off” by other edges, i.e. which can be + * reached from the endpoint without crossing other edges. This decreases the number of fake + * endings and thus speeds up routing, without any undesirable side effects for that use case. + * + * Asking the user to download extra maps is neither practical for a TraFF decoder which runs in + * the background and may decode many locations, one by one, nor is it needed (if maps are + * missing, we do not need to decode traffic reports for them). + * + * Eliminating fenced-off edges from the snapping candidates has an undesirable side effect for + * TraFF location decoding on dual-carriageway roads: if the reference points are outside the + * carriageways, only one direction gets considered for snapping, as the opposite direction is + * fenced off by it. This may lead to situations like these: + * + * ~~~ + * --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<-- + * -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->-- + * |< <| + * + * (-- carriageway, + junction, < > direction, |< end point, <| start point, == route) + * ~~~ + * + * Therefore, in decoding mode, the router will never exit with `RouterResultCode::NeedMoreMaps` + * but tries to find a route with the existing maps, or exits without a route. When snapping + * endpoints to edges, it considers all edges within the given radius, fenced off or not. + */ + IndexRouter::Mode GetMode() { return IndexRouter::Mode::Decoding; } private: }; From 76fce016bbeaecbb66dae149d2c7439991fe9995 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 4 Jun 2025 00:06:31 +0300 Subject: [PATCH 066/252] [traffxml] Process delay in traffic impact Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 99 ++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 25eb3513b..c90c43ae5 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -3,6 +3,9 @@ //#include "traffxml/traff_foo.hpp" #include "geometry/distance_on_sphere.hpp" +#include "geometry/mercator.hpp" + +#include "indexer/feature.hpp" #include "openlr/decoded_path.hpp" #include "openlr/openlr_decoder.hpp" @@ -63,6 +66,12 @@ auto constexpr kAttributePenalty = 4; */ auto constexpr kReducedAttributePenalty = 2; +/* + * Invalid feature ID. + * Borrowed from indexer/feature_decl.hpp. + */ +uint32_t constexpr kInvalidFeatureId = std::numeric_limits::max(); + TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) @@ -74,7 +83,73 @@ TraffDecoder::TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryI void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded) { + traffic::SpeedGroup fromDelay = traffic::SpeedGroup::Unknown; + + /* + * Convert delay into speed group (skip if the location is blocked). + * TODO: mapping delay to speed group is not optimal, as a long delay on a short location can + * increase travel time by several orders of magnitude. + * Assume a stretch of road with a speed limit of 60 km/h, i.e. 1 km/min, and a delay of 30 min. + * The lowest speed group, G0, translates to 8% of the posted limit. Since travel time ratio is + * the inverse of speed ratio, in order to represent 30 seconds of delay, the location would have + * to be long enough to take ~8% of that time to traverse under normal conditions, i.e. 2.4 min., + * or 2.4 km at 60 km/h. For shorter locations, the speed group will underestimate the delay. + * Higher speeds and longer delays would increase the required length. This is somewhat offset by + * the fact that the router adds a somewhat arbitrary extra penalty to segments for which traffic + * problems are reported. + */ + if ((impact.m_delayMins > 0) && (impact.m_speedGroup != traffic::SpeedGroup::TempBlock)) + { + double normalDurationS = 0; + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + { + /* + * We are initializing two structures for map access here: an MWM handle to get maxspeeds, + * and a FeaturesLoaderGuard to retrieve points. This may not be very elegant, but works. + * Since both involve somewhat complex structures, a rewrite might not be simple, although + * improvements are certainly welcome. + */ + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + auto const speeds = routing::LoadMaxspeeds(handle); + + FeaturesLoaderGuard g(m_dataSource, dit->first); + uint32_t lastFid = kInvalidFeatureId; + std::vector points; + routing::MaxspeedType speedKmPH = routing::kInvalidSpeed; + for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) + { + // read points only once per feature ID, not once per segment + if (lastFid != cit->first.GetFid()) + { + auto f = g.GetOriginalFeatureByIndex(cit->first.GetFid()); + f->ResetGeometry(); + assign_range(points, f->GetPoints(FeatureType::BEST_GEOMETRY)); + lastFid = cit->first.GetFid(); + auto const speed = speeds->GetMaxspeed(cit->first.GetFid()); + speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); + } + auto const idx = cit->first.GetIdx(); + // TODO sum of all lengths may differ from route length by up to ~6%, no idea why + auto const length = mercator::DistanceOnEarth(points[idx], points[idx + 1]); + if (speedKmPH != routing::kInvalidSpeed) + normalDurationS += length * kOneMpSInKmpH / speedKmPH; + } + } + auto const delayedDurationS = normalDurationS + impact.m_delayMins * 60; + fromDelay = traffic::GetSpeedGroupByPercentage(normalDurationS * 100.0f / delayedDurationS); + + LOG(LINFO, ("Normal duration:", normalDurationS, "delayed duration:", delayedDurationS, "speed group:", DebugPrint(fromDelay))); + } + for (auto dit = decoded.begin(); dit != decoded.end(); dit++) + { + std::unique_ptr speeds = nullptr; + if ((impact.m_speedGroup != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) + { + // load maxspeeds once per MWM and only if needed + auto const handle = m_dataSource.GetMwmHandleById(dit->first); + speeds = routing::LoadMaxspeeds(handle); + } for (auto cit = dit->second.begin(); cit != dit->second.end(); cit++) { /* @@ -83,17 +158,15 @@ void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml * the rest. */ traffic::SpeedGroup sg = impact.m_speedGroup; - /* - * TODO also process m_delayMins if greater than zero. - * This would require a separate pass over all edges, calculating length, - * total (normal) travel time (length / maxspeed), then a speed group based on - * (normal_travel_time / delayed_travel_time) – which is the same as the ratio between - * reduced and normal speed. That would give us a third potential speed group. - */ + + if ((sg != traffic::SpeedGroup::TempBlock) && (fromDelay != traffic::SpeedGroup::Unknown)) + // process delay + if ((sg == traffic::SpeedGroup::Unknown) || (fromDelay < sg)) + sg = fromDelay; + if ((sg != traffic::SpeedGroup::TempBlock) && (impact.m_maxspeed != traffxml::kMaxspeedNone)) { - auto const handle = m_dataSource.GetMwmHandleById(dit->first); - auto const speeds = routing::LoadMaxspeeds(handle); + // process maxspeed if (speeds) { traffic::SpeedGroup fromMaxspeed = traffic::SpeedGroup::Unknown; @@ -106,16 +179,10 @@ void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml sg = fromMaxspeed; } } - /* - * TODO fully process TrafficImpact (unless m_speedGroup is TempBlock, which overrules everything else) - * If no maxspeed or delay is set, just give out speed groups. - * Else, examine segments, length, normal travel time, travel time considering impact, and - * determine the closest matching speed group. - */ } - // TODO process all TrafficImpact fields and determine the speed group based on that cit->second = sg; } + } } void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) From dd7ed98c1ac99aba663174196328ffbc86faf31f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 4 Jun 2025 21:35:56 +0300 Subject: [PATCH 067/252] [traffic] Use enabled state instead of Start() Signed-off-by: mvglasow --- map/framework.cpp | 3 +-- map/traffic_manager.cpp | 17 ++++++----------- map/traffic_manager.hpp | 41 +++++++++++++++++++---------------------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 05f92c748..46b7c68f4 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -376,7 +376,6 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors()); - m_trafficManager.SetEnabled(LoadTrafficEnabled()); m_isolinesManager.SetEnabled(LoadIsolinesEnabled()); @@ -390,7 +389,7 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) if (loadMaps) LoadMapsSync(); - m_trafficManager.Start(); + m_trafficManager.SetEnabled(LoadTrafficEnabled()); } Framework::~Framework() diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index f6e41767e..3fe4e02e2 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -72,7 +72,6 @@ TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn coun , m_maxCacheSizeBytes(maxCacheSizeBytes) #endif , m_isRunning(true) - , m_isStarted(false) , m_isPaused(false) , m_thread(&TrafficManager::ThreadRoutine, this) { @@ -117,7 +116,10 @@ void TrafficManager::SetEnabled(bool enabled) std::lock_guard lock(m_mutex); if (enabled == IsEnabled()) return; - Clear(); + if (enabled && !m_traffDecoder) + // deferred decoder initialization (requires maps to be loaded) + m_traffDecoder = make_unique(m_dataSource, m_countryInfoGetterFn, + m_countryParentNameGetterFn, m_messageCache); ChangeState(enabled ? TrafficState::Enabled : TrafficState::Disabled); } @@ -129,13 +131,6 @@ void TrafficManager::SetEnabled(bool enabled) m_observer.OnTrafficInfoClear(); } -void TrafficManager::Start() -{ - m_traffDecoder = make_unique(m_dataSource, m_countryInfoGetterFn, - m_countryParentNameGetterFn, m_messageCache); - m_isStarted = true; -} - void TrafficManager::Clear() { // TODO no longer needed @@ -561,7 +556,7 @@ bool TrafficManager::WaitForRequest() if (!m_isRunning) return false; - if (m_isStarted) + if (IsEnabled()) { // if we have feeds in the queue, return immediately if (!m_feedQueue.empty()) @@ -592,7 +587,7 @@ bool TrafficManager::WaitForRequest() return false; // this works as long as wait timeout is at least equal to the poll interval - if (m_isStarted) + if (IsEnabled()) m_isPollNeeded |= timeout; LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged)); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c8f8967bf..1a87ef717 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -98,11 +98,26 @@ class TrafficManager final /** * @brief Enables or disables the traffic manager. * - * This sets the internal state and notifies the drape engine. Enabling the traffic manager will - * invalidate its data, disabling it will notify the observer that traffic data has been cleared. + * This sets the internal state and notifies the drape engine. + * + * Upon creation, the traffic manager is disabled and will not poll any sources or process any + * feeds until enabled. Feeds received through `Push()` will be added to the queue before the + * traffic manager is started, but will not be processed any further until the traffic manager is + * started. + * + * MWMs must be loaded before first enabling the traffic manager. * * Calling this function with `enabled` identical to the current state is a no-op. * + * @todo Currently, all MWMs must be loaded before calling `SetEnabled()`, as MWMs loaded after + * that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added + * (and removed) – note that this affects the `DataSource`, not the set of active MWMs. + * + * @todo Enabling the traffic manager will invalidate its data, disabling it will notify the + * observer that traffic data has been cleared. This is old logic, to be reviewed/removed. + * + * @todo State/pause/resume logic is not fully implemented ATM and needs to be revisited. + * * @param enabled True to enable, false to disable */ void SetEnabled(bool enabled); @@ -117,20 +132,6 @@ class TrafficManager final /** * @brief Starts the traffic manager. * - * After creation, the traffic manager will not poll any sources or process any feeds until it is - * started. Feeds received through `Push()` will be added to the queue before the traffic manager - * is started, but will not be processed any further until the traffic manager is started. - * - * MWMs must be loaded before starting the traffic manager. - * - * @todo Currently, all MWMs must be loaded before calling `Start()`, as MWMs loaded after that - * will not get picked up. We need to extend `TrafficManager` to react to MWMs being added (and - * removed) – note that this affects the `DataSource`, not the set of active MWMs. - * - * @todo Start is currently not integrated with state or pause/resume logic (all of which might - * not be fully implemented ATM). If the traffic manager is not started, no message processing - * (other than filling the queue and deduplication) will take place, regardless of state. Starting - * the traffic manager will not change the state it reports. */ void Start(); @@ -145,7 +146,8 @@ class TrafficManager final * This happens when a new MWM file is downloaded, the traffic manager is enabled after being * disabled or resumed after being paused. * - * @todo this goes for the old MWM arch, see if this makes sense for TraFF. + * @todo this goes for the old MWM arch. For TraFF we need to refresh the MWM set for the decoder + * and possibly decode locations again (MWMs might have changed, or new ones added). */ void Invalidate(); @@ -530,11 +532,6 @@ class TrafficManager final // which allows a client to make conditional requests. std::map m_trafficETags; - /** - * Whether the traffic manager should begin receiving information. - */ - std::atomic m_isStarted; - std::atomic m_isPaused; /** From 2592bcf042b59516dfe0a297942fe5f6774f3138 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 5 Jun 2025 21:33:18 +0300 Subject: [PATCH 068/252] WIP: [traffxml] traff_assessment_tool, based on openlr_assessment_tool Skeleton without any traff functionality so far Contains some obsolete code, commented out with #ifdef openlr_obsolete Signed-off-by: mvglasow --- traffxml/CMakeLists.txt | 4 + traffxml/traff_assessment_tool/CMakeLists.txt | 30 + traffxml/traff_assessment_tool/Info.plist | 11 + traffxml/traff_assessment_tool/main.cpp | 41 ++ traffxml/traff_assessment_tool/mainwindow.cpp | 422 +++++++++++++ traffxml/traff_assessment_tool/mainwindow.hpp | 64 ++ traffxml/traff_assessment_tool/map_widget.cpp | 29 + traffxml/traff_assessment_tool/map_widget.hpp | 46 ++ .../points_controller_delegate_base.hpp | 40 ++ .../traffic_drawer_delegate_base.hpp | 25 + .../traff_assessment_tool/traffic_mode.cpp | 577 ++++++++++++++++++ .../traff_assessment_tool/traffic_mode.hpp | 141 +++++ .../traff_assessment_tool/traffic_panel.cpp | 81 +++ .../traff_assessment_tool/traffic_panel.hpp | 50 ++ .../trafficmodeinitdlg.cpp | 54 ++ .../trafficmodeinitdlg.h | 36 ++ .../trafficmodeinitdlg.ui | 111 ++++ 17 files changed, 1762 insertions(+) create mode 100644 traffxml/traff_assessment_tool/CMakeLists.txt create mode 100644 traffxml/traff_assessment_tool/Info.plist create mode 100644 traffxml/traff_assessment_tool/main.cpp create mode 100644 traffxml/traff_assessment_tool/mainwindow.cpp create mode 100644 traffxml/traff_assessment_tool/mainwindow.hpp create mode 100644 traffxml/traff_assessment_tool/map_widget.cpp create mode 100644 traffxml/traff_assessment_tool/map_widget.hpp create mode 100644 traffxml/traff_assessment_tool/points_controller_delegate_base.hpp create mode 100644 traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp create mode 100644 traffxml/traff_assessment_tool/traffic_mode.cpp create mode 100644 traffxml/traff_assessment_tool/traffic_mode.hpp create mode 100644 traffxml/traff_assessment_tool/traffic_panel.cpp create mode 100644 traffxml/traff_assessment_tool/traffic_panel.hpp create mode 100644 traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp create mode 100644 traffxml/traff_assessment_tool/trafficmodeinitdlg.h create mode 100644 traffxml/traff_assessment_tool/trafficmodeinitdlg.ui diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index 029698d24..2db32693a 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -16,3 +16,7 @@ target_link_libraries(${PROJECT_NAME} openlr coding ) + +if (NOT SKIP_QT_GUI) + omim_add_tool_subdirectory(traff_assessment_tool) +endif() diff --git a/traffxml/traff_assessment_tool/CMakeLists.txt b/traffxml/traff_assessment_tool/CMakeLists.txt new file mode 100644 index 000000000..5ff1c6cc9 --- /dev/null +++ b/traffxml/traff_assessment_tool/CMakeLists.txt @@ -0,0 +1,30 @@ +project(traff_assessment_tool) + +set(SRC + main.cpp + mainwindow.cpp + mainwindow.hpp + map_widget.cpp + map_widget.hpp + points_controller_delegate_base.hpp + traffic_drawer_delegate_base.hpp + traffic_mode.cpp + traffic_mode.hpp + traffic_panel.cpp + traffic_panel.hpp + trafficmodeinitdlg.cpp + trafficmodeinitdlg.h + trafficmodeinitdlg.ui +) + +omim_add_executable(${PROJECT_NAME} ${SRC}) + +set_target_properties(${PROJECT_NAME} PROPERTIES AUTOUIC ON AUTOMOC ON) + +target_link_libraries(${PROJECT_NAME} + openlr + qt_common + map + gflags::gflags + traffxml +) diff --git a/traffxml/traff_assessment_tool/Info.plist b/traffxml/traff_assessment_tool/Info.plist new file mode 100644 index 000000000..384699b4b --- /dev/null +++ b/traffxml/traff_assessment_tool/Info.plist @@ -0,0 +1,11 @@ + + + + + NSPrincipalClass + NSApplication + + NSHighResolutionCapable + True + + diff --git a/traffxml/traff_assessment_tool/main.cpp b/traffxml/traff_assessment_tool/main.cpp new file mode 100644 index 000000000..810d7bb4f --- /dev/null +++ b/traffxml/traff_assessment_tool/main.cpp @@ -0,0 +1,41 @@ +#include "mainwindow.hpp" + +#include "qt/qt_common/helpers.hpp" + +#include "map/framework.hpp" + +#include + +#include + +namespace +{ +DEFINE_string(resources_path, "", "Path to resources directory"); +DEFINE_string(data_path, "", "Path to data directory"); +} // namespace + +int main(int argc, char * argv[]) +{ + gflags::SetUsageMessage("Visualize and check matched routes."); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + Platform & platform = GetPlatform(); + if (!FLAGS_resources_path.empty()) + platform.SetResourceDir(FLAGS_resources_path); + if (!FLAGS_data_path.empty()) + platform.SetWritableDirForTests(FLAGS_data_path); + + Q_INIT_RESOURCE(resources_common); + QApplication app(argc, argv); + + qt::common::SetDefaultSurfaceFormat(app.platformName()); + + FrameworkParams params; + + Framework framework(params); + traffxml::MainWindow mainWindow(framework); + + mainWindow.showMaximized(); + + return app.exec(); +} diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp new file mode 100644 index 000000000..e9d51da06 --- /dev/null +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -0,0 +1,422 @@ +#include "traffxml/traff_assessment_tool/mainwindow.hpp" + +#include "traffxml/traff_assessment_tool/map_widget.hpp" +#include "traffxml/traff_assessment_tool/points_controller_delegate_base.hpp" +#include "traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp" +#include "traffxml/traff_assessment_tool/traffic_panel.hpp" +#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h" + +#include "map/framework.hpp" + +#include "drape_frontend/drape_api.hpp" + +#include "routing/data_source.hpp" +#include "routing/features_road_graph.hpp" +#include "routing/road_graph.hpp" + +#include "routing_common/car_model.hpp" + +#include "storage/country_parent_getter.hpp" + +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace traffxml +{ +namespace +{ +class TrafficDrawerDelegate : public TrafficDrawerDelegateBase +{ + static constexpr char const * kEncodedLineId = "encodedPath"; + static constexpr char const * kDecodedLineId = "decodedPath"; + static constexpr char const * kGoldenLineId = "goldenPath"; + +public: + explicit TrafficDrawerDelegate(Framework & framework) + : m_framework(framework) + , m_drapeApi(m_framework.GetDrapeApi()) + , m_bm(framework.GetBookmarkManager()) + { + } + + void SetViewportCenter(m2::PointD const & center) override + { + m_framework.SetViewportCenter(center); + } + + void DrawDecodedSegments(std::vector const & points) override + { + CHECK(!points.empty(), ("Points must not be empty.")); + + LOG(LINFO, ("Decoded segment", points)); + m_drapeApi.AddLine(kDecodedLineId, + df::DrapeApiLineData(points, dp::Color(0, 0, 255, 255)) + .Width(3.0f).ShowPoints(true /* markPoints */)); + } + + void DrawEncodedSegment(std::vector const & points) override + { + LOG(LINFO, ("Encoded segment", points)); + m_drapeApi.AddLine(kEncodedLineId, + df::DrapeApiLineData(points, dp::Color(255, 0, 0, 255)) + .Width(3.0f).ShowPoints(true /* markPoints */)); + } + + void DrawGoldenPath(std::vector const & points) override + { + m_drapeApi.AddLine(kGoldenLineId, + df::DrapeApiLineData(points, dp::Color(255, 127, 36, 255)) + .Width(4.0f).ShowPoints(true /* markPoints */)); + } + + void ClearGoldenPath() override + { + m_drapeApi.RemoveLine(kGoldenLineId); + } + + void ClearAllPaths() override + { + m_drapeApi.Clear(); + } + + void VisualizePoints(std::vector const & points) override + { + auto editSession = m_bm.GetEditSession(); + editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true); + for (auto const & p : points) + editSession.CreateUserMark(p); + } + + void ClearAllVisualizedPoints() override + { + m_bm.GetEditSession().ClearGroup(UserMark::Type::DEBUG_MARK); + } + +private: + Framework & m_framework; + df::DrapeApi & m_drapeApi; + BookmarkManager & m_bm; +}; + +bool PointsMatch(m2::PointD const & a, m2::PointD const & b) +{ + auto constexpr kToleranceDistanceM = 1.0; + return mercator::DistanceOnEarth(a, b) < kToleranceDistanceM; +} + +class PointsControllerDelegate : public PointsControllerDelegateBase +{ +public: + explicit PointsControllerDelegate(Framework & framework) + : m_framework(framework) + , m_dataSource(const_cast(GetDataSource()), nullptr /* numMwmIDs */) + , m_roadGraph(m_dataSource, routing::IRoadGraph::Mode::ObeyOnewayTag, + std::make_unique(storage::CountryParentGetter{})) + { + } + + std::vector GetAllJunctionPointsInViewport() const override + { + std::vector points; + auto const & rect = m_framework.GetCurrentViewport(); + auto const pushPoint = [&points, &rect](m2::PointD const & point) + { + if (!rect.IsPointInside(point)) + return; + for (auto const & p : points) + { + if (PointsMatch(point, p)) + return; + } + points.push_back(point); + }; + + auto const pushFeaturePoints = [&pushPoint](FeatureType & ft) + { + if (ft.GetGeomType() != feature::GeomType::Line) + return; + + /// @todo Transported (railway=rail) are also present here :) + auto const roadClass = ftypes::GetHighwayClass(feature::TypesHolder(ft)); + if (roadClass == ftypes::HighwayClass::Undefined || + roadClass == ftypes::HighwayClass::Pedestrian) + { + return; + } + ft.ForEachPoint(pushPoint, scales::GetUpperScale()); + }; + + GetDataSource().ForEachInRect(pushFeaturePoints, rect, scales::GetUpperScale()); + return points; + } + + std::pair, m2::PointD> GetCandidatePoints( + m2::PointD const & p) const override + { + auto constexpr kInvalidIndex = std::numeric_limits::max(); + + std::vector points; + m2::PointD pointOnFt; + indexer::ForEachFeatureAtPoint(GetDataSource(), [&points, &p, &pointOnFt](FeatureType & ft) + { + if (ft.GetGeomType() != feature::GeomType::Line) + return; + + ft.ParseGeometry(FeatureType::BEST_GEOMETRY); + + auto minDistance = std::numeric_limits::max(); + auto bestPointIndex = kInvalidIndex; + for (size_t i = 0; i < ft.GetPointsCount(); ++i) + { + auto const & fp = ft.GetPoint(i); + auto const distance = mercator::DistanceOnEarth(fp, p); + if (PointsMatch(fp, p) && distance < minDistance) + { + bestPointIndex = i; + minDistance = distance; + } + } + + if (bestPointIndex != kInvalidIndex) + { + points.emplace_back(ft.GetID(), bestPointIndex); + pointOnFt = ft.GetPoint(bestPointIndex); + } + }, p); + + return std::make_pair(points, pointOnFt); + } + + std::vector GetReachablePoints(m2::PointD const & p) const override + { + routing::FeaturesRoadGraph::EdgeListT edges; + m_roadGraph.GetOutgoingEdges(geometry::PointWithAltitude(p, geometry::kDefaultAltitudeMeters), + edges); + + std::vector points; + for (auto const & e : edges) + points.push_back(e.GetEndJunction().GetPoint()); + return points; + } + + ClickType CheckClick(m2::PointD const & clickPoint, + m2::PointD const & lastClickedPoint, + std::vector const & reachablePoints) const override + { + // == Comparison is safe here since |clickPoint| is adjusted by GetFeaturesPointsByPoint + // so to be equal the closest feature's one. + if (clickPoint == lastClickedPoint) + return ClickType::Remove; + for (auto const & p : reachablePoints) + { + if (PointsMatch(clickPoint, p)) + return ClickType::Add; + } + return ClickType::Miss; + } + +private: + DataSource const & GetDataSource() const { return m_framework.GetDataSource(); } + + Framework & m_framework; + routing::MwmDataSource m_dataSource; + routing::FeaturesRoadGraph m_roadGraph; +}; +} // namespace + + +MainWindow::MainWindow(Framework & framework) + : m_framework(framework) +{ + m_mapWidget = new MapWidget(m_framework, this /* parent */); + + m_layout = new QHBoxLayout(); + m_layout->addWidget(m_mapWidget); + + auto * window = new QWidget(); + window->setLayout(m_layout); + window->setGraphicsEffect(nullptr); + + setCentralWidget(window); + + setWindowTitle(tr("Organic Maps")); + setWindowIcon(QIcon(":/ui/logo.png")); + + QMenu * fileMenu = new QMenu("File", this); + menuBar()->addMenu(fileMenu); + + fileMenu->addAction("Open sample", QKeySequence("Ctrl+O"), this, &MainWindow::OnOpenTrafficSample); + + m_closeTrafficSampleAction = fileMenu->addAction("Close sample", QKeySequence("Ctrl+W"), this, &MainWindow::OnCloseTrafficSample); + m_saveTrafficSampleAction = fileMenu->addAction("Save sample", QKeySequence("Ctrl+S"), this, &MainWindow::OnSaveTrafficSample); + + fileMenu->addSeparator(); + +#ifdef openlr_obsolete + m_goldifyMatchedPathAction = fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficMode->GoldifyMatchedPath(); }); + m_startEditingAction = fileMenu->addAction("Edit", QKeySequence("Ctrl+E"), + [this] { + m_trafficMode->StartBuildingPath(); + m_mapWidget->SetMode(MapWidget::Mode::TrafficMarkup); + m_commitPathAction->setEnabled(true /* enabled */); + m_cancelPathAction->setEnabled(true /* enabled */); + }); + m_commitPathAction = fileMenu->addAction("Accept path", + QKeySequence("Ctrl+A"), + [this] { + m_trafficMode->CommitPath(); + m_mapWidget->SetMode(MapWidget::Mode::Normal); + }); + m_cancelPathAction = fileMenu->addAction("Revert path", + QKeySequence("Ctrl+R"), + [this] { + m_trafficMode->RollBackPath(); + m_mapWidget->SetMode(MapWidget::Mode::Normal); + }); + m_ignorePathAction = fileMenu->addAction("Ignore path", + QKeySequence("Ctrl+I"), + [this] { + m_trafficMode->IgnorePath(); + m_mapWidget->SetMode(MapWidget::Mode::Normal); + }); + + m_goldifyMatchedPathAction->setEnabled(false /* enabled */); + m_closeTrafficSampleAction->setEnabled(false /* enabled */); + m_saveTrafficSampleAction->setEnabled(false /* enabled */); + m_startEditingAction->setEnabled(false /* enabled */); + m_commitPathAction->setEnabled(false /* enabled */); + m_cancelPathAction->setEnabled(false /* enabled */); + m_ignorePathAction->setEnabled(false /* enabled */); +#endif +} + +void MainWindow::CreateTrafficPanel(std::string const & dataFilePath) +{ + m_trafficMode = new TrafficMode(dataFilePath, + m_framework.GetDataSource(), + std::make_unique(m_framework), + std::make_unique(m_framework)); + + connect(m_mapWidget, &MapWidget::TrafficMarkupClick, + m_trafficMode, &TrafficMode::OnClick); + connect(m_trafficMode, &TrafficMode::EditingStopped, + this, &MainWindow::OnPathEditingStop); + connect(m_trafficMode, &TrafficMode::SegmentSelected, + [](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); }); + + m_docWidget = new QDockWidget(tr("Routes"), this); + addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget); + + m_docWidget->setWidget(new TrafficPanel(m_trafficMode, m_docWidget)); + + m_docWidget->adjustSize(); + m_docWidget->setMinimumWidth(400); + m_docWidget->show(); +} + +void MainWindow::DestroyTrafficPanel() +{ + removeDockWidget(m_docWidget); + delete m_docWidget; + m_docWidget = nullptr; + + delete m_trafficMode; + m_trafficMode = nullptr; + + m_mapWidget->SetMode(MapWidget::Mode::Normal); +} + +void MainWindow::OnOpenTrafficSample() +{ + TrafficModeInitDlg dlg; + dlg.exec(); + if (dlg.result() != QDialog::DialogCode::Accepted) + return; + + try + { + CreateTrafficPanel(dlg.GetDataFilePath()); + } +#ifdef openlr_obsolete + catch (TrafficModeError const & e) +#else + // TODO do we need to catch exceptions other than our own here? + catch (RootException const & e) +#endif + { + QMessageBox::critical(this, "Data loading error", QString("Can't load data file.")); + LOG(LERROR, (e.Msg())); + return; + } + +#ifdef openlr_obsolete + m_goldifyMatchedPathAction->setEnabled(true /* enabled */); + m_closeTrafficSampleAction->setEnabled(true /* enabled */); + m_saveTrafficSampleAction->setEnabled(true /* enabled */); + m_startEditingAction->setEnabled(true /* enabled */); + m_ignorePathAction->setEnabled(true /* enabled */); +#endif +} + +void MainWindow::OnCloseTrafficSample() +{ + // TODO(mgsergio): + // If not saved, ask a user if he/she wants to save. + // OnSaveTrafficSample() + +#ifdef openlr_obsolete + m_goldifyMatchedPathAction->setEnabled(false /* enabled */); + m_saveTrafficSampleAction->setEnabled(false /* enabled */); + m_closeTrafficSampleAction->setEnabled(false /* enabled */); + m_startEditingAction->setEnabled(false /* enabled */); + m_commitPathAction->setEnabled(false /* enabled */); + m_cancelPathAction->setEnabled(false /* enabled */); + m_ignorePathAction->setEnabled(false /* enabled */); +#endif + + DestroyTrafficPanel(); +} + +void MainWindow::OnSaveTrafficSample() +{ + // TODO(mgsergio): Add default filename. + auto const & fileName = QFileDialog::getSaveFileName(this, "Save sample"); + if (fileName.isEmpty()) + return; + +#ifdef openlr_obsolete + if (!m_trafficMode->SaveSampleAs(fileName.toStdString())) + { + QMessageBox::critical( + this, "Saving error", + QString("Can't save file: ") + strerror(errno)); + } +#endif +} + +void MainWindow::OnPathEditingStop() +{ +#ifdef openlr_obsolete + m_commitPathAction->setEnabled(false /* enabled */); + m_cancelPathAction->setEnabled(false /* enabled */); + m_cancelPathAction->setEnabled(false /* enabled */); +#endif +} +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp new file mode 100644 index 000000000..9ad80e2fa --- /dev/null +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "base/string_utils.hpp" + +#include + +#include + +class Framework; +class QHBoxLayout; + +namespace traffxml +{ +class MapWidget; +class TrafficMode; +class WebView; +} + +namespace df +{ +class DrapeApi; +} + +class QDockWidget; + +namespace traffxml +{ +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(Framework & framework); + +private: + void CreateTrafficPanel(std::string const & dataFilePath); + void DestroyTrafficPanel(); + + void OnOpenTrafficSample(); + void OnCloseTrafficSample(); + void OnSaveTrafficSample(); + void OnPathEditingStop(); + + Framework & m_framework; + + traffxml::TrafficMode * m_trafficMode = nullptr; + QDockWidget * m_docWidget = nullptr; + +#ifdef openlr_obsolete + QAction * m_goldifyMatchedPathAction = nullptr; +#endif + QAction * m_saveTrafficSampleAction = nullptr; + QAction * m_closeTrafficSampleAction = nullptr; +#ifdef openlr_obsolete + QAction * m_startEditingAction = nullptr; + QAction * m_commitPathAction = nullptr; + QAction * m_cancelPathAction = nullptr; + QAction * m_ignorePathAction = nullptr; +#endif + + traffxml::MapWidget * m_mapWidget = nullptr; + QHBoxLayout * m_layout = nullptr; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/map_widget.cpp b/traffxml/traff_assessment_tool/map_widget.cpp new file mode 100644 index 000000000..97232c054 --- /dev/null +++ b/traffxml/traff_assessment_tool/map_widget.cpp @@ -0,0 +1,29 @@ +#include "traffxml/traff_assessment_tool/map_widget.hpp" + +#include "qt/qt_common/helpers.hpp" + +#include "map/framework.hpp" + +#include + +namespace traffxml +{ +MapWidget::MapWidget(Framework & framework, QWidget * parent) + : Base(framework, false /* screenshotMode */, parent) +{ +} + +void MapWidget::mousePressEvent(QMouseEvent * e) +{ + Base::mousePressEvent(e); + + if (qt::common::IsRightButton(e)) + ShowInfoPopup(e, GetDevicePoint(e)); + + if (m_mode == Mode::TrafficMarkup) + { + auto pt = GetDevicePoint(e); + emit TrafficMarkupClick(m_framework.PtoG(pt), e->button()); + } +} +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/map_widget.hpp b/traffxml/traff_assessment_tool/map_widget.hpp new file mode 100644 index 000000000..7931fd1b0 --- /dev/null +++ b/traffxml/traff_assessment_tool/map_widget.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "qt/qt_common/map_widget.hpp" + +namespace +{ +class PointsController; +} // namespace + +class Framework; + +namespace traffxml +{ +class MapWidget : public qt::common::MapWidget +{ + Q_OBJECT + + using Base = qt::common::MapWidget; + +public: + enum class Mode + { + Normal, + TrafficMarkup + }; + + MapWidget(Framework & framework, QWidget * parent); + ~MapWidget() override = default; + + void SetMode(Mode const mode) { m_mode = mode; } + + QSize sizeHint() const override + { + return QSize(800, 600); + } + +signals: + void TrafficMarkupClick(m2::PointD const & p, Qt::MouseButton const b); + +protected: + void mousePressEvent(QMouseEvent * e) override; + +private: + Mode m_mode = Mode::Normal; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp b/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp new file mode 100644 index 000000000..5f838bfa5 --- /dev/null +++ b/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "indexer/feature.hpp" + +#include "geometry/point2d.hpp" + +#include +#include + +namespace traffxml +{ +using FeaturePoint = std::pair; + +/// This class is responsible for collecting junction points and +/// checking user's clicks. +class PointsControllerDelegateBase +{ +public: + enum class ClickType + { + Miss, + Add, + Remove + }; + + virtual ~PointsControllerDelegateBase() = default; + + virtual std::vector GetAllJunctionPointsInViewport() const = 0; + /// Returns all junction points at a given location in the form of feature id and + /// point index in the feature. + virtual std::pair, m2::PointD> GetCandidatePoints( + m2::PointD const & p) const = 0; + // Returns all points that are one step reachable from |p|. + virtual std::vector GetReachablePoints(m2::PointD const & p) const = 0; + + virtual ClickType CheckClick(m2::PointD const & clickPoint, + m2::PointD const & lastClickedPoint, + std::vector const & reachablePoints) const = 0; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp b/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp new file mode 100644 index 000000000..756bf1903 --- /dev/null +++ b/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace traffxml +{ +/// This class is used to delegate segments drawing to the DrapeEngine. +class TrafficDrawerDelegateBase +{ +public: + virtual ~TrafficDrawerDelegateBase() = default; + + virtual void SetViewportCenter(m2::PointD const & center) = 0; + + virtual void DrawDecodedSegments(std::vector const & points) = 0; + virtual void DrawEncodedSegment(std::vector const & points) = 0; + virtual void DrawGoldenPath(std::vector const & points) = 0; + + virtual void ClearGoldenPath() = 0; + virtual void ClearAllPaths() = 0; + + virtual void VisualizePoints(std::vector const & points) = 0; + virtual void ClearAllVisualizedPoints() = 0; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/traffic_mode.cpp b/traffxml/traff_assessment_tool/traffic_mode.cpp new file mode 100644 index 000000000..f0e5511ea --- /dev/null +++ b/traffxml/traff_assessment_tool/traffic_mode.cpp @@ -0,0 +1,577 @@ +#include "traffic_mode.hpp" + +#ifdef openlr_obsolete +#include "openlr/openlr_model_xml.hpp" +#endif + +#include "indexer/data_source.hpp" + +#include "base/assert.hpp" +#include "base/scope_guard.hpp" + +#include +#include + +namespace traffxml +{ +namespace +{ +void RemovePointFromPull(m2::PointD const & toBeRemoved, std::vector & pool) +{ + pool.erase( + remove_if(begin(pool), end(pool), + [&toBeRemoved](m2::PointD const & p) { return p.EqualDxDy(toBeRemoved, 1e-6); }), + end(pool)); +} + +std::vector GetReachablePoints(m2::PointD const & srcPoint, + std::vector const path, + PointsControllerDelegateBase const & pointsDelegate, + size_t const lookbackIndex) +{ + auto reachablePoints = pointsDelegate.GetReachablePoints(srcPoint); + if (lookbackIndex < path.size()) + { + auto const & toBeRemoved = path[path.size() - lookbackIndex - 1]; + RemovePointFromPull(toBeRemoved, reachablePoints); + } + return reachablePoints; +} +} // namespace + +#ifdef openlr_obsolete +namespace impl +{ +// static +size_t const RoadPointCandidate::kInvalidId = std::numeric_limits::max(); + +/// This class denotes a "non-deterministic" feature point. +/// I.e. it is a set of all pairs +/// located at a specified coordinate. +/// Only one point at a time is considered active. +RoadPointCandidate::RoadPointCandidate(std::vector const & points, + m2::PointD const & coord) + : m_coord(coord) + , m_points(points) +{ + LOG(LDEBUG, ("Candidate points:", points)); +} + +void RoadPointCandidate::ActivateCommonPoint(RoadPointCandidate const & rpc) +{ + for (auto const & fp1 : m_points) + { + for (auto const & fp2 : rpc.m_points) + { + if (fp1.first == fp2.first) + { + SetActivePoint(fp1.first); + return; + } + } + } + CHECK(false, ("One common feature id should exist.")); +} + +FeaturePoint const & RoadPointCandidate::GetPoint() const +{ + CHECK_NOT_EQUAL(m_activePointIndex, kInvalidId, ("No point is active.")); + return m_points[m_activePointIndex]; +} + +m2::PointD const & RoadPointCandidate::GetCoordinate() const +{ + return m_coord; +} + +void RoadPointCandidate::SetActivePoint(FeatureID const & fid) +{ + for (size_t i = 0; i < m_points.size(); ++i) + { + if (m_points[i].first == fid) + { + m_activePointIndex = i; + return; + } + } + CHECK(false, ("One point should match.")); +} +} // namespace impl +#endif + +// TrafficMode ------------------------------------------------------------------------------------- +TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & dataSource, + std::unique_ptr drawerDelegate, + std::unique_ptr pointsDelegate, + QObject * parent) + : QAbstractTableModel(parent) + , m_dataSource(dataSource) + , m_drawerDelegate(std::move(drawerDelegate)) + , m_pointsDelegate(std::move(pointsDelegate)) +{ + // TODO(mgsergio): Collect stat how many segments of each kind were parsed. + pugi::xml_document doc; + if (!doc.load_file(dataFileName.data())) + MYTHROW(TrafficModeError, ("Can't load file:", strerror(errno))); + + // Save root node without children. + { + auto const root = doc.document_element(); + auto node = m_template.append_child(root.name()); + for (auto const & attr : root.attributes()) + node.append_copy(attr); + } + + // Select all Segment elements that are direct children of the root. + auto const segments = doc.document_element().select_nodes("./Segment"); + +#ifdef openlr_obsolete + try + { + for (auto const & xpathNode : segments) + { + auto const xmlSegment = xpathNode.node(); + + openlr::Path matchedPath; + openlr::Path fakePath; + openlr::Path goldenPath; + + openlr::LinearSegment segment; + + // TODO(mgsergio): Unify error handling interface of openlr_xml_mode and decoded_path parsers. + auto const partnerSegmentXML = xmlSegment.child("reportSegments"); + if (!openlr::SegmentFromXML(partnerSegmentXML, segment)) + MYTHROW(TrafficModeError, ("An error occurred while parsing: can't parse segment")); + + if (auto const route = xmlSegment.child("Route")) + openlr::PathFromXML(route, m_dataSource, matchedPath); + if (auto const route = xmlSegment.child("FakeRoute")) + openlr::PathFromXML(route, m_dataSource, fakePath); + if (auto const route = xmlSegment.child("GoldenRoute")) + openlr::PathFromXML(route, m_dataSource, goldenPath); + + uint32_t positiveOffsetM = 0; + uint32_t negativeOffsetM = 0; + if (auto const reportSegmentLRC = partnerSegmentXML.child("ReportSegmentLRC")) + { + if (auto const method = reportSegmentLRC.child("method")) + { + if (auto const locationReference = method.child("olr:locationReference")) + { + if (auto const optionLinearLocationReference = locationReference + .child("olr:optionLinearLocationReference")) + { + if (auto const positiveOffset = optionLinearLocationReference.child("olr:positiveOffset")) + positiveOffsetM = UintValueFromXML(positiveOffset); + + if (auto const negativeOffset = optionLinearLocationReference.child("olr:negativeOffset")) + negativeOffsetM = UintValueFromXML(negativeOffset); + } + } + } + } + + m_segments.emplace_back(segment, positiveOffsetM, negativeOffsetM, matchedPath, fakePath, + goldenPath, partnerSegmentXML); + if (auto const status = xmlSegment.child("Ignored")) + { + if (status.text().as_bool()) + m_segments.back().Ignore(); + } + } + } + catch (openlr::DecodedPathLoadError const & e) + { + MYTHROW(TrafficModeError, ("An exception occurred while parsing", dataFileName, e.Msg())); + } +#endif + + LOG(LINFO, (segments.size(), "segments are loaded.")); +} + +// TODO(mgsergio): Check if a path was committed, or commit it. +bool TrafficMode::SaveSampleAs(std::string const & fileName) const +{ + CHECK(!fileName.empty(), ("Can't save to an empty file.")); + + pugi::xml_document result; + result.reset(m_template); + auto root = result.document_element(); + +#ifdef openlr_obsolete + for (auto const & sc : m_segments) + { + auto segment = root.append_child("Segment"); + segment.append_copy(sc.GetPartnerXMLSegment()); + + if (sc.GetStatus() == SegmentCorrespondence::Status::Ignored) + { + segment.append_child("Ignored").text() = true; + } + if (sc.HasMatchedPath()) + { + auto node = segment.append_child("Route"); + openlr::PathToXML(sc.GetMatchedPath(), node); + } + if (sc.HasFakePath()) + { + auto node = segment.append_child("FakeRoute"); + openlr::PathToXML(sc.GetFakePath(), node); + } + if (sc.HasGoldenPath()) + { + auto node = segment.append_child("GoldenRoute"); + openlr::PathToXML(sc.GetGoldenPath(), node); + } + } +#endif + + result.save_file(fileName.data(), " " /* indent */); + return true; +} + +int TrafficMode::rowCount(const QModelIndex & parent) const +{ +#ifdef openlr_obsolete + return static_cast(m_segments.size()); +#else + // TODO return a meaningful value + return 0; +#endif +} + +int TrafficMode::columnCount(const QModelIndex & parent) const { return 4; } + +QVariant TrafficMode::data(const QModelIndex & index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= rowCount()) + return QVariant(); + + if (role != Qt::DisplayRole && role != Qt::EditRole) + return QVariant(); + +#ifdef openlr_obsolete + if (index.column() == 0) + return m_segments[index.row()].GetPartnerSegmentId(); + + if (index.column() == 1) + return static_cast(m_segments[index.row()].GetStatus()); + + if (index.column() == 2) + return m_segments[index.row()].GetPositiveOffset(); + + if (index.column() == 3) + return m_segments[index.row()].GetNegativeOffset(); +#endif + + return QVariant(); +} + +QVariant TrafficMode::headerData(int section, Qt::Orientation orientation, + int role /* = Qt::DisplayRole */) const +{ + if (orientation != Qt::Horizontal && role != Qt::DisplayRole) + return QVariant(); + + switch (section) + { + case 0: return "Segment id"; break; + case 1: return "Status code"; break; + case 2: return "Positive offset (Meters)"; break; + case 3: return "Negative offset (Meters)"; break; + } + UNREACHABLE(); +} + +void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection const &) +{ +#ifdef openlr_obsolete + ASSERT(!selected.empty(), ()); + ASSERT(!m_segments.empty(), ()); + + auto const row = selected.front().top(); + + CHECK_LESS(static_cast(row), m_segments.size(), ()); + m_currentSegment = &m_segments[row]; + + auto const & partnerSegment = m_currentSegment->GetPartnerSegment(); + auto const & partnerSegmentPoints = partnerSegment.GetMercatorPoints(); + auto const & viewportCenter = partnerSegmentPoints.front(); + + m_drawerDelegate->ClearAllPaths(); + // TODO(mgsergio): Use a better way to set viewport and scale. + m_drawerDelegate->SetViewportCenter(viewportCenter); + m_drawerDelegate->DrawEncodedSegment(partnerSegmentPoints); + if (m_currentSegment->HasMatchedPath()) + m_drawerDelegate->DrawDecodedSegments(GetPoints(m_currentSegment->GetMatchedPath())); + if (m_currentSegment->HasGoldenPath()) + m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); + + emit SegmentSelected(static_cast(partnerSegment.m_segmentId)); +#endif +} + +Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + + return QAbstractItemModel::flags(index); +} + +#ifdef openlr_obsolete +void TrafficMode::GoldifyMatchedPath() +{ + if (!m_currentSegment->HasMatchedPath()) + { + QMessageBox::information(nullptr /* parent */, "Error", + "The selected segment does not have a matched path"); + return; + } + + if (!StartBuildingPathChecks()) + return; + + m_currentSegment->SetGoldenPath(m_currentSegment->GetMatchedPath()); + m_goldenPath.clear(); + m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); +} + +void TrafficMode::StartBuildingPath() +{ + if (!StartBuildingPathChecks()) + return; + + m_currentSegment->SetGoldenPath({}); + + m_buildingPath = true; + m_drawerDelegate->ClearGoldenPath(); + m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport()); +} + +void TrafficMode::PushPoint(m2::PointD const & coord, std::vector const & points) +{ + impl::RoadPointCandidate point(points, coord); + if (!m_goldenPath.empty()) + m_goldenPath.back().ActivateCommonPoint(point); + m_goldenPath.push_back(point); +} + +void TrafficMode::PopPoint() +{ + CHECK(!m_goldenPath.empty(), ("Attempt to pop point from an empty path.")); + m_goldenPath.pop_back(); +} + +void TrafficMode::CommitPath() +{ + CHECK(m_currentSegment, ("No segments selected")); + + if (!m_buildingPath) + MYTHROW(TrafficModeError, ("Path building is not started")); + + SCOPE_GUARD(guard, [this] { emit EditingStopped(); }); + + m_buildingPath = false; + m_drawerDelegate->ClearAllVisualizedPoints(); + + if (m_goldenPath.size() == 1) + { + LOG(LDEBUG, ("Golden path is empty")); + return; + } + + CHECK_GREATER(m_goldenPath.size(), 1, ("Path cannot consist of only one point")); + + // Activate last point. Since no more points will be availabe we link it to the same + // feature as the previous one was linked to. + m_goldenPath.back().ActivateCommonPoint(m_goldenPath[GetPointsCount() - 2]); + + openlr::Path path; + for (size_t i = 1; i < GetPointsCount(); ++i) + { + auto const prevPoint = m_goldenPath[i - 1]; + auto point = m_goldenPath[i]; + + // The start and the end of the edge should lie on the same feature. + point.ActivateCommonPoint(prevPoint); + + auto const & prevFt = prevPoint.GetPoint(); + auto const & ft = point.GetPoint(); + + path.push_back(Edge::MakeReal( + ft.first, prevFt.second < ft.second /* forward */, base::checked_cast(prevFt.second), + geometry::PointWithAltitude(prevPoint.GetCoordinate(), 0 /* altitude */), + geometry::PointWithAltitude(point.GetCoordinate(), 0 /* altitude */))); + } + + m_currentSegment->SetGoldenPath(path); + m_goldenPath.clear(); +} + +void TrafficMode::RollBackPath() +{ + CHECK(m_currentSegment, ("No segments selected")); + CHECK(m_buildingPath, ("No path building is in progress.")); + + m_buildingPath = false; + + // TODO(mgsergio): Add a method for common visual manipulations. + m_drawerDelegate->ClearAllVisualizedPoints(); + m_drawerDelegate->ClearGoldenPath(); + if (m_currentSegment->HasGoldenPath()) + m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); + + m_goldenPath.clear(); + emit EditingStopped(); +} + +void TrafficMode::IgnorePath() +{ + CHECK(m_currentSegment, ("No segments selected")); + + if (m_currentSegment->HasGoldenPath()) + { + auto const btn = + QMessageBox::question(nullptr /* parent */, "Override warning", + "The selected segment has a golden path. Do you want to discard it?"); + if (btn == QMessageBox::No) + return; + } + + m_buildingPath = false; + + // TODO(mgsergio): Add a method for common visual manipulations. + m_drawerDelegate->ClearAllVisualizedPoints(); + m_drawerDelegate->ClearGoldenPath(); + + m_currentSegment->Ignore(); + m_goldenPath.clear(); + emit EditingStopped(); +} + +size_t TrafficMode::GetPointsCount() const +{ + return m_goldenPath.size(); +} + +m2::PointD const & TrafficMode::GetPoint(size_t const index) const +{ + return m_goldenPath[index].GetCoordinate(); +} + +m2::PointD const & TrafficMode::GetLastPoint() const +{ + CHECK(!m_goldenPath.empty(), ("Attempt to get point from an empty path.")); + return m_goldenPath.back().GetCoordinate(); +} + +std::vector TrafficMode::GetGoldenPathPoints() const +{ + std::vector coordinates; + for (auto const & roadPoint : m_goldenPath) + coordinates.push_back(roadPoint.GetCoordinate()); + return coordinates; +} + +// TODO(mgsergio): Draw the first point when the path size is 1. +void TrafficMode::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button) +{ + if (!m_buildingPath) + return; + + auto const currentPathLength = GetPointsCount(); + auto const lastClickedPoint = currentPathLength != 0 + ? GetLastPoint() + : m2::PointD::Zero(); + + auto const & p = m_pointsDelegate->GetCandidatePoints(clickPoint); + auto const & candidatePoints = p.first; + clickPoint = p.second; + if (candidatePoints.empty()) + return; + + auto reachablePoints = GetReachablePoints(clickPoint, GetGoldenPathPoints(), *m_pointsDelegate, + 0 /* lookBackIndex */); + auto const & clickablePoints = currentPathLength != 0 + ? GetReachablePoints(lastClickedPoint, GetGoldenPathPoints(), *m_pointsDelegate, + 1 /* lookbackIndex */) + // TODO(mgsergio): This is not quite correct since view port can change + // since first call to visualize points. But it's ok in general. + : m_pointsDelegate->GetAllJunctionPointsInViewport(); + + using ClickType = PointsControllerDelegateBase::ClickType; + switch (m_pointsDelegate->CheckClick(clickPoint, lastClickedPoint, clickablePoints)) + { + case ClickType::Add: + // TODO(mgsergio): Think of refactoring this with if (accumulator.empty) + // instead of pushing point first ad then removing last selection. + PushPoint(clickPoint, candidatePoints); + + if (currentPathLength > 0) + { + // TODO(mgsergio): Should I remove lastClickedPoint from clickablePoints + // as well? + RemovePointFromPull(lastClickedPoint, reachablePoints); + m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints()); + } + + m_drawerDelegate->ClearAllVisualizedPoints(); + m_drawerDelegate->VisualizePoints(reachablePoints); + m_drawerDelegate->VisualizePoints({clickPoint}); + break; + case ClickType::Remove: // TODO(mgsergio): Rename this case. + if (button == Qt::MouseButton::LeftButton) // RemovePoint + { + m_drawerDelegate->ClearAllVisualizedPoints(); + m_drawerDelegate->ClearGoldenPath(); + + PopPoint(); + if (m_goldenPath.empty()) + { + m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport()); + } + else + { + m_drawerDelegate->VisualizePoints(GetReachablePoints( + GetLastPoint(), GetGoldenPathPoints(), *m_pointsDelegate, 1 /* lookBackIndex */)); + } + + if (GetPointsCount() > 1) + m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints()); + } + else if (button == Qt::MouseButton::RightButton) + { + CommitPath(); + } + break; + case ClickType::Miss: + // TODO(mgsergio): This situation should be handled by checking candidatePoitns.empty() above. + // Not shure though if all cases are handled by that check. + return; + } +} + +bool TrafficMode::StartBuildingPathChecks() const +{ + CHECK(m_currentSegment, ("A segment should be selected before path building is started.")); + + if (m_buildingPath) + MYTHROW(TrafficModeError, ("Path building already in progress.")); + + if (m_currentSegment->HasGoldenPath()) + { + auto const btn = QMessageBox::question( + nullptr /* parent */, "Override warning", + "The selected segment already has a golden path. Do you want to override?"); + if (btn == QMessageBox::No) + return false; + } + + return true; +} +#endif +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/traffic_mode.hpp b/traffxml/traff_assessment_tool/traffic_mode.hpp new file mode 100644 index 000000000..edf31be11 --- /dev/null +++ b/traffxml/traff_assessment_tool/traffic_mode.hpp @@ -0,0 +1,141 @@ +#pragma once + +#include "points_controller_delegate_base.hpp" +#ifdef openlr_obsolete +#include "segment_correspondence.hpp" +#endif +#include "traffic_drawer_delegate_base.hpp" + +#ifdef openlr_obsolete +#include "openlr/decoded_path.hpp" +#endif + +#include "indexer/data_source.hpp" + +#include "base/exception.hpp" + +#include + +#include +#include +#include + +#include + + +class QItemSelection; +class Selection; + +DECLARE_EXCEPTION(TrafficModeError, RootException); + +namespace traffxml +{ +#ifdef openlr_obsolete +namespace impl +{ +/// This class denotes a "non-deterministic" feature point. +/// I.e. it is a set of all pairs +/// located at a specified coordinate. +/// Only one point at a time is considered active. +class RoadPointCandidate +{ +public: + RoadPointCandidate(std::vector const & points, + m2::PointD const & coord); + + void ActivateCommonPoint(RoadPointCandidate const & rpc); + openlr::FeaturePoint const & GetPoint() const; + m2::PointD const & GetCoordinate() const; + +private: + static size_t const kInvalidId; + + void SetActivePoint(FeatureID const & fid); + + m2::PointD m_coord = m2::PointD::Zero(); + std::vector m_points; + + size_t m_activePointIndex = kInvalidId; +}; +} // namespace impl +#endif + +/// This class is used to map sample ids to real data +/// and change sample evaluations. +class TrafficMode : public QAbstractTableModel +{ + Q_OBJECT + +public: + // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything. + TrafficMode(std::string const & dataFileName, DataSource const & dataSource, + std::unique_ptr drawerDelegate, + std::unique_ptr pointsDelegate, + QObject * parent = Q_NULLPTR); + + bool SaveSampleAs(std::string const & fileName) const; + + int rowCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + + QVariant data(const QModelIndex & index, int role) const Q_DECL_OVERRIDE; + + Qt::ItemFlags flags(QModelIndex const & index) const Q_DECL_OVERRIDE; + + bool IsBuildingPath() const { return m_buildingPath; } +#ifdef openlr_obsolete + void GoldifyMatchedPath(); + void StartBuildingPath(); + void PushPoint(m2::PointD const & coord, + std::vector const & points); + void PopPoint(); + void CommitPath(); + void RollBackPath(); + void IgnorePath(); + + size_t GetPointsCount() const; + m2::PointD const & GetPoint(size_t const index) const; + m2::PointD const & GetLastPoint() const; + std::vector GetGoldenPathPoints() const; +#endif + +public slots: + void OnItemSelected(QItemSelection const & selected, QItemSelection const &); + void OnClick(m2::PointD const & clickPoint, Qt::MouseButton const button) + { +#ifdef openlr_obsolete + HandlePoint(clickPoint, button); +#endif + } + +signals: + void EditingStopped(); + void SegmentSelected(int segmentId); + +private: +#ifdef openlr_obsolete + void HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button); + bool StartBuildingPathChecks() const; +#endif + + DataSource const & m_dataSource; +#ifdef openlr_obsolete + std::vector m_segments; + // Non-owning pointer to an element of m_segments. + SegmentCorrespondence * m_currentSegment = nullptr; +#endif + + std::unique_ptr m_drawerDelegate; + std::unique_ptr m_pointsDelegate; + + bool m_buildingPath = false; +#ifdef openlr_obsolete + std::vector m_goldenPath; +#endif + + // Clone this document and add things to its clone when saving sample. + pugi::xml_document m_template; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/traffic_panel.cpp b/traffxml/traff_assessment_tool/traffic_panel.cpp new file mode 100644 index 000000000..89030d16b --- /dev/null +++ b/traffxml/traff_assessment_tool/traffic_panel.cpp @@ -0,0 +1,81 @@ +#include "traffxml/traff_assessment_tool/traffic_panel.hpp" + +#include +#include +#include +#include +#include +#include + +namespace traffxml +{ +// ComboBoxDelegate -------------------------------------------------------------------------------- +ComboBoxDelegate::ComboBoxDelegate(QObject * parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget * ComboBoxDelegate::createEditor(QWidget * parent, QStyleOptionViewItem const & option, + QModelIndex const & index) const +{ + auto * editor = new QComboBox(parent); + editor->setFrame(false); + editor->setEditable(false); + editor->addItems({"Unevaluated", "Positive", "Negative", "RelPositive", "RelNegative", "Ignore"}); + + return editor; +} + +void ComboBoxDelegate::setEditorData(QWidget * editor, QModelIndex const & index) const +{ + auto const value = index.model()->data(index, Qt::EditRole).toString(); + static_cast(editor)->setCurrentText(value); +} + +void ComboBoxDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, + QModelIndex const & index) const +{ + model->setData(index, static_cast(editor)->currentText(), Qt::EditRole); +} + +void ComboBoxDelegate::updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option, + QModelIndex const & index) const +{ + editor->setGeometry(option.rect); +} + +// TrafficPanel ------------------------------------------------------------------------------------ +TrafficPanel::TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent) + : QWidget(parent) +{ + CreateTable(trafficModel); + + auto * layout = new QVBoxLayout(); + layout->addWidget(m_table); + setLayout(layout); + + // Select first segment by default; + auto const & index = m_table->model()->index(0, 0); + m_table->selectionModel()->select(index, QItemSelectionModel::Select); +} + +void TrafficPanel::CreateTable(QAbstractItemModel * trafficModel) +{ + m_table = new QTableView(); + m_table->setFocusPolicy(Qt::NoFocus); + m_table->setAlternatingRowColors(true); + m_table->setShowGrid(false); + m_table->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_table->verticalHeader()->setVisible(false); + m_table->horizontalHeader()->setVisible(true); + m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + m_table->setModel(trafficModel); + m_table->setItemDelegate(new ComboBoxDelegate()); + + connect(m_table->selectionModel(), + SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)), + trafficModel, SLOT(OnItemSelected(QItemSelection const &, QItemSelection const &))); +} +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/traffic_panel.hpp b/traffxml/traff_assessment_tool/traffic_panel.hpp new file mode 100644 index 000000000..27ab4d033 --- /dev/null +++ b/traffxml/traff_assessment_tool/traffic_panel.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +class QAbstractItemModel; +class QComboBox; +class QTableView; +class QWidget; + +namespace traffxml +{ +class ComboBoxDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + ComboBoxDelegate(QObject * parent = 0); + + QWidget * createEditor(QWidget * parent, QStyleOptionViewItem const & option, + QModelIndex const & index) const Q_DECL_OVERRIDE; + + void setEditorData(QWidget * editor, QModelIndex const & index) const Q_DECL_OVERRIDE; + + void setModelData(QWidget * editor, QAbstractItemModel * model, + QModelIndex const & index) const Q_DECL_OVERRIDE; + + void updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option, + QModelIndex const & index) const Q_DECL_OVERRIDE; +}; + +class TrafficPanel : public QWidget +{ + Q_OBJECT + +public: + explicit TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent); + +private: + void CreateTable(QAbstractItemModel * trafficModel); + void FillTable(); + +signals: + +public slots: + // void OnCheckBoxClicked(int row, int state); + +private: + QTableView * m_table = Q_NULLPTR; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp new file mode 100644 index 000000000..f916d6125 --- /dev/null +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp @@ -0,0 +1,54 @@ +#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h" +#include "ui_trafficmodeinitdlg.h" + +#include "platform/settings.hpp" + +#include + +#include + +namespace +{ +std::string const kDataFilePath = "LastOpenlrAssessmentDataFilePath"; +} // namespace + +namespace traffxml +{ +TrafficModeInitDlg::TrafficModeInitDlg(QWidget * parent) : + QDialog(parent), + m_ui(new Ui::TrafficModeInitDlg) +{ + m_ui->setupUi(this); + + std::string lastDataFilePath; + if (settings::Get(kDataFilePath, lastDataFilePath)) + m_ui->dataFileName->setText(QString::fromStdString(lastDataFilePath)); + + connect(m_ui->chooseDataFileButton, &QPushButton::clicked, [this](bool) { + SetFilePathViaDialog(*m_ui->dataFileName, tr("Choose data file"), "*.xml"); + }); +} + +TrafficModeInitDlg::~TrafficModeInitDlg() +{ + delete m_ui; +} + +void TrafficModeInitDlg::accept() +{ + m_dataFileName = m_ui->dataFileName->text().trimmed().toStdString(); + settings::Set(kDataFilePath, m_dataFileName); + QDialog::accept(); +} + +void TrafficModeInitDlg::SetFilePathViaDialog(QLineEdit & dest, QString const & title, + QString const & filter) +{ + QFileDialog openFileDlg(nullptr, title, {} /* directory */, filter); + openFileDlg.exec(); + if (openFileDlg.result() != QDialog::DialogCode::Accepted) + return; + + dest.setText(openFileDlg.selectedFiles().first()); +} +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.h b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h new file mode 100644 index 000000000..6ad874e14 --- /dev/null +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +class QLineEdit; + +namespace Ui { +class TrafficModeInitDlg; +} + +namespace traffxml +{ +class TrafficModeInitDlg : public QDialog +{ + Q_OBJECT + +public: + explicit TrafficModeInitDlg(QWidget * parent = nullptr); + ~TrafficModeInitDlg(); + + std::string GetDataFilePath() const { return m_dataFileName; } + +private: + void SetFilePathViaDialog(QLineEdit & dest, QString const & title, + QString const & filter = {}); +public slots: + void accept() override; + +private: + Ui::TrafficModeInitDlg * m_ui; + + std::string m_dataFileName; +}; +} // namespace traffxml diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui b/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui new file mode 100644 index 000000000..a7821ff6a --- /dev/null +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui @@ -0,0 +1,111 @@ + + + TrafficModeInitDlg + + + + 0 + 0 + 482 + 122 + + + + + 0 + 0 + + + + Select traffic files + + + true + + + + + 20 + 10 + 441 + 101 + + + + + + + Cancel + + + + + + + Choose... + + + + + + + Ok + + + + + + + + + + true + + + <html><head/><body><p>Data file:</p></body></html> + + + + + + + + dataFileName + chooseDataFileButton + + + + + cancelButton + clicked() + TrafficModeInitDlg + reject() + + + 290 + 103 + + + 164 + 98 + + + + + okButton + clicked() + TrafficModeInitDlg + accept() + + + 405 + 105 + + + 59 + 94 + + + + + From 62ee9d5b46da5d437ae4d7f544ca8c019b1843e2 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 17:08:34 +0300 Subject: [PATCH 069/252] [traffic] Abort event loop run immediately if TrafficManager is disabled Signed-off-by: mvglasow --- map/traffic_manager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 3fe4e02e2..a439210e4 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -472,6 +472,9 @@ void TrafficManager::ThreadRoutine() while (WaitForRequest()) { + if (!IsEnabled()) + continue; + // TODO clean out expired messages LOG(LINFO, ("start loop, active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); From 394a6673e5418c45f9c45a3e6145dc8aae5729fe Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 17:08:56 +0300 Subject: [PATCH 070/252] [traffxml] Silence compiler warning Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index c90c43ae5..12fc9a6c5 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -419,7 +419,7 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf for (auto segment : segments) { LOG(LINFO, (" Segment:", segment.m_segmentId)); - for (int i = 0; i < segment.m_locationReference.m_points.size(); i++) + for (size_t i = 0; i < segment.m_locationReference.m_points.size(); i++) { LOG(LINFO, (" ", i, ":", segment.m_locationReference.m_points[i].m_latLon)); if (i < segment.m_locationReference.m_points.size() - 1) From 5531b1129ba1af40608e1fec1f6d97440ca2b0b7 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 17:09:11 +0300 Subject: [PATCH 071/252] [traffxml] Code comment Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 12fc9a6c5..a67ec2d91 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -129,7 +129,12 @@ void TraffDecoder::ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml speedKmPH = speed.GetSpeedKmPH(cit->first.GetDir() == traffic::TrafficInfo::RoadSegmentId::kForwardDirection); } auto const idx = cit->first.GetIdx(); - // TODO sum of all lengths may differ from route length by up to ~6%, no idea why + /* + * TODO sum of all lengths may differ from route length by up to ~6% in either direction. + * This is partly due to the fact that route length also includes fake endings, which we are + * not counting here. However, this only explains routes being longer than the sum of all + * lengths calculated here, yet in some cases the route is shorter. + */ auto const length = mercator::DistanceOnEarth(points[idx], points[idx + 1]); if (speedKmPH != routing::kInvalidSpeed) normalDurationS += length * kOneMpSInKmpH / speedKmPH; From ba9980ba360b8a37c1cf813e70995441a80b50e6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 18:08:46 +0300 Subject: [PATCH 072/252] [traffic] Introduce test mode for traffic manager Signed-off-by: mvglasow --- map/framework.cpp | 2 + map/framework.hpp | 11 ++++ map/traffic_manager.cpp | 67 ++++++++++++++++--------- map/traffic_manager.hpp | 55 ++++++++++++++++++++ traffxml/traff_assessment_tool/main.cpp | 2 + 5 files changed, 113 insertions(+), 24 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 46b7c68f4..25a63094e 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -374,6 +374,8 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) editor.SetDelegate(make_unique(m_featuresFetcher.GetDataSource())); editor.SetInvalidateFn([this](){ InvalidateRect(GetCurrentViewport()); }); + if (params.m_trafficTestMode) + m_trafficManager.SetTestMode(); m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors()); diff --git a/map/framework.hpp b/map/framework.hpp index 51ad421cc..c98d03490 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -98,11 +98,22 @@ class Loader; /// build version for screenshots. //#define FIXED_LOCATION +/** + * @brief Initialization parameters for the framework. + * + * `FrameworkParams` is intended for parameters which are hardcoded rather than read from a + * configuration. It allows test cases to run on a tailored configuration. + */ struct FrameworkParams { bool m_enableDiffs = true; size_t m_numSearchAPIThreads = 1; + /** + * @brief Whether the traffic manager should start in test mode. + */ + bool m_trafficTestMode = false; + FrameworkParams() = default; FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index a439210e4..82f9b983c 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -126,7 +126,10 @@ void TrafficManager::SetEnabled(bool enabled) m_drapeEngine.SafeCall(&df::DrapeEngine::EnableTraffic, enabled); if (enabled) + { Invalidate(); + m_canSetMode = false; + } else m_observer.OnTrafficInfoClear(); } @@ -477,27 +480,30 @@ void TrafficManager::ThreadRoutine() // TODO clean out expired messages - LOG(LINFO, ("start loop, active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); - - // this is a no-op if active MWMs have not changed - if (!SetSubscriptionArea()) + if (!IsTestMode()) { - LOG(LWARNING, ("SetSubscriptionArea failed.")); - if (!IsSubscribed()) - // do not skip out of the loop, we may need to process pushed feeds - LOG(LWARNING, ("No subscription, no traffic data will be retrieved.")); - } + LOG(LINFO, ("start loop, active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); + + // this is a no-op if active MWMs have not changed + if (!SetSubscriptionArea()) + { + LOG(LWARNING, ("SetSubscriptionArea failed.")); + if (!IsSubscribed()) + // do not skip out of the loop, we may need to process pushed feeds + LOG(LWARNING, ("No subscription, no traffic data will be retrieved.")); + } - /* + /* * Fetch traffic data if needed and we have a subscription. * m_isPollNeeded may be set by WaitForRequest() and set/unset by SetSubscriptionArea(). */ - if (m_isPollNeeded && IsSubscribed()) - { - if (!Poll()) + if (m_isPollNeeded && IsSubscribed()) { - LOG(LWARNING, ("Poll failed.")); - // TODO set failed status somewhere and retry + if (!Poll()) + { + LOG(LWARNING, ("Poll failed.")); + // TODO set failed status somewhere and retry + } } } LOG(LINFO, (m_feedQueue.size(), "feed(s) in queue")); @@ -568,21 +574,24 @@ bool TrafficManager::WaitForRequest() return true; } - // if update interval has elapsed, return immediately - auto const currentTime = steady_clock::now(); - auto const passedSeconds = currentTime - m_lastResponseTime; - if (passedSeconds >= kUpdateInterval) + if (!IsTestMode()) { - LOG(LINFO, ("last response was", passedSeconds, "ago, returning immediately")); - m_isPollNeeded = true; - return true; + // if update interval has elapsed, return immediately + auto const currentTime = steady_clock::now(); + auto const passedSeconds = currentTime - m_lastResponseTime; + if (passedSeconds >= kUpdateInterval) + { + LOG(LINFO, ("last response was", passedSeconds, "ago, returning immediately")); + m_isPollNeeded = true; + return true; + } } } LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { - return !m_isRunning || m_activeMwmsChanged; + return !m_isRunning || (m_activeMwmsChanged && !IsTestMode()); }); // check again if we got terminated while waiting (or woken up because we got terminated) @@ -593,7 +602,7 @@ bool TrafficManager::WaitForRequest() if (IsEnabled()) m_isPollNeeded |= timeout; - LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged)); + LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged, "test mode:", IsTestMode())); return true; } @@ -960,6 +969,16 @@ void TrafficManager::SetSimplifiedColorScheme(bool simplified) m_drapeEngine.SafeCall(&df::DrapeEngine::SetSimplifiedTrafficColors, simplified); } +void TrafficManager::SetTestMode() +{ + if (!m_canSetMode) + { + LOG(LWARNING, ("Mode cannot be set once the traffic manager has been enabled")); + return; + } + m_mode = Mode::Test; +} + std::string DebugPrint(TrafficManager::TrafficState state) { switch (state) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 1a87ef717..43b8cc1f9 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -62,6 +62,31 @@ class TrafficManager final ExpiredApp }; + /** + * @brief The mode for the traffic manager. + * + * Future versions may introduce further test modes. Therefore, always use `TrafficManager::IsTestMode()` + * to verify if the traffic manager is running in test mode. + */ + enum class Mode + { + /** + * Traffic manager mode for normal operation. + * + * This is the default mode unless something else is explicitly set. + */ + Normal, + /** + * Test mode. + * + * This mode will prevent the traffic manager from automatically subscribing to sources and + * polling them. It will still receive and process push feeds. + * + * Future versions may introduce further behavior changes, and/or introduce more test modes. + */ + Test + }; + struct MyPosition { m2::PointD m_position = m2::PointD(0.0, 0.0); @@ -161,6 +186,24 @@ class TrafficManager final void SetSimplifiedColorScheme(bool simplified); bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; } + /** + * @brief Whether the traffic manager is operating in test mode. + */ + bool IsTestMode() { return m_mode != Mode::Normal; } + + /** + * @brief Switches the traffic manager into test mode. + * + * The mode can only be set before the traffic manager is first enabled. After that, this method + * will log a warning but otherwise do nothing. + * + * In test mode, the traffic manager will not subscribe to sources or poll them automatically. + * It will still receive and process push feeds. + * + * Future versions may introduce further behavior changes. + */ + void SetTestMode(); + private: /** * @brief Holds information about pending or previous traffic requests pertaining to an MWM. @@ -481,6 +524,18 @@ class TrafficManager final std::pair m_currentPosition = {MyPosition(), false}; std::pair m_currentModelView = {ScreenBase(), false}; + /** + * The mode in which the traffic manager is running. + */ + Mode m_mode = Mode::Normal; + + /** + * Whether the traffic manager accepts mode changes. + * + * Mode cannt be set after the traffic manager has been enabled for the first time. + */ + bool m_canSetMode = true; + std::atomic m_state; TrafficStateChangedFn m_onStateChangedFn; diff --git a/traffxml/traff_assessment_tool/main.cpp b/traffxml/traff_assessment_tool/main.cpp index 810d7bb4f..188c5adb6 100644 --- a/traffxml/traff_assessment_tool/main.cpp +++ b/traffxml/traff_assessment_tool/main.cpp @@ -32,6 +32,8 @@ int main(int argc, char * argv[]) FrameworkParams params; + params.m_trafficTestMode = true; + Framework framework(params); traffxml::MainWindow mainWindow(framework); From daaf52d27ddaac5c01addb487f717c5dd5054d90 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 20:37:28 +0300 Subject: [PATCH 073/252] [traffic] Fix Push() and make it public for testing Signed-off-by: mvglasow --- map/traffic_manager.cpp | 11 +++++++---- map/traffic_manager.hpp | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 82f9b983c..6d16e4bec 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -372,9 +372,12 @@ bool TrafficManager::Poll() void TrafficManager::Push(traffxml::TraffFeed feed) { - std::lock_guard lock(m_mutex); - m_feedQueue.push_back(feed); - // TODO should we update m_lastResponseTime? + { + std::lock_guard lock(m_mutex); + m_feedQueue.push_back(feed); + // TODO should we update m_lastResponseTime? + } + m_condition.notify_one(); } void TrafficManager::ConsolidateFeedQueue() @@ -591,7 +594,7 @@ bool TrafficManager::WaitForRequest() LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { - return !m_isRunning || (m_activeMwmsChanged && !IsTestMode()); + return !m_isRunning || (m_activeMwmsChanged && !IsTestMode()) || !m_feedQueue.empty(); }); // check again if we got terminated while waiting (or woken up because we got terminated) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 43b8cc1f9..97c7f0196 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -204,6 +204,17 @@ class TrafficManager final */ void SetTestMode(); + /** + * @brief Processes a traffic feed received through a push operation. + * + * Push is safe to call from any thread. + * + * Push operations are not supported on all platforms. + * + * @param feed The traffic feed. + */ + void Push(traffxml::TraffFeed feed); + private: /** * @brief Holds information about pending or previous traffic requests pertaining to an MWM. @@ -313,17 +324,6 @@ class TrafficManager final */ bool Poll(); - /** - * @brief Processes a traffic feed received through a push operation. - * - * Push is safe to call from any thread. - * - * Push operations are not supported on all platforms. - * - * @param feed The traffic feed. - */ - void Push(traffxml::TraffFeed feed); - /** * @brief Consolidates the feed queue. * From f30316d8684cfb5cd2674991ab5c9b0a3b6045d9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 6 Jun 2025 20:38:19 +0300 Subject: [PATCH 074/252] [traff_assessment_tool] Open TraFF feed Signed-off-by: mvglasow --- map/traffic_manager.cpp | 1 + traffxml/traff_assessment_tool/mainwindow.cpp | 35 ++++++++++++++++--- traffxml/traff_assessment_tool/mainwindow.hpp | 3 ++ .../trafficmodeinitdlg.cpp | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 6d16e4bec..d701f5831 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -594,6 +594,7 @@ bool TrafficManager::WaitForRequest() LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { + // return true for any condition we want to process immediately return !m_isRunning || (m_activeMwmsChanged && !IsTestMode()) || !m_feedQueue.empty(); }); diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index e9d51da06..c60ba3a5d 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -18,6 +18,8 @@ #include "storage/country_parent_getter.hpp" +#include "traffxml/traff_model_xml.hpp" + #include "geometry/mercator.hpp" #include "geometry/point2d.hpp" @@ -350,21 +352,44 @@ void MainWindow::OnOpenTrafficSample() if (dlg.result() != QDialog::DialogCode::Accepted) return; + pugi::xml_document document; + LOG(LINFO, ("Attempting to load:", dlg.GetDataFilePath())); + auto const load_result = document.load_file(dlg.GetDataFilePath().data()); + if (!load_result) + { + QMessageBox::critical(this, "Data loading error", QString::asprintf("Can't load file %s: %s", dlg.GetDataFilePath().data(), load_result.description())); + LOG(LERROR, ("Can't load file", dlg.GetDataFilePath(), ":", load_result.description())); + return; + } + + std::setlocale(LC_ALL, "en_US.UTF-8"); + traffxml::TraffFeed feed; + if (traffxml::ParseTraff(document, feed)) + { + LOG(LINFO, ("TraFF data parsed successfully, pushing")); + m_framework.GetTrafficManager().Push(feed); + LOG(LINFO, ("Push completed")); + } + else + { + QMessageBox::critical(this, "Data loading error", QString("An error occurred parsing the TraFF feed")); + LOG(LWARNING, ("An error occurred parsing the TraFF feed")); + return; + } + +// TODO create traffic panel with TraFF messages +#if 0 try { CreateTrafficPanel(dlg.GetDataFilePath()); } -#ifdef openlr_obsolete catch (TrafficModeError const & e) -#else - // TODO do we need to catch exceptions other than our own here? - catch (RootException const & e) -#endif { QMessageBox::critical(this, "Data loading error", QString("Can't load data file.")); LOG(LERROR, (e.Msg())); return; } +#endif #ifdef openlr_obsolete m_goldifyMatchedPathAction->setEnabled(true /* enabled */); diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp index 9ad80e2fa..50101344e 100644 --- a/traffxml/traff_assessment_tool/mainwindow.hpp +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -36,6 +36,9 @@ class MainWindow : public QMainWindow void CreateTrafficPanel(std::string const & dataFilePath); void DestroyTrafficPanel(); + /** + * Called when the user requests to open a sample file. + */ void OnOpenTrafficSample(); void OnCloseTrafficSample(); void OnSaveTrafficSample(); diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp index f916d6125..8560ead4a 100644 --- a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp @@ -9,7 +9,7 @@ namespace { -std::string const kDataFilePath = "LastOpenlrAssessmentDataFilePath"; +std::string const kDataFilePath = "LastTraffAssessmentDataFilePath"; } // namespace namespace traffxml From 488159e2f94febf1ecf89d4266b3c054198bbea3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 14:41:24 +0300 Subject: [PATCH 075/252] [traffic] Implement Clear() Signed-off-by: mvglasow --- map/traffic_manager.cpp | 114 ++++++++++++++++++++++++++++++---------- map/traffic_manager.hpp | 54 +++++++++++-------- 2 files changed, 116 insertions(+), 52 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index d701f5831..049e0644a 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -136,17 +136,38 @@ void TrafficManager::SetEnabled(bool enabled) void TrafficManager::Clear() { -// TODO no longer needed + { + std::lock_guard lock(m_mutex); + + LOG(LINFO, ("Messages in cache:", m_messageCache.size())); + LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); + LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); + LOG(LINFO, ("MWM cache size:", m_mwmCache.size())); + LOG(LINFO, ("Clearing...")); + // TODO no longer needed #ifdef traffic_dead_code - m_currentCacheSizeBytes = 0; + m_currentCacheSizeBytes = 0; #endif - m_mwmCache.clear(); - m_lastDrapeMwmsByRect.clear(); - m_lastRoutingMwmsByRect.clear(); - m_activeDrapeMwms.clear(); - m_activeRoutingMwms.clear(); - m_requestedMwms.clear(); - m_trafficETags.clear(); + m_messageCache.clear(); + m_feedQueue.clear(); + m_allMwmColoring.clear(); + m_mwmCache.clear(); + + // TODO figure out which of the ones below we still need + m_lastDrapeMwmsByRect.clear(); + m_lastRoutingMwmsByRect.clear(); + // TODO clearing these breaks ForEachActiveMwm, can we leave them? + //m_activeDrapeMwms.clear(); + //m_activeRoutingMwms.clear(); + m_requestedMwms.clear(); + m_trafficETags.clear(); + + LOG(LINFO, ("Messages in cache:", m_messageCache.size())); + LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); + LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); + LOG(LINFO, ("MWM cache size:", m_mwmCache.size())); + } + OnTrafficDataUpdate(); } void TrafficManager::SetDrapeEngine(ref_ptr engine) @@ -450,21 +471,29 @@ void TrafficManager::DecodeFirstMessage() m_feedQueue.erase(m_feedQueue.begin()); } - // check if message is actually newer - auto it = m_messageCache.find(message.m_id); - bool process = (it == m_messageCache.end()); - if (!process) - process = (it->second.m_updateTime < message.m_updateTime); - if (!process) { - LOG(LINFO, ("message", message.m_id, "is already in cache, skipping")); - return; + std::lock_guard lock(m_mutex); + + // check if message is actually newer + auto it = m_messageCache.find(message.m_id); + bool process = (it == m_messageCache.end()); + if (!process) + process = (it->second.m_updateTime < message.m_updateTime); + if (!process) + { + LOG(LINFO, ("message", message.m_id, "is already in cache, skipping")); + return; + } } LOG(LINFO, (" ", message.m_id, ":", message)); m_traffDecoder->DecodeMessage(message); - // store message in cache - m_messageCache.insert_or_assign(message.m_id, message); + { + std::lock_guard lock(m_mutex); + + // store message in cache + m_messageCache.insert_or_assign(message.m_id, message); + } // store message coloring in AllMwmColoring // TODO trigger full cache processing if segments were removed or traffic has eased traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); @@ -518,7 +547,9 @@ void TrafficManager::ThreadRoutine() DecodeFirstMessage(); // set new coloring for MWMs - OnTrafficDataUpdate(m_allMwmColoring); + // `m_mutex` is obtained inside the method, no need to do it here + // TODO drop the argument, use class member inside method + OnTrafficDataUpdate(); // TODO no longer needed #ifdef traffic_dead_code @@ -688,15 +719,21 @@ void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) } #endif -void TrafficManager::OnTrafficDataUpdate(std::map & trafficCache) +void TrafficManager::OnTrafficDataUpdate() { + bool feedQueueEmpty = false; + + { + std::lock_guard lock(m_mutex); + feedQueueEmpty = m_feedQueue.empty(); + } // Whether to notify the Drape engine of the update. - bool notifyDrape = (m_feedQueue.empty()); + bool notifyDrape = (feedQueueEmpty); // Whether to notify the observer of the update. - bool notifyObserver = (m_feedQueue.empty()); + bool notifyObserver = (feedQueueEmpty); - if (!m_feedQueue.empty()) + if (!feedQueueEmpty) { auto const currentTime = steady_clock::now(); auto const drapeAge = currentTime - m_lastDrapeUpdate; @@ -720,13 +757,14 @@ void TrafficManager::OnTrafficDataUpdate(std::map lock(m_mutex); + ASSERT(mwmId.IsAlive(), ()); - auto tcit = trafficCache.find(mwmId); - if (tcit != trafficCache.end()) + auto tcit = m_allMwmColoring.find(mwmId); + if (tcit != m_allMwmColoring.end()) { - std::lock_guard lock(m_mutex); - traffic::TrafficInfo::Coloring coloring = tcit->second; LOG(LINFO, ("Setting new coloring for", mwmId, "with", coloring.size(), "entries")); traffic::TrafficInfo info(mwmId, std::move(coloring)); @@ -758,6 +796,24 @@ void TrafficManager::OnTrafficDataUpdate(std::map(mwmId)); + m_lastDrapeUpdate = steady_clock::now(); + } + + if (notifyObserver) + { + // Update traffic colors for routing. + m_observer.OnTrafficInfoRemoved(mwmId); + m_lastObserverUpdate = steady_clock::now(); + } + } }); } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 97c7f0196..c47a1723a 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -215,6 +215,26 @@ class TrafficManager final */ void Push(traffxml::TraffFeed feed); + /** + * @brief Clears the entire traffic cache. + * + * This is currently called when the traffic manager is enabled or disabled. + * + * The old MWM traffic architecture was somewhat liberal in clearing its cache and re-fetching + * traffic data. This was possible because data was pre-processed and required no processing + * beyond deserialization, whereas TraFF data is more expensive to recreate. Also, the old + * architecture lacked any explicit notion of expiration; the app decided that data was to be + * considered stale after a certain period of time. TraFF, in contrast, has an explicit expiration + * time for each message, which can be anywhere from a few minutes to several weeks or months. + * Messages that have expired get deleted individually. + * For this reason, the TraFF message cache should not be cleared out under normal conditions + * (the main exception being tests). + * + * @todo Currently not implemented for TraFF; implement it for test purposes but do not call when + * the enabled state changes. + */ + void Clear(); + private: /** * @brief Holds information about pending or previous traffic requests pertaining to an MWM. @@ -365,9 +385,9 @@ class TrafficManager final /** * @brief Processes new traffic data. * - * @param trafficCache The new per-MWM colorings (preprocessed traffic information). + * The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`. */ - void OnTrafficDataUpdate(std::map &trafficCache); + void OnTrafficDataUpdate(); // TODO no longer needed #ifdef traffic_dead_code @@ -444,26 +464,6 @@ class TrafficManager final */ void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force); - /** - * @brief Clears the entire traffic cache. - * - * This is currently called when the traffic manager is enabled or disabled. - * - * The old MWM traffic architecture was somewhat liberal in clearing its cache and re-fetching - * traffic data. This was possible because data was pre-processed and required no processing - * beyond deserialization, whereas TraFF data is more expensive to recreate. Also, the old - * architecture lacked any explicit notion of expiration; the app decided that data was to be - * considered stale after a certain period of time. TraFF, in contrast, has an explicit expiration - * time for each message, which can be anywhere from a few minutes to several weeks or months. - * Messages that have expired get deleted individually. - * For this reason, the TraFF message cache should not be cleared out under normal conditions - * (the main exception being tests). - * - * @todo Currently not implemented for TraFF; implement it for test purposes but do not call when - * the enabled state changes. - */ - void Clear(); - /** * @brief Removes traffic data for one specific MWM from the cache. * @@ -575,7 +575,7 @@ class TrafficManager final * * Invalidate(), clears the vector but not the set. * * UpdateActiveMwms(), uses the vector to detect changes. If so, it updates both vector and set. * - * Clear() clears both the set and the vector. + * Clear() clears both the set and the vector. (Clearing the set is currently disabled as it breaks ForEachActiveMwm.) */ std::vector m_lastDrapeMwmsByRect; std::set m_activeDrapeMwms; @@ -647,6 +647,10 @@ class TrafficManager final * @brief Cache of all currently active TraFF messages. * * Keys are message IDs, values are messages. + * + * Threads must lock `m_mutex` before accessing `m_messageCache`, as access can happen from + * multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI + * thread). */ std::map m_messageCache; @@ -659,6 +663,10 @@ class TrafficManager final /** * @brief Map between MWM IDs and their colorings. + * + * Threads must lock `m_mutex` before accessing `m_allMwmColoring`, as access can happen from + * multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI + * thread). */ std::map m_allMwmColoring; }; From 9f4b6d73cea3d40eb5a14f8c3841f709a8ff16c5 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 14:41:54 +0300 Subject: [PATCH 076/252] [traff_assessment_tool] Add UI for Clear() Signed-off-by: mvglasow --- traffxml/traff_assessment_tool/mainwindow.cpp | 6 ++++++ traffxml/traff_assessment_tool/mainwindow.hpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index c60ba3a5d..da3391310 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -265,6 +265,7 @@ MainWindow::MainWindow(Framework & framework) menuBar()->addMenu(fileMenu); fileMenu->addAction("Open sample", QKeySequence("Ctrl+O"), this, &MainWindow::OnOpenTrafficSample); + fileMenu->addAction("Clear TraFF cache", QKeySequence("Ctrl+D"), this, &MainWindow::OnClearCache); m_closeTrafficSampleAction = fileMenu->addAction("Close sample", QKeySequence("Ctrl+W"), this, &MainWindow::OnCloseTrafficSample); m_saveTrafficSampleAction = fileMenu->addAction("Save sample", QKeySequence("Ctrl+S"), this, &MainWindow::OnSaveTrafficSample); @@ -400,6 +401,11 @@ void MainWindow::OnOpenTrafficSample() #endif } +void MainWindow::OnClearCache() +{ + m_framework.GetTrafficManager().Clear(); +} + void MainWindow::OnCloseTrafficSample() { // TODO(mgsergio): diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp index 50101344e..e0b9a9058 100644 --- a/traffxml/traff_assessment_tool/mainwindow.hpp +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -40,6 +40,11 @@ class MainWindow : public QMainWindow * Called when the user requests to open a sample file. */ void OnOpenTrafficSample(); + + /** + * Called when the user requests to clear the cache. + */ + void OnClearCache(); void OnCloseTrafficSample(); void OnSaveTrafficSample(); void OnPathEditingStop(); From 3eb99e952c45f3e83d2c72106a99aecc37969ee8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 14:42:24 +0300 Subject: [PATCH 077/252] [map] Documentation and comments Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 +- map/traffic_manager.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 049e0644a..b30bb18b5 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -584,7 +584,7 @@ void TrafficManager::ThreadRoutine() mwms.clear(); #endif } - // Calling Unsubscribe() form the worker thread on exit makes thread synchronization easier + // Calling Unsubscribe() from the worker thread on exit makes thread synchronization easier Unsubscribe(); } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c47a1723a..487c70829 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -532,7 +532,7 @@ class TrafficManager final /** * Whether the traffic manager accepts mode changes. * - * Mode cannt be set after the traffic manager has been enabled for the first time. + * Mode cannot be set after the traffic manager has been enabled for the first time. */ bool m_canSetMode = true; @@ -631,7 +631,7 @@ class TrafficManager final * @brief Whether a poll operation is needed. * * Used in the worker thread. A poll operation is needed unless a subscription (or subscription - * change) operation was performed before and a feed was received a part of it. + * change) operation was performed before and a feed was received as part of it. */ bool m_isPollNeeded; From 588332a23bfffca65d62bb6aeb5247030c39bf28 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 15:06:37 +0300 Subject: [PATCH 078/252] [traffic] Remove dead code Signed-off-by: mvglasow --- map/traffic_manager.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index b30bb18b5..696fab397 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -156,9 +156,6 @@ void TrafficManager::Clear() // TODO figure out which of the ones below we still need m_lastDrapeMwmsByRect.clear(); m_lastRoutingMwmsByRect.clear(); - // TODO clearing these breaks ForEachActiveMwm, can we leave them? - //m_activeDrapeMwms.clear(); - //m_activeRoutingMwms.clear(); m_requestedMwms.clear(); m_trafficETags.clear(); From ae5dea4a53186889e3a170a2067b70255a3b5087 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 15:19:49 +0300 Subject: [PATCH 079/252] [traffic] Comment out more obsolete code Signed-off-by: mvglasow --- map/traffic_manager.cpp | 10 +++++++++- map/traffic_manager.hpp | 11 +++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 696fab397..f8642b278 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -156,8 +156,10 @@ void TrafficManager::Clear() // TODO figure out which of the ones below we still need m_lastDrapeMwmsByRect.clear(); m_lastRoutingMwmsByRect.clear(); +#ifdef traffic_dead_code m_requestedMwms.clear(); m_trafficETags.clear(); +#endif LOG(LINFO, ("Messages in cache:", m_messageCache.size())); LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); @@ -182,6 +184,8 @@ void TrafficManager::OnMwmDeregistered(platform::LocalCountryFile const & countr if (!IsEnabled()) return; +// TODO no longer needed +#ifdef traffic_dead_code { std::lock_guard lock(m_mutex); @@ -197,6 +201,7 @@ void TrafficManager::OnMwmDeregistered(platform::LocalCountryFile const & countr ClearCache(mwmId); } +#endif } void TrafficManager::OnDestroySurface() @@ -663,7 +668,10 @@ void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId, bool force) if (needRequesting) { +// TODO no longer needed +#ifdef traffic_dead_code m_requestedMwms.push_back(mwmId); +#endif m_condition.notify_one(); } } @@ -883,7 +891,6 @@ void TrafficManager::ShrinkCacheToAllowableSize() } } } -#endif void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId) { @@ -913,6 +920,7 @@ void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId) m_lastDrapeMwmsByRect.clear(); m_lastRoutingMwmsByRect.clear(); } +#endif bool TrafficManager::IsEnabled() const { diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 487c70829..eb862cfc1 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -464,6 +464,8 @@ class TrafficManager final */ void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force); + // TODO no longer needed +#ifdef traffic_dead_code /** * @brief Removes traffic data for one specific MWM from the cache. * @@ -476,8 +478,6 @@ class TrafficManager final * @param mwmId The mwmId for which to remove traffic data. */ void ClearCache(MwmSet::MwmId const & mwmId); -// TODO no longer needed -#ifdef traffic_dead_code void ShrinkCacheToAllowableSize(); #endif @@ -582,17 +582,24 @@ class TrafficManager final std::vector m_lastRoutingMwmsByRect; std::set m_activeRoutingMwms; +// TODO no longer needed +#ifdef traffic_dead_code // The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. // It is one of several mechanisms that HTTP provides for web cache validation, // which allows a client to make conditional requests. std::map m_trafficETags; +#endif std::atomic m_isPaused; +// TODO no longer needed +#ifdef traffic_dead_code /** * @brief MWMs for which to retrieve traffic data. */ std::vector m_requestedMwms; +#endif + std::mutex m_mutex; /** From 8cffe8fa648350b8341420882ff63acc9a008ec1 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 16:41:22 +0300 Subject: [PATCH 080/252] [traffic] Documentation and comments Signed-off-by: mvglasow --- map/traffic_manager.cpp | 1 + map/traffic_manager.hpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index f8642b278..56a79a8b2 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -136,6 +136,7 @@ void TrafficManager::SetEnabled(bool enabled) void TrafficManager::Clear() { + // TODO what should we do about subscriptions? { std::lock_guard lock(m_mutex); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index eb862cfc1..96acda64b 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -600,6 +600,11 @@ class TrafficManager final std::vector m_requestedMwms; #endif + /** + * @brief Mutex for access to shared members. + * + * Threads which access shared members (see documentation) must lock this mutex while doing so. + */ std::mutex m_mutex; /** From d72bd9e00ef2518ec9cd71564d2378c2e835ae1f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 7 Jun 2025 20:10:57 +0300 Subject: [PATCH 081/252] [traffic] Update traffic for all MWMs, active or not Signed-off-by: mvglasow --- map/traffic_manager.cpp | 16 +++++++++++----- map/traffic_manager.hpp | 8 ++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 56a79a8b2..9b0f8a6fb 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -756,17 +756,23 @@ void TrafficManager::OnTrafficDataUpdate() /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * - * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData(). + * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData(); + * modification: cycle over all MWMs (active or not). * trafficCache lookup is original code. - * TrafficInfo construction is taken fron TheadRoutine(), with modifications (different constructor). + * TrafficInfo construction is taken fron ThreadRoutine(), with modifications (different constructor). * Updating m_mwmCache is from RequestTrafficData(MwmSet::MwmId const &, bool), with modifications. * The remainder of the loop is from OnTrafficDataResponse(traffic::TrafficInfo &&), with some modifications - * (deciding whether to notify a component and managing timestamps is original code) + * (deciding whether to notify a component and managing timestamps is original code). + * Existing coloring deletion (if there is no new coloring) is original code. */ - // TODO do this for each MWM, active or not - ForEachActiveMwm([this, notifyDrape, notifyObserver](MwmSet::MwmId const & mwmId) { + ForEachMwm([this, notifyDrape, notifyObserver](std::shared_ptr info) { std::lock_guard lock(m_mutex); + if (info->GetCountryName().starts_with(WORLD_FILE_NAME)) + return; + + MwmSet::MwmId const mwmId(info); + ASSERT(mwmId.IsAlive(), ()); auto tcit = m_allMwmColoring.find(mwmId); if (tcit != m_allMwmColoring.end()) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 96acda64b..1ba78b605 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -503,6 +503,14 @@ class TrafficManager final void Pause(); void Resume(); + template + void ForEachMwm(F && f) const + { + std::vector> allMwmInfo; + m_dataSource.GetMwmsInfo(allMwmInfo); + std::for_each(allMwmInfo.begin(), allMwmInfo.end(), std::forward(f)); + } + template void ForEachActiveMwm(F && f) const { From d47713516d713de12de565a9f50e913de2f57540 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 8 Jun 2025 19:28:27 +0300 Subject: [PATCH 082/252] [traffxml] Ensure decoder uses newly-added maps Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 91 ++++++++++++++++++++++---------------- traffxml/traff_decoder.hpp | 27 ++++++++++- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index a67ec2d91..632354b7d 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -528,30 +528,38 @@ RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGet std::map & messageCache) : TraffDecoder(dataSource, countryInfoGetter, countryParentNameGetter, messageCache) { + m_dataSource.AddObserver(*this); InitRouter(); } +void RoutingTraffDecoder::OnMapRegistered(platform::LocalCountryFile const & localFile) +{ + std::lock_guard lock(m_mutex); + // register with our router instance, unless it is World or WorldCoasts. + if (!localFile.GetCountryName().starts_with(WORLD_FILE_NAME)) + m_numMwmIds->RegisterFile(localFile.GetCountryFile()); +} + bool RoutingTraffDecoder::InitRouter() { + std::lock_guard lock(m_mutex); if (m_router) return true; - // code mostly from RoutingManager::SetRouterImpl(RouterType) /* - * RoutingManager::SetRouterImpl(RouterType) calls m_delegate.RegisterCountryFilesOnRoute(numMwmIds). - * m_delegate is the framework, and the routine cycles through the countries in storage. - * As we don’t have access to storage, we get our country files from the data source. + * Code based on RoutingManager::SetRouterImpl(RouterType), which calls + * m_delegate.RegisterCountryFilesOnRoute(numMwmIds); m_delegate being the framework instance. + * RegisterCountryFilesOnRoute() is protected and uses a private `Storage` instance. + * We therefore have to resort to populating `m_numMwmIds` from `m_dataSource`. Unlike the + * “original”, this will only include MWMs loaded on startup, not those added later. + * For these, we register ourselves as an MwmSet::Observer and add maps to `m_numMwms` as they + * are registered. + * World and WorldCoasts must be excluded (as in the original routine), as they would cause the + * router to return bogus routes. Just like the original, we use a string comparison for this. */ std::vector> mwmsInfo; m_dataSource.GetMwmsInfo(mwmsInfo); - /* TODO this should include all countries (whether we have the MWM or not), except World and WorldCoasts. - * Excluding World and WorldCoasts is important, else the router will return bogus routes. - * Storage uses a string comparison for filtering, we do the same here. - storage.ForEachCountry([&](storage::Country const & country) - { - numMwmIds->RegisterFile(country.GetFile()); - }); - */ + for (auto mwmInfo : mwmsInfo) if (!mwmInfo->GetCountryName().starts_with(WORLD_FILE_NAME)) m_numMwmIds->RegisterFile(mwmInfo->GetLocalFile().GetCountryFile()); @@ -702,41 +710,46 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa */ routing::RouterDelegate delegate; delegate.SetTimeout(kRouterTimeoutSec); + routing::RouterResultCode code; + std::shared_ptr route; - if (!m_router && !InitRouter()) - return; + { + std::lock_guard lock(m_mutex); - /* + if (!m_router && !InitRouter()) + return; + + /* * TODO is that for following a track? If so, can we use that with just 2–3 reference points? * – Doesn’t look like it, m_guides only seems to get used in test functions */ - //router->SetGuides(std::move(m_guides)); - //m_guides.clear(); + //router->SetGuides(std::move(m_guides)); + //m_guides.clear(); - auto route = std::make_shared(m_router->GetName(), routeId); - routing::RouterResultCode code; + route = std::make_shared(m_router->GetName(), routeId); - base::Timer timer; - double elapsedSec = 0.0; + base::Timer timer; + double elapsedSec = 0.0; - try - { - LOG(LINFO, ("Calculating the route of direct length", checkpoints.GetSummaryLengthBetweenPointsMeters(), - "m. checkpoints:", checkpoints, "startDirection:", startDirection, "router name:", m_router->GetName())); - - // Run basic request. - code = m_router->CalculateRoute(checkpoints, startDirection, adjustToPrevRoute, - delegate, *route); - m_router->SetGuides({}); - elapsedSec = timer.ElapsedSeconds(); // routing time - LogCode(code, elapsedSec); - LOG(LINFO, ("ETA:", route->GetTotalTimeSec(), "sec.")); - } - catch (RootException const & e) - { - code = routing::RouterResultCode::InternalError; - LOG(LERROR, ("Exception happened while calculating route:", e.Msg())); - return; + try + { + LOG(LINFO, ("Calculating the route of direct length", checkpoints.GetSummaryLengthBetweenPointsMeters(), + "m. checkpoints:", checkpoints, "startDirection:", startDirection, "router name:", m_router->GetName())); + + // Run basic request. + code = m_router->CalculateRoute(checkpoints, startDirection, adjustToPrevRoute, + delegate, *route); + m_router->SetGuides({}); + elapsedSec = timer.ElapsedSeconds(); // routing time + LogCode(code, elapsedSec); + LOG(LINFO, ("ETA:", route->GetTotalTimeSec(), "sec.")); + } + catch (RootException const & e) + { + code = routing::RouterResultCode::InternalError; + LOG(LERROR, ("Exception happened while calculating route:", e.Msg())); + return; + } } if (code == routing::RouterResultCode::NoError) diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 4207932bc..81524f06c 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -3,6 +3,7 @@ #include "traffxml/traff_model.hpp" #include "indexer/data_source.hpp" +#include "indexer/mwm_set.hpp" #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" @@ -173,7 +174,8 @@ class OpenLrV3TraffDecoder : public TraffDecoder /** * @brief A `TraffDecoder` implementation which internally uses the routing engine. */ -class RoutingTraffDecoder : public TraffDecoder +class RoutingTraffDecoder : public TraffDecoder, + public MwmSet::Observer { public: class DecoderRouter : public routing::IndexRouter @@ -283,6 +285,18 @@ class RoutingTraffDecoder : public TraffDecoder const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); + /** + * @brief Called when a map is registered for the first time and can be used. + */ + void OnMapRegistered(platform::LocalCountryFile const & localFile) override; + + /** + * @brief Called when a map is deregistered and can no longer be used. + * + * This implementation does nothing, as `NumMwmIds` does not support removal. + */ + virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {} + protected: /** * @brief Initializes the router. @@ -327,6 +341,17 @@ class RoutingTraffDecoder : public TraffDecoder private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); + /** + * @brief Mutex for access to shared members. + * + * This is to prevent adding newly-registered maps while the router is in use. + * + * @todo As per the `MwmSet::Observer` documentation, implementations should be quick and lean, + * as they may be called from any thread. Locking a mutex may be in conflict with this, as it may + * mean locking up the caller while a location is being decoded. + */ + std::mutex m_mutex; + std::shared_ptr m_numMwmIds = std::make_shared(); std::unique_ptr m_router; std::optional m_message = std::nullopt; From b98fe1999c83be6c6e344bfae8e61fc04aedabae Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 8 Jun 2025 20:01:35 +0300 Subject: [PATCH 083/252] Documentation Signed-off-by: mvglasow --- indexer/mwm_set.hpp | 46 ++++++++++---- map/framework.hpp | 13 +++- platform/local_country_file.hpp | 106 ++++++++++++++++++++++---------- routing/road_point.hpp | 9 +-- routing/segment.hpp | 22 ++++--- routing_common/num_mwm_id.hpp | 30 +++++++++ 6 files changed, 168 insertions(+), 58 deletions(-) diff --git a/indexer/mwm_set.hpp b/indexer/mwm_set.hpp index 4c649fae5..badc5ed58 100644 --- a/indexer/mwm_set.hpp +++ b/indexer/mwm_set.hpp @@ -141,8 +141,10 @@ class MwmSet explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {} virtual ~MwmSet() = default; - // Mwm handle, which is used to refer to mwm and prevent it from - // deletion when its FileContainer is used. + /** + * @brief Mwm handle, which is used to refer to mwm and prevent it from deletion when its + * FileContainer is used. + */ class MwmHandle { public: @@ -230,19 +232,26 @@ class MwmSet BadFile }; - // An Observer interface to MwmSet. Note that these functions can - // be called from *ANY* thread because most signals are sent when - // some thread releases its MwmHandle, so overrides must be as fast - // as possible and non-blocking when it's possible. + /** + * @brief An Observer interface to `MwmSet`. + * + * Note that these functions can be called from *ANY* thread because most signals are sent when + * some thread releases its MwmHandle, so overrides must be as fast as possible and non-blocking + * when it's possible. + */ class Observer { public: virtual ~Observer() = default; - // Called when a map is registered for the first time and can be used. + /** + * @brief Called when a map is registered for the first time and can be used. + */ virtual void OnMapRegistered(platform::LocalCountryFile const & /* localFile */) {} - // Called when a map is deregistered and can no longer be used. + /** + * @brief Called when a map is deregistered and can no longer be used. + */ virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {} }; @@ -290,7 +299,14 @@ class MwmSet /// @todo In fact, std::shared_ptr is a MwmId. Seems like better to make vector interface. void GetMwmsInfo(std::vector> & info) const; - // Clears caches and mwm's registry. All known mwms won't be marked as DEREGISTERED. + /** + * @brief Clears caches and mwm's registry. + * + * All known mwms won't be marked as DEREGISTERED. + * + * @todo what does “all won’t be marked” mean? Not all will be marked/some might not be marked? + * Or all will be unmarked? + */ void Clear(); void ClearCache(); @@ -334,10 +350,18 @@ class MwmSet ProcessEventList(events); } - // Sets |status| in |info|, adds corresponding event to |event|. + /** + * @brief Sets `status` in `info`, adds corresponding event to `event`. + * @param info + * @param status + * @param events + */ void SetStatus(MwmInfo & info, MwmInfo::Status status, EventList & events); - // Triggers observers on each event in |events|. + /** + * @brief Triggers observers on each event in `events`. + * @param events + */ void ProcessEventList(EventList & events); /// @precondition This function is always called under mutex m_lock. diff --git a/map/framework.hpp b/map/framework.hpp index c98d03490..341f5f69b 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -491,11 +491,18 @@ class Framework : public PositionProvider, std::unique_ptr m_descriptionsLoader; public: - // Moves viewport to the search result and taps on it. + /** + * @brief Moves viewport to the search result and taps on it. + * @param res + * @param animation + */ void SelectSearchResult(search::Result const & res, bool animation); - // Cancels all searches, stops location follow and then selects - // search result. + /** + * @brief Cancels all searches, stops location follow and then selects search result. + * @param res + * @param animation + */ void ShowSearchResult(search::Result const & res, bool animation = true); size_t ShowSearchResults(search::Results const & results); diff --git a/platform/local_country_file.hpp b/platform/local_country_file.hpp index d384571c8..510b415c1 100644 --- a/platform/local_country_file.hpp +++ b/platform/local_country_file.hpp @@ -13,53 +13,88 @@ namespace platform { -// This class represents a path to disk files corresponding to some -// country region. -// -// This class also wraps World.mwm and WorldCoasts.mwm -// files from resource bundle, when they can't be found in a data -// directory. In this exceptional case, directory will be empty and -// SyncWithDisk()/DeleteFromDisk()/GetPath()/GetSize() will return -// incorrect results. -// -// In any case, when you're going to read a file LocalCountryFile points to, -// use platform::GetCountryReader(). +/** + * @brief Represents a path to disk files corresponding to some country region. + * + * This class also wraps World.mwm and WorldCoasts.mwm files from resource bundle, when they can't + * be found in a data directory. In this exceptional case, directory will be empty and + * `SyncWithDisk()`/`DeleteFromDisk()`/`GetPath()`/`GetSize()` will return incorrect results. + * + * In any case, when you're going to read a file LocalCountryFile points to, use + * `platform::GetCountryReader()`. + */ class LocalCountryFile { public: LocalCountryFile(); - // Creates an instance holding a path to countryFile's in a - // directory. Note that no disk operations are not performed until - // SyncWithDisk() is called. - // The directory must contain a full path to the country file. + /** + * @brief Creates an instance holding a path to countryFile's in a directory. + * + * Note that no disk operations are performed until `SyncWithDisk()` is called. + * + * @param directory full path to the country file + * @param countryFile + * @param version + */ LocalCountryFile(std::string directory, CountryFile countryFile, int64_t version); - // Syncs internal state like availability of files, their sizes etc. with disk. - // Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in - // this method but it's not implemented by performance reasons. This check is done on - // building routes stage. + /** + * @brief Syncs internal state like availability of files, their sizes etc. with disk. + * + * Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in + * this method but it's not implemented by performance reasons. This check is done on + * building routes stage. + */ void SyncWithDisk(); - // Removes specified file from disk if it is known for LocalCountryFile, i.e. - // it was found by a previous SyncWithDisk() call. + /** + * @brief Deletes a file from disk. + * + * Removes the specified file from disk for `LocalCountryFile`, if it is known, i.e. it was found + * by a previous SyncWithDisk() call. + * @param type + */ void DeleteFromDisk(MapFileType type) const; - // Returns path to a file. - // Return value may be empty until SyncWithDisk() is called. + /** + * @brief Returns the path to a file. + * + * Return value may be empty until SyncWithDisk() is called. + * + * @param type + * @return + */ std::string GetPath(MapFileType type) const; std::string GetFileName(MapFileType type) const; - // Returns size of a file. - // Return value may be zero until SyncWithDisk() is called. + /** + * @brief Returns the size of a file. + * + * Return value may be zero until SyncWithDisk() is called. + * + * @param type + * @return + */ uint64_t GetSize(MapFileType type) const; - // Returns true when some files are found during SyncWithDisk. - // Return value is false until SyncWithDisk() is called. + /** + * @brief Returns true when files are found during `SyncWithDisk()`. + * + * Return value is false until `SyncWithDisk()` is called. + * + * @return + */ bool HasFiles() const; - // Checks whether files specified in filesMask are on disk. - // Return value will be false until SyncWithDisk() is called. + /** + * @brief Checks whether files specified in filesMask are on disk. + * + * Return value will be false until SyncWithDisk() is called. + * + * @param type + * @return + */ bool OnDisk(MapFileType type) const; bool IsInBundle() const { return m_directory.empty(); } @@ -74,8 +109,17 @@ class LocalCountryFile bool ValidateIntegrity() const; - // Creates LocalCountryFile for test purposes, for a country region - // with countryFileName (without any extensions). Automatically performs sync with disk. + // + /** + * @brief Creates a `LocalCountryFile` for test purposes. + * + * Creates a `LocalCountryFile` for test purposes, for a country region with `countryFileName`. + * Automatically performs sync with disk. + * + * @param countryFileName The filename, without any extension. + * @param version The data version. + * @return + */ static LocalCountryFile MakeForTesting(std::string countryFileName, int64_t version = 0); // Used in generator only to simplify getting instance from path. diff --git a/routing/road_point.hpp b/routing/road_point.hpp index 346681a68..ddf053eeb 100644 --- a/routing/road_point.hpp +++ b/routing/road_point.hpp @@ -9,10 +9,11 @@ namespace routing { -// RoadPoint is a unique identifier for any road point in mwm file. -// -// Contains feature id and point id. -// Point id is the ordinal number of the point in the road. +/** + * @brief A unique identifier for any road point in an mwm file. + * + * It contains a feature id and point id. The point id is the ordinal number of the point in the road. + */ class RoadPoint final { public: diff --git a/routing/segment.hpp b/routing/segment.hpp index b10e25b92..ed886576e 100644 --- a/routing/segment.hpp +++ b/routing/segment.hpp @@ -9,14 +9,16 @@ namespace routing { -// This is directed road segment used as vertex in index graph. -// -// You can imagine the segment as a material arrow. -// Head of each arrow is connected to fletchings of next arrows with invisible links: -// these are the edges of the graph. -// -// Position of the segment is a position of the arrowhead: GetPointId(true). -// This position is used in heuristic and edges weight calculations. +/** + * @brief A directed road segment used as a vertex in the index graph. + * + * You can imagine the segment as a material arrow. + * Head of each arrow is connected to fletchings of next arrows with invisible links: + * these are the edges of the graph. + * + * Position of the segment is a position of the arrowhead: GetPointId(true). + * This position is used in heuristic and edges weight calculations. + */ class Segment final { public: @@ -82,7 +84,9 @@ class SegmentEdge final bool operator<(SegmentEdge const & edge) const; private: - // Target is vertex going to for outgoing edges, vertex going from for ingoing edges. + /** + * @brief Vertex going to for outgoing edges, vertex going from for ingoing edges. + */ Segment m_target; RouteWeight m_weight; }; diff --git a/routing_common/num_mwm_id.hpp b/routing_common/num_mwm_id.hpp index 3852be217..5212e268e 100644 --- a/routing_common/num_mwm_id.hpp +++ b/routing_common/num_mwm_id.hpp @@ -17,11 +17,21 @@ using NumMwmId = std::uint16_t; NumMwmId constexpr kFakeNumMwmId = std::numeric_limits::max(); NumMwmId constexpr kGeneratorMwmId = 0; +/** + * @brief A numbered list of country files. + */ class NumMwmIds final { public: bool IsEmpty() const { return m_idToFile.empty(); } + /** + * @brief Registers a file, i.e. adds it to the instance. + * + * If the instance already contains the file, this is a no-op. + * + * @param file + */ void RegisterFile(platform::CountryFile const & file) { if (ContainsFile(file)) @@ -34,22 +44,42 @@ class NumMwmIds final //LOG(LDEBUG, ("MWM:", file.GetName(), "=", id)); } + /** + * @brief Whether this instance contains a given file. + * @param file + * @return + */ bool ContainsFile(platform::CountryFile const & file) const { return m_fileToId.find(file) != m_fileToId.cend(); } + /** + * @brief Whether this instance contains a file at a given index. + * @param mwmId The index. + * @return + */ bool ContainsFileForMwm(NumMwmId mwmId) const { return mwmId < m_idToFile.size(); } + /** + * @brief Returns a file by index. + * @param mwmId The index. + * @return + */ platform::CountryFile const & GetFile(NumMwmId mwmId) const { ASSERT_LESS(mwmId, m_idToFile.size(), ()); return m_idToFile[mwmId]; } + /** + * @brief Returns the index for a given file. + * @param file + * @return + */ NumMwmId GetId(platform::CountryFile const & file) const { auto const it = m_fileToId.find(file); From b48310e6a580a252e609c6dde72283accb7e9666 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 9 Jun 2025 21:56:06 +0300 Subject: [PATCH 084/252] [traffxml] Parse duration quantifier and use it for delays Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 17 +++++++++--- traffxml/traff_model.hpp | 25 +++++++++++++++++- traffxml/traff_model_xml.cpp | 51 +++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 278208a2b..68e099daa 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -200,8 +200,14 @@ std::optional TraffMessage::GetTrafficImpact() impact.m_maxspeed = event.m_speed.value(); // TODO if no explicit speed given, look up in kEventMaxspeedMap (once we have entries) - // TODO if event is in delay class and has an explicit duration quantifier, use that and skip the map lookup. - if (auto it = kEventDelayMap.find(event.m_type); it != kEventDelayMap.end()) + if (event.m_class == EventClass::Delay + && event.m_type != EventType::DelayClearance + && event.m_type != EventType::DelayForecastWithdrawn + && event.m_type != EventType::DelaySeveralHours + && event.m_type != EventType::DelayUncertainDuration + && event.m_qDurationMins) + impact.m_delayMins = event.m_qDurationMins.value(); + else if (auto it = kEventDelayMap.find(event.m_type); it != kEventDelayMap.end()) impact.m_delayMins = it->second; // TempBlock overrules everything else, return immediately @@ -458,7 +464,12 @@ std::string DebugPrint(TraffEvent event) os << "type: " << DebugPrint(event.m_type) << ", "; os << "length: " << (event.m_length ? std::to_string(event.m_length.value()) : "nullopt") << ", "; os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", "; - // TODO optional quantifier + os << "q_duration: " + << (event.m_qDurationMins + ? std::format("{:1d}:{:02d}", event.m_qDurationMins.value() / 60, event.m_qDurationMins.value() % 60) + : "nullopt") + << ", "; + // TODO other quantifiers os << "speed: " << (event.m_speed ? std::to_string(event.m_speed.value()) : "nullopt"); // TODO supplementary information os << " }"; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 43e84e30b..3de56b0e0 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -109,6 +109,19 @@ enum class RoadClass Other }; +enum class QuantifierType +{ + Dimension, + Duration, + Int, + Ints, + Speed, + Temperature, + Time, + Weight, + Invalid +}; + /* * When adding a new event class to this enum, be sure to do the following: * @@ -312,7 +325,17 @@ struct TraffEvent EventType m_type = EventType::Invalid; std::optional m_length; std::optional m_probability; - // TODO optional quantifier + std::optional m_qDurationMins; + /* + * TODO remaining quantifiers + * q_dimension + * q_int + * q_ints + * q_speed + * q_temperature + * q_time + * q_weight + */ std::optional m_speed; // TODO supplementary information }; diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index dcc719c9a..3edaa3f0b 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -480,6 +480,53 @@ bool LocationFromXml(pugi::xml_node node, TraffLocation & location) return true; } +/** + * @brief Retrieves a `TraffQuantifier` from an XML element. + * + * The TraFF specification allows only one quantifier per event. The quantifier type depends on the + * event type, and not all events allow quantifiers. + * + * Quantifiers which violate these constraints are not filtered out, i.e. this function may return a + * quantifier for event types that do not allow quantifiers, or of a type illegal for the event type. + * If an event contains multiple quantifiers of different types, any one of these quantifiers may be + * returned, with no preference for legal quantifiers over illegal ones. + * + * @param node The node from which to retrieve the quantifier (`event`). + * @return The quantifier, or `std::nullopt` + */ +std::optional OptionalDurationFromXml(pugi::xml_attribute attribute) +{ + std::string durationString; + if (!StringFromXml(attribute, durationString)) + return std::nullopt; + + /* + * Valid time formats: + * 01:30 (hh:mm) + * 1 h + * 30 min + */ + std::regex durationRegex("(([0-9]+):([0-9]{2}))|(([0-9]+) *h)|(([0-9]+) *min)"); + std::smatch durationMatcher; + + if (std::regex_search(durationString, durationMatcher, durationRegex)) + { + if (!durationMatcher.str(2).empty() && !durationMatcher.str(3).empty()) + return std::stoi(durationMatcher[2]) * 60 + std::stoi(durationMatcher[3]); + else if (!durationMatcher.str(5).empty()) + return std::stoi(durationMatcher[5]) * 60; + else if (!durationMatcher.str(7).empty()) + return std::stoi(durationMatcher[7]); + UNREACHABLE(); + return std::nullopt; + } + else + { + LOG(LINFO, ("Not a valid duration:", durationString)); + return std::nullopt; + } +} + /** * @brief Retrieves a `TraffEvent` from an XML element. * @param node The XML element to retrieve (`event`). @@ -514,7 +561,9 @@ bool EventFromXml(pugi::xml_node node, TraffEvent & event) event.m_length = OptionalIntegerFromXml(node.attribute("length")); event.m_probability = OptionalIntegerFromXml(node.attribute("probability")); - // TODO optional quantifier (not yet implemented in struct) + event.m_qDurationMins = OptionalDurationFromXml(node.attribute("q_duration")); + + // TODO other quantifiers (not yet implemented in struct) event.m_speed = OptionalIntegerFromXml(node.attribute("speed")); From df13e279b62fb05f84507f352d546d48ea82712c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 12 Jun 2025 00:36:17 +0300 Subject: [PATCH 085/252] [traffic] Override EdgeEstimator::CalcOffroad() Signed-off-by: mvglasow --- routing/edge_estimator.hpp | 2 +- traffxml/traff_decoder.cpp | 15 +++++++++++++++ traffxml/traff_decoder.hpp | 12 ++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/routing/edge_estimator.hpp b/routing/edge_estimator.hpp index 1a9fc83b5..4df9c934b 100644 --- a/routing/edge_estimator.hpp +++ b/routing/edge_estimator.hpp @@ -106,7 +106,7 @@ class EdgeEstimator * @param purpose The purpose for which the result is to be used. * @return Travel time in seconds. */ - double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const; + virtual double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const; /** * @brief Returns the travel time along a segment. diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 632354b7d..1e0ca3d9e 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -470,6 +470,21 @@ double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose purpo UNREACHABLE(); } +double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, + Purpose purpose) const +{ + if (purpose == Purpose::ETA) + return 0.0; + + double result = ms::DistanceOnEarth(from, to); + + LOG(LINFO, ("Distance:", result, "weighted:", result * kOffroadPenalty)); + + result *= kOffroadPenalty; + + return result; +} + double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const { double result = road.GetDistance(segment.GetSegmentIdx()); diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 81524f06c..252b219bb 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -273,6 +273,18 @@ class RoutingTraffDecoder : public TraffDecoder, } // EdgeEstimator overrides: + + /** + * @brief Estimates travel time between two points along a direct fake edge. + * + * Estimates time in seconds it takes to go from point `from` to point `to` along direct fake edge. + * + * @param from The start point. + * @param to The destination point. + * @param purpose The purpose for which the result is to be used. + * @return Travel time in seconds. + */ + double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const override; double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; double GetUTurnPenalty(Purpose /* purpose */) const override; double GetFerryLandingPenalty(Purpose purpose) const override; From 2d3ca8014bc3c8f18d9e85c5b81368a7ff22cb37 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 13 Jun 2025 23:18:29 +0300 Subject: [PATCH 086/252] Documentation Signed-off-by: mvglasow --- indexer/feature.hpp | 19 +++++++++++++++++++ indexer/ftypes_matcher.hpp | 35 ++++++++++++++++++++++++++++++----- routing/road_point.hpp | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/indexer/feature.hpp b/indexer/feature.hpp index b1bc8979d..d7d4cd62a 100644 --- a/indexer/feature.hpp +++ b/indexer/feature.hpp @@ -75,6 +75,18 @@ class FeatureType // (number of points in inner triangle-strips). using PointsBufferT = buffer_vector; + /** + * @brief Retrieves the points of the feature. + * + * Depending on `scale`, the geometry may be simplified by reducing groups of nearby points to + * one point. If `scale` equals `FeatureType::BEST_GEOMETRY`, no such simplification takes place. + * + * Points are cached between calls and `scale` may not be honored if cached points are returned. + * To reliably enforce `scale`, call `ResetGemoetry()` immediately prior to `GetPoints()`. + * + * @param scale The map scale + * @return The points of the feature, simplified according to `scale`. + */ PointsBufferT const & GetPoints(int scale); PointsBufferT const & GetTrianglesAsPoints(int scale); @@ -82,6 +94,13 @@ class FeatureType FeatureID const & GetID() const { return m_id; } void ParseHeader2(); + + /** + * @brief Resets the geometry. + * + * This discards any cached points, resulting in points being re-fetched the next time + * `GetPoints()` or `GetTrianglesAsPoints()` is called. + */ void ResetGeometry(); void ParseGeometry(int scale); void ParseTriangles(int scale); diff --git a/indexer/ftypes_matcher.hpp b/indexer/ftypes_matcher.hpp index 22fb1b388..e12d18bc8 100644 --- a/indexer/ftypes_matcher.hpp +++ b/indexer/ftypes_matcher.hpp @@ -624,20 +624,45 @@ double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType); uint64_t GetPopulationByRadius(double r); //@} -// Highway class. The order is important. -// The enum values follow from the biggest roads (Trunk) to the smallest ones (Service). +/** + * @brief Highway class. + * + * The order is important. The enum values follow from the biggest roads (Trunk) to the smallest ones (Service). + */ enum class HighwayClass { - Undefined = 0, // There has not been any attempt of calculating HighwayClass. + /** + * Used when there has not been any attempt of calculating HighwayClass. + */ + Undefined = 0, + /** + * Motorway or trunk. + */ Trunk, Primary, Secondary, Tertiary, + /** + * Unclassified, residential, living street and `highway=road`. + */ LivingStreet, + /** + * Service, track, busway and `man_made=pier`. + */ Service, + /** + * Anything not intended for motorized traffic: pedestrian, footway, bridleway, steps, cycleway, + * path and also `highway=construction`. + */ Pedestrian, - Transported, // Vehicles are transported by train or ferry. - Count // This value is used for internals only. + /** + * Vehicles are transported by train or ferry. + */ + Transported, + /** + * This value is used for internals only. + */ + Count }; std::string DebugPrint(HighwayClass const cls); diff --git a/routing/road_point.hpp b/routing/road_point.hpp index ddf053eeb..60f5d1f8f 100644 --- a/routing/road_point.hpp +++ b/routing/road_point.hpp @@ -10,7 +10,7 @@ namespace routing { /** - * @brief A unique identifier for any road point in an mwm file. + * @brief A unique identifier for any point on a road in an mwm file. * * It contains a feature id and point id. The point id is the ordinal number of the point in the road. */ From d574b536baf1cb9b9ac7dd1fcdd208cdca8769af Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 17:03:14 +0300 Subject: [PATCH 087/252] [traffxml] Fix ISO8601 parser regex Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 68e099daa..75fc35d9c 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -96,7 +96,7 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) * 11: :00 (UTC offset, minutes, prefixed with separator) * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) */ - std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); + std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(\\.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); std::smatch iso8601Matcher; if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) From ef3de2c781a09109d6c82adff96a781f21152e38 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 17:30:39 +0300 Subject: [PATCH 088/252] [traffxml] Use std::chrono::system_clock for IsoTime Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 48 +++++++++++++++++++++++++--------------- traffxml/traff_model.hpp | 5 +++-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 75fc35d9c..3977b8423 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -78,6 +78,21 @@ const std::map kEventDelayMap{ std::optional IsoTime::ParseIsoTime(std::string timeString) { + /* + * TODO this is ugly because we need to work around some compiler deficiencies. + * + * Ideally, we would be using `std::chrono::time_point` and parse the + * string using `std::chrono::from_stream`, using `%FT%T%z` for the format string. + * This works in GCC 14+ and is pleasantly liberal about the time zone format (all of +01, +0100 + * and +01:00 are parsed correctly). Alas, Ubuntu 24.04 (currently the default dev platform) comes + * with GCC 13.2, which lacks this support. Clang, as of mid-2025, doesn’t support it at all. + * + * The workaround is therefore to use `std::chrono::time_point`, which + * exposes the same API as its `utc_clock` counterpart, making transition at a later point easy. + * In addition, however, it can be constructed from `std::time_t`, which we can generate from + * `std::tm`. Still not the prettiest way (as it relies on legacy C functions which are not + * thread-safe), but the best we can get until we have proper compiler support for `from_stream`. + */ /* * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher * will contain the following items: @@ -113,9 +128,12 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; - // Call timegm once to normalize tm; return value can be discarded - timegm(&tm); - IsoTime result(tm); + + std::time_t tt = timegm(&tm); + + std::chrono::time_point tp = std::chrono::system_clock::from_time_t(tt); + + IsoTime result(tp); return result; } else @@ -127,34 +145,26 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) IsoTime IsoTime::Now() { - std::time_t t = std::time(nullptr); - std::tm* tm = std::gmtime(&t); - return IsoTime(*tm); + return IsoTime(std::chrono::system_clock::now()); } -IsoTime::IsoTime(std::tm tm) - : m_tm(tm) +IsoTime::IsoTime(std::chrono::time_point tp) + : m_tp(tp) {} bool IsoTime::IsPast() { - std::time_t t_now = std::time(nullptr); - std::time_t t_tm = timegm(&m_tm); - return t_tm < t_now; + return m_tp < std::chrono::system_clock::now(); } bool IsoTime::operator< (IsoTime & rhs) { - std::time_t t_lhs = std::mktime(&m_tm); - std::time_t t_rhs = std::mktime(&rhs.m_tm); - return t_lhs < t_rhs; + return m_tp < rhs.m_tp; } bool IsoTime::operator> (IsoTime & rhs) { - std::time_t t_lhs = std::mktime(&m_tm); - std::time_t t_rhs = std::mktime(&rhs.m_tm); - return t_lhs > t_rhs; + return m_tp > rhs.m_tp; } bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs) @@ -287,7 +297,9 @@ string DebugPrint(LinearSegmentSource source) std::string DebugPrint(IsoTime time) { std::ostringstream os; - os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z"); + //os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z"); + // %FT%T%z + os << std::format("{0:%F} {0:%T} {0:%z}", time.m_tp); return os.str(); } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 3de56b0e0..967db4b13 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -9,6 +9,7 @@ #include "traffic/speed_groups.hpp" #include "traffic/traffic_info.hpp" +#include #include #include #include @@ -76,9 +77,9 @@ class IsoTime private: friend std::string DebugPrint(IsoTime time); - IsoTime(std::tm tm); + IsoTime(std::chrono::time_point tp); - std::tm m_tm; + std::chrono::time_point m_tp; }; // TODO enum urgency From e3d86be324a00712d2d9e7fda74fa5b9b28e7150 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 18:44:05 +0300 Subject: [PATCH 089/252] [traffxml] Use std::chrono:utc_clock for IsoTime, improve parsing Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 44 +++++++++++++++++++--------------------- traffxml/traff_model.hpp | 4 ++-- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 3977b8423..0a3581732 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -79,19 +79,17 @@ const std::map kEventDelayMap{ std::optional IsoTime::ParseIsoTime(std::string timeString) { /* - * TODO this is ugly because we need to work around some compiler deficiencies. + * We cannot use `std::chrono::from_stream` because it requires GCC 14+, and as of mid-2025, the + * supported development platform (Ubuntu 24.04) has GCC 13.2. Clang still does not support it. * - * Ideally, we would be using `std::chrono::time_point` and parse the - * string using `std::chrono::from_stream`, using `%FT%T%z` for the format string. - * This works in GCC 14+ and is pleasantly liberal about the time zone format (all of +01, +0100 - * and +01:00 are parsed correctly). Alas, Ubuntu 24.04 (currently the default dev platform) comes - * with GCC 13.2, which lacks this support. Clang, as of mid-2025, doesn’t support it at all. + * As a reasonably portable workaround, we first parse the time string into its constituent values + * using a regex, then build a `sys_seconds` instance from the values and use `clock_cast` to + * convert it to a `std::chrono::time_point` instance, which is the data + * type we use internally. * - * The workaround is therefore to use `std::chrono::time_point`, which - * exposes the same API as its `utc_clock` counterpart, making transition at a later point easy. - * In addition, however, it can be constructed from `std::time_t`, which we can generate from - * `std::tm`. Still not the prettiest way (as it relies on legacy C functions which are not - * thread-safe), but the best we can get until we have proper compiler support for `from_stream`. + * Once we have proper support for `std::chrono::from_stream` in all toolchains we support, this + * function can be rewritten accordingly. In GCC 14+, using `%FT%T%z` for the format string will + * work with all known UTC offset formats (+01, +0100 and +01:00), just like the regex does. */ /* * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher @@ -121,17 +119,17 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) if (offset_h < 0) offset_m *= -1; - std::tm tm = {}; - tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; - tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; - tm.tm_mday = std::stoi(iso8601Matcher[3]); - tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; - tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; - tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; + const auto y = static_cast(std::stoi(iso8601Matcher[1])); + const auto mo = static_cast(std::stoi(iso8601Matcher[2])); + const auto d = static_cast(std::stoi(iso8601Matcher[3])); + const std::chrono::hours h{std::stoi(iso8601Matcher[4]) - offset_h}; + const std::chrono::minutes min{std::stoi(iso8601Matcher[5]) - offset_m}; + const std::chrono::seconds s{static_cast(std::stof(iso8601Matcher[6]) + 0.5f)}; - std::time_t tt = timegm(&tm); + std::chrono::sys_seconds sys_s = std::chrono::sys_days{y/mo/d}; + sys_s = sys_s + h + min + s; - std::chrono::time_point tp = std::chrono::system_clock::from_time_t(tt); + std::chrono::time_point tp = std::chrono::clock_cast(sys_s); IsoTime result(tp); return result; @@ -145,16 +143,16 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) IsoTime IsoTime::Now() { - return IsoTime(std::chrono::system_clock::now()); + return IsoTime(std::chrono::utc_clock::now()); } -IsoTime::IsoTime(std::chrono::time_point tp) +IsoTime::IsoTime(std::chrono::time_point tp) : m_tp(tp) {} bool IsoTime::IsPast() { - return m_tp < std::chrono::system_clock::now(); + return m_tp < std::chrono::utc_clock::now(); } bool IsoTime::operator< (IsoTime & rhs) diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 967db4b13..a463098f1 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -77,9 +77,9 @@ class IsoTime private: friend std::string DebugPrint(IsoTime time); - IsoTime(std::chrono::time_point tp); + IsoTime(std::chrono::time_point tp); - std::chrono::time_point m_tp; + std::chrono::time_point m_tp; }; // TODO enum urgency From 0681171d691e1a5d82ad7f178eb80bef33a256c1 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 21:14:00 +0300 Subject: [PATCH 090/252] [traffxml] Introduce timestamp shift operation Signed-off-by: mvglasow --- traffxml/traff_model.cpp | 18 ++++++++++++++++++ traffxml/traff_model.hpp | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 0a3581732..544ed73e0 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -153,6 +153,12 @@ IsoTime::IsoTime(std::chrono::time_point tp) bool IsoTime::IsPast() { return m_tp < std::chrono::utc_clock::now(); +}\ + +void IsoTime::Shift(IsoTime nowRef) +{ + auto const offset = std::chrono::utc_clock::now() - nowRef.m_tp; + m_tp += offset; } bool IsoTime::operator< (IsoTime & rhs) @@ -256,6 +262,18 @@ std::optional TraffMessage::GetTrafficImpact() return std::nullopt; } +void TraffMessage::ShiftTimestamps() +{ + IsoTime nowRef = m_updateTime; + m_receiveTime.Shift(nowRef); + m_updateTime.Shift(nowRef); + m_expirationTime.Shift(nowRef); + if (m_startTime) + m_startTime.value().Shift(nowRef); + if (m_endTime) + m_endTime.value().Shift(nowRef); +} + void MergeMultiMwmColoring(MultiMwmColoring & delta, MultiMwmColoring & target) { // for each mwm in delta diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index a463098f1..26512b81e 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -72,6 +72,17 @@ class IsoTime */ bool IsPast(); + /** + * @brief Shifts time to the present. + * + * This method is intended for testing. It shifts the timestamp by a fixed amount, so that + * `nowRef` corresponds to current time. After this method returns, the timestamp will have the + * same offset from current time that it had from `nowRef` at the time the call was made. + * + * @param nowRef + */ + void Shift(IsoTime nowRef); + bool operator< (IsoTime & rhs); bool operator> (IsoTime & rhs); private: @@ -362,6 +373,16 @@ struct TraffMessage */ std::optional GetTrafficImpact(); + /** + * @brief Shifts timestamps to the present. + * + * This method is intended for testing. It shifts the timestamps of the message by a fixed amount, + * so that `m_updateTime` corresponds to current time, and all other timestamps maintain their + * offset to `m_updateTime`. If `m_startTime` and/or `m_endTime` are set, they may be adjusted + * further to maintain their offset from midnight or the full hour (currently not implemented). + */ + void ShiftTimestamps(); + std::string m_id; IsoTime m_receiveTime = IsoTime::Now(); IsoTime m_updateTime = IsoTime::Now(); From fbaa5470fda65e38a5e8e490344d824663c8b4d9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 21:16:18 +0300 Subject: [PATCH 091/252] [traff_assessment_tool] Shift timestamps read from TraFF files Signed-off-by: mvglasow --- traffxml/traff_assessment_tool/mainwindow.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index da3391310..075533020 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -365,10 +365,17 @@ void MainWindow::OnOpenTrafficSample() std::setlocale(LC_ALL, "en_US.UTF-8"); traffxml::TraffFeed feed; + traffxml::TraffFeed shiftedFeed; if (traffxml::ParseTraff(document, feed)) { + for (auto message : feed) + { + // `ShiftTimestamps()` will not change the message in `feed`, therefore construct a new feed + message.ShiftTimestamps(); + shiftedFeed.push_back(message); + } LOG(LINFO, ("TraFF data parsed successfully, pushing")); - m_framework.GetTrafficManager().Push(feed); + m_framework.GetTrafficManager().Push(shiftedFeed); LOG(LINFO, ("Push completed")); } else From db3ed87b92a21d7bb0c477e48e0f66fa042013f0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 14 Jun 2025 21:16:41 +0300 Subject: [PATCH 092/252] [traff_assessment_tool] Update window title Signed-off-by: mvglasow --- traffxml/traff_assessment_tool/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 075533020..2a7a8d815 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -258,7 +258,7 @@ MainWindow::MainWindow(Framework & framework) setCentralWidget(window); - setWindowTitle(tr("Organic Maps")); + setWindowTitle(tr("TraFF Assessment Tool")); setWindowIcon(QIcon(":/ui/logo.png")); QMenu * fileMenu = new QMenu("File", this); From f31541efb2ea7e9e36f5846bb7764e45ac5c9381 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 15 Jun 2025 16:40:03 +0300 Subject: [PATCH 093/252] [traffxml] Purge expired messages Signed-off-by: mvglasow --- map/traffic_manager.cpp | 34 ++++++++++++++++--- map/traffic_manager.hpp | 10 +++++- traffxml/traff_assessment_tool/mainwindow.cpp | 6 ++++ traffxml/traff_assessment_tool/mainwindow.hpp | 5 +++ traffxml/traff_model.cpp | 15 ++++++++ traffxml/traff_model.hpp | 21 ++++++++++++ 6 files changed, 86 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 9b0f8a6fb..a3d3dfb5c 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -22,6 +22,12 @@ namespace * Poll interval for traffic data */ auto constexpr kUpdateInterval = minutes(1); + +/** + * Purge interval for expired traffic messages + */ +auto constexpr kPurgeInterval = minutes(1); + auto constexpr kOutdatedDataTimeout = minutes(5) + kUpdateInterval; auto constexpr kNetworkErrorTimeout = minutes(20); @@ -404,6 +410,21 @@ void TrafficManager::Push(traffxml::TraffFeed feed) m_condition.notify_one(); } +void TrafficManager::PurgeExpiredMessages() +{ + std::lock_guard lock(m_mutex); + LOG(LINFO, ("before:", m_messageCache.size(), "message(s)")); + traffxml::IsoTime now = traffxml::IsoTime::Now(); + for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) + { + if (it->second.IsExpired(now)) + it = m_messageCache.erase(it); + else + ++it; + } + LOG(LINFO, ("after:", m_messageCache.size(), "message(s)")); +} + void TrafficManager::ConsolidateFeedQueue() { std::lock_guard lock(m_mutex); @@ -504,7 +525,8 @@ void TrafficManager::DecodeFirstMessage() void TrafficManager::ThreadRoutine() { - // initially, treat drape and observer as having just been updated + // initially, treat last purge and drape/observer update as having just happened + auto lastPurged = steady_clock::now(); m_lastDrapeUpdate = steady_clock::now(); m_lastObserverUpdate = steady_clock::now(); @@ -513,11 +535,15 @@ void TrafficManager::ThreadRoutine() if (!IsEnabled()) continue; - // TODO clean out expired messages - if (!IsTestMode()) { - LOG(LINFO, ("start loop, active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); + if (steady_clock::now() - lastPurged >= kPurgeInterval) + { + lastPurged == steady_clock::now(); + PurgeExpiredMessages(); + } + + LOG(LINFO, ("active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); // this is a no-op if active MWMs have not changed if (!SetSubscriptionArea()) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 1ba78b605..6ffc19b56 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -198,7 +198,8 @@ class TrafficManager final * will log a warning but otherwise do nothing. * * In test mode, the traffic manager will not subscribe to sources or poll them automatically. - * It will still receive and process push feeds. + * Expired messages will not get purged automatically, but `PurgeExpiredMessages()` can be called + * to purge expired messages once. The traffic manager will still receive and process push feeds. * * Future versions may introduce further behavior changes. */ @@ -215,6 +216,13 @@ class TrafficManager final */ void Push(traffxml::TraffFeed feed); + /** + * @brief Purges expired messages from the cache. + * + * This method is safe to call from any thread. + */ + void PurgeExpiredMessages(); + /** * @brief Clears the entire traffic cache. * diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 2a7a8d815..0313c663f 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -265,6 +265,7 @@ MainWindow::MainWindow(Framework & framework) menuBar()->addMenu(fileMenu); fileMenu->addAction("Open sample", QKeySequence("Ctrl+O"), this, &MainWindow::OnOpenTrafficSample); + fileMenu->addAction("Purge expired messages", QKeySequence("Ctrl+P"), this, &MainWindow::OnPurgeExpiredMessages); fileMenu->addAction("Clear TraFF cache", QKeySequence("Ctrl+D"), this, &MainWindow::OnClearCache); m_closeTrafficSampleAction = fileMenu->addAction("Close sample", QKeySequence("Ctrl+W"), this, &MainWindow::OnCloseTrafficSample); @@ -408,6 +409,11 @@ void MainWindow::OnOpenTrafficSample() #endif } +void MainWindow::OnPurgeExpiredMessages() +{ + m_framework.GetTrafficManager().PurgeExpiredMessages(); +} + void MainWindow::OnClearCache() { m_framework.GetTrafficManager().Clear(); diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp index e0b9a9058..02fa4803f 100644 --- a/traffxml/traff_assessment_tool/mainwindow.hpp +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -41,6 +41,11 @@ class MainWindow : public QMainWindow */ void OnOpenTrafficSample(); + /** + * Called when the user requests to purge expired messages. + */ + void OnPurgeExpiredMessages(); + /** * Called when the user requests to clear the cache. */ diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 544ed73e0..54cf18943 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -195,6 +195,21 @@ bool operator==(TraffLocation const & lhs, TraffLocation const & rhs) && (lhs.m_to == rhs.m_to); } +IsoTime TraffMessage::GetEffectiveExpirationTime() +{ + IsoTime result = m_expirationTime; + if (m_startTime && m_startTime.value() > result) + result = m_startTime.value(); + if (m_endTime && m_endTime.value() > result) + result = m_endTime.value(); + return result; +} + +bool TraffMessage::IsExpired(IsoTime now) +{ + return GetEffectiveExpirationTime() < now; +} + std::optional TraffMessage::GetTrafficImpact() { // no events, no impact diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 26512b81e..05f5d7675 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -359,6 +359,27 @@ using MultiMwmColoring = std::map Date: Sun, 15 Jun 2025 20:42:48 +0300 Subject: [PATCH 094/252] [traffic] Handle removed segments or eased traffic impact Signed-off-by: mvglasow --- map/traffic_manager.cpp | 38 +++++++++++++++++++++++++++++++++++--- map/traffic_manager.hpp | 4 ++++ traffxml/traff_model.cpp | 2 +- traffxml/traff_model.hpp | 2 +- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index a3d3dfb5c..bc6739433 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -518,9 +518,18 @@ void TrafficManager::DecodeFirstMessage() // store message in cache m_messageCache.insert_or_assign(message.m_id, message); } - // store message coloring in AllMwmColoring - // TODO trigger full cache processing if segments were removed or traffic has eased - traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); + /* + * TODO detect if we can do a quick update: + * - new message which does not replace any existing message + * - coloring “wins” over replaced message: + * - contains all the segments of the previous message (always true when location is the same) + * - speed groups are the same or lower as in previous message (always true when all members of + * traffic impact are unchanged or have worsened) – for this purpose, closure is considered + * lower than any other speed group + * In this case, run: + * traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); + * Otherwise, set a flag indicating we need to process coloring in full. + */ } void TrafficManager::ThreadRoutine() @@ -779,6 +788,20 @@ void TrafficManager::OnTrafficDataUpdate() LOG(LINFO, ("Announcing traffic update, notifyDrape:", notifyDrape, "notifyObserver:", notifyObserver)); + /* + * TODO introduce a flag to indicate we need to fully reprocess coloring, skip if it is false. + * The flag would get set when messages get deleted (including any clear/purge operations), + * or when a new message is added without indicating a simplified update in `DecodeFirstMessage()`. + * When we reprocess coloring in full (the block below), reset this flag. + */ + { + std::lock_guard lock(m_mutex); + + m_allMwmColoring.clear(); + for (const auto & [id, message] : m_messageCache) + traffxml::MergeMultiMwmColoring(message.m_decoded, m_allMwmColoring); + } + /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * @@ -819,6 +842,15 @@ void TrafficManager::OnTrafficDataUpdate() UpdateState(); + /* + * TODO BUG: this leaves behind deleted segments + * If an update removes segments (but the MWM still has segments), some (the first added?) + * may be left behind but will disappear on the next update. This has been observed with + * TraFF Assessment Tool when updating a message with another one resulting in fewer + * segments. Unclear if routing is also affected. The number of segments in the affected + * MWM does not change between the botched and the working update, indicating the correct + * coloring was passed and the problem is on the receiving end. + */ if (notifyDrape) { m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 6ffc19b56..337bd4813 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -394,6 +394,10 @@ class TrafficManager final * @brief Processes new traffic data. * * The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`. + * `m_allMwmColoring` is rebuilt from per-message colorings in `m_messageCache` as needed. + * + * This method is normally called from the traffic worker thread. Test tools may also call it from + * other threads. */ void OnTrafficDataUpdate(); diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 54cf18943..bd639e48b 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -289,7 +289,7 @@ void TraffMessage::ShiftTimestamps() m_endTime.value().Shift(nowRef); } -void MergeMultiMwmColoring(MultiMwmColoring & delta, MultiMwmColoring & target) +void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target) { // for each mwm in delta for (auto [mwmId, coloring] : delta) diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 05f5d7675..578fe3733 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -450,7 +450,7 @@ using TraffFeed = std::vector; * @param delta Contains the entries to be added. * @param target Receives the added entries. */ -void MergeMultiMwmColoring(MultiMwmColoring &delta, MultiMwmColoring & target); +void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target); std::string DebugPrint(IsoTime time); std::string DebugPrint(Directionality directionality); From 173b5e1718fc3cbc7ac5e62da4976c98c1c4d175 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 15 Jun 2025 20:46:25 +0300 Subject: [PATCH 095/252] [traffic] Update clear/purge logic to use update mechanism Signed-off-by: mvglasow --- map/traffic_manager.cpp | 9 +++++++-- map/traffic_manager.hpp | 11 ++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index bc6739433..78dfc7508 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -157,7 +157,6 @@ void TrafficManager::Clear() #endif m_messageCache.clear(); m_feedQueue.clear(); - m_allMwmColoring.clear(); m_mwmCache.clear(); // TODO figure out which of the ones below we still need @@ -411,6 +410,12 @@ void TrafficManager::Push(traffxml::TraffFeed feed) } void TrafficManager::PurgeExpiredMessages() +{ + PurgeExpiredMessagesImpl(); + OnTrafficDataUpdate(); +} + +void TrafficManager::PurgeExpiredMessagesImpl() { std::lock_guard lock(m_mutex); LOG(LINFO, ("before:", m_messageCache.size(), "message(s)")); @@ -549,7 +554,7 @@ void TrafficManager::ThreadRoutine() if (steady_clock::now() - lastPurged >= kPurgeInterval) { lastPurged == steady_clock::now(); - PurgeExpiredMessages(); + PurgeExpiredMessagesImpl(); } LOG(LINFO, ("active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 337bd4813..2f6536bbc 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -219,7 +219,7 @@ class TrafficManager final /** * @brief Purges expired messages from the cache. * - * This method is safe to call from any thread. + * This method is safe to call from any thread, except for the traffic worker thread. */ void PurgeExpiredMessages(); @@ -352,6 +352,15 @@ class TrafficManager final */ bool Poll(); + /** + * @brief Purges expired messages from the cache. + * + * This is the internal conterpart of `PurgeExpiredMessages()`. It is safe to call from any + * thread. Unlike `PurgeExpiredMessages()`, it does not wake the worker thread, making it suitable + * for use on the worker thread. + */ + void PurgeExpiredMessagesImpl(); + /** * @brief Consolidates the feed queue. * From ed15925251b6ba22369ecd9d97efbffa769ccd01 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 15 Jun 2025 20:49:12 +0300 Subject: [PATCH 096/252] [traffxml] Remove some log output Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 -- traffxml/traff_decoder.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 78dfc7508..afa2546b1 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -873,8 +873,6 @@ void TrafficManager::OnTrafficDataUpdate() } else { - LOG(LINFO, ("Removing coloring for", mwmId)); - if (notifyDrape) { m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 1e0ca3d9e..1cd522e85 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -478,8 +478,6 @@ double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, double result = ms::DistanceOnEarth(from, to); - LOG(LINFO, ("Distance:", result, "weighted:", result * kOffroadPenalty)); - result *= kOffroadPenalty; return result; From 04b2059ca07bc883f29233e788945df6edc3c182 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 15 Jun 2025 22:04:53 +0300 Subject: [PATCH 097/252] [traffic] workaround for drape bug when updating segments Signed-off-by: mvglasow --- map/traffic_manager.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index afa2546b1..60dd83454 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -847,17 +847,19 @@ void TrafficManager::OnTrafficDataUpdate() UpdateState(); - /* - * TODO BUG: this leaves behind deleted segments - * If an update removes segments (but the MWM still has segments), some (the first added?) - * may be left behind but will disappear on the next update. This has been observed with - * TraFF Assessment Tool when updating a message with another one resulting in fewer - * segments. Unclear if routing is also affected. The number of segments in the affected - * MWM does not change between the botched and the working update, indicating the correct - * coloring was passed and the problem is on the receiving end. - */ if (notifyDrape) { + /* + * TODO calling ClearTrafficCache before UpdateTraffic is a workaround for a bug in the + * Drape engine: some segments found in the old coloring but not in the new one may get + * left behind. This was not a problem for MapsWithMe as the set of segments never + * changed, but is an issue wherever the segment set is dynamic. Workaround is to clear + * before sending an update. Ultimately, the processing logic for UpdateTraffic needs to + * be fixed, but the code is hard to read (involves multiple messages getting thrown back + * and forth between threads). + */ + m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, + static_cast(mwmId)); m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, static_cast(info)); m_lastDrapeUpdate = steady_clock::now(); From a43e83d2806b6c60dc8e66194aa12757d097e42f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 15 Jun 2025 22:17:19 +0300 Subject: [PATCH 098/252] [traff_assessment_tool] Use path of last file (if any) for file dialog Signed-off-by: mvglasow --- .../traff_assessment_tool/trafficmodeinitdlg.cpp | 13 +++++++++---- traffxml/traff_assessment_tool/trafficmodeinitdlg.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp index 8560ead4a..e6eab99cf 100644 --- a/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp @@ -4,6 +4,7 @@ #include "platform/settings.hpp" #include +#include #include @@ -20,12 +21,16 @@ TrafficModeInitDlg::TrafficModeInitDlg(QWidget * parent) : { m_ui->setupUi(this); + QString directory = {}; std::string lastDataFilePath; if (settings::Get(kDataFilePath, lastDataFilePath)) + { m_ui->dataFileName->setText(QString::fromStdString(lastDataFilePath)); + directory = QFileInfo(QString::fromStdString(lastDataFilePath)).absolutePath(); + } - connect(m_ui->chooseDataFileButton, &QPushButton::clicked, [this](bool) { - SetFilePathViaDialog(*m_ui->dataFileName, tr("Choose data file"), "*.xml"); + connect(m_ui->chooseDataFileButton, &QPushButton::clicked, [this, directory](bool) { + SetFilePathViaDialog(*m_ui->dataFileName, tr("Choose data file"), directory, "*.xml"); }); } @@ -42,9 +47,9 @@ void TrafficModeInitDlg::accept() } void TrafficModeInitDlg::SetFilePathViaDialog(QLineEdit & dest, QString const & title, - QString const & filter) + QString const & directory, QString const & filter) { - QFileDialog openFileDlg(nullptr, title, {} /* directory */, filter); + QFileDialog openFileDlg(nullptr, title, directory, filter); openFileDlg.exec(); if (openFileDlg.result() != QDialog::DialogCode::Accepted) return; diff --git a/traffxml/traff_assessment_tool/trafficmodeinitdlg.h b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h index 6ad874e14..5377ee444 100644 --- a/traffxml/traff_assessment_tool/trafficmodeinitdlg.h +++ b/traffxml/traff_assessment_tool/trafficmodeinitdlg.h @@ -23,7 +23,7 @@ class TrafficModeInitDlg : public QDialog std::string GetDataFilePath() const { return m_dataFileName; } private: - void SetFilePathViaDialog(QLineEdit & dest, QString const & title, + void SetFilePathViaDialog(QLineEdit & dest, QString const & title, const QString & directory, QString const & filter = {}); public slots: void accept() override; From af8b748c595c652dfeb98bb952cef5400c31348c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 16 Jun 2025 22:09:36 +0300 Subject: [PATCH 099/252] [traffxml] Add traff_storage Signed-off-by: mvglasow --- traffxml/CMakeLists.txt | 2 + traffxml/traff_storage.cpp | 76 +++++++++++++++++++++++++++++++++++++ traffxml/traff_storage.hpp | 77 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 traffxml/traff_storage.cpp create mode 100644 traffxml/traff_storage.hpp diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index 2db32693a..93b4937d3 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -7,6 +7,8 @@ set(SRC traff_model.hpp traff_model_xml.cpp traff_model_xml.hpp + traff_storage.cpp + traff_storage.hpp ) omim_add_library(${PROJECT_NAME} ${SRC}) diff --git a/traffxml/traff_storage.cpp b/traffxml/traff_storage.cpp new file mode 100644 index 000000000..50fbb1038 --- /dev/null +++ b/traffxml/traff_storage.cpp @@ -0,0 +1,76 @@ +#include "traffxml/traff_storage.hpp" + +#include "platform/platform.hpp" + +#include "coding/internal/file_data.hpp" + +#include "base/logging.hpp" + +#include + +namespace +{ +std::string GetFilePath(std::string const & fileName) { return GetPlatform().WritablePathForFile(fileName); } +} // namespace + +namespace traffxml +{ +// StorageLocal ------------------------------------------------------------------------------------ +bool LocalStorage::Save(pugi::xml_document const & doc) +{ + auto const filePath = GetFilePath(m_fileName); + + std::lock_guard guard(m_mutex); + + return base::WriteToTempAndRenameToFile(filePath, [&doc](std::string const & fileName) { + return doc.save_file(fileName.data(), " " /* indent */); + }); +} + +bool LocalStorage::Load(pugi::xml_document & doc) +{ + auto const filePath = GetFilePath(m_fileName); + + std::lock_guard guard(m_mutex); + + auto const result = doc.load_file(filePath.c_str()); + /* + * Note: status_file_not_found is ok for our use cases: + * - editor: if a user has never made any edits. + * - traffic: if no traffic information has ever been retrieved (first run) + */ + if (result != pugi::status_ok && result != pugi::status_file_not_found) + { + LOG(LERROR, ("Can't load file from disk:", filePath)); + return false; + } + + return true; +} + +bool LocalStorage::Reset() +{ + std::lock_guard guard(m_mutex); + + return base::DeleteFileX(GetFilePath(m_fileName)); +} + +// StorageMemory ----------------------------------------------------------------------------------- +bool InMemoryStorage::Save(pugi::xml_document const & doc) +{ + m_doc.reset(doc); + return true; +} + +bool InMemoryStorage::Load(pugi::xml_document & doc) +{ + doc.reset(m_doc); + return true; +} + +bool InMemoryStorage::Reset() +{ + m_doc.reset(); + return true; +} +} // namespace traffxml diff --git a/traffxml/traff_storage.hpp b/traffxml/traff_storage.hpp new file mode 100644 index 000000000..4cbac8127 --- /dev/null +++ b/traffxml/traff_storage.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include + +/* + * TODO combine this header and its CPP file with editor/editor_storage.hpp and its CPP counterpart, + * give it an appropriate namespace and place it where both editor and traffic can use it. + * `traff_storage` is essentially copied from `editor_storage` with just a few modifications: + * - namespace + * - instead of relying on a hardcoded file name, `LocalStorage` is initialized with a file name + * - log output, variable names and comments changed to be use case neutral (no API changes) + * - refactoring: no global `using namespace` (no API changes) + * Apart from the first two points, this file can serve as a drop-in replacement for `editor_storage`. + * Traffic uses only `LocalStorage`, the rest has been kept to ease migration to a unified storage + * component. + */ + +namespace traffxml +{ +/** + * @brief Storage interface for XML data. + */ +class StorageBase +{ +public: + virtual ~StorageBase() = default; + + virtual bool Save(pugi::xml_document const & doc) = 0; + virtual bool Load(pugi::xml_document & doc) = 0; + virtual bool Reset() = 0; +}; + +/** + * @brief Class which saves/loads XML data to/from local file. + * @note this class IS thread-safe. + */ +class LocalStorage : public StorageBase +{ +public: + /** + * @brief Constructs a `LocalStorage` instance. + * @param fileName The file name and path where the file in question will be persisted. It is + * interpreted relative to the platform-specific path; absolute paths are not supported as some + * platforms restrict applications’ access to files outside their designated path. + */ + LocalStorage(std::string & fileName) + : m_fileName(fileName) + {} + + // StorageBase overrides: + bool Save(pugi::xml_document const & doc) override; + bool Load(pugi::xml_document & doc) override; + bool Reset() override; + +private: + std::string m_fileName; + std::mutex m_mutex; +}; + +/** + * @brief Class which saves/loads data to/from xml_document class instance. + * @note this class is NOT thread-safe. + */ +class InMemoryStorage : public StorageBase +{ +public: + // StorageBase overrides: + bool Save(pugi::xml_document const & doc) override; + bool Load(pugi::xml_document & doc) override; + bool Reset() override; + +private: + pugi::xml_document m_doc; +}; +} // namespace traffxml From 371a58f6f923fb6fe92c46a8bba306c940094ebc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 16 Jun 2025 22:12:28 +0300 Subject: [PATCH 100/252] [traffic] Use traff_storage to read hardcoded poll feeds Signed-off-by: mvglasow --- map/traffic_manager.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 60dd83454..45fefdabc 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -13,6 +13,7 @@ #include "platform/platform.hpp" #include "traffxml/traff_model_xml.hpp" +#include "traffxml/traff_storage.hpp" using namespace std::chrono; @@ -367,16 +368,14 @@ bool TrafficManager::IsSubscribed() bool TrafficManager::Poll() { // TODO - //std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany.xml"); - std::string path("/home/michael/src/organicmaps/data/test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml"); - //std::string path("/home/michael/src/organicmaps/data/test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml"); + //std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany.xml"); + std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml"); + //std::string fileName("test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml"); + traffxml::LocalStorage storage(fileName); pugi::xml_document document; - auto const load_result = document.load_file(path.data()); + auto const load_result = storage.Load(document); if (!load_result) - { - LOG(LERROR, ("Can't load file", path, ":", load_result.description())); return false; - } std::setlocale(LC_ALL, "en_US.UTF-8"); traffxml::TraffFeed feed; From 9fb08bdc5614c42c0e467f73a3fcb5f65454af20 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 17:23:22 +0300 Subject: [PATCH 101/252] [traffxml] Store message cache in file So far only API and tests, and without decoded segments Signed-off-by: mvglasow --- map/traffic_manager.cpp | 6 + map/traffic_manager.hpp | 11 + traffxml/traff_assessment_tool/mainwindow.cpp | 7 + traffxml/traff_model.cpp | 5 + traffxml/traff_model.hpp | 6 + traffxml/traff_model_xml.cpp | 191 ++++++++++++++++-- traffxml/traff_model_xml.hpp | 38 +++- 7 files changed, 246 insertions(+), 18 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 45fefdabc..965a88b15 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -107,6 +107,12 @@ void TrafficManager::Teardown() m_thread.join(); } +std::map TrafficManager::GetMessageCache() +{ + std::lock_guard lock(m_mutex); + return m_messageCache; +} + TrafficManager::TrafficState TrafficManager::GetState() const { return m_state; diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 2f6536bbc..b3ca6d83d 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -111,6 +111,17 @@ class TrafficManager final void Teardown(); + /** + * @brief Returns a copy of the cache of all currently active TraFF messages. + * + * For testing purposes. + * + * Keys are message IDs, values are messages. + * + * This method is safe to call from any thread. + */ + std::map GetMessageCache(); + TrafficState GetState() const; void SetStateListener(TrafficStateChangedFn const & onStateChangedFn); diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 0313c663f..4476dc345 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -445,6 +445,13 @@ void MainWindow::OnSaveTrafficSample() if (fileName.isEmpty()) return; + pugi::xml_document document; + + auto const messageCache = m_framework.GetTrafficManager().GetMessageCache(); + + traffxml::GenerateTraff(messageCache, document); + document.save_file(fileName.toStdString().data(), " " /* indent */); + #ifdef openlr_obsolete if (!m_trafficMode->SaveSampleAs(fileName.toStdString())) { diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index bd639e48b..028f720b5 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -161,6 +161,11 @@ void IsoTime::Shift(IsoTime nowRef) m_tp += offset; } +std::string IsoTime::ToString() const +{ + return std::format("{0:%F}T{0:%T}{0:%Ez}", time_point_cast(m_tp)); +} + bool IsoTime::operator< (IsoTime & rhs) { return m_tp < rhs.m_tp; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 578fe3733..40bded85a 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -83,6 +83,12 @@ class IsoTime */ void Shift(IsoTime nowRef); + /** + * @brief Returns a string representation of the instance. + * @return The timestamp in ISO 8601 format. + */ + std::string ToString() const; + bool operator< (IsoTime & rhs); bool operator> (IsoTime & rhs); private: diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 3edaa3f0b..4fd1cf910 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -9,34 +9,43 @@ #include #include +#include + #include using namespace std; namespace traffxml { -const std::map kDirectionalityMap{ +template +boost::bimap +MakeBimap(std::initializer_list::value_type> list) +{ + return boost::bimap(list.begin(), list.end()); +} + +const boost::bimap kDirectionalityMap = MakeBimap({ {"ONE_DIRECTION", Directionality::OneDirection}, {"BOTH_DIRECTIONS", Directionality::BothDirections} -}; +}); -const std::map kRampsMap{ +const boost::bimap kRampsMap = MakeBimap({ {"ALL_RAMPS", Ramps::All}, {"ENTRY_RAMP", Ramps::Entry}, {"EXIT_RAMP", Ramps::Exit}, {"NONE", Ramps::None} -}; +}); -const std::map kRoadClassMap{ +const boost::bimap kRoadClassMap = MakeBimap({ {"MOTORWAY", RoadClass::Motorway}, {"TRUNK", RoadClass::Trunk}, {"PRIMARY", RoadClass::Primary}, {"SECONDARY", RoadClass::Secondary}, {"TERTIARY", RoadClass::Tertiary}, {"OTHER", RoadClass::Other} -}; +}); -const std::map kEventClassMap{ +const boost::bimap kEventClassMap = MakeBimap({ {"INVALID", EventClass::Invalid}, {"ACTIVITY", EventClass::Activity}, {"AUTHORITY", EventClass::Authority}, @@ -52,9 +61,9 @@ const std::map kEventClassMap{ {"SECURITY", EventClass::Security}, {"TRANSPORT", EventClass::Transport}, {"WEATHER", EventClass::Weather} -}; +}); -const std::map kEventTypeMap{ +const boost::bimap kEventTypeMap = MakeBimap({ {"INVALID", EventType::Invalid}, // TODO Activity*, Authority*, Carpool* (not in enum yet) {"CONGESTION_CLEARED", EventType::CongestionCleared}, @@ -105,7 +114,7 @@ const std::map kEventTypeMap{ {"RESTRICTION_SPEED_LIMIT", EventType::RestrictionSpeedLimit}, {"RESTRICTION_SPEED_LIMIT_LIFTED", EventType::RestrictionSpeedLimitLifted}, // TODO Security*, Transport*, Weather* (not in enum yet) -}; +}); /** * @brief Retrieves an integer value from an attribute. @@ -291,13 +300,13 @@ std::optional OptionalBoolFromXml(pugi::xml_attribute attribute) * @return `true` on success, `false` if the attribute is not set or its value is not found in `map`. */ template -bool EnumFromXml(pugi::xml_attribute attribute, Value & value, std::map map) +bool EnumFromXml(pugi::xml_attribute attribute, Value & value, boost::bimap map) { std::string string; if (StringFromXml(attribute, string)) { - auto it = map.find(string); - if (it != map.end()) + auto it = map.left.find(string); + if (it != map.left.end()) { value = it->second; return true; @@ -320,13 +329,13 @@ bool EnumFromXml(pugi::xml_attribute attribute, Value & value, std::map -std::optional OptionalEnumFromXml(pugi::xml_attribute attribute, std::map map) +std::optional OptionalEnumFromXml(pugi::xml_attribute attribute, boost::bimap map) { std::string string; if (StringFromXml(attribute, string)) { - auto it = map.find(string); - if (it != map.end()) + auto it = map.left.find(string); + if (it != map.left.end()) return it->second; else LOG(LWARNING, ("Unknown value for", attribute.name(), ":", string, "(ignoring)")); @@ -334,6 +343,18 @@ std::optional OptionalEnumFromXml(pugi::xml_attribute attribute, std::map return std::nullopt; } +template +void EnumToXml(Value const & value, std::string name, pugi::xml_node & node, boost::bimap const & map) +{ + auto it = map.right.find(value); + if (it != map.right.end()) + node.append_attribute(name).set_value(it->second); + else + { + ASSERT(false, ("Enum value not found in map for", name)); + } +} + /** * @brief Retrieves the IDs of replaced messages from an XML element. * @param node The XML element to retrieve (`merge`). @@ -431,6 +452,19 @@ std::optional OptionalPointFromXml(pugi::xml_node node) return result; } +void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNode) +{ + auto node = parentNode.append_child(name); + if (point.m_distance) + node.append_attribute("distance").set_value(point.m_junctionName.value()); + if (point.m_junctionName) + node.append_attribute("junction_name").set_value(point.m_junctionName.value()); + if (point.m_junctionRef) + node.append_attribute("junction_ref").set_value(point.m_junctionRef.value()); + + node.text() = std::format("{0:+.5f} {1:+.5f}", point.m_coordinates.m_lat, point.m_coordinates.m_lon); +} + /** * @brief Retrieves a `TraffLocation` from an XML element. * @param node The XML element to retrieve (`location`). @@ -480,6 +514,47 @@ bool LocationFromXml(pugi::xml_node node, TraffLocation & location) return true; } +void LocationToXml(TraffLocation const & location, pugi::xml_node & node) +{ + if (location.m_country) + node.append_attribute("country").set_value(location.m_country.value()); + if (location.m_destination) + node.append_attribute("destination").set_value(location.m_destination.value()); + if (location.m_direction) + node.append_attribute("direction").set_value(location.m_direction.value()); + EnumToXml(location.m_directionality, "directionality", node, kDirectionalityMap); + + // TODO fuzziness (not yet implemented in struct) + + if (location.m_origin) + node.append_attribute("origin").set_value(location.m_origin.value()); + EnumToXml(location.m_ramps, "ramps", node, kRampsMap); + if (location.m_roadClass) + EnumToXml(location.m_roadClass.value(), "roadClass", node, kRoadClassMap); + // TODO roadIsUrban (disabled for now) + if (location.m_roadRef) + node.append_attribute("roadRef").set_value(location.m_roadRef.value()); + if (location.m_roadName) + node.append_attribute("roadName").set_value(location.m_roadName.value()); + if (location.m_territory) + node.append_attribute("territory").set_value(location.m_territory.value()); + if (location.m_town) + node.append_attribute("town").set_value(location.m_town.value()); + + if (location.m_from) + PointToXml(location.m_from.value(), "from", node); + if (location.m_at) + PointToXml(location.m_at.value(), "at", node); + if (location.m_via) + PointToXml(location.m_via.value(), "via", node); + if (location.m_notVia) + PointToXml(location.m_notVia.value(), "not_via", node); + if (location.m_to) + PointToXml(location.m_to.value(), "to", node); + + // TODO decoded segments +} + /** * @brief Retrieves a `TraffQuantifier` from an XML element. * @@ -571,6 +646,26 @@ bool EventFromXml(pugi::xml_node node, TraffEvent & event) return true; } +void EventToXml(TraffEvent const & event, pugi::xml_node & node) +{ + EnumToXml(event.m_class, "class", node, kEventClassMap); + EnumToXml(event.m_type, "type", node, kEventTypeMap); + if (event.m_length) + node.append_attribute("length").set_value(event.m_length.value()); + if (event.m_probability) + node.append_attribute("probability").set_value(event.m_probability.value()); + + if (event.m_qDurationMins) + node.append_attribute("q_duration").set_value(std::format("{:%H:%M}", std::chrono::minutes(event.m_qDurationMins.value()))); + + // TODO other quantifiers (not yet implemented in struct) + + if (event.m_speed) + node.append_attribute("speed").set_value(event.m_speed.value()); + + // TODO supplementary information (not yet implemented in struct) +} + /** * @brief Retrieves the TraFF events associsted with a message from an XML element. * @param node The XML element to retrieve (`events`). @@ -664,6 +759,49 @@ bool MessageFromXml(pugi::xml_node node, TraffMessage & message) return true; } +void MessageToXml(TraffMessage const & message, pugi::xml_node node) +{ + node.append_attribute("id").set_value(message.m_id); + node.append_attribute("receive_time").set_value(message.m_receiveTime.ToString()); + node.append_attribute("update_time").set_value(message.m_updateTime.ToString()); + node.append_attribute("expiration_time").set_value(message.m_expirationTime.ToString()); + if (message.m_startTime) + node.append_attribute("start_time").set_value(message.m_startTime.value().ToString()); + if (message.m_endTime) + node.append_attribute("end_time").set_value(message.m_endTime.value().ToString()); + + node.append_attribute("cancellation").set_value(message.m_cancellation); + node.append_attribute("forecast").set_value(message.m_forecast); + + // TODO urgency (not yet implemented in struct) + + if (!message.m_replaces.empty()) + { + auto mergeNode = node.append_child("merge"); + for (auto const & id : message.m_replaces) + { + auto replacesNode = mergeNode.append_child("replaces"); + replacesNode.append_attribute("id").set_value(id); + } + } + + if (message.m_location) + { + auto locationNode = node.append_child("location"); + LocationToXml(message.m_location.value(), locationNode); + } + + if (!message.m_events.empty()) + { + auto eventsNode = node.append_child("events"); + for (auto const event : message.m_events) + { + auto eventNode = eventsNode.append_child("event"); + EventToXml(event, eventNode); + } + } +} + bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) { bool result = false; @@ -690,6 +828,27 @@ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) return result; } +void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document) +{ + auto root = document.append_child("feed"); + for (auto const & message : feed) + { + auto child = root.append_child("message"); + MessageToXml(message, child); + } +} + +void GenerateTraff(std::map const & messages, + pugi::xml_document & document) +{ + auto root = document.append_child("feed"); + for (auto const & [id, message] : messages) + { + auto child = root.append_child("message"); + MessageToXml(message, child); + } +} + std::string FiltersToXml(std::vector & bboxRects) { std::ostringstream os; diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index fa2320982..34ad26d2a 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -23,8 +23,6 @@ namespace traffxml * The name of the root element is not verified, but the `message` elements must be its immediate * children. * - * Custom elements and attributes which are not part of the TraFF specification are ignored. - * * Values which cannot be parsed correctly are skipped. * * Events whose event type does not match their event class are skipped. @@ -36,12 +34,48 @@ namespace traffxml * Parsing the feed will report failure if all its messages fail to parse, but not if it has no * messages. * + * @note Custom elements and attributes which are not part of the TraFF specification are currently + * ignored. Future versions may process certain custom elements. + * * @param document The XML document from which to retrieve the messages. * @param feed Receives the TraFF feed. * @return `true` on success, `false` on failure. */ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed); +/** + * @brief Generates XML from a TraFF feed. + * + * The resulting document largely conforms to the TraFF specification (currently version 0.8), but + * may contain custom elements. + * + * The root element of the generated XML document is `feed`. + * + * @note Currently no custom elements are generated. Future versions may add the location decoded + * into MWM IDs, feature IDs, directions and segments, along with their speed groups. + * + * @param feed The TraFF feed to encode. + * @param document The XML document in which to store the messages. + */ +void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document); + +/** + * @brief Generates XML from a map of TraFF messages. + * + * The resulting document largely conforms to the TraFF specification (currently version 0.8), but + * may contain custom elements. + * + * The root element of the generated XML document is `feed`. + * + * @note Currently no custom elements are generated. Future versions may add the location decoded + * into MWM IDs, feature IDs, directions and segments, along with their speed groups. + * + * @param messages A map whose values contain the TraFF messages to encode. + * @param document The XML document in which to store the messages. + */ +void GenerateTraff(std::map const & messages, + pugi::xml_document & document); + /** * @brief Generates a list of XML `filter` elements from a vector of rects representing bboxes. * From 89d1365fee0f90ef16283f02d2fdc74305bd7e49 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 17:37:53 +0300 Subject: [PATCH 102/252] [traffxml] Make some arguments const & Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 40 +++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 4fd1cf910..c6c0200fd 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -122,7 +122,7 @@ const boost::bimap kEventTypeMap = MakeBimap OptionalIntegerFromXml(pugi::xml_attribute attribute) +std::optional OptionalIntegerFromXml(pugi::xml_attribute const & attribute) { if (attribute.empty()) return std::nullopt; @@ -143,7 +143,7 @@ std::optional OptionalIntegerFromXml(pugi::xml_attribute attribute) * @param attribute The XML attribute to retrieve. * @return `true` on success, `false` if the attribute is not set or does not contain a float value. */ -std::optional OptionalFloatFromXml(pugi::xml_attribute attribute) +std::optional OptionalFloatFromXml(pugi::xml_attribute const & attribute) { if (attribute.empty()) return std::nullopt; @@ -165,7 +165,7 @@ std::optional OptionalFloatFromXml(pugi::xml_attribute attribute) * @param string Receives the string retrieved. * @return `true` on success, `false` if the attribute is not set or set to an empty string. */ -bool StringFromXml(pugi::xml_attribute attribute, std::string & string) +bool StringFromXml(pugi::xml_attribute const & attribute, std::string & string) { if (attribute.empty()) return false; @@ -180,7 +180,7 @@ bool StringFromXml(pugi::xml_attribute attribute, std::string & string) * @param string Receives the string retrieved. * @return `true` on success, `false` if the node does not exist. */ -bool StringFromXml(pugi::xml_node node, std::string & string) +bool StringFromXml(pugi::xml_node const & node, std::string & string) { if (!node) return false; @@ -194,7 +194,7 @@ bool StringFromXml(pugi::xml_node node, std::string & string) * @param attribute The XML attribute to retrieve. * @return The string, or `std::nullopt` if the attribute is not set or set to an empty string. */ -std::optional OptionalStringFromXml(pugi::xml_attribute attribute) +std::optional OptionalStringFromXml(pugi::xml_attribute const & attribute) { std::string result; if (!StringFromXml(attribute, result)) @@ -221,7 +221,7 @@ std::optional OptionalStringFromXml(pugi::xml_attribute attribute) * @param tm Receives the parsed time. * @return `true` on success, `false` if the attribute is not set or does not contain a timestamp. */ -bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) +bool TimeFromXml(pugi::xml_attribute const & attribute, IsoTime & tm) { std::string timeString; if (!StringFromXml(attribute, timeString)) @@ -253,7 +253,7 @@ bool TimeFromXml(pugi::xml_attribute attribute, IsoTime & tm) * @param attribute The XML attribute from which to receive time. * @return The parsed time, or `std::nullopt` if the attribute is not set or does not contain a timestamp. */ -std::optional OptionalTimeFromXml(pugi::xml_attribute attribute) +std::optional OptionalTimeFromXml(pugi::xml_attribute const & attribute) { IsoTime result = IsoTime::Now(); if (!TimeFromXml(attribute, result)) @@ -267,7 +267,7 @@ std::optional OptionalTimeFromXml(pugi::xml_attribute attribute) * @param defaultValue The default value to return. * @return The value of the attribute, or `defaultValue` if the attribute is not set. */ -bool BoolFromXml(pugi::xml_attribute attribute, bool defaultValue) +bool BoolFromXml(pugi::xml_attribute const & attribute, bool defaultValue) { if (attribute.empty()) return defaultValue; @@ -279,7 +279,7 @@ bool BoolFromXml(pugi::xml_attribute attribute, bool defaultValue) * @param attribute The XML attribute to retrieve. * @return The value of the attribute, or `std::nullopt` if the attribute is not set. */ -std::optional OptionalBoolFromXml(pugi::xml_attribute attribute) +std::optional OptionalBoolFromXml(pugi::xml_attribute const & attribute) { if (attribute.empty()) return std::nullopt; @@ -300,7 +300,8 @@ std::optional OptionalBoolFromXml(pugi::xml_attribute attribute) * @return `true` on success, `false` if the attribute is not set or its value is not found in `map`. */ template -bool EnumFromXml(pugi::xml_attribute attribute, Value & value, boost::bimap map) +bool EnumFromXml(pugi::xml_attribute const & attribute, Value & value, + boost::bimap const & map) { std::string string; if (StringFromXml(attribute, string)) @@ -329,7 +330,8 @@ bool EnumFromXml(pugi::xml_attribute attribute, Value & value, boost::bimap -std::optional OptionalEnumFromXml(pugi::xml_attribute attribute, boost::bimap map) +std::optional OptionalEnumFromXml(pugi::xml_attribute const & attribute, + boost::bimap const & map) { std::string string; if (StringFromXml(attribute, string)) @@ -361,7 +363,7 @@ void EnumToXml(Value const & value, std::string name, pugi::xml_node & node, boo * @param replacedIds Receives the replaced IDs. * @return `true` on success (including if the node contains no replaced IDs), `false` if the node does not exist or does not contain valid data. */ -bool ReplacedMessageIdsFromXml(pugi::xml_node node, std::vector & replacedIds) +bool ReplacedMessageIdsFromXml(pugi::xml_node const & node, std::vector & replacedIds) { if (!node) return false; @@ -398,7 +400,7 @@ bool ReplacedMessageIdsFromXml(pugi::xml_node node, std::vector & r * @param latLon Receives the latitude/longitude pair. * @return `true` on success, `false` if the node does not exist or does not contain valid coordinates. */ -bool LatLonFromXml(pugi::xml_node node, ms::LatLon & latLon) +bool LatLonFromXml(pugi::xml_node const & node, ms::LatLon & latLon) { if (!node) return false; @@ -431,7 +433,7 @@ bool LatLonFromXml(pugi::xml_node node, ms::LatLon & latLon) * @param node The XML element to retrieve (any child of `location`). * @return The point, or `std::nullopt` if the node does not exist or does not contain valid point data, */ -std::optional OptionalPointFromXml(pugi::xml_node node) +std::optional OptionalPointFromXml(pugi::xml_node const & node) { if (!node) return std::nullopt; @@ -471,7 +473,7 @@ void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNo * @param location Receives the location. * @return `true` on success, `false` if the node does not exist or does not contain valid location data. */ -bool LocationFromXml(pugi::xml_node node, TraffLocation & location) +bool LocationFromXml(pugi::xml_node const & node, TraffLocation & location) { if (!node) return false; @@ -569,7 +571,7 @@ void LocationToXml(TraffLocation const & location, pugi::xml_node & node) * @param node The node from which to retrieve the quantifier (`event`). * @return The quantifier, or `std::nullopt` */ -std::optional OptionalDurationFromXml(pugi::xml_attribute attribute) +std::optional OptionalDurationFromXml(pugi::xml_attribute const & attribute) { std::string durationString; if (!StringFromXml(attribute, durationString)) @@ -608,7 +610,7 @@ std::optional OptionalDurationFromXml(pugi::xml_attribute attribute) * @param event Receives the event. * @return `true` on success, `false` if the node does not exist or does not contain valid event data. */ -bool EventFromXml(pugi::xml_node node, TraffEvent & event) +bool EventFromXml(pugi::xml_node const & node, TraffEvent & event) { std::string eventClass; if (!StringFromXml(node.attribute("class"), eventClass)) @@ -672,7 +674,7 @@ void EventToXml(TraffEvent const & event, pugi::xml_node & node) * @param events Receives the events. * @return `true` on success, `false` if the node does not exist or does not contain valid event data (including if the node contains no events). */ -bool EventsFromXml(pugi::xml_node node, std::vector & events) +bool EventsFromXml(pugi::xml_node const & node, std::vector & events) { if (!node) return false; @@ -704,7 +706,7 @@ bool EventsFromXml(pugi::xml_node node, std::vector & events) * @param message Receives the message. * @return `true` on success, `false` if the node does not exist or does not contain valid message data. */ -bool MessageFromXml(pugi::xml_node node, TraffMessage & message) +bool MessageFromXml(pugi::xml_node const & node, TraffMessage & message) { if (!StringFromXml(node.attribute("id"), message.m_id)) { From 81a31d6b42a38c207b3bf4d1d8bdb1bc3dead028 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 17:52:29 +0300 Subject: [PATCH 103/252] [traffxml] Documentation and comments Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index c6c0200fd..b61bde7e9 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -445,8 +445,6 @@ std::optional OptionalPointFromXml(pugi::xml_node const & node) return std::nullopt; } - // TODO optional float m_distance (not yet implemented in struct) - result.m_junctionName = OptionalStringFromXml(node.attribute("junction_name")); result.m_junctionRef = OptionalStringFromXml(node.attribute("junction_ref")); result.m_distance = OptionalFloatFromXml(node.attribute("distance")); @@ -669,7 +667,7 @@ void EventToXml(TraffEvent const & event, pugi::xml_node & node) } /** - * @brief Retrieves the TraFF events associsted with a message from an XML element. + * @brief Retrieves the TraFF events associated with a message from an XML element. * @param node The XML element to retrieve (`events`). * @param events Receives the events. * @return `true` on success, `false` if the node does not exist or does not contain valid event data (including if the node contains no events). From 247f88254e0570388be1ee52e299be48d9465bca Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 18:13:15 +0300 Subject: [PATCH 104/252] [traffxml] Fix erroneous parsing of event length Signed-off-by: mvglasow --- traffxml/traff_model.hpp | 2 +- traffxml/traff_model_xml.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index 40bded85a..e169e1208 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -341,7 +341,7 @@ struct TraffEvent { EventClass m_class = EventClass::Invalid; EventType m_type = EventType::Invalid; - std::optional m_length; + std::optional m_length; std::optional m_probability; std::optional m_qDurationMins; /* diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index b61bde7e9..bc9026527 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -122,13 +122,13 @@ const boost::bimap kEventTypeMap = MakeBimap OptionalIntegerFromXml(pugi::xml_attribute const & attribute) +std::optional OptionalIntegerFromXml(pugi::xml_attribute const & attribute) { if (attribute.empty()) return std::nullopt; try { - uint8_t result = std::stoi(attribute.as_string()); + int result = std::stoi(attribute.as_string()); return result; } catch (std::invalid_argument const& ex) From f132022e6000f55f48bbc72f060f1940f50d070d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 19:57:01 +0300 Subject: [PATCH 105/252] [traffxml] Documentation Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index bc9026527..e6335ad16 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -17,6 +17,12 @@ using namespace std; namespace traffxml { +/** + * @brief Creates and initializes a `boost::bimap`. + * + * @param list A braced initializer list of left-right tuples. + * @return A new bimap instance of the tuples in `list`. + */ template boost::bimap MakeBimap(std::initializer_list::value_type> list) @@ -345,6 +351,17 @@ std::optional OptionalEnumFromXml(pugi::xml_attribute const & attribute, return std::nullopt; } +/** + * @brief Stores an enum value in an attribute. + * + * The enum value is translated into a string using `map`. An attribute named `name` is then added + * to `node`, with the translated value. + * + * @param value The enum value. + * @param name The name of the attribute to store the value in. + * @param node The node to which the attribute will be added. + * @param map A map between strings and their respective enum values. + */ template void EnumToXml(Value const & value, std::string name, pugi::xml_node & node, boost::bimap const & map) { @@ -452,6 +469,13 @@ std::optional OptionalPointFromXml(pugi::xml_node const & node) return result; } +/** + * @brief Adds a TraFF point to a node. + * + * @param point The TraFF point. + * @param name The name of the node to store the TraFF point in. + * @param parentNode The parent node to which the new node will be added (`location`). + */ void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNode) { auto node = parentNode.append_child(name); @@ -514,6 +538,12 @@ bool LocationFromXml(pugi::xml_node const & node, TraffLocation & location) return true; } +/** + * @brief Stores a TraFF location in a node. + * + * @param location The TraFF location. + * @param node The `location` node to store the location in. + */ void LocationToXml(TraffLocation const & location, pugi::xml_node & node) { if (location.m_country) @@ -646,6 +676,12 @@ bool EventFromXml(pugi::xml_node const & node, TraffEvent & event) return true; } +/** + * @brief Stores a TraFF event in a node. + * + * @param event The TraFF event. + * @param node The `event` node to store the event in. + */ void EventToXml(TraffEvent const & event, pugi::xml_node & node) { EnumToXml(event.m_class, "class", node, kEventClassMap); @@ -759,6 +795,12 @@ bool MessageFromXml(pugi::xml_node const & node, TraffMessage & message) return true; } +/** + * @brief Stores a TraFF message in a node. + * + * @param message The TraFF message. + * @param node The `message` node to store the message in. + */ void MessageToXml(TraffMessage const & message, pugi::xml_node node) { node.append_attribute("id").set_value(message.m_id); From dd65e89f8fdb4b5db989bc82ed73f0b77c3f4643 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 20 Jun 2025 21:40:39 +0300 Subject: [PATCH 106/252] [traffic] Feature-complete cache persistence, including decoded coloring Signed-off-by: mvglasow --- map/traffic_manager.cpp | 134 ++++++++++- map/traffic_manager.hpp | 29 +++ traffxml/traff_assessment_tool/mainwindow.cpp | 2 +- traffxml/traff_model_xml.cpp | 227 +++++++++++++++++- traffxml/traff_model_xml.hpp | 20 +- traffxml/traff_storage.hpp | 2 +- 6 files changed, 396 insertions(+), 18 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 965a88b15..df7382df4 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -13,7 +13,6 @@ #include "platform/platform.hpp" #include "traffxml/traff_model_xml.hpp" -#include "traffxml/traff_storage.hpp" using namespace std::chrono; @@ -43,6 +42,16 @@ auto constexpr kDrapeUpdateInterval = seconds(10); * Interval at which the traffic observer gets traffic updates while messages are being processed. */ auto constexpr kObserverUpdateInterval = minutes(1); + +/** + * Interval at which the message cache file is updated while messages are being processed. + */ +auto constexpr kStorageUpdateInterval = minutes(1); + +/** + * File name at which traffic data is persisted. + */ +auto constexpr kTrafficXMLFileName = "traffic.xml"; } // namespace TrafficManager::CacheEntry::CacheEntry() @@ -125,14 +134,28 @@ void TrafficManager::SetStateListener(TrafficStateChangedFn const & onStateChang void TrafficManager::SetEnabled(bool enabled) { + /* + * Whether to notify interested parties that traffic data has been updated. + * This is based on the return value of RestoreCache(). + */ + bool notifyUpdate = false; { std::lock_guard lock(m_mutex); if (enabled == IsEnabled()) return; - if (enabled && !m_traffDecoder) - // deferred decoder initialization (requires maps to be loaded) - m_traffDecoder = make_unique(m_dataSource, m_countryInfoGetterFn, - m_countryParentNameGetterFn, m_messageCache); + if (enabled) + { + if (!m_traffDecoder) + // deferred decoder initialization (requires maps to be loaded) + m_traffDecoder = make_unique(m_dataSource, m_countryInfoGetterFn, + m_countryParentNameGetterFn, m_messageCache); + if (!m_storage && !IsTestMode()) + { + m_storage = make_unique(kTrafficXMLFileName); + notifyUpdate = RestoreCache(); + m_lastStorageUpdate = steady_clock::now(); + } + } ChangeState(enabled ? TrafficState::Enabled : TrafficState::Disabled); } @@ -140,7 +163,11 @@ void TrafficManager::SetEnabled(bool enabled) if (enabled) { - Invalidate(); + if (notifyUpdate) + OnTrafficDataUpdate(); + else + // TODO After sorting out invalidation, figure out if we need that here. + Invalidate(); m_canSetMode = false; } else @@ -227,6 +254,28 @@ void TrafficManager::OnRecoverSurface() Resume(); } +/* + * TODO Revisit invalidation logic. + * We currently invalidate when enabling and resuming, and when a new MWM file is downloaded + * (behavior inherited from MapsWithMe). + * Traffic data in MapsWithMe was a set of pre-decoded messages per MWM; the whole set would get + * re-fetched periodically. Invalidation meant discarding and re-fetching all traffic data. + * This logic is different for TraFF: + * - Messages expire individually or get replaced by updates, thus there is hardly ever a reason + * to discard messages. + * - Messages are decoded into segments in the app. Discarding decoded segments may be needed on + * a per-message basis for the following reasons: + * - the message is replaced by a new one and the location or traffic situation has changed + * (this is dealt with as part of the message update process) + * - one of the underlying MWMs has been updated to a new version + * - a new MWM has been added, and a message location that previously could not be decoded + * completely now can + * The sensible equivalent in TraFF would be to discard and re-generate decoded locations, and + * possibly poll for updates. Discarding and re-generating decoded locations could be done + * selectively: + * - compare map versions of decoded segments to current map version + * - figure out when a new map has been added, and which segments are affected by it + */ void TrafficManager::Invalidate() { if (!IsEnabled()) @@ -369,6 +418,56 @@ bool TrafficManager::IsSubscribed() return !m_subscriptionId.empty(); } +bool TrafficManager::RestoreCache() +{ + ASSERT(m_storage, ("m_storage cannot be null")); + pugi::xml_document document; + if (!m_storage->Load(document)) + { + LOG(LWARNING, ("Failed to reload cache from storage")); + return false; + } + + traffxml::TraffFeed feedIn; + traffxml::TraffFeed feedOut; + bool hasDecoded = false; + bool hasUndecoded = false; + if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feedIn)) + { + while (!feedIn.empty()) + { + traffxml::TraffMessage message; + std::swap(message, feedIn.front()); + feedIn.erase(feedIn.begin()); + + if (!message.IsExpired(traffxml::IsoTime::Now())) + { + if (!message.m_decoded.empty()) + { + hasDecoded = true; + // store message in cache + m_messageCache.insert_or_assign(message.m_id, message); + } + else + { + hasUndecoded = true; + // message needs decoding, prepare to enqueue + feedOut.push_back(message); + } + } + } + if (!feedOut.empty()) + m_feedQueue.insert(m_feedQueue.begin(), feedOut); + // update notification is caller’s responsibility + return hasDecoded && !hasUndecoded; + } + else + { + LOG(LWARNING, ("An error occurred parsing the cache file")); + } + return false; +} + // TODO make this work with multiple sources (e.g. Android) // TODO deal with subscriptions rejected by the server (delete, resubscribe) bool TrafficManager::Poll() @@ -385,7 +484,7 @@ bool TrafficManager::Poll() std::setlocale(LC_ALL, "en_US.UTF-8"); traffxml::TraffFeed feed; - if (traffxml::ParseTraff(document, feed)) + if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed)) { { std::lock_guard lock(m_mutex); @@ -784,13 +883,34 @@ void TrafficManager::OnTrafficDataUpdate() // Whether to notify the observer of the update. bool notifyObserver = (feedQueueEmpty); + // Whether to update the cache file. + bool updateStorage = (feedQueueEmpty); + if (!feedQueueEmpty) { auto const currentTime = steady_clock::now(); auto const drapeAge = currentTime - m_lastDrapeUpdate; auto const observerAge = currentTime - m_lastObserverUpdate; + auto const storageAge = currentTime - m_lastStorageUpdate; notifyDrape = (drapeAge >= kDrapeUpdateInterval); notifyObserver = (observerAge >= kObserverUpdateInterval); + updateStorage = (storageAge >= kStorageUpdateInterval); + } + + if (!m_storage || IsTestMode()) + updateStorage = false; + + if (updateStorage) + { + std::lock_guard lock(m_mutex); + + pugi::xml_document document; + + traffxml::GenerateTraff(m_messageCache, document); + if (!m_storage->Save(document)) + LOG(LWARNING, ("Storing message cache to file failed.")); + + m_lastStorageUpdate = steady_clock::now(); } if (!notifyDrape && !notifyObserver) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index b3ca6d83d..46cb20d4f 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -13,6 +13,7 @@ #include "traffxml/traff_decoder.hpp" #include "traffxml/traff_model.hpp" +#include "traffxml/traff_storage.hpp" #include "geometry/point2d.hpp" #include "geometry/polyline2d.hpp" @@ -356,6 +357,22 @@ class TrafficManager final */ bool IsSubscribed(); + /** + * @brief Restores the message cache from file storage. + * + * @note The caller must lock `m_mutex` prior to calling this function, as it makes unprotected + * changes to shared data structures. + * + * @note The return value indicates whether actions related to a traffic update should be taken, + * such as notifying the routing and drape engine. It is true if at least one message with a + * decoded location was read, and no messages without decoded locations. If messages without a + * decoded location were read, the return value is false, as the location decoding will trigger + * updates by itself. If errors occurred and no messages are read, the return value is also false. + * + * @return True if a traffic update needs to be sent, false if not + */ + bool RestoreCache(); + /** * @brief Polls the traffic service for updates. * @@ -667,6 +684,11 @@ class TrafficManager final */ std::chrono::time_point m_lastObserverUpdate; + /** + * @brief When the cache file was last updated. + */ + std::chrono::time_point m_lastStorageUpdate; + /** * @brief Whether active MWMs have changed since the last request. */ @@ -706,6 +728,13 @@ class TrafficManager final */ std::map m_messageCache; + /** + * @brief The storage instance. + * + * Used to persist the TraFF message cache between sessions. + */ + std::unique_ptr m_storage; + /** * @brief The TraFF decoder instance. * diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 4476dc345..ef2440139 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -367,7 +367,7 @@ void MainWindow::OnOpenTrafficSample() std::setlocale(LC_ALL, "en_US.UTF-8"); traffxml::TraffFeed feed; traffxml::TraffFeed shiftedFeed; - if (traffxml::ParseTraff(document, feed)) + if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed)) { for (auto message : feed) { diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index e6335ad16..2466b5e65 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -122,6 +122,42 @@ const boost::bimap kEventTypeMap = MakeBimap kSpeedGroupMap = MakeBimap({ + {"G0", traffic::SpeedGroup::G0}, + {"G1", traffic::SpeedGroup::G1}, + {"G2", traffic::SpeedGroup::G2}, + {"G3", traffic::SpeedGroup::G3}, + {"G4", traffic::SpeedGroup::G4}, + {"G5", traffic::SpeedGroup::G5}, + {"TEMP_BLOCK", traffic::SpeedGroup::TempBlock}, + {"UNKNOWN", traffic::SpeedGroup::Unknown} +}); + +/** + * @brief Retrieves an integer value from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @param value The variable which will receive the value, must be of an integer type + * @return `true` on success, `false` if the attribute is not set or does not contain an integer value. + */ +template +bool IntegerFromXml(pugi::xml_attribute const & attribute, Value & value) +{ + if (attribute.empty()) + return false; + try + { + value = static_cast(is_signed::value + ? std::stoll(attribute.as_string()) + : std::stoull(attribute.as_string())); + return true; + } + catch (std::invalid_argument const& ex) + { + return false; + } +} + /** * @brief Retrieves an integer value from an attribute. * @@ -581,8 +617,6 @@ void LocationToXml(TraffLocation const & location, pugi::xml_node & node) PointToXml(location.m_notVia.value(), "not_via", node); if (location.m_to) PointToXml(location.m_to.value(), "to", node); - - // TODO decoded segments } /** @@ -734,13 +768,180 @@ bool EventsFromXml(pugi::xml_node const & node, std::vector & events return result; } +/** + * @brief Retrieves a coloring segment (segment with speed group) from XML + * @param node The `segment` node + * @param coloring The coloring to which the segment will be added. + * @return true if each segment was parsed successfully, false if errors occurred (in this case, + * the decoded coloring for this message should be discarded and regenerated from scratch) + */ +bool SegmentFromXml(pugi::xml_node const & node, + std::map & coloring) +{ + uint32_t fid; + uint16_t idx; + uint8_t dir; + if (IntegerFromXml(node.attribute("fid"), fid) + && IntegerFromXml(node.attribute("idx"), idx) + && IntegerFromXml(node.attribute("dir"), dir)) + { + traffic::TrafficInfo::RoadSegmentId segment(fid, idx, dir); + traffic::SpeedGroup sg = traffic::SpeedGroup::Unknown; + if (EnumFromXml(node.attribute("speed_group"), sg, kSpeedGroupMap)) + coloring[segment] = sg; + else + { + LOG(LWARNING, ("missing or invalid speed group for", segment, "(aborting)")); + return false; + } + } + else + { + LOG(LWARNING, ("segment with incomplete information (fid, idx, dir), aborting")); + return false; + } + return true; +} + +/** + * @brief Retrieves coloring for a single MWM from XML. + * + * This function returns false if errors occurred during decoding (due to invalid data), or if the + * data version to which the segments refer does not coincide with the currently used version of the + * corresponding MWM. In this case, the entire coloring for this message should be discarded and the + * message should be decoded from scratch. + * + * @todo Errors in segments are currently not considered, i.e. this function may return true even if + * one or more segments have errors. + * + * @param node The `coloring` node. + * @param dataSource The data source for coloring. + * @param decoded Receives the decoded global coloring. + * @return whether the decoded segments can be used, see description + */ +bool ColoringFromXml(pugi::xml_node const & node, DataSource const & dataSource, + MultiMwmColoring & decoded) +{ + std::string countryName; + if (!StringFromXml(node.attribute("country_name"), countryName)) + { + LOG(LWARNING, ("coloring element without coutry_name attribute, skipping")); + return false; + } + auto const & mwmId = dataSource.GetMwmIdByCountryFile(platform::CountryFile(countryName)); + if (!mwmId.IsAlive()) + { + LOG(LWARNING, ("Can’t get MWM ID for country", countryName, "(skipping)")); + return false; + } + + uint64_t version = 0; + if (!IntegerFromXml(node.attribute("version"), version)) + { + LOG(LWARNING, ("Can’t get version for country", countryName, "(skipping)")); + return false; + } + else if (version != mwmId.GetInfo()->GetVersion()) + { + LOG(LINFO, ("XML data for country", countryName, "has version", version, "while MWM has", mwmId.GetInfo()->GetVersion(), "(skipping)")); + return false; + } + + auto const segmentNodes = node.select_nodes("./segment"); + + if (segmentNodes.empty()) + return true; + + std::map coloring; + + for (auto const & segmentXpathNode : segmentNodes) + { + auto const & segmentNode = segmentXpathNode.node(); + if (!SegmentFromXml(segmentNode, coloring)) + return false; + } + + if (!coloring.empty()) + decoded[mwmId] = coloring; + + return true; +} + +/** + * @brief Stores coloring for an indidual MWM in an XML node. + * + * The vaues of `mwmId` will be added to `node` as attributes. The segments and their traffic group + * will be added to `node` as child nodes. + * + * @param mwmId + * @param coloring + * @param node The `coloring` node to store the coloring in. + */ +void ColoringToXml(MwmSet::MwmId const & mwmId, + std::map const & coloring, + pugi::xml_node node) +{ + node.append_attribute("country_name").set_value(mwmId.GetInfo()->GetCountryName()); + node.append_attribute("version").set_value(mwmId.GetInfo()->GetVersion()); + for (auto & [segId, sg] : coloring) + { + auto segNode = node.append_child("segment"); + segNode.append_attribute("fid").set_value(segId.GetFid()); + segNode.append_attribute("idx").set_value(segId.GetIdx()); + segNode.append_attribute("dir").set_value(segId.GetDir()); + EnumToXml(sg, "speed_group", segNode, kSpeedGroupMap); + } +} + +/** + * @brief Retrieves global coloring from XML. + * + * If the MWM version does not match for at least one MWM, no coloring is decoded (`decoded` is + * empty after this function returns) and the message needs to be decoded from scratch. + * + * @param node The `mwm_coloring` node. + * @param dataSource The data source for coloring, see `ParseTraff()`. + * @param decoded Receives the decoded global coloring. + */ +void AllMwmColoringFromXml(pugi::xml_node const & node, + std::optional> dataSource, + MultiMwmColoring & decoded) +{ + if (!node) + return; + + if (!dataSource) + { + LOG(LWARNING, ("Message has mwm_coloring but it cannot be parsed as no data source was specified")); + return; + } + + auto const coloringNodes = node.select_nodes("./coloring"); + + if (coloringNodes.empty()) + return; + + for (auto const & coloringXpathNode : coloringNodes) + { + auto const & coloringNode = coloringXpathNode.node(); + if (!ColoringFromXml(coloringNode, dataSource->get(), decoded)) + { + decoded.clear(); + return; + } + } +} + /** * @brief Retrieves a TraFF message from an XML element. * @param node The XML element to retrieve (`message`). + * @param dataSource The data source for coloring, see `ParseTraff()`. * @param message Receives the message. * @return `true` on success, `false` if the node does not exist or does not contain valid message data. */ -bool MessageFromXml(pugi::xml_node const & node, TraffMessage & message) +bool MessageFromXml(pugi::xml_node const & node, + std::optional> dataSource, + TraffMessage & message) { if (!StringFromXml(node.attribute("id"), message.m_id)) { @@ -779,7 +980,9 @@ bool MessageFromXml(pugi::xml_node const & node, TraffMessage & message) if (!message.m_cancellation) { message.m_location.emplace(); - if (!LocationFromXml(node.child("location"), message.m_location.value())) + if (LocationFromXml(node.child("location"), message.m_location.value())) + AllMwmColoringFromXml(node.child("mwm_coloring"), dataSource, message.m_decoded); + else { message.m_location.reset(); LOG(LWARNING, ("Message", message.m_id, "has no location but is not a cancellation message")); @@ -842,9 +1045,21 @@ void MessageToXml(TraffMessage const & message, pugi::xml_node node) EventToXml(event, eventNode); } } + + if (!message.m_decoded.empty()) + { + auto allMwmColoringNode = node.append_child("mwm_coloring"); + for (auto & [mwmId, coloring] : message.m_decoded) + { + auto coloringNode = allMwmColoringNode.append_child("coloring"); + ColoringToXml(mwmId, coloring, coloringNode); + } + } } -bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) +bool ParseTraff(pugi::xml_document const & document, + std::optional> dataSource, + TraffFeed & feed) { bool result = false; @@ -859,7 +1074,7 @@ bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed) { auto const messageNode = xpathNode.node(); TraffMessage message; - if (MessageFromXml(messageNode, message)) + if (MessageFromXml(messageNode, dataSource, message)) { feed.push_back(message); result = true; diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index 34ad26d2a..ad2ef1371 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -4,6 +4,9 @@ #include "geometry/rect2d.hpp" +#include "indexer/data_source.hpp" + +#include #include #include @@ -34,14 +37,25 @@ namespace traffxml * Parsing the feed will report failure if all its messages fail to parse, but not if it has no * messages. * - * @note Custom elements and attributes which are not part of the TraFF specification are currently - * ignored. Future versions may process certain custom elements. + * In addition to the TraFF specification, we also use a custom extension, `mwm_coloring`, which is + * a child of `message` and holds decoded traffic coloring. In order to parse it, `dataSource` must + * be specified. If `dataSource` is `nullopt`, coloring will be ignored. It is recommended to pass + * `dataSource` if, and only if, parsing an XML stream that is expected to contain traffic coloring. + * + * @note To pass a reference to the framework data source (assuming the `framework` is the framework + * instance), use `std::cref(framework.GetDataSource())`. + * + * @note Custom elements and attributes which are not part of the TraFF specification, other than + * `mwm_coloring`, are ignored. * * @param document The XML document from which to retrieve the messages. + * @param dataSource The data source for coloring, see description. * @param feed Receives the TraFF feed. * @return `true` on success, `false` on failure. */ -bool ParseTraff(pugi::xml_document const & document, TraffFeed & feed); +bool ParseTraff(pugi::xml_document const & document, + std::optional> dataSource, + TraffFeed & feed); /** * @brief Generates XML from a TraFF feed. diff --git a/traffxml/traff_storage.hpp b/traffxml/traff_storage.hpp index 4cbac8127..f59bfb071 100644 --- a/traffxml/traff_storage.hpp +++ b/traffxml/traff_storage.hpp @@ -45,7 +45,7 @@ class LocalStorage : public StorageBase * interpreted relative to the platform-specific path; absolute paths are not supported as some * platforms restrict applications’ access to files outside their designated path. */ - LocalStorage(std::string & fileName) + LocalStorage(std::string const & fileName) : m_fileName(fileName) {} From f02b1538e725c4020c347cddb1521c963889a378 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 24 Jun 2025 19:33:48 +0300 Subject: [PATCH 107/252] Documentation Signed-off-by: mvglasow --- storage/storage.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/storage/storage.hpp b/storage/storage.hpp index 92100159b..be2bace71 100644 --- a/storage/storage.hpp +++ b/storage/storage.hpp @@ -139,14 +139,15 @@ struct NodeStatuses bool m_groupNode; }; -// This class is used for downloading, updating and deleting maps. -// Storage manages a queue of mwms to be downloaded. -// Every operation with this queue must be executed -// on the storage thread. In the current implementation, the storage -// thread coincides with the main (UI) thread. -// Downloading of only one mwm at a time is supported, so while the -// mwm at the top of the queue is being downloaded (or updated by -// applying a diff file) all other mwms have to wait. +/** + * @brief The Storage class is used for downloading, updating and deleting maps. + * + * Storage manages a queue of mwms to be downloaded. Every operation with this queue must be + * executed on the storage thread. In the current implementation, the storage thread coincides with + * the main (UI) thread. Downloading of only one mwm at a time is supported, so while the mwm at the + * top of the queue is being downloaded (or updated by applying a diff file) all other mwms have to + * wait. + */ class Storage final : public QueuedCountry::Subscriber { public: From 23922f1c2b5d338d4a4b9ca0a952c7c795e3d509 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 24 Jun 2025 21:55:17 +0300 Subject: [PATCH 108/252] [traffic] Invalidate per MWM on download (untested) Signed-off-by: mvglasow --- map/framework.cpp | 2 +- map/traffic_manager.cpp | 52 +++++++++++++++++++++++++++++++++++++++++ map/traffic_manager.hpp | 12 ++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/map/framework.cpp b/map/framework.cpp index 25a63094e..f5f5d2b9b 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -432,9 +432,9 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, MwmSet::MwmId const & id = res.first; if (id.IsAlive()) rect = id.GetInfo()->m_bordersRect; + m_trafficManager.Invalidate(id); } - m_trafficManager.Invalidate(); m_transitManager.Invalidate(); m_isolinesManager.Invalidate(); diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index df7382df4..9811f0a33 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -290,6 +290,58 @@ void TrafficManager::Invalidate() UpdateMyPosition(m_currentPosition.first); } +void TrafficManager::Invalidate(MwmSet::MwmId const & mwmId) +{ + auto const mwmRect = mwmId.GetInfo()->m_bordersRect; // m2::RectD + traffxml::TraffFeed invalidated; + + { + std::lock_guard lock(m_mutex); + + for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) + { + if (!it->second.m_location) + continue; + + bool isInvalid = false; + + // invalidate if decoded location uses a previous version of the MWM + for (auto const & [decodedMwmId, coloring] : it->second.m_decoded) + if (decodedMwmId.GetInfo()->GetCountryName() == mwmId.GetInfo()->GetCountryName()) + isInvalid = true; + + // invalidate if bounding rect of reference points intersects with bounding rect of MWM + if (!isInvalid) + { + m2::RectD locationRect; + for (auto const & point : {it->second.m_location.value().m_from, + it->second.m_location.value().m_via, + it->second.m_location.value().m_at, + it->second.m_location.value().m_to}) + if (point) + locationRect.Add(mercator::FromLatLon(point.value().m_coordinates)); + isInvalid = locationRect.IsIntersect(mwmRect); + } + + if (isInvalid) + { + traffxml::TraffMessage message(it->second); + message.m_decoded.clear(); + invalidated.push_back(message); + it = m_messageCache.erase(it); + } + else + ++it; + } + + if (!invalidated.empty()) + { + m_feedQueue.insert(m_feedQueue.begin(), invalidated); + m_condition.notify_one(); + } + } +} + void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, std::vector & lastMwmsByRect, std::set & activeMwms) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 46cb20d4f..d03238e20 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -188,6 +188,18 @@ class TrafficManager final */ void Invalidate(); + /** + * @brief Invalidates traffic information. + * + * Invalidation happens when a new MWM file is downloaded (it may or may not replace an older + * version), and pertains to that MWM. Locations which refer to any version of this MWM, or whose + * enclosing rectangle overlaps with that of the MWM, are discarded and recreated to ensure the + * new MWM is considered. + * + * @param mwmId The newly addded MWM. + */ + void Invalidate(MwmSet::MwmId const & mwmId); + void OnDestroySurface(); void OnRecoverSurface(); void OnMwmDeregistered(platform::LocalCountryFile const & countryFile); From 964368f5d42a2515540622a80296c04fa8d3e44b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 28 Jun 2025 14:28:35 +0300 Subject: [PATCH 109/252] [traffic] Replace full invalidation with subscription recalculation Signed-off-by: mvglasow --- map/traffic_manager.cpp | 32 +++----------------------------- map/traffic_manager.hpp | 33 +++++++++++++++++---------------- 2 files changed, 20 insertions(+), 45 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 9811f0a33..016cd9140 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -166,8 +166,7 @@ void TrafficManager::SetEnabled(bool enabled) if (notifyUpdate) OnTrafficDataUpdate(); else - // TODO After sorting out invalidation, figure out if we need that here. - Invalidate(); + RecalculateSubscription(); m_canSetMode = false; } else @@ -254,36 +253,11 @@ void TrafficManager::OnRecoverSurface() Resume(); } -/* - * TODO Revisit invalidation logic. - * We currently invalidate when enabling and resuming, and when a new MWM file is downloaded - * (behavior inherited from MapsWithMe). - * Traffic data in MapsWithMe was a set of pre-decoded messages per MWM; the whole set would get - * re-fetched periodically. Invalidation meant discarding and re-fetching all traffic data. - * This logic is different for TraFF: - * - Messages expire individually or get replaced by updates, thus there is hardly ever a reason - * to discard messages. - * - Messages are decoded into segments in the app. Discarding decoded segments may be needed on - * a per-message basis for the following reasons: - * - the message is replaced by a new one and the location or traffic situation has changed - * (this is dealt with as part of the message update process) - * - one of the underlying MWMs has been updated to a new version - * - a new MWM has been added, and a message location that previously could not be decoded - * completely now can - * The sensible equivalent in TraFF would be to discard and re-generate decoded locations, and - * possibly poll for updates. Discarding and re-generating decoded locations could be done - * selectively: - * - compare map versions of decoded segments to current map version - * - figure out when a new map has been added, and which segments are affected by it - */ -void TrafficManager::Invalidate() +void TrafficManager::RecalculateSubscription() { if (!IsEnabled()) return; - m_lastDrapeMwmsByRect.clear(); - m_lastRoutingMwmsByRect.clear(); - if (m_currentModelView.second) UpdateViewport(m_currentModelView.first); if (m_currentPosition.second) @@ -1272,7 +1246,7 @@ void TrafficManager::Resume() return; m_isPaused = false; - Invalidate(); + RecalculateSubscription(); } void TrafficManager::SetSimplifiedColorScheme(bool simplified) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index d03238e20..ff615b70f 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -150,9 +150,6 @@ class TrafficManager final * that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added * (and removed) – note that this affects the `DataSource`, not the set of active MWMs. * - * @todo Enabling the traffic manager will invalidate its data, disabling it will notify the - * observer that traffic data has been cleared. This is old logic, to be reviewed/removed. - * * @todo State/pause/resume logic is not fully implemented ATM and needs to be revisited. * * @param enabled True to enable, false to disable @@ -176,25 +173,30 @@ class TrafficManager final void UpdateMyPosition(MyPosition const & myPosition); /** - * @brief Invalidates traffic information. + * @brief Recalculates the TraFF subscription area. + * + * The subscription area needs to be recalculated when the traffic manager goes from disabled to + * enabled, or when it is resumed after being paused, as the subscription area is not updated + * while the traffic manager is disabled or paused. * - * Invalidating causes traffic data to be re-requested. + * If the subscription are has changed, this triggers a change of the active TraFF subscription. * - * This happens when a new MWM file is downloaded, the traffic manager is enabled after being - * disabled or resumed after being paused. + * No traffic data is discarded, but sources will be polled for an update, which may turn out + * larger than usual if the traffic manager was in disabled/paused state for an extended period of + * time or the subscription area has changed. * - * @todo this goes for the old MWM arch. For TraFF we need to refresh the MWM set for the decoder - * and possibly decode locations again (MWMs might have changed, or new ones added). + * @todo Routing is currently not considered (active MWMs are based on viewport and current + * position). */ - void Invalidate(); + void RecalculateSubscription(); /** - * @brief Invalidates traffic information. + * @brief Invalidates traffic information for the specified MWM. * - * Invalidation happens when a new MWM file is downloaded (it may or may not replace an older - * version), and pertains to that MWM. Locations which refer to any version of this MWM, or whose - * enclosing rectangle overlaps with that of the MWM, are discarded and recreated to ensure the - * new MWM is considered. + * Invalidation of traffic data is always per MWM and affects locations which refer to any version + * of this MWM, or whose enclosing rectangle overlaps with that of the MWM. The decoded segments + * for these locations are discarded and decoded again, ensuring they are based on the new MWM. + * The TraFF messages themselves remain unchanged. * * @param mwmId The newly addded MWM. */ @@ -641,7 +643,6 @@ class TrafficManager final * Methods which use both, but in a different way: * * * ClearCache(), removes the requested MWM from the set but clears the vector completely. - * * Invalidate(), clears the vector but not the set. * * UpdateActiveMwms(), uses the vector to detect changes. If so, it updates both vector and set. * * Clear() clears both the set and the vector. (Clearing the set is currently disabled as it breaks ForEachActiveMwm.) From b418cf659c90e3d6f52cbdf269477a5424c55a60 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 29 Jun 2025 19:30:00 +0300 Subject: [PATCH 110/252] Documentation Signed-off-by: mvglasow --- routing/index_router.hpp | 3 +- routing/routing_session.hpp | 98 ++++++++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/routing/index_router.hpp b/routing/index_router.hpp index ff561c3c5..822f96820 100644 --- a/routing/index_router.hpp +++ b/routing/index_router.hpp @@ -95,7 +95,8 @@ class IndexRouter : public IRouter * @param countryParentNameGetterFn Function which converts a country name into the name of its parent country) * @param countryFileFn Function which converts a pointer to its country name * @param countryRectFn Function which returns the rect for a country - * @param numMwmIds + * @param numMwmIds MWMs to use for route calculation (this should include all MWMs, whether or + * not we have the file locally, but not World or WorldCoasts) * @param numMwmTree * @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`) * @param dataSource The MWM data source diff --git a/routing/routing_session.hpp b/routing/routing_session.hpp index 41b517977..d528430f6 100644 --- a/routing/routing_session.hpp +++ b/routing/routing_session.hpp @@ -40,9 +40,12 @@ class RouteMatchingInfo; namespace routing { -/// \breaf This class is responsible for the route built in the program. -/// \note All method of this class should be called from ui thread if there's no -/// a special note near a method. +/** + * @brief This class is responsible for the route built in the program. + * + * @note Methods of this class may only be called from the UI thread, unless the method + * documentation states otherwise. + */ class RoutingSession : public traffic::TrafficObserver, public traffic::TrafficCache { friend struct UnitClass_AsyncGuiThreadTestWithRoutingSession_TestFollowRoutePercentTest; @@ -66,24 +69,105 @@ class RoutingSession : public traffic::TrafficObserver, public traffic::TrafficC m2::PointD GetStartPoint() const; m2::PointD GetEndPoint() const; + /** + * @brief Whether routing is currently active. + * + * A route is considered active if a destination has been entered and the route has ben built or + * is currently being built or rebuilt, even if the user is not currently following it. + * + * @return True if active, false if not + */ bool IsActive() const; + + /** + * @brief Whether the route is currently navigable. + * + * The route is considered navigable if it has been built and following mode is enabled, and the + * user either has not yet started following the route, is currently following it or has reached + * their destination but not yet closed the session. The route is no longer navigable if the user + * leaves it. + * + * @return True if navigable, false if not. + */ bool IsNavigable() const; + + /** + * @brief Whether a built route exists. + * + * A route is considered built if it is navigable (see `IsNavigable()`), but retains that state + * even if the user leaves the route. + * + * @return True if built, false if not. + */ bool IsBuilt() const; - /// \returns true if a new route is in process of building rebuilding or - /// if a route is being rebuilt in case the user left the route, and false otherwise. + + /** + * @brief Whether a route is currently being built or rebuilt. + * + * This is the case if a new route is in process of building, rebuilding, or if a route is being + * rebuilt after the user has left the route. + * + * @return True if building or rebuilding, false otherwise. + */ bool IsBuilding() const; + + /** + * @brief Whether the route is currently being built from scratch. + * + * This is the case if a new route is currently being built, but not if an already existing route + * is being rebuilt. + * + * @return True if building, false otherwise (also if rebuilding). + */ bool IsBuildingOnly() const; + + /** + * @brief Whether the route is currently being rebuilt. + * + * This is the case if an already existing route is being rebuilt, but not if a new route is + * currently being built. + * + * @return True if rebuilding, false otherwise (also if building from scratch). + */ bool IsRebuildingOnly() const; + + /** + * @brief Whether the route is finished. + * + * The route is considered finished if the destination point has been reached but the session has + * not been closed. + * + * @return True if finished, false otherwise. + */ bool IsFinished() const; + + /** + * @brief Whether a route has been built while following mode is disabled. + * + * @return True if a route has been built and following mode disabled, false otherwise. + */ bool IsNoFollowing() const; + + /** + * @brief Whether the user is currently following a previously built route. + * + * This is the case if the route has been built, route following is enabled, the user has started + * following the route and not left it. + * + * @return True if the user is following the route, false otherwise. + */ bool IsOnRoute() const; + bool IsFollowing() const; void Reset(); void SetState(SessionState state); - /// \returns true if altitude information along |m_route| is available and - /// false otherwise. + /** + * @brief Whether altitude information along the route is available. + * + * @return True if altitude information along `m_route` is available, false otherwise. + */ bool HasRouteAltitude() const; bool IsRouteId(uint64_t routeId) const; bool IsRouteValid() const; From 9eeac05fdf2ba732cd11387b3ef6bc862d3877ab Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 29 Jun 2025 21:13:36 +0300 Subject: [PATCH 111/252] [traffic] Update routing MWMs as route changes Signed-off-by: mvglasow --- map/traffic_manager.cpp | 86 +++++++++++++++++++++++++++---- map/traffic_manager.hpp | 42 ++++++++++----- routing/absent_regions_finder.cpp | 23 +++++---- routing/absent_regions_finder.hpp | 36 ++++++++++++- routing/async_router.cpp | 7 +++ routing/async_router.hpp | 9 ++++ routing/routing_session.cpp | 7 +++ routing/routing_session.hpp | 9 ++++ 8 files changed, 182 insertions(+), 37 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 016cd9140..9a48c806d 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -75,12 +75,12 @@ TrafficManager::CacheEntry::CacheEntry(time_point const & requestT TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn &countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, - traffic::TrafficObserver & observer) + routing::RoutingSession & routingSession) : m_dataSource(dataSource) , m_countryInfoGetterFn(countryInfoGetter) , m_countryParentNameGetterFn(countryParentNameGetter) , m_getMwmsByRectFn(getMwmsByRectFn) - , m_observer(observer) + , m_routingSession(routingSession) , m_currentDataVersion(0) , m_state(TrafficState::Disabled) // TODO no longer needed @@ -92,6 +92,11 @@ TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn coun , m_thread(&TrafficManager::ThreadRoutine, this) { CHECK(m_getMwmsByRectFn != nullptr, ()); + GetPlatform().RunTask(Platform::Thread::Gui, [this]() { + m_routingSession.SetChangeSessionStateCallback([this](routing::SessionState previous, routing::SessionState current) { + OnChangeRoutingSessionState(previous, current); + }); + }); } TrafficManager::~TrafficManager() @@ -170,7 +175,7 @@ void TrafficManager::SetEnabled(bool enabled) m_canSetMode = false; } else - m_observer.OnTrafficInfoClear(); + m_routingSession.OnTrafficInfoClear(); } void TrafficManager::Clear() @@ -194,7 +199,7 @@ void TrafficManager::Clear() // TODO figure out which of the ones below we still need m_lastDrapeMwmsByRect.clear(); - m_lastRoutingMwmsByRect.clear(); + m_lastPositionMwmsByRect.clear(); #ifdef traffic_dead_code m_requestedMwms.clear(); m_trafficETags.clear(); @@ -253,6 +258,65 @@ void TrafficManager::OnRecoverSurface() Resume(); } +void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current) +{ + // TODO assert we’re running on the UI thread + LOG(LINFO, ("Routing session state changed from", previous, "to", current)); + LOG(LINFO, ("Running on thread", std::this_thread::get_id())); + /* + * Filter based on session state (see routing_callbacks.hpp for states and transitions). + * + * GetAllRegions() seems to get fresh data during build (presumably also rebuild), which will be + * available on the next state transition (to RouteNotStarted or OnRoute) and remain unchanged + * until the next route (re)build. Therefore, calling GetAllRegions() when new state is one of + * RouteNotStarted, OnRoute or RouteNoFollowing, and clearing MWMs when the new state is + * NoValidRoute, and ignoring all other transitions, should work for our purposes. + * There may be cases where we first calculate the route, then subscribe to the regions and only + * then get notified about a traffic problem on the route, requiring us to recalculate. + */ + std::set mwmNames; + if (current == routing::SessionState::RouteNotStarted + || current == routing::SessionState::OnRoute + || current == routing::SessionState::RouteNoFollowing) + /* + * GetAllRegions() may block when run for the first time. This should happen during routing, so + * the call here would always return cached results without blocking, causing no problems on the + * UI thread. + * + * Note that this method is called before the state is updated internally, thus `GetAllRegions()` + * and any other functions would still have `previous` as their state. + */ + m_routingSession.GetAllRegions(mwmNames); + else if (current == routing::SessionState::NoValidRoute) + mwmNames.clear(); + else + // for all other states, keep current set + return; + + LOG(LINFO, ("Router MWMs:", mwmNames)); + + std::set mwms; + for (auto const & mwmName : mwmNames) + { + MwmSet::MwmId mwmId = m_dataSource.GetMwmIdByCountryFile(platform::CountryFile(mwmName)); + if (mwmId.IsAlive()) + mwms.insert(mwmId); + } + + LOG(LINFO, ("MWM set:", mwms)); + + { + std::lock_guard lock(m_mutex); + + if (mwms != m_activeRoutingMwms) + { + m_activeMwmsChanged = true; + std::swap(mwms, m_activeRoutingMwms); + RequestTrafficData(); + } + } +} + void TrafficManager::RecalculateSubscription() { if (!IsEnabled()) @@ -262,6 +326,7 @@ void TrafficManager::RecalculateSubscription() UpdateViewport(m_currentModelView.first); if (m_currentPosition.second) UpdateMyPosition(m_currentPosition.first); + // TODO update routing – if needed } void TrafficManager::Invalidate(MwmSet::MwmId const & mwmId) @@ -351,9 +416,7 @@ void TrafficManager::UpdateMyPosition(MyPosition const & myPosition) m2::RectD const rect = mercator::RectByCenterXYAndSizeInMeters(myPosition.m_position, kSquareSideM / 2.0); // Request traffic. - UpdateActiveMwms(rect, m_lastRoutingMwmsByRect, m_activeRoutingMwms); - - // @TODO Do all routing stuff. + UpdateActiveMwms(rect, m_lastPositionMwmsByRect, m_activePositionMwms); } void TrafficManager::UpdateViewport(ScreenBase const & screen) @@ -849,8 +912,8 @@ void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId, bool force) void TrafficManager::RequestTrafficData() { - if ((m_activeDrapeMwms.empty() && m_activeRoutingMwms.empty()) || !IsEnabled() || - IsInvalidState() || m_isPaused) + if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) + || !IsEnabled() || IsInvalidState() || m_isPaused) { return; } @@ -1019,7 +1082,7 @@ void TrafficManager::OnTrafficDataUpdate() if (notifyObserver) { // Update traffic colors for routing. - m_observer.OnTrafficInfoAdded(std::move(info)); + m_routingSession.OnTrafficInfoAdded(std::move(info)); m_lastObserverUpdate = steady_clock::now(); } } @@ -1036,7 +1099,7 @@ void TrafficManager::OnTrafficDataUpdate() if (notifyObserver) { // Update traffic colors for routing. - m_observer.OnTrafficInfoRemoved(mwmId); + m_routingSession.OnTrafficInfoRemoved(mwmId); m_lastObserverUpdate = steady_clock::now(); } } @@ -1086,6 +1149,7 @@ void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info) void TrafficManager::UniteActiveMwms(std::set & activeMwms) const { activeMwms.insert(m_activeDrapeMwms.cbegin(), m_activeDrapeMwms.cend()); + activeMwms.insert(m_activePositionMwms.cbegin(), m_activePositionMwms.cend()); activeMwms.insert(m_activeRoutingMwms.cbegin(), m_activeRoutingMwms.cend()); } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index ff615b70f..cc74b56bd 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -9,6 +9,8 @@ #include "indexer/mwm_set.hpp" +#include "routing/routing_session.hpp" + #include "storage/country_info_getter.hpp" #include "traffxml/traff_decoder.hpp" @@ -107,7 +109,7 @@ class TrafficManager final CountryInfoGetterFn countryInfoGetter, CountryParentNameGetterFn const & countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, - traffic::TrafficObserver & observer); + routing::RoutingSession & routingSession); ~TrafficManager(); void Teardown(); @@ -477,8 +479,8 @@ class TrafficManager final * @brief Updates `activeMwms` and requests traffic data. * * The old and new list of active MWMs may refer either to those used by the rendering engine - * (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those used by the routing engine - * (`m_lastRoutingMwmsByRect`/`m_activeRoutingMwms`). + * (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those around the current position. + * (`m_lastPositionMwmsByRect`/`m_activePositionMwms`). * * The method first determines the list of MWMs overlapping with `rect`. If it is identical to * `lastMwmsByRect`, the method returns immediately. Otherwise, it stores the new set in @@ -561,6 +563,8 @@ class TrafficManager final bool IsInvalidState() const; + void OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current); + void UniteActiveMwms(std::set & activeMwms) const; void Pause(); @@ -586,7 +590,14 @@ class TrafficManager final CountryInfoGetterFn m_countryInfoGetterFn; CountryParentNameGetterFn m_countryParentNameGetterFn; GetMwmsByRectFn m_getMwmsByRectFn; - traffic::TrafficObserver & m_observer; + + /* + * Originally this was m_observer, of type traffic::TrafficObserver. Since routing::RoutingSession + * inherits from that class, and an interface to the routing session is needed in order to + * determine the MWMs for which we need traffic information, the type was changed and the member + * renamed to reflect that. + */ + routing::RoutingSession & m_routingSession; df::DrapeEngineSafePtr m_drapeEngine; std::atomic m_currentDataVersion; @@ -624,14 +635,16 @@ class TrafficManager final std::condition_variable m_condition; /* - * To determine for which MWMs we need traffic data, we need to keep track of two groups of MWMs: - * those used by the renderer (i.e. in or just around the viewport) and those used by the routing - * engine (i.e. those within a certain area around the route endpoints). + * To determine for which MWMs we need traffic data, we need to keep track of 3 groups of MWMs: + * those used by the renderer (i.e. in or just around the viewport), those within a certain area + * around the current position, and those used by the routing engine (only if currently routing). + * + * Routing MWMs are stored as a set. * - * Each group is stored twice: as a set and as a vector. The set always holds the MWMs which were - * last seen in use. Both get updated together when active MWMs are added or removed. However, - * the vector is used as a reference to detect changes. It may get cleared when the set is not, - * which is used to invalidate the set without destroying its contents. + * The other groups arestored twice: as a set and as a vector. The set always holds the MWMs which + * were last seen in use. Both get updated together when active MWMs are added or removed. + * However, the vector is used as a reference to detect changes. It may get cleared when the set + * is not, which is used to invalidate the set without destroying its contents. * * Methods which use only the set: * @@ -642,14 +655,15 @@ class TrafficManager final * * Methods which use both, but in a different way: * - * * ClearCache(), removes the requested MWM from the set but clears the vector completely. - * * UpdateActiveMwms(), uses the vector to detect changes. If so, it updates both vector and set. + * * (dead code) ClearCache(), removes the requested MWM from the set but clears the vector completely. + * * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it updates both vector and set. * * Clear() clears both the set and the vector. (Clearing the set is currently disabled as it breaks ForEachActiveMwm.) */ std::vector m_lastDrapeMwmsByRect; std::set m_activeDrapeMwms; - std::vector m_lastRoutingMwmsByRect; + std::vector m_lastPositionMwmsByRect; + std::set m_activePositionMwms; std::set m_activeRoutingMwms; // TODO no longer needed diff --git a/routing/absent_regions_finder.cpp b/routing/absent_regions_finder.cpp index 24bdc1d50..5039baa9d 100644 --- a/routing/absent_regions_finder.cpp +++ b/routing/absent_regions_finder.cpp @@ -19,6 +19,8 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate) { + m_regions.clear(); + if (m_routerThread) { m_routerThread->Cancel(); @@ -52,20 +54,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set & regions) void AbsentRegionsFinder::GetAllRegions(std::set & countries) { - countries.clear(); - - if (!m_routerThread) - return; + // Note: if called from `RoutingSession` callback, m_state will still have its pre-update value. + if (m_routerThread) + { + m_routerThread->Join(); - m_routerThread->Join(); + for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) + { + if (!mwmName.empty()) + m_regions.emplace(mwmName); + } - for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) - { - if (!mwmName.empty()) - countries.emplace(mwmName); + m_routerThread.reset(); } - m_routerThread.reset(); + countries = m_regions; } bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const diff --git a/routing/absent_regions_finder.hpp b/routing/absent_regions_finder.hpp index 8028b6009..0f1541020 100644 --- a/routing/absent_regions_finder.hpp +++ b/routing/absent_regions_finder.hpp @@ -25,9 +25,27 @@ class AbsentRegionsFinder // Creates new thread |m_routerThread| and starts routing in it. void GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate); - // Waits for the routing thread |m_routerThread| to finish and returns results from it. + + /** + * @brief Retrieves the MWMs needed to build the route. + * + * When called for the first time after `GenerateAbsentRegions()`, this method waits for the + * routing thread `m_routerThread` to finish and returns results from it. Results are cached and + * subsequent calls are served from the cache. + * + * @param countries Receives the list of MWM names. + */ void GetAllRegions(std::set & countries); - // Waits for the results from GetAllRegions() and returns only regions absent on the device. + + /** + * @brief Retrieves the missing MWMs needed to build the route. + * + * This calls `GetAllRegions()` and strips from the result all regions already present on the + * device, leaving only the missing ones. If the call to `GetAllRegions()` is the first one after + * calling `GenerateAbsentRegions()`, this involves waiting for the router thread to finish. + * + * @param absentCountries Receives the list of missing MWM names. + */ void GetAbsentRegions(std::set & absentCountries); private: @@ -40,5 +58,19 @@ class AbsentRegionsFinder DataSource & m_dataSource; std::unique_ptr m_routerThread; + + /** + * @brief Mutex for access to `m_regions`. + * + * Threads which access `m_regions` must lock this mutex while doing so. + */ + std::mutex m_mutex; + + /** + * @brief Regions required for building the last route. + * + * This member is cleared by `GenerateAbsentRegions()` and populated by `GetAllRegions()`. + */ + std::set m_regions; }; } // namespace routing diff --git a/routing/async_router.cpp b/routing/async_router.cpp index 161fc5102..3e96815b9 100644 --- a/routing/async_router.cpp +++ b/routing/async_router.cpp @@ -83,6 +83,13 @@ bool AsyncRouter::FindClosestProjectionToRoad(m2::PointD const & point, return m_router->FindClosestProjectionToRoad(point, direction, radius, proj); } +void AsyncRouter::GetAllRegions(std::set & countries) +{ + if (!m_absentRegionsFinder) + return; + m_absentRegionsFinder->GetAllRegions(countries); +} + void AsyncRouter::RouterDelegateProxy::OnProgress(float progress) { ProgressCallback onProgress = nullptr; diff --git a/routing/async_router.hpp b/routing/async_router.hpp index d03e13987..d9c70f09f 100644 --- a/routing/async_router.hpp +++ b/routing/async_router.hpp @@ -62,6 +62,15 @@ class AsyncRouter final bool FindClosestProjectionToRoad(m2::PointD const & point, m2::PointD const & direction, double radius, EdgeProj & proj); + /** + * @brief Retrieves the MWMs needed to build the route. + * + * Waits for the routing thread to finish and returns the list of MWM names from it. + * + * @param countries Receives the list of MWM names. + */ + void GetAllRegions(std::set & countries); + private: /// Worker thread function void ThreadFunc(); diff --git a/routing/routing_session.cpp b/routing/routing_session.cpp index 2068825f8..fb21ab773 100644 --- a/routing/routing_session.cpp +++ b/routing/routing_session.cpp @@ -470,6 +470,13 @@ double RoutingSession::GetCompletionPercent() const return percent; } +void RoutingSession::GetAllRegions(std::set & countries) +{ + if (!m_router) + return; + m_router->GetAllRegions(countries); +} + void RoutingSession::PassCheckpoints() { CHECK_THREAD_CHECKER(m_threadChecker, ()); diff --git a/routing/routing_session.hpp b/routing/routing_session.hpp index d528430f6..95f643963 100644 --- a/routing/routing_session.hpp +++ b/routing/routing_session.hpp @@ -258,6 +258,15 @@ class RoutingSession : public traffic::TrafficObserver, public traffic::TrafficC double GetCompletionPercent() const; + /** + * @brief Retrieves the MWMs needed to build the route. + * + * Waits for the `RegionsRouter` thread to finish and returns the list of MWM names from it. + * + * @param countries Receives the list of MWM names. + */ + void GetAllRegions(std::set & countries); + private: struct DoReadyCallback { From 4324e329e52ac0bb097c3654cee2e849e9e7d1dc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 1 Jul 2025 21:02:14 +0300 Subject: [PATCH 112/252] [routing] Documentation Signed-off-by: mvglasow --- routing/absent_regions_finder.hpp | 14 +++++++++++--- routing/async_router.hpp | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/routing/absent_regions_finder.hpp b/routing/absent_regions_finder.hpp index 0f1541020..a79fad151 100644 --- a/routing/absent_regions_finder.hpp +++ b/routing/absent_regions_finder.hpp @@ -14,8 +14,12 @@ namespace routing { using LocalFileCheckerFn = std::function; -// Encapsulates generation of mwm names of absent regions needed for building the route between -// |checkpoints|. For this purpose the new thread is used. +/** + * @brief Generates a list of MWMs needed to build a route. + * + * The `AbsentRegionsFinder` class encapsulates generation of MWM names of absent regions needed + * for building the route between `checkpoints`. For this purpose a separate worker thread is used. + */ class AbsentRegionsFinder { public: @@ -23,7 +27,11 @@ class AbsentRegionsFinder LocalFileCheckerFn const & localFileChecker, std::shared_ptr numMwmIds, DataSource & dataSource); - // Creates new thread |m_routerThread| and starts routing in it. + /** + * @brief Creates new thread `m_routerThread` and starts routing in it. + * @param checkpoints The checkpoints of the route (start, optional intermediate points, destination) + * @param delegate + */ void GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate); /** diff --git a/routing/async_router.hpp b/routing/async_router.hpp index d9c70f09f..26690a227 100644 --- a/routing/async_router.hpp +++ b/routing/async_router.hpp @@ -23,11 +23,15 @@ namespace routing { -/// Dispatches a route calculation on a worker thread +/** + * @brief The AsyncRouter class is a wrapper class to run routing routines in a different thread. + * + * It encapsulates an `IRouter` (or subclass) instance, set with `SetRouter()`, and runs it in a + * separate worker thread to calculate the route. + */ class AsyncRouter final { public: - /// AsyncRouter is a wrapper class to run routing routines in the different thread AsyncRouter(PointCheckCallback const & pointCheckCallback); ~AsyncRouter(); From 4f4d376a4a160964440bfcd0dfc6b820b7bae80b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 1 Jul 2025 22:59:27 +0300 Subject: [PATCH 113/252] [traffic] Comment out unused code Signed-off-by: mvglasow --- map/traffic_manager.cpp | 6 ++++++ map/traffic_manager.hpp | 3 +++ 2 files changed, 9 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 9a48c806d..8b6051d50 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -56,7 +56,10 @@ auto constexpr kTrafficXMLFileName = "traffic.xml"; TrafficManager::CacheEntry::CacheEntry() : m_isLoaded(false) +// TODO no longer needed +#ifdef traffic_dead_code , m_dataSize(0) +#endif , m_retriesCount(0) , m_isWaitingForResponse(false) , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) @@ -64,7 +67,10 @@ TrafficManager::CacheEntry::CacheEntry() TrafficManager::CacheEntry::CacheEntry(time_point const & requestTime) : m_isLoaded(false) +// TODO no longer needed +#ifdef traffic_dead_code , m_dataSize(0) +#endif , m_lastActiveTime(requestTime) , m_lastRequestTime(requestTime) , m_retriesCount(0) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index cc74b56bd..44ab7fa62 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -285,10 +285,13 @@ class TrafficManager final */ bool m_isLoaded; +// TODO no longer needed +#ifdef traffic_dead_code /** * @brief The amount of memory occupied by the coloring for this MWM. */ size_t m_dataSize; +#endif /** * @brief When the last update request occurred, not including forced updates. From f32493faaafc3aed6790358c10c5cdf6aa28f894 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 1 Jul 2025 22:59:51 +0300 Subject: [PATCH 114/252] [traffxml] Comment and documentation cleanup Signed-off-by: mvglasow --- traffxml/traff_model.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index e169e1208..f7d44531c 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -25,13 +25,6 @@ constexpr uint8_t kMaxspeedNone = 255; * converted from ISO 8601 which refers to the same UTC time as its ISO 8601 representation. * Time zone information is not guaranteed to be preserved: `13:37+01:00` may be returned e.g. as * `12:37Z` or `06:37-06:00`. - * - * Code using `IsoTime` must not rely on it being identical to any other type, as this is not - * guaranteed to be stable. - */ -/* - * Where no time zone is indicated, the timestamp shall always be interpreted as UTC. - * `IsoTime` currently maps to `std::tm`, but this is not guaranteed. */ class IsoTime { From 6656c7e4419c8c6fa7ef751c53b5755a709cee18 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 3 Jul 2025 23:22:17 +0300 Subject: [PATCH 115/252] [traffxml] Make sources pluggable Signed-off-by: mvglasow --- map/framework.cpp | 8 + map/traffic_manager.cpp | 152 ++----- map/traffic_manager.hpp | 121 ++--- traffxml/CMakeLists.txt | 2 + traffxml/traff_assessment_tool/mainwindow.cpp | 2 +- traffxml/traff_source.cpp | 105 +++++ traffxml/traff_source.hpp | 426 ++++++++++++++++++ 7 files changed, 657 insertions(+), 159 deletions(-) create mode 100644 traffxml/traff_source.cpp create mode 100644 traffxml/traff_source.hpp diff --git a/map/framework.cpp b/map/framework.cpp index f5f5d2b9b..6315099d6 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -23,6 +23,8 @@ #include "storage/routing_helpers.hpp" #include "storage/storage_helpers.hpp" +#include "traffxml/traff_source.hpp" + #include "drape_frontend/color_constants.hpp" #include "drape_frontend/gps_track_point.hpp" #include "drape_frontend/visual_params.hpp" @@ -392,6 +394,12 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) LoadMapsSync(); m_trafficManager.SetEnabled(LoadTrafficEnabled()); + + /* + * MockTraffSource for debugging purposes. + * TODO Replace with a real source, parametrized and conditionally loaded, once we have one. + */ + traffxml::MockTraffSource::Create(m_trafficManager); } Framework::~Framework() diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 8b6051d50..121fa0b17 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -439,78 +439,31 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) UpdateActiveMwms(screen.ClipRect(), m_lastDrapeMwmsByRect, m_activeDrapeMwms); } -std::string TrafficManager::GetMwmFilters(std::set & mwms) -{ - std::vector rects; - for (auto mwmId : mwms) - rects.push_back(mwmId.GetInfo()->m_bordersRect); - return traffxml::FiltersToXml(rects); -} - -// TODO make this work with multiple sources (e.g. Android) -bool TrafficManager::Subscribe(std::set & mwms) -{ - // TODO what if we’re subscribed already? - std::string filterList = GetMwmFilters(mwms); - // TODO - LOG(LINFO, ("Would subscribe to:\n", filterList)); - m_subscriptionId = "placeholder_subscription_id"; - m_isPollNeeded = true; // would be false if we got a feed here - return true; -} - -// TODO make this work with multiple sources (e.g. Android) -bool TrafficManager::ChangeSubscription(std::set & mwms) -{ - if (!IsSubscribed()) - return false; - std::string filterList = GetMwmFilters(mwms); - // TODO - LOG(LINFO, ("Would change subscription", m_subscriptionId, "to:\n", filterList)); - m_isPollNeeded = true; // would be false if we got a feed here - return true; -} - -bool TrafficManager::SetSubscriptionArea() +void TrafficManager::SubscribeOrChangeSubscription() { std::set activeMwms; - if (!IsSubscribed()) + if (m_activeMwmsChanged) { { std::lock_guard lock(m_mutex); m_activeMwmsChanged = false; UniteActiveMwms(activeMwms); } - if (!Subscribe(activeMwms)) - return false; - } - else if (m_activeMwmsChanged) - { + { - std::lock_guard lock(m_mutex); - m_activeMwmsChanged = false; - UniteActiveMwms(activeMwms); + std::lock_guard lock(m_trafficSourceMutex); + for (auto & source : m_trafficSources) + source->SubscribeOrChangeSubscription(activeMwms); } - if (!ChangeSubscription(activeMwms)) - return false; } - return true; } -// TODO make this work with multiple sources (e.g. Android) void TrafficManager::Unsubscribe() { - if (!IsSubscribed()) - return; - // TODO - LOG(LINFO, ("Would unsubscribe from", m_subscriptionId)); - m_subscriptionId.clear(); -} - -bool TrafficManager::IsSubscribed() -{ - return !m_subscriptionId.empty(); + std::lock_guard lock(m_trafficSourceMutex); + for (auto & source : m_trafficSources) + source->Unsubscribe(); } bool TrafficManager::RestoreCache() @@ -563,49 +516,40 @@ bool TrafficManager::RestoreCache() return false; } -// TODO make this work with multiple sources (e.g. Android) -// TODO deal with subscriptions rejected by the server (delete, resubscribe) -bool TrafficManager::Poll() +void TrafficManager::Poll() { - // TODO - //std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany.xml"); - std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml"); - //std::string fileName("test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml"); - traffxml::LocalStorage storage(fileName); - pugi::xml_document document; - auto const load_result = storage.Load(document); - if (!load_result) - return false; + std::lock_guard lock(m_trafficSourceMutex); + for (auto & source : m_trafficSources) + if (source->IsPollNeeded()) + source->Poll(); +} - std::setlocale(LC_ALL, "en_US.UTF-8"); - traffxml::TraffFeed feed; - if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed)) - { - { - std::lock_guard lock(m_mutex); - m_feedQueue.push_back(feed); - } - m_lastResponseTime = steady_clock::now(); - m_isPollNeeded = false; - return true; - } - else +void TrafficManager::ReceiveFeed(traffxml::TraffFeed feed) +{ { - LOG(LWARNING, ("An error occurred parsing the TraFF feed")); - // TODO should we really reset m_isPollNeeded here? - m_isPollNeeded = false; - return false; + std::lock_guard lock(m_mutex); + m_feedQueue.push_back(feed); } + m_condition.notify_one(); } -void TrafficManager::Push(traffxml::TraffFeed feed) +void TrafficManager::RegisterSource(std::unique_ptr source) { + std::set activeMwms; + { std::lock_guard lock(m_mutex); - m_feedQueue.push_back(feed); - // TODO should we update m_lastResponseTime? + UniteActiveMwms(activeMwms); } - m_condition.notify_one(); + + source->SubscribeOrChangeSubscription(activeMwms); + + { + std::lock_guard lock(m_trafficSourceMutex); + m_trafficSources.push_back(std::move(source)); + } + + m_isPollNeeded = true; } void TrafficManager::PurgeExpiredMessages() @@ -759,25 +703,17 @@ void TrafficManager::ThreadRoutine() LOG(LINFO, ("active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); // this is a no-op if active MWMs have not changed - if (!SetSubscriptionArea()) - { - LOG(LWARNING, ("SetSubscriptionArea failed.")); - if (!IsSubscribed()) - // do not skip out of the loop, we may need to process pushed feeds - LOG(LWARNING, ("No subscription, no traffic data will be retrieved.")); - } + SubscribeOrChangeSubscription(); /* - * Fetch traffic data if needed and we have a subscription. - * m_isPollNeeded may be set by WaitForRequest() and set/unset by SetSubscriptionArea(). - */ - if (m_isPollNeeded && IsSubscribed()) + * Poll sources if needed. + * m_isPollNeeded may be set by WaitForRequest() and set/unset by SubscribeOrChangeSubscription(). + */ + if (m_isPollNeeded) { - if (!Poll()) - { - LOG(LWARNING, ("Poll failed.")); - // TODO set failed status somewhere and retry - } + m_lastResponseTime = steady_clock::now(); + m_isPollNeeded = false; + Poll(); } } LOG(LINFO, (m_feedQueue.size(), "feed(s) in queue")); @@ -1152,6 +1088,12 @@ void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info) } #endif +void TrafficManager::GetActiveMwms(std::set & activeMwms) +{ + std::lock_guard lock(m_mutex); + UniteActiveMwms(activeMwms); +} + void TrafficManager::UniteActiveMwms(std::set & activeMwms) const { activeMwms.insert(m_activeDrapeMwms.cbegin(), m_activeDrapeMwms.cend()); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 44ab7fa62..50256815b 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -15,6 +15,7 @@ #include "traffxml/traff_decoder.hpp" #include "traffxml/traff_model.hpp" +#include "traffxml/traff_source.hpp" #include "traffxml/traff_storage.hpp" #include "geometry/point2d.hpp" @@ -36,7 +37,7 @@ #include #include -class TrafficManager final +class TrafficManager final : public traffxml::TraffSourceManager { public: using CountryInfoGetterFn = std::function; @@ -234,15 +235,36 @@ class TrafficManager final void SetTestMode(); /** - * @brief Processes a traffic feed received through a push operation. + * @brief Processes a traffic feed. * - * Push is safe to call from any thread. + * The feed may be a result of a pull operation, or received through a push operation. + * (Push operations are not supported by all sources.) * - * Push operations are not supported on all platforms. + * This method is safe to call from any thread. * * @param feed The traffic feed. */ - void Push(traffxml::TraffFeed feed); + virtual void ReceiveFeed(traffxml::TraffFeed feed) override; + + /** + * @brief Registers a `TraffSource`. + * @param source The source. + */ + virtual void RegisterSource(std::unique_ptr source) override; + + /** + * @brief Retrieves all currently active MWMs. + * + * This method retrieves all MWMs in the viewport, within a certain distance of the current + * position (if there is a valid position) or part of the route (if any), and stores them in + * `activeMwms`. + * + * This method locks `m_mutex` and is therefore safe to call from any thread. Callers which + * already hold `m_mutex` can use the private `UniteActiveMwms()` method instead. + * + * @param activeMwms Retrieves the list of active MWMs. + */ + virtual void GetActiveMwms(std::set & activeMwms) override; /** * @brief Purges expired messages from the cache. @@ -332,50 +354,21 @@ class TrafficManager final }; /** - * @brief Returns a TraFF filter list for a set of MWMs. - * - * @param mwms The MWMs for which a filter list is to be created. - * @return A `filter_list` in XML format. - */ - std::string GetMwmFilters(std::set & mwms); - - /** - * @brief Subscribes to a traffic service. + * @brief Ensures every TraFF source has a subscription covering all currently active MWMs. * - * @param mwms The MWMs for which data is needed. - * @return true on success, false on failure. + * This method cycles through all TraFF sources in `m_trafficSources` and calls + * `SubscribeOrChangeSubscription()` on each of them. */ - bool Subscribe(std::set & mwms); + void SubscribeOrChangeSubscription(); /** - * @brief Changes an existing traffic subscription. + * @brief Unsubscribes from all traffic services we are subscribed to. * - * @param mwms The new set of MWMs for which data is needed. - * @return true on success, false on failure. - */ - bool ChangeSubscription(std::set & mwms); - - /** - * @brief Ensures we have a subscription covering all currently active MWMs. - * - * This method subscribes to a traffic service if not already subscribed, or changes the existing - * subscription otherwise. - * - * @return true on success, false on failure. - */ - bool SetSubscriptionArea(); - - /** - * @brief Unsubscribes from a traffic service we are subscribed to. + * This method cycles through all TraFF sources in `m_trafficSources` and calls `Unsubscribe()` + * on each of them. */ void Unsubscribe(); - /** - * @brief Whether we are currently subscribed to a traffic service. - * @return - */ - bool IsSubscribed(); - /** * @brief Restores the message cache from file storage. * @@ -393,11 +386,12 @@ class TrafficManager final bool RestoreCache(); /** - * @brief Polls the traffic service for updates. + * @brief Polls all traffic services for updates. * - * @return true on success, false on failure. + * This method cycles through all TraFF sources in `m_trafficSources` and calls `IsPollNeeded()` + * on each of them. If this method returns true, it then calls `Poll()` on the source. */ - bool Poll(); + void Poll(); /** * @brief Purges expired messages from the cache. @@ -568,6 +562,18 @@ class TrafficManager final void OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current); + /** + * @brief Retrieves all currently active MWMs. + * + * This method retrieves all MWMs in the viewport, within a certain distance of the current + * position (if there is a valid position) or part of the route (if any), and stores them in + * `activeMwms`. + * + * The caller must hold `m_mutex` prior to calling this method. `GetActiveMwms()` is available + * as a convenience wrapper which locks `m_mutex`, calls this method and releases it. + * + * @param activeMwms Retrieves the list of active MWMs. + */ void UniteActiveMwms(std::set & activeMwms) const; void Pause(); @@ -634,6 +640,13 @@ class TrafficManager final std::map m_mwmCache; + /** + * @brief The TraFF sources from which we get traffic information. + * + * Threads must lock `m_trafficSourceMutex` prior to accessing this member. + */ + std::vector> m_trafficSources; + bool m_isRunning; std::condition_variable m_condition; @@ -691,9 +704,18 @@ class TrafficManager final * @brief Mutex for access to shared members. * * Threads which access shared members (see documentation) must lock this mutex while doing so. + * + * @note To access `m_trafficSource`, lock `m_trafficSourceMutex`, not this mutex. */ std::mutex m_mutex; + /** + * @brief Mutex for access to `m_trafficSources`. + * + * Threads which access `m_trafficSources` must lock this mutex while doing so. + */ + std::mutex m_trafficSourceMutex; + /** * @brief Worker thread which fetches traffic updates. */ @@ -724,18 +746,11 @@ class TrafficManager final */ bool m_activeMwmsChanged = false; - /** - * @brief The subscription ID received from the traffic server. - * - * An empty subscription ID means no subscription. - */ - std::string m_subscriptionId; - /** * @brief Whether a poll operation is needed. * - * Used in the worker thread. A poll operation is needed unless a subscription (or subscription - * change) operation was performed before and a feed was received as part of it. + * Used in the worker thread to indicate we need to poll all sources. The poll operation may still + * be inhibited for individual sources. */ bool m_isPollNeeded; diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index 93b4937d3..834f73918 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -7,6 +7,8 @@ set(SRC traff_model.hpp traff_model_xml.cpp traff_model_xml.hpp + traff_source.cpp + traff_source.hpp traff_storage.cpp traff_storage.hpp ) diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index ef2440139..3d551a6b5 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -376,7 +376,7 @@ void MainWindow::OnOpenTrafficSample() shiftedFeed.push_back(message); } LOG(LINFO, ("TraFF data parsed successfully, pushing")); - m_framework.GetTrafficManager().Push(shiftedFeed); + m_framework.GetTrafficManager().ReceiveFeed(shiftedFeed); LOG(LINFO, ("Push completed")); } else diff --git a/traffxml/traff_source.cpp b/traffxml/traff_source.cpp new file mode 100644 index 000000000..502d3cc95 --- /dev/null +++ b/traffxml/traff_source.cpp @@ -0,0 +1,105 @@ +#include "traffxml/traff_source.hpp" + +#include "traffxml/traff_model_xml.hpp" +#include "traffxml/traff_storage.hpp" + +#include "geometry/rect2d.hpp" + +#include + +namespace traffxml { +TraffSource::TraffSource(TraffSourceManager & manager) + : m_manager(manager) +{} + +void TraffSource::SubscribeOrChangeSubscription(std::set & mwms) +{ + std::lock_guard lock(m_mutex); + if (!IsSubscribed()) + Subscribe(mwms); + else + ChangeSubscription(mwms); +} + +std::string TraffSource::GetMwmFilters(std::set & mwms) +{ + std::vector rects; + for (auto mwmId : mwms) + rects.push_back(mwmId.GetInfo()->m_bordersRect); + return traffxml::FiltersToXml(rects); +} + +void MockTraffSource::Create(TraffSourceManager & manager) +{ + std::unique_ptr source = std::unique_ptr(new MockTraffSource(manager)); + manager.RegisterSource(std::move(source)); +} + +MockTraffSource::MockTraffSource(TraffSourceManager & manager) + : TraffSource(manager) +{} + +void MockTraffSource::Subscribe(std::set & mwms) +{ + std::string filterList = GetMwmFilters(mwms); + LOG(LINFO, ("Would subscribe to:\n", filterList)); + m_subscriptionId = "placeholder_subscription_id"; + m_nextRequestTime = std::chrono::steady_clock::now(); // would be in the future if we got a feed here +} + +void MockTraffSource::ChangeSubscription(std::set & mwms) +{ + if (!IsSubscribed()) + return; + std::string filterList = GetMwmFilters(mwms); + LOG(LINFO, ("Would change subscription", m_subscriptionId, "to:\n", filterList)); + m_nextRequestTime = std::chrono::steady_clock::now(); // would be in the future if we got a feed here +} + +void MockTraffSource::Unsubscribe() +{ + std::lock_guard lock(m_mutex); + if (!IsSubscribed()) + return; + LOG(LINFO, ("Would unsubscribe from", m_subscriptionId)); + m_subscriptionId.clear(); +} + +bool MockTraffSource::IsPollNeeded() +{ + return m_nextRequestTime.load() <= std::chrono::steady_clock::now(); +} + +void MockTraffSource::Poll() +{ + //std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany.xml"); + std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml"); + //std::string fileName("test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml"); + traffxml::LocalStorage storage(fileName); + pugi::xml_document document; + auto const load_result = storage.Load(document); + if (!load_result) + return; + + m_lastRequestTime = std::chrono::steady_clock::now(); + std::setlocale(LC_ALL, "en_US.UTF-8"); + traffxml::TraffFeed feed; + if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed)) + { + m_lastResponseTime = std::chrono::steady_clock::now(); + m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval; + m_lastAvailability = Availability::IsAvailable; + m_manager.ReceiveFeed(feed); + } + else + { + LOG(LWARNING, ("An error occurred parsing the TraFF feed")); + m_lastAvailability = Availability::Error; + /* + * TODO how should we deal with future requests? + * Static files usually don’t change. + */ + m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval; + } +} +} // namespace traffxml diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp new file mode 100644 index 000000000..8497c6a5f --- /dev/null +++ b/traffxml/traff_source.hpp @@ -0,0 +1,426 @@ +#pragma once + +#include "traffxml/traff_model.hpp" + +#include "indexer/mwm_set.hpp" + +#include +#include +#include + +namespace traffxml +{ +class TraffSource; + +/** + * @brief Abstract class which manages TraFF sources. + * + * `TraffSource` and its subclasses register with `TraffSourceManager` upon creation. The + * `TraffSourceManager` calls `TraffSource` methods to manage its subscription and poll for + * messages, and exposes a method to deliver message feeds. + */ +class TraffSourceManager +{ +public: + /** + * @brief Retrieves all currently active MWMs. + * + * This method retrieves all MWMs for which traffic data is needed (viewport, current position + * and route) and stores them in `activeMwms`. + * + * Implementations must ensure thread safety, so that this method can be called from any thread. + * + * @param activeMwms Retrieves the list of active MWMs. + */ + virtual void GetActiveMwms(std::set & activeMwms) = 0; + + /** + * @brief Processes a traffic feed. + * + * The feed may be a result of a pull operation, or received through a push operation. + * (Push operations are not supported by all sources.) + * + * This method is safe to call from any thread. + * + * @param feed The traffic feed. + */ + virtual void ReceiveFeed(traffxml::TraffFeed feed) = 0; + + /** + * @brief Registers a `TraffSource`. + * @param source The source. + */ + virtual void RegisterSource(std::unique_ptr source) = 0; +}; + +/** + * @brief Abstract base class for TraFF sources. + * + * Subclasses encapsulate various forms of TraFF sources. The base class provides methods for + * subscription management, message retrieval and service status. + * + * Any `TraffSource` method may call `TrafficManager` methods exposed through the + * `TraffSourceManager` interface. The traffic manager must therefore ensure there is no conflict + * between thread-synchronization mechanisms held when calling a `TraffSource` method and those + * which may get requested when that method calls a `TraffSourceManager` method. + * + * Each subclass should implement a non-public constructor (private if the subclass is final, + * protected otherwise) and a public factory method. The factory method takes the same arguments + * as the constructor, creates an instance wrapped in a `std::unique_ptr` and registers it with + * the `TraffSourceManager`. It can be implemented as follows: + * ``` + * void SomeTraffSource::Create(TraffSourceManager & manager, SomeOtherArg & otherArg) + * { + * std::unique_ptr source = std::unique_ptr(new SomeTraffSource(manager, otherArg)); + * manager.RegisterSource(std::move(source)); + * } + * ``` + * + * Each subclass must provide implementations for `Subscribe()`, `ChangeSubscription()`, + * `Unsubscribe()`, `IsPollNeeded()` and `Poll()`. + * + * Most of these methods can be called from any thread, including the UI thread (see documentation + * of individual methods for details). This has two implications: + * + * Subclasses must ensure thread safety for methods they implement, in particular regarding access + * to shared members. This can be done by locking `m_mutex`. + * + * Also, methods should not block or perform lengthy operations. Network operations must be + * delegated to a separate thread (attempting a network operation on the UI thread will cause the + * application to be killed on Android). + * + * This class provides various protected members which subclasses can build upon. These include a + * reference to the `TraffSourceManager`, a mutex for thread-safe access, a subscription ID, + * timestamps for the last request and response, as well as for the next request, a retry count + * for failed operations, and an indication of a pending request. + */ +class TraffSource +{ +public: + /** + * @brief Whether traffic data is available. + * + * The default value upon creating a new instance should be `Unknown`. After that, the value + * should be changed based on the result of the last TraFF operation, as detailed below: + * + * `OK` changes the status from `Unknown`, or any error which would be resolved by the last + * operation, to `IsAvailable`. + * + * `INVALID` indicates a condition which should be treated as a bug, either in the source or its + * backend. It should generate a log entry, and changes the status to `Error`. + * + * `SUBSCRIPTION_REJECTED` changes the status to `SubscriptionRejected`. + * + * `NOT_COVERED` changes the status to `NotCovered`. + * + * `PARTIALLY_COVERED` has the same effect as `OK`. + * + * `SUBSCRIPTION_UNKNOWN` should be handled by clearing the subscription ID and resubscribing, + * then setting the status based on the result of the new subscription. + * + * `INTERNAL_ERROR` changes the status to `Error`. + * + * If the source does not seem to be connected to a valid backend (e.g. if a HTTP source responds + * with an HTTP error), the status should be changed to `Error`. + * + * If a TraFF `GET_CAPABILITIES` request returns a minimum version higher than supported by this + * application, the status should be changed to `ExpiredApp` and no further requests to the source + * should be attempted. + * + * @todo Should `PARTIALLY_COVERED`, or `GET_CAPABILITIES` reporting a target version higher than + * supported, be stored in the class instance? + */ + enum class Availability + { + /** + * The source is working normally. + * This status is reached after the first request was made, if it is successful. + */ + IsAvailable, + /** + * The source, or its backend, rejected the subscription. + * This may happen for various reasons, possibly because the requested area was too large. + * An existing subscription ID (if any) remains valid, but no poll operations should be + * attempted until the subscription is changed successfully. + */ + SubscriptionRejected, + /** + * The requested area is not covered by the source. + * An existing subscription ID (if any) remains valid, but poll operations will not return any + * messages until the subscription is changed successfully. + */ + NotCovered, + /** + * The source has reported an internal error, has reported an invalid request or returned + * invalid data. + * The failed operation should be retried at a resonably chosen interval. After the source + * resumes normal operation, previously issued subscription IDs may no longer be valid (in which + * case the caller should attempt to resubscribe) and/or messages may be repeated. + */ + Error, + /** The app does not support the minimum TraFF version required by the source. */ + ExpiredApp, + /** No request was made yet. */ + Unknown + }; + + /** + * @brief Ensures we have a subscription covering the MWMs indicated. + * + * This method subscribes to a traffic service if not already subscribed, or changes the existing + * subscription otherwise. + * + * The default implementation acquires the mutex before running the following code: + * + * ``` + * if (!IsSubscribed()) + * Subscribe(mwms); + * else + * ChangeSubscription(mwms); + * ``` + * + * Therefore, `IsSubscribed()`, `Subscribe()` and `ChangeSubscription()` need not (and should not) + * acquire the mutex on their own. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void SubscribeOrChangeSubscription(std::set & mwms); + + /** + * @brief Whether this source should be polled. + * + * Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and + * poll the source only if the result is true. + * + * It is up to the source to decide when to return true or false. Typically a source would return + * false if another request is still pending, a predefined poll interval has not yet elapsed since + * the previous successful response, during the retry interval following an error, or if an error + * is not recoverable (such as `ExpiredApp`). In all other case it would return true. + * + * This method is only called from the `TrafficManager` worker thread. + * + * @return true if the source should be polled, false if not. + */ + virtual bool IsPollNeeded() = 0; + + /** + * @brief Polls the traffic service for updates. + * + * For sources which reliably push data, this implementation may do nothing. + * + * It is up to the caller to call `IsPollNeeded()` prior to calling this function, and use its + * result to decide whether or not to poll, or to force a poll operation. + * + * Sources should handle cases in which the backend responds with `SUBSCRIPTION_UNKNOWN`, usually + * by deleting the subscription ID and resubscribing to the set of active MWMs. The set of active + * MWMs can be retrieved by calling `m_manager.GetActiveMwms()`. + * + * This method is only called from the `TrafficManager` worker thread. + */ + virtual void Poll() = 0; + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + * + * Unsubscribing without being subscribed is a no-op. + */ + virtual void Unsubscribe() = 0; + +protected: + /** + * @brief Constructs a new `TraffSource`. + * @param manager The `TrafficSourceManager` instance to register the source with. + */ + TraffSource(TraffSourceManager & manager); + + /** + * @brief Returns a TraFF filter list for a set of MWMs. + * + * @param mwms The MWMs for which a filter list is to be created. + * @return A `filter_list` in XML format. + */ + static std::string GetMwmFilters(std::set & mwms); + + /** + * @brief Subscribes to a traffic service. + * + * If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is + * acquired before this method is called, and implementations do not need to (and should not) + * acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`. + * + * @param mwms The MWMs for which data is needed. + */ + virtual void Subscribe(std::set & mwms) = 0; + + /** + * @brief Changes an existing traffic subscription. + * + * If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is + * acquired before this method is called, and implementations do not need to (and should not) + * acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`. + * + * Sources should handle cases in which the backend responds with `SUBSCRIPTION_UNKNOWN`, usually + * by deleting the subscription ID and resubscribing to `mwms`. Asynchronous implementations, in + * which `mwms` may no longer be available when the operation completes, can retrieve the set of + * active MWMs can be retrieved by calling `m_manager.GetActiveMwms()`. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void ChangeSubscription(std::set & mwms) = 0; + + /** + * @brief Whether we are currently subscribed to a traffic service. + * + * If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is + * acquired before this method is called, and implementations do not need to (and should not) + * acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`. + * + * @return true if subscribed, false if not. + */ + virtual bool IsSubscribed() { return !m_subscriptionId.empty(); } + + TraffSourceManager & m_manager; + + /** + * @brief Mutex for access to shared members. + * + * Any access to members shared between threads must be protected by obtaining this mutex first. + */ + std::mutex m_mutex; + + /** + * @brief The subscription ID received from the backend. + * + * An empty subscription ID means no subscription. + */ + std::string m_subscriptionId; + + /** + * @brief When the last update request occurred. + * + * This timestamp is the basis for determining whether an update is needed. + * + * It is initially in the past. Subclasses that use it should update it whenever a request is made. + */ + std::atomic> m_lastRequestTime; + + /** + * @brief When the last response was received. + * + * This timestamp is the basis for determining whether a network request timed out, or if data is + * outdated. + * + * It is initially in the past. Subclasses that use it should update it whenever a response to a + * request is received. + */ + std::atomic> m_lastResponseTime; + + /** + * @brief When the next request should be made. + * + * This timestamp is initiated to current time and updated when a request is made, or a response + * is received. + * + * It is initially in the present. Subclasses that use it should update it on every request or + * response, setting it a defined timespan into the future. + */ + std::atomic> m_nextRequestTime = std::chrono::steady_clock::now(); + + /** + * @brief The number of failed traffic requests for this source. + * + * Reset when a request is successful. + */ + std::atomic m_retriesCount = 0; + + /** + * @brief Whether a request is currently pending for this source. + * + * Set to `true` when a request is scheduled, reverted to `false` when a response is received or + * the request fails. + */ + std::atomic m_isWaitingForResponse = false; + + /** + * @brief The last reported availability of the traffic source. + * + * See the documentation of `Availability` for possible values and their meanings. + * + * Availability is `Unknown` until a result for the first request (positive or negative) has been + * received. Subclasses must update this value, ensuring it always correctly reflects the status + * of the source. + */ + std::atomic m_lastAvailability = Availability::Unknown; + +private: + DISALLOW_COPY(TraffSource); +}; + +/** + * @brief A mock TraFF source. + * + * This source will accept any and all subscription requests and return a static subscription ID. + * Polling will return a static set of messages. + */ +class MockTraffSource : public TraffSource +{ +public: + /** + * @brief Creates a new `MockTraffSource` instance and registers it with the traffic manager. + * + * @param manager The traffic manager to register the new instance with + */ + static void Create(TraffSourceManager & manager); + + /** + * @brief Subscribes to a traffic service. + * + * @param mwms The MWMs for which data is needed. + */ + virtual void Subscribe(std::set & mwms) override; + + /** + * @brief Changes an existing traffic subscription. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void ChangeSubscription(std::set & mwms) override; + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + */ + virtual void Unsubscribe() override; + + /** + * @brief Whether this source should be polled. + * + * Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and + * poll the source only if the result is true. + * + * This implementation uses `m_nextRequestTime` to determine when the next poll is due. When a + * feed is received, `m_nextRequestTime` is set to a point in time 5 minutes in the future. As + * long as `m_nextRequestTime` is in the future, this method returns false. + * + * @return true if the source should be polled, false if not. + */ + virtual bool IsPollNeeded() override; + + /** + * @brief Polls the traffic service for updates. + */ + virtual void Poll() override; + +protected: + /** + * @brief Constructs a new `MockTraffSource`. + * @param manager The `TrafficSourceManager` instance to register the source with. + */ + MockTraffSource(TraffSourceManager & manager); + +private: + /** + * @brief The update interval, 5 minutes. + */ + static auto constexpr m_updateInterval = std::chrono::minutes(5); +}; +} // namespace traffxml From 121bdc4af8944739565d0e0aec513238fb15be47 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 6 Jul 2025 20:49:55 +0300 Subject: [PATCH 116/252] [traffic] Do not announce traffic updates if nothing has changed Signed-off-by: mvglasow --- map/traffic_manager.cpp | 22 +++++++++++++++++++--- map/traffic_manager.hpp | 4 +++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 121fa0b17..796e30cd4 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -558,19 +558,24 @@ void TrafficManager::PurgeExpiredMessages() OnTrafficDataUpdate(); } -void TrafficManager::PurgeExpiredMessagesImpl() +bool TrafficManager::PurgeExpiredMessagesImpl() { std::lock_guard lock(m_mutex); + bool result = false; LOG(LINFO, ("before:", m_messageCache.size(), "message(s)")); traffxml::IsoTime now = traffxml::IsoTime::Now(); for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) { if (it->second.IsExpired(now)) + { it = m_messageCache.erase(it); + result = true; + } else ++it; } LOG(LINFO, ("after:", m_messageCache.size(), "message(s)")); + return result; } void TrafficManager::ConsolidateFeedQueue() @@ -692,12 +697,21 @@ void TrafficManager::ThreadRoutine() if (!IsEnabled()) continue; + /* + * Whether to call OnTrafficDataUpdate() at the end of the current round. + * The logic may fail to catch cases in which the first message in queue replaces another + * message without changing coloring. This would usually occur in a larger feed, where other + * messages would likely require an announcement, making this a minor issue. A single round + * (after a timeout) with no messages expired and an empty queue would not trigger an update. + */ + bool hasUpdates = false; + if (!IsTestMode()) { if (steady_clock::now() - lastPurged >= kPurgeInterval) { lastPurged == steady_clock::now(); - PurgeExpiredMessagesImpl(); + hasUpdates |= PurgeExpiredMessagesImpl(); } LOG(LINFO, ("active MWMs changed:", m_activeMwmsChanged, ", poll needed:", m_isPollNeeded)); @@ -720,6 +734,7 @@ void TrafficManager::ThreadRoutine() // consolidate feed queue (remove older messages in favor of newer ones) ConsolidateFeedQueue(); + hasUpdates |= !m_feedQueue.empty(); // decode one message and add it to the cache DecodeFirstMessage(); @@ -727,7 +742,8 @@ void TrafficManager::ThreadRoutine() // set new coloring for MWMs // `m_mutex` is obtained inside the method, no need to do it here // TODO drop the argument, use class member inside method - OnTrafficDataUpdate(); + if (hasUpdates) + OnTrafficDataUpdate(); // TODO no longer needed #ifdef traffic_dead_code diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 50256815b..2118c2fb2 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -399,8 +399,10 @@ class TrafficManager final : public traffxml::TraffSourceManager * This is the internal conterpart of `PurgeExpiredMessages()`. It is safe to call from any * thread. Unlike `PurgeExpiredMessages()`, it does not wake the worker thread, making it suitable * for use on the worker thread. + * + * @return true if messages were purged, false if not */ - void PurgeExpiredMessagesImpl(); + bool PurgeExpiredMessagesImpl(); /** * @brief Consolidates the feed queue. From daf344b27f14c0b412699ee1f20e3262dc167a87 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 7 Jul 2025 23:02:24 +0300 Subject: [PATCH 117/252] [traffic] Remove m_mwmCache and related logic Signed-off-by: mvglasow --- map/traffic_manager.cpp | 125 ++++++++++++++++------------------------ map/traffic_manager.hpp | 43 +++++--------- 2 files changed, 62 insertions(+), 106 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 796e30cd4..f89525740 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -54,6 +54,8 @@ auto constexpr kStorageUpdateInterval = minutes(1); auto constexpr kTrafficXMLFileName = "traffic.xml"; } // namespace +// TODO no longer needed +#ifdef traffic_dead_code TrafficManager::CacheEntry::CacheEntry() : m_isLoaded(false) // TODO no longer needed @@ -77,6 +79,7 @@ TrafficManager::CacheEntry::CacheEntry(time_point const & requestT , m_isWaitingForResponse(true) , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) {} +#endif TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn &countryParentNameGetter, @@ -193,7 +196,6 @@ void TrafficManager::Clear() LOG(LINFO, ("Messages in cache:", m_messageCache.size())); LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); - LOG(LINFO, ("MWM cache size:", m_mwmCache.size())); LOG(LINFO, ("Clearing...")); // TODO no longer needed #ifdef traffic_dead_code @@ -201,7 +203,10 @@ void TrafficManager::Clear() #endif m_messageCache.clear(); m_feedQueue.clear(); +// TODO no longer needed +#ifdef traffic_dead_code m_mwmCache.clear(); +#endif // TODO figure out which of the ones below we still need m_lastDrapeMwmsByRect.clear(); @@ -214,7 +219,6 @@ void TrafficManager::Clear() LOG(LINFO, ("Messages in cache:", m_messageCache.size())); LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); - LOG(LINFO, ("MWM cache size:", m_mwmCache.size())); } OnTrafficDataUpdate(); } @@ -318,7 +322,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, { m_activeMwmsChanged = true; std::swap(mwms, m_activeRoutingMwms); - RequestTrafficData(); + RequestTrafficSubscription(); } } } @@ -405,7 +409,7 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, if (mwm.IsAlive()) activeMwms.insert(mwm); } - RequestTrafficData(); + RequestTrafficSubscription(); } } @@ -835,40 +839,7 @@ bool TrafficManager::WaitForRequest() return true; } -void TrafficManager::RequestTrafficData(MwmSet::MwmId const & mwmId, bool force) -{ - bool needRequesting = false; - auto const currentTime = steady_clock::now(); - auto const it = m_mwmCache.find(mwmId); - if (it == m_mwmCache.end()) - { - needRequesting = true; - m_mwmCache.insert(std::make_pair(mwmId, CacheEntry(currentTime))); - } - else - { - auto const passedSeconds = currentTime - it->second.m_lastRequestTime; - if (passedSeconds >= kUpdateInterval || force) - { - needRequesting = true; - it->second.m_isWaitingForResponse = true; - it->second.m_lastRequestTime = currentTime; - } - if (!force) - it->second.m_lastActiveTime = currentTime; - } - - if (needRequesting) - { -// TODO no longer needed -#ifdef traffic_dead_code - m_requestedMwms.push_back(mwmId); -#endif - m_condition.notify_one(); - } -} - -void TrafficManager::RequestTrafficData() +void TrafficManager::RequestTrafficSubscription() { if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) || !IsEnabled() || IsInvalidState() || m_isPaused) @@ -878,9 +849,17 @@ void TrafficManager::RequestTrafficData() ForEachActiveMwm([this](MwmSet::MwmId const & mwmId) { ASSERT(mwmId.IsAlive(), ()); - RequestTrafficData(mwmId, false /* force */); +// TODO no longer needed +#ifdef traffic_dead_code + m_mwmCache.insert(mwmId); +#endif }); + m_condition.notify_one(); + +// TODO no longer needed +#ifdef traffic_dead_code UpdateState(); +#endif } // TODO no longer needed @@ -982,13 +961,12 @@ void TrafficManager::OnTrafficDataUpdate() /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * - * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData(); - * modification: cycle over all MWMs (active or not). + * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData() + * (now RequestTrafficSubscription()), modification: cycle over all MWMs (active or not). * trafficCache lookup is original code. * TrafficInfo construction is taken fron ThreadRoutine(), with modifications (different constructor). - * Updating m_mwmCache is from RequestTrafficData(MwmSet::MwmId const &, bool), with modifications. * The remainder of the loop is from OnTrafficDataResponse(traffic::TrafficInfo &&), with some modifications - * (deciding whether to notify a component and managing timestamps is original code). + * (removed CacheEntry logic; deciding whether to notify a component and managing timestamps is original code). * Existing coloring deletion (if there is no new coloring) is original code. */ ForEachMwm([this, notifyDrape, notifyObserver](std::shared_ptr info) { @@ -1007,42 +985,34 @@ void TrafficManager::OnTrafficDataUpdate() LOG(LINFO, ("Setting new coloring for", mwmId, "with", coloring.size(), "entries")); traffic::TrafficInfo info(mwmId, std::move(coloring)); - m_mwmCache.try_emplace(mwmId, CacheEntry(steady_clock::now())); +// TODO no longer needed +#ifdef traffic_dead_code + UpdateState(); +#endif - auto it = m_mwmCache.find(mwmId); - if (it != m_mwmCache.end()) + if (notifyDrape) { - it->second.m_isLoaded = true; - it->second.m_lastResponseTime = steady_clock::now(); - it->second.m_isWaitingForResponse = false; - it->second.m_lastAvailability = info.GetAvailability(); - - UpdateState(); - - if (notifyDrape) - { - /* - * TODO calling ClearTrafficCache before UpdateTraffic is a workaround for a bug in the - * Drape engine: some segments found in the old coloring but not in the new one may get - * left behind. This was not a problem for MapsWithMe as the set of segments never - * changed, but is an issue wherever the segment set is dynamic. Workaround is to clear - * before sending an update. Ultimately, the processing logic for UpdateTraffic needs to - * be fixed, but the code is hard to read (involves multiple messages getting thrown back - * and forth between threads). - */ - m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, - static_cast(mwmId)); - m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, - static_cast(info)); - m_lastDrapeUpdate = steady_clock::now(); - } + /* + * TODO calling ClearTrafficCache before UpdateTraffic is a workaround for a bug in the + * Drape engine: some segments found in the old coloring but not in the new one may get + * left behind. This was not a problem for MapsWithMe as the set of segments never + * changed, but is an issue wherever the segment set is dynamic. Workaround is to clear + * before sending an update. Ultimately, the processing logic for UpdateTraffic needs to + * be fixed, but the code is hard to read (involves multiple messages getting thrown back + * and forth between threads). + */ + m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, + static_cast(mwmId)); + m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, + static_cast(info)); + m_lastDrapeUpdate = steady_clock::now(); + } - if (notifyObserver) - { - // Update traffic colors for routing. - m_routingSession.OnTrafficInfoAdded(std::move(info)); - m_lastObserverUpdate = steady_clock::now(); - } + if (notifyObserver) + { + // Update traffic colors for routing. + m_routingSession.OnTrafficInfoAdded(std::move(info)); + m_lastObserverUpdate = steady_clock::now(); } } else @@ -1181,6 +1151,8 @@ bool TrafficManager::IsInvalidState() const return m_state == TrafficState::NetworkError; } +// TODO no longer needed +#ifdef traffic_dead_code void TrafficManager::UpdateState() { if (!IsEnabled() || IsInvalidState()) @@ -1238,6 +1210,7 @@ void TrafficManager::UpdateState() else ChangeState(TrafficState::Enabled); } +#endif void TrafficManager::ChangeState(TrafficState newState) { diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 2118c2fb2..267259e34 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -46,6 +46,7 @@ class TrafficManager final : public traffxml::TraffSourceManager /** * @brief Global state of traffic information. */ + // TODO apart from `Disabled` and `Enabled`, all states are obsolete enum class TrafficState { /** Traffic is disabled, no traffic data will be retrieved or considered for routing. */ @@ -294,6 +295,8 @@ class TrafficManager final : public traffxml::TraffSourceManager void Clear(); private: +// TODO no longer needed +#ifdef traffic_dead_code /** * @brief Holds information about pending or previous traffic requests pertaining to an MWM. */ @@ -307,13 +310,10 @@ class TrafficManager final : public traffxml::TraffSourceManager */ bool m_isLoaded; -// TODO no longer needed -#ifdef traffic_dead_code /** * @brief The amount of memory occupied by the coloring for this MWM. */ size_t m_dataSize; -#endif /** * @brief When the last update request occurred, not including forced updates. @@ -352,6 +352,7 @@ class TrafficManager final : public traffxml::TraffSourceManager traffic::TrafficInfo::Availability m_lastAvailability; }; +#endif /** * @brief Ensures every TraFF source has a subscription covering all currently active MWMs. @@ -466,7 +467,7 @@ class TrafficManager final : public traffxml::TraffSourceManager * If the MWM is no longer active, this method returns immediately after that. * * If the retry limit has not been reached, the MWM is re-inserted into the list by calling - * `RequestTrafficData(MwmSet::MwmId, bool)` with `force` set to true. Otherwise, the retry count + * `RequestTrafficSubscription(MwmSet::MwmId, bool)` with `force` set to true. Otherwise, the retry count * is reset and the state updated accordingly. * * @param info @@ -498,12 +499,9 @@ class TrafficManager final : public traffxml::TraffSourceManager // This is a group of methods that haven't their own synchronization inside. /** - * @brief Requests a refresh of traffic data for all currently active MWMs. + * @brief Requests a refresh of traffic subscriptions to match all currently active MWMs. * - * This method is the entry point for periodic traffic data refresh operations. It cycles through - * all active MWMs and calls `RequestTrafficData(MwmSet::MwmId, bool)` on each `MwmId`, - * scheduling a refresh if needed. The actual network operation is performed asynchronously on a - * separate thread. + * The actual call to the TraFF sources is performed asynchronously on a separate thread. * * The method does nothing if the `TrafficManager` instance is disabled, paused, in an invalid * state (`NetworkError`) or if neither the rendering engine nor the routing engine have any @@ -511,24 +509,9 @@ class TrafficManager final : public traffxml::TraffSourceManager * * This method is unsynchronized; the caller must lock `m_mutex` prior to calling it. */ - void RequestTrafficData(); - - /** - * @brief Requests a refresh of traffic data for a single MWM. - * - * This method first checks if traffic data for the given MWM needs to be refreshed, which is the - * case if no traffic data has ever been fetched for the given MWM, the update interval has - * expired or `force` is true. In that case, the method inserts the `mwmId` into the list of MWMs - * for which to update traffic and wakes up the worker thread. - * - * This method is unsynchronized; the caller must lock `m_mutex` prior to calling it. - * - * @param mwmId Describes the MWM for which traffic data is to be refreshed. - * @param force If true, a refresh is requested even if the update interval has not expired. - */ - void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force); + void RequestTrafficSubscription(); - // TODO no longer needed +// TODO no longer needed #ifdef traffic_dead_code /** * @brief Removes traffic data for one specific MWM from the cache. @@ -543,7 +526,6 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void ClearCache(MwmSet::MwmId const & mwmId); void ShrinkCacheToAllowableSize(); -#endif /** * @brief Updates the state of the traffic manager based on the state of all MWMs used by the renderer. @@ -558,6 +540,7 @@ class TrafficManager final : public traffxml::TraffSourceManager * `TrafficState::NoData`, `TrafficState::Outdated`, `TrafficState::Enabled`. */ void UpdateState(); +#endif void ChangeState(TrafficState newState); bool IsInvalidState() const; @@ -638,9 +621,9 @@ class TrafficManager final : public traffxml::TraffSourceManager #ifdef traffic_dead_code size_t m_maxCacheSizeBytes; size_t m_currentCacheSizeBytes = 0; -#endif std::map m_mwmCache; +#endif /** * @brief The TraFF sources from which we get traffic information. @@ -666,9 +649,9 @@ class TrafficManager final : public traffxml::TraffSourceManager * * Methods which use only the set: * - * * RequestTrafficData(), exits if empty, otherwise cycles through the set. + * * RequestTrafficSubscription(), exits if empty, otherwise cycles through the set. * * OnTrafficRequestFailed(), determines if an MWM is still active and the request should be retried. - * * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficData() or to shrink the cache). + * * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription() or to shrink the cache). * * UpdateState(), cycles through the set to determine the state of traffic requests (renderer only). * * Methods which use both, but in a different way: From d03b47bee00c45b977e5f0dff6a63528a438234e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 8 Jul 2025 21:53:02 +0300 Subject: [PATCH 118/252] [traffic] Refactoring Signed-off-by: mvglasow --- map/traffic_manager.cpp | 18 ++++++++++++++---- map/traffic_manager.hpp | 30 +++++++++++++----------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index f89525740..c7068c921 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -322,7 +322,12 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, { m_activeMwmsChanged = true; std::swap(mwms, m_activeRoutingMwms); - RequestTrafficSubscription(); + + if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) + || !IsEnabled() || IsInvalidState() || m_isPaused) + return; + + m_condition.notify_one(); } } } @@ -409,7 +414,12 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, if (mwm.IsAlive()) activeMwms.insert(mwm); } - RequestTrafficSubscription(); + + if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) + || !IsEnabled() || IsInvalidState() || m_isPaused) + return; + + m_condition.notify_one(); } } @@ -839,6 +849,8 @@ bool TrafficManager::WaitForRequest() return true; } +// TODO no longer needed +#ifdef traffic_dead_code void TrafficManager::RequestTrafficSubscription() { if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) @@ -862,8 +874,6 @@ void TrafficManager::RequestTrafficSubscription() #endif } -// TODO no longer needed -#ifdef traffic_dead_code void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) { std::lock_guard lock(m_mutex); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 267259e34..3c4591b56 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -498,6 +498,8 @@ class TrafficManager final : public traffxml::TraffSourceManager // This is a group of methods that haven't their own synchronization inside. +// TODO no longer needed +#ifdef traffic_dead_code /** * @brief Requests a refresh of traffic subscriptions to match all currently active MWMs. * @@ -511,8 +513,6 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void RequestTrafficSubscription(); -// TODO no longer needed -#ifdef traffic_dead_code /** * @brief Removes traffic data for one specific MWM from the cache. * @@ -642,24 +642,20 @@ class TrafficManager final : public traffxml::TraffSourceManager * * Routing MWMs are stored as a set. * - * The other groups arestored twice: as a set and as a vector. The set always holds the MWMs which + * The other groups are stored twice: as a set and as a vector. The set always holds the MWMs which * were last seen in use. Both get updated together when active MWMs are added or removed. - * However, the vector is used as a reference to detect changes. It may get cleared when the set - * is not, which is used to invalidate the set without destroying its contents. + * However, the vector is used as a reference to detect changes. Clear() clears the vector but not + * the set, invalidating the set without destroying its contents. * * Methods which use only the set: * * * RequestTrafficSubscription(), exits if empty, otherwise cycles through the set. - * * OnTrafficRequestFailed(), determines if an MWM is still active and the request should be retried. - * * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription() or to shrink the cache). - * * UpdateState(), cycles through the set to determine the state of traffic requests (renderer only). + * * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription()). * * Methods which use both, but in a different way: * - * * (dead code) ClearCache(), removes the requested MWM from the set but clears the vector completely. - * * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it updates both vector and set. - * - * Clear() clears both the set and the vector. (Clearing the set is currently disabled as it breaks ForEachActiveMwm.) + * * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it + * updates both vector and set, but adds MWMs to the set only if they are alive. */ std::vector m_lastDrapeMwmsByRect; std::set m_activeDrapeMwms; @@ -667,6 +663,11 @@ class TrafficManager final : public traffxml::TraffSourceManager std::set m_activePositionMwms; std::set m_activeRoutingMwms; + /** + * @brief Whether active MWMs have changed since the last request. + */ + bool m_activeMwmsChanged = false; + // TODO no longer needed #ifdef traffic_dead_code // The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. @@ -726,11 +727,6 @@ class TrafficManager final : public traffxml::TraffSourceManager */ std::chrono::time_point m_lastStorageUpdate; - /** - * @brief Whether active MWMs have changed since the last request. - */ - bool m_activeMwmsChanged = false; - /** * @brief Whether a poll operation is needed. * From 75197a11a850a8d0be3c39ec6573037d19dda671 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 8 Jul 2025 22:50:54 +0300 Subject: [PATCH 119/252] [traffic] Consider routing MWMs when updating subscriptions on resume Signed-off-by: mvglasow --- map/traffic_manager.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index c7068c921..07b5cab21 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -341,7 +341,24 @@ void TrafficManager::RecalculateSubscription() UpdateViewport(m_currentModelView.first); if (m_currentPosition.second) UpdateMyPosition(m_currentPosition.first); - // TODO update routing – if needed + + { + std::lock_guard lock(m_mutex); + + /* + * If UpdateViewport() or UpdateMyPosition() had changes, they would also have updated the + * routing MWMs and reset m_activeMwmsChanged. If neither of them had changes and + * m_activeMwmsChanged is true, it indicates changes in route MWMs which we need to process. + */ + if (m_activeMwmsChanged) + { + if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) + || IsInvalidState() || m_isPaused) + return; + + m_condition.notify_one(); + } + } } void TrafficManager::Invalidate(MwmSet::MwmId const & mwmId) From cc58eaa50acdc4c4e683a71943826249d0997c02 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 9 Jul 2025 18:57:57 +0300 Subject: [PATCH 120/252] [traffic] Restore and document enable/disable/pause/resume logic Signed-off-by: mvglasow --- map/traffic_manager.cpp | 10 +++++----- map/traffic_manager.hpp | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 07b5cab21..5137790df 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -334,7 +334,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, void TrafficManager::RecalculateSubscription() { - if (!IsEnabled()) + if (!IsEnabled() || m_isPaused) return; if (m_currentModelView.second) @@ -353,7 +353,7 @@ void TrafficManager::RecalculateSubscription() if (m_activeMwmsChanged) { if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) - || IsInvalidState() || m_isPaused) + || IsInvalidState()) return; m_condition.notify_one(); @@ -725,7 +725,7 @@ void TrafficManager::ThreadRoutine() while (WaitForRequest()) { - if (!IsEnabled()) + if (!IsEnabled() || m_isPaused) continue; /* @@ -824,7 +824,7 @@ bool TrafficManager::WaitForRequest() if (!m_isRunning) return false; - if (IsEnabled()) + if (IsEnabled() && !m_isPaused) { // if we have feeds in the queue, return immediately if (!m_feedQueue.empty()) @@ -859,7 +859,7 @@ bool TrafficManager::WaitForRequest() return false; // this works as long as wait timeout is at least equal to the poll interval - if (IsEnabled()) + if (IsEnabled() && !m_isPaused) m_isPollNeeded |= timeout; LOG(LINFO, ("timeout:", timeout, "active MWMs changed:", m_activeMwmsChanged, "test mode:", IsTestMode())); diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 3c4591b56..c3af00aca 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -141,12 +141,13 @@ class TrafficManager final : public traffxml::TraffSourceManager * * This sets the internal state and notifies the drape engine. * - * Upon creation, the traffic manager is disabled and will not poll any sources or process any - * feeds until enabled. Feeds received through `Push()` will be added to the queue before the - * traffic manager is started, but will not be processed any further until the traffic manager is - * started. + * Upon creation, the traffic manager is disabled. MWMs must be loaded before first enabling the + * traffic manager. * - * MWMs must be loaded before first enabling the traffic manager. + * While disabled, the traffic manager will not update its subscription area (upon being enabled + * again, it will do so if necessary). It will not poll any sources or process any messages. Feeds + * added via `ReceiveFeed()` will be added to the queue but will not be processed until the + * traffic manager is re-enabled. * * Calling this function with `enabled` identical to the current state is a no-op. * @@ -154,8 +155,6 @@ class TrafficManager final : public traffxml::TraffSourceManager * that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added * (and removed) – note that this affects the `DataSource`, not the set of active MWMs. * - * @todo State/pause/resume logic is not fully implemented ATM and needs to be revisited. - * * @param enabled True to enable, false to disable */ void SetEnabled(bool enabled); @@ -561,7 +560,34 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void UniteActiveMwms(std::set & activeMwms) const; + /** + * @brief Pauses the traffic manager. + * + * Upon creation, the traffic manager is not paused. + * + * While paused, the traffic manager will not update its subscription area (upon being enabled + * again, it will do so if necessary). It will not poll any sources or process any messages. Feeds + * added via `ReceiveFeed()` will be added to the queue but will not be processed until the + * traffic manager is resumed. + * + * Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except + * it does not change the external state. It is intended for internal use by the framework. + */ void Pause(); + + /** + * @brief Resumes the traffic manager. + * + * Upon creation, the traffic manager is not paused. Resuming a traffic manager that is not paused + * is a no-op. + * + * Upon resume, the traffic manager will recalculate its subscription area and change its + * subscription if necessary. It will continue processing feeds in the queue, including those + * received before or while the traffic manager was paused. + * + * Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except + * it does not change the external state. It is intended for internal use by the framework. + */ void Resume(); template From e82575348725ea8b9fd98d41f269bf28545880b9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 9 Jul 2025 20:15:20 +0300 Subject: [PATCH 121/252] [traffic] Remove obsolete code Signed-off-by: mvglasow --- map/traffic_manager.cpp | 328 +--------------------------------------- map/traffic_manager.hpp | 154 +------------------ 2 files changed, 8 insertions(+), 474 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 5137790df..faa669bbf 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -54,33 +54,6 @@ auto constexpr kStorageUpdateInterval = minutes(1); auto constexpr kTrafficXMLFileName = "traffic.xml"; } // namespace -// TODO no longer needed -#ifdef traffic_dead_code -TrafficManager::CacheEntry::CacheEntry() - : m_isLoaded(false) -// TODO no longer needed -#ifdef traffic_dead_code - , m_dataSize(0) -#endif - , m_retriesCount(0) - , m_isWaitingForResponse(false) - , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) -{} - -TrafficManager::CacheEntry::CacheEntry(time_point const & requestTime) - : m_isLoaded(false) -// TODO no longer needed -#ifdef traffic_dead_code - , m_dataSize(0) -#endif - , m_lastActiveTime(requestTime) - , m_lastRequestTime(requestTime) - , m_retriesCount(0) - , m_isWaitingForResponse(true) - , m_lastAvailability(traffic::TrafficInfo::Availability::Unknown) -{} -#endif - TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn &countryParentNameGetter, GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, @@ -92,10 +65,6 @@ TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn coun , m_routingSession(routingSession) , m_currentDataVersion(0) , m_state(TrafficState::Disabled) -// TODO no longer needed -#ifdef traffic_dead_code - , m_maxCacheSizeBytes(maxCacheSizeBytes) -#endif , m_isRunning(true) , m_isPaused(false) , m_thread(&TrafficManager::ThreadRoutine, this) @@ -189,36 +158,11 @@ void TrafficManager::SetEnabled(bool enabled) void TrafficManager::Clear() { - // TODO what should we do about subscriptions? { std::lock_guard lock(m_mutex); - LOG(LINFO, ("Messages in cache:", m_messageCache.size())); - LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); - LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); - LOG(LINFO, ("Clearing...")); - // TODO no longer needed -#ifdef traffic_dead_code - m_currentCacheSizeBytes = 0; -#endif m_messageCache.clear(); m_feedQueue.clear(); -// TODO no longer needed -#ifdef traffic_dead_code - m_mwmCache.clear(); -#endif - - // TODO figure out which of the ones below we still need - m_lastDrapeMwmsByRect.clear(); - m_lastPositionMwmsByRect.clear(); -#ifdef traffic_dead_code - m_requestedMwms.clear(); - m_trafficETags.clear(); -#endif - - LOG(LINFO, ("Messages in cache:", m_messageCache.size())); - LOG(LINFO, ("Feeds in queue:", m_feedQueue.size())); - LOG(LINFO, ("MWMs with coloring:", m_allMwmColoring.size())); } OnTrafficDataUpdate(); } @@ -235,27 +179,7 @@ void TrafficManager::SetCurrentDataVersion(int64_t dataVersion) void TrafficManager::OnMwmDeregistered(platform::LocalCountryFile const & countryFile) { - if (!IsEnabled()) - return; - -// TODO no longer needed -#ifdef traffic_dead_code - { - std::lock_guard lock(m_mutex); - - MwmSet::MwmId mwmId; - for (auto const & cacheEntry : m_mwmCache) - { - if (cacheEntry.first.IsDeregistered(countryFile)) - { - mwmId = cacheEntry.first; - break; - } - } - - ClearCache(mwmId); - } -#endif + // TODO we don’t need this any more (called by Framework::OnMapDeregistered()) } void TrafficManager::OnDestroySurface() @@ -772,42 +696,8 @@ void TrafficManager::ThreadRoutine() // set new coloring for MWMs // `m_mutex` is obtained inside the method, no need to do it here - // TODO drop the argument, use class member inside method if (hasUpdates) OnTrafficDataUpdate(); - -// TODO no longer needed -#ifdef traffic_dead_code - for (auto const & mwm : mwms) - { - if (!mwm.IsAlive()) - continue; - - traffic::TrafficInfo info(mwm, m_currentDataVersion); - - std::string tag; - { - std::lock_guard lock(m_mutex); - tag = m_trafficETags[mwm]; - } - - if (info.ReceiveTrafficData(tag)) - { - OnTrafficDataResponse(std::move(info)); - } - else - { - LOG(LWARNING, ("Traffic request failed. Mwm =", mwm)); - OnTrafficRequestFailed(std::move(info)); - } - - { - std::lock_guard lock(m_mutex); - m_trafficETags[mwm] = tag; - } - } - mwms.clear(); -#endif } // Calling Unsubscribe() from the worker thread on exit makes thread synchronization easier Unsubscribe(); @@ -866,62 +756,6 @@ bool TrafficManager::WaitForRequest() return true; } -// TODO no longer needed -#ifdef traffic_dead_code -void TrafficManager::RequestTrafficSubscription() -{ - if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) - || !IsEnabled() || IsInvalidState() || m_isPaused) - { - return; - } - - ForEachActiveMwm([this](MwmSet::MwmId const & mwmId) { - ASSERT(mwmId.IsAlive(), ()); -// TODO no longer needed -#ifdef traffic_dead_code - m_mwmCache.insert(mwmId); -#endif - }); - m_condition.notify_one(); - -// TODO no longer needed -#ifdef traffic_dead_code - UpdateState(); -#endif -} - -void TrafficManager::OnTrafficRequestFailed(traffic::TrafficInfo && info) -{ - std::lock_guard lock(m_mutex); - - auto it = m_mwmCache.find(info.GetMwmId()); - if (it == m_mwmCache.end()) - return; - - it->second.m_isWaitingForResponse = false; - it->second.m_lastAvailability = info.GetAvailability(); - - if (info.GetAvailability() == traffic::TrafficInfo::Availability::Unknown && - !it->second.m_isLoaded) - { - if (m_activeDrapeMwms.find(info.GetMwmId()) != m_activeDrapeMwms.cend() || - m_activeRoutingMwms.find(info.GetMwmId()) != m_activeRoutingMwms.cend()) - { - if (it->second.m_retriesCount < kMaxRetriesCount) - RequestTrafficData(info.GetMwmId(), true /* force */); - ++it->second.m_retriesCount; - } - else - { - it->second.m_retriesCount = 0; - } - } - - UpdateState(); -} -#endif - void TrafficManager::OnTrafficDataUpdate() { bool feedQueueEmpty = false; @@ -1012,11 +846,6 @@ void TrafficManager::OnTrafficDataUpdate() LOG(LINFO, ("Setting new coloring for", mwmId, "with", coloring.size(), "entries")); traffic::TrafficInfo info(mwmId, std::move(coloring)); -// TODO no longer needed -#ifdef traffic_dead_code - UpdateState(); -#endif - if (notifyDrape) { /* @@ -1061,46 +890,6 @@ void TrafficManager::OnTrafficDataUpdate() }); } -// TODO no longer needed -#ifdef traffic_dead_code -void TrafficManager::OnTrafficDataResponse(traffic::TrafficInfo && info) -{ - { - std::lock_guard lock(m_mutex); - - auto it = m_mwmCache.find(info.GetMwmId()); - if (it == m_mwmCache.end()) - return; - - it->second.m_isLoaded = true; - it->second.m_lastResponseTime = steady_clock::now(); - it->second.m_isWaitingForResponse = false; - it->second.m_lastAvailability = info.GetAvailability(); - - if (!info.GetColoring().empty()) - { - // Update cache. - size_t constexpr kElementSize = sizeof(traffic::TrafficInfo::RoadSegmentId) + sizeof(traffic::SpeedGroup); - size_t const dataSize = info.GetColoring().size() * kElementSize; - m_currentCacheSizeBytes += (dataSize - it->second.m_dataSize); - it->second.m_dataSize = dataSize; - ShrinkCacheToAllowableSize(); - } - - UpdateState(); - } - - if (!info.GetColoring().empty()) - { - m_drapeEngine.SafeCall(&df::DrapeEngine::UpdateTraffic, - static_cast(info)); - - // Update traffic colors for routing. - m_observer.OnTrafficInfoAdded(std::move(info)); - } -} -#endif - void TrafficManager::GetActiveMwms(std::set & activeMwms) { std::lock_guard lock(m_mutex); @@ -1114,60 +903,6 @@ void TrafficManager::UniteActiveMwms(std::set & activeMwms) const activeMwms.insert(m_activeRoutingMwms.cbegin(), m_activeRoutingMwms.cend()); } -// TODO no longer needed -#ifdef traffic_dead_code -void TrafficManager::ShrinkCacheToAllowableSize() -{ - // Calculating number of different active mwms. - std::set activeMwms; - UniteActiveMwms(activeMwms); - size_t const numActiveMwms = activeMwms.size(); - - if (m_currentCacheSizeBytes > m_maxCacheSizeBytes && m_mwmCache.size() > numActiveMwms) - { - std::multimap, MwmSet::MwmId> seenTimings; - for (auto const & mwmInfo : m_mwmCache) - seenTimings.insert(std::make_pair(mwmInfo.second.m_lastActiveTime, mwmInfo.first)); - - auto itSeen = seenTimings.begin(); - while (m_currentCacheSizeBytes > m_maxCacheSizeBytes && m_mwmCache.size() > numActiveMwms) - { - ClearCache(itSeen->second); - ++itSeen; - } - } -} - -void TrafficManager::ClearCache(MwmSet::MwmId const & mwmId) -{ - auto const it = m_mwmCache.find(mwmId); - if (it == m_mwmCache.end()) - return; - - if (it->second.m_isLoaded) - { -// TODO no longer needed -#ifdef traffic_dead_code - ASSERT_GREATER_OR_EQUAL(m_currentCacheSizeBytes, it->second.m_dataSize, ()); - m_currentCacheSizeBytes -= it->second.m_dataSize; -#endif - - m_drapeEngine.SafeCall(&df::DrapeEngine::ClearTrafficCache, mwmId); - - GetPlatform().RunTask(Platform::Thread::Gui, [this, mwmId]() - { - m_observer.OnTrafficInfoRemoved(mwmId); - }); - } - m_mwmCache.erase(it); - m_trafficETags.erase(mwmId); - m_activeDrapeMwms.erase(mwmId); - m_activeRoutingMwms.erase(mwmId); - m_lastDrapeMwmsByRect.clear(); - m_lastRoutingMwmsByRect.clear(); -} -#endif - bool TrafficManager::IsEnabled() const { return m_state != TrafficState::Disabled; @@ -1178,67 +913,6 @@ bool TrafficManager::IsInvalidState() const return m_state == TrafficState::NetworkError; } -// TODO no longer needed -#ifdef traffic_dead_code -void TrafficManager::UpdateState() -{ - if (!IsEnabled() || IsInvalidState()) - return; - - auto const currentTime = steady_clock::now(); - auto maxPassedTime = steady_clock::duration::zero(); - - bool waiting = false; - bool networkError = false; - bool expiredApp = false; - bool expiredData = false; - bool noData = false; - - for (MwmSet::MwmId const & mwmId : m_activeDrapeMwms) - { - auto it = m_mwmCache.find(mwmId); - ASSERT(it != m_mwmCache.end(), ()); - - if (it->second.m_isWaitingForResponse) - { - waiting = true; - } - else - { - expiredApp |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::ExpiredApp; - expiredData |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::ExpiredData; - noData |= it->second.m_lastAvailability == traffic::TrafficInfo::Availability::NoData; - - if (it->second.m_isLoaded) - { - auto const timeSinceLastResponse = currentTime - it->second.m_lastResponseTime; - if (timeSinceLastResponse > maxPassedTime) - maxPassedTime = timeSinceLastResponse; - } - else if (it->second.m_retriesCount >= kMaxRetriesCount) - { - networkError = true; - } - } - } - - if (networkError || maxPassedTime >= kNetworkErrorTimeout) - ChangeState(TrafficState::NetworkError); - else if (waiting) - ChangeState(TrafficState::WaitingData); - else if (expiredApp) - ChangeState(TrafficState::ExpiredApp); - else if (expiredData) - ChangeState(TrafficState::ExpiredData); - else if (noData) - ChangeState(TrafficState::NoData); - else if (maxPassedTime >= kOutdatedDataTimeout) - ChangeState(TrafficState::Outdated); - else - ChangeState(TrafficState::Enabled); -} -#endif - void TrafficManager::ChangeState(TrafficState newState) { if (m_state == newState) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index c3af00aca..fc12ecc1f 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -46,7 +46,13 @@ class TrafficManager final : public traffxml::TraffSourceManager /** * @brief Global state of traffic information. */ - // TODO apart from `Disabled` and `Enabled`, all states are obsolete + /* + * TODO clean out obsolete states. + * Only `Disabled` and `Enabled` are currently used, but some might be reactivated in the future + * and platforms (android/iphone) still evaluate all states. + * `ExpiredData` is definitely obsolete, as traffic data is no longer dependent on a particular + * map version, but still evaluated by android/iphone code. + */ enum class TrafficState { /** Traffic is disabled, no traffic data will be retrieved or considered for routing. */ @@ -294,64 +300,6 @@ class TrafficManager final : public traffxml::TraffSourceManager void Clear(); private: -// TODO no longer needed -#ifdef traffic_dead_code - /** - * @brief Holds information about pending or previous traffic requests pertaining to an MWM. - */ - struct CacheEntry - { - CacheEntry(); - explicit CacheEntry(std::chrono::time_point const & requestTime); - - /** - * @brief Whether we have traffic data for this MWM. - */ - bool m_isLoaded; - - /** - * @brief The amount of memory occupied by the coloring for this MWM. - */ - size_t m_dataSize; - - /** - * @brief When the last update request occurred, not including forced updates. - * - * This timestamp is the basis for eliminating the oldest entries from the cache. - */ - std::chrono::time_point m_lastActiveTime; - - /** - * @brief When the last update request occurred, including forced updates. - * - * This timestamp is the basis for determining whether an update is needed. - */ - std::chrono::time_point m_lastRequestTime; - - /** - * @brief When the last response was received. - * - * This timestamp is the basis for determining whether a network request timed out, or if data is outdated. - */ - std::chrono::time_point m_lastResponseTime; - - /** - * @brief The number of failed traffic requests for this MWM. - * - * Reset when the MWM becomes inactive. - */ - int m_retriesCount; - - /** - * @brief Whether a request is currently pending for this MWM. - * - * Set to `true` when a request is scheduled, reverted to `false` when a response is received or the request fails. - */ - bool m_isWaitingForResponse; - - traffic::TrafficInfo::Availability m_lastAvailability; - }; -#endif /** * @brief Ensures every TraFF source has a subscription covering all currently active MWMs. @@ -453,27 +401,6 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void OnTrafficDataUpdate(); -// TODO no longer needed -#ifdef traffic_dead_code - void OnTrafficDataResponse(traffic::TrafficInfo && info); - /** - * @brief Processes a failed traffic request. - * - * This method gets called when a traffic request has failed. - * - * It updates the `m_isWaitingForResponse` and `m_lastAvailability` of `info. - * - * If the MWM is no longer active, this method returns immediately after that. - * - * If the retry limit has not been reached, the MWM is re-inserted into the list by calling - * `RequestTrafficSubscription(MwmSet::MwmId, bool)` with `force` set to true. Otherwise, the retry count - * is reset and the state updated accordingly. - * - * @param info - */ - void OnTrafficRequestFailed(traffic::TrafficInfo && info); -#endif - /** * @brief Updates `activeMwms` and requests traffic data. * @@ -497,49 +424,6 @@ class TrafficManager final : public traffxml::TraffSourceManager // This is a group of methods that haven't their own synchronization inside. -// TODO no longer needed -#ifdef traffic_dead_code - /** - * @brief Requests a refresh of traffic subscriptions to match all currently active MWMs. - * - * The actual call to the TraFF sources is performed asynchronously on a separate thread. - * - * The method does nothing if the `TrafficManager` instance is disabled, paused, in an invalid - * state (`NetworkError`) or if neither the rendering engine nor the routing engine have any - * active MWMs. - * - * This method is unsynchronized; the caller must lock `m_mutex` prior to calling it. - */ - void RequestTrafficSubscription(); - - /** - * @brief Removes traffic data for one specific MWM from the cache. - * - * This would be used when an MWM file gets deregistered and its traffic data is no longer needed. - * With the old MWM traffic architecture (pre-processed sets of segments), this method was also - * used to shrink the cache to stay below a certain size (no longer possible with TraFF, due to - * the data structures being more complex, and also due to re-fetching data being expensive in - * terms of computing time). - * - * @param mwmId The mwmId for which to remove traffic data. - */ - void ClearCache(MwmSet::MwmId const & mwmId); - void ShrinkCacheToAllowableSize(); - - /** - * @brief Updates the state of the traffic manager based on the state of all MWMs used by the renderer. - * - * This method cycles through the state of all MWMs used by the renderer (MWMs used by the - * routing engine but not by the rendering engine are not considered), examines their traffic - * state and sets the global state accordingly. - * - * For a description of states, see `TrafficState`. The order of states is as follows, the first - * state whose conditions are fulfilled becomes the new state: `TrafficState::NetworkError`, - * `TrafficState::WaitingData`, `TrafficState::ExpiredApp`, `TrafficState::ExpiredData`, - * `TrafficState::NoData`, `TrafficState::Outdated`, `TrafficState::Enabled`. - */ - void UpdateState(); -#endif void ChangeState(TrafficState newState); bool IsInvalidState() const; @@ -643,14 +527,6 @@ class TrafficManager final : public traffxml::TraffSourceManager bool m_hasSimplifiedColorScheme = true; -// TODO no longer needed -#ifdef traffic_dead_code - size_t m_maxCacheSizeBytes; - size_t m_currentCacheSizeBytes = 0; - - std::map m_mwmCache; -#endif - /** * @brief The TraFF sources from which we get traffic information. * @@ -694,24 +570,8 @@ class TrafficManager final : public traffxml::TraffSourceManager */ bool m_activeMwmsChanged = false; -// TODO no longer needed -#ifdef traffic_dead_code - // The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. - // It is one of several mechanisms that HTTP provides for web cache validation, - // which allows a client to make conditional requests. - std::map m_trafficETags; -#endif - std::atomic m_isPaused; -// TODO no longer needed -#ifdef traffic_dead_code - /** - * @brief MWMs for which to retrieve traffic data. - */ - std::vector m_requestedMwms; -#endif - /** * @brief Mutex for access to shared members. * From a20d1453e00f60e5db8973280704e8a1acb1c950 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 9 Jul 2025 20:29:01 +0300 Subject: [PATCH 122/252] [traffic] Documentation Signed-off-by: mvglasow --- map/traffic_manager.hpp | 26 +++++--------------------- traffxml/traff_decoder.hpp | 2 +- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index fc12ecc1f..db753c886 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -160,6 +160,7 @@ class TrafficManager final : public traffxml::TraffSourceManager * @todo Currently, all MWMs must be loaded before calling `SetEnabled()`, as MWMs loaded after * that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added * (and removed) – note that this affects the `DataSource`, not the set of active MWMs. + * See `Framework::OnMapDeregistered()` implementation for the opposite case (MWM deregistered). * * @param enabled True to enable, false to disable */ @@ -188,14 +189,11 @@ class TrafficManager final : public traffxml::TraffSourceManager * enabled, or when it is resumed after being paused, as the subscription area is not updated * while the traffic manager is disabled or paused. * - * If the subscription are has changed, this triggers a change of the active TraFF subscription. + * If the subscription area has changed, this triggers a change of the active TraFF subscription. * * No traffic data is discarded, but sources will be polled for an update, which may turn out * larger than usual if the traffic manager was in disabled/paused state for an extended period of * time or the subscription area has changed. - * - * @todo Routing is currently not considered (active MWMs are based on viewport and current - * position). */ void RecalculateSubscription(); @@ -280,22 +278,10 @@ class TrafficManager final : public traffxml::TraffSourceManager void PurgeExpiredMessages(); /** - * @brief Clears the entire traffic cache. - * - * This is currently called when the traffic manager is enabled or disabled. - * - * The old MWM traffic architecture was somewhat liberal in clearing its cache and re-fetching - * traffic data. This was possible because data was pre-processed and required no processing - * beyond deserialization, whereas TraFF data is more expensive to recreate. Also, the old - * architecture lacked any explicit notion of expiration; the app decided that data was to be - * considered stale after a certain period of time. TraFF, in contrast, has an explicit expiration - * time for each message, which can be anywhere from a few minutes to several weeks or months. - * Messages that have expired get deleted individually. - * For this reason, the TraFF message cache should not be cleared out under normal conditions - * (the main exception being tests). + * @brief Clears the traffic message cache and feed queue. * - * @todo Currently not implemented for TraFF; implement it for test purposes but do not call when - * the enabled state changes. + * This is intended for testing purposes and clears the message cache, as well as the feed queue. + * Subscriptions are not changed. */ void Clear(); @@ -384,10 +370,8 @@ class TrafficManager final : public traffxml::TraffSourceManager * currently active MWMs into the list of MWMs to update; otherwise, it leaves the list as it is. * In either case, it populates `mwms` with the list and returns. * - * @param mwms Receives a list of MWMs for which to update traffic data. * @return `true` during normal operation, `false` during teardown (signaling the event loop to exit). */ - // TODO mwms argument is no longer needed bool WaitForRequest(); /** diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 252b219bb..672d1b225 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -189,7 +189,7 @@ class RoutingTraffDecoder : public TraffDecoder, * @param countryRectFn Function which returns the rect for a country * @param numMwmIds * @param numMwmTree - * @param trafficCache Tre traffic cache (used only if `vehicleType` is `VehicleType::Car`) + * @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`) * @param dataSource The MWM data source * @param decoder The `TraffDecoder` instance to which this router instance is coupled */ From 3f58c6ee208b87dbf4d3cbe05589b2dba3fa019a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 20 Jul 2025 13:14:23 +0300 Subject: [PATCH 123/252] [traffic] Implement HttpTraffSource Signed-off-by: mvglasow --- map/framework.cpp | 5 +- map/traffic_manager.cpp | 4 +- traffxml/traff_model.cpp | 17 +++ traffxml/traff_model.hpp | 107 +++++++++++++++++++ traffxml/traff_model_xml.cpp | 94 +++++++++++++++- traffxml/traff_model_xml.hpp | 13 +++ traffxml/traff_source.cpp | 202 +++++++++++++++++++++++++++++++++++ traffxml/traff_source.hpp | 115 ++++++++++++++++++++ 8 files changed, 549 insertions(+), 8 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 6315099d6..ccb72ac32 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -399,7 +399,10 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) * MockTraffSource for debugging purposes. * TODO Replace with a real source, parametrized and conditionally loaded, once we have one. */ - traffxml::MockTraffSource::Create(m_trafficManager); + //traffxml::MockTraffSource::Create(m_trafficManager); + + // For testing purposes, HttpTraffSource pointing to a hardcoded local instance + traffxml::HttpTraffSource::Create(m_trafficManager, "http://traff:8080/subscription-manager"); } Framework::~Framework() diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index faa669bbf..980350ffb 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -497,7 +497,8 @@ void TrafficManager::RegisterSource(std::unique_ptr sourc UniteActiveMwms(activeMwms); } - source->SubscribeOrChangeSubscription(activeMwms); + if (!activeMwms.empty()) + source->SubscribeOrChangeSubscription(activeMwms); { std::lock_guard lock(m_trafficSourceMutex); @@ -699,7 +700,6 @@ void TrafficManager::ThreadRoutine() if (hasUpdates) OnTrafficDataUpdate(); } - // Calling Unsubscribe() from the worker thread on exit makes thread synchronization easier Unsubscribe(); } diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 028f720b5..0add21815 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -456,6 +456,23 @@ std::string DebugPrint(EventType eventType) UNREACHABLE(); } +std::string DebugPrint(ResponseStatus status) +{ + switch (status) + { + case ResponseStatus::Ok: return "Ok"; + case ResponseStatus::InvalidOperation: return "InvalidOperation"; + case ResponseStatus::SubscriptionRejected: return "SubscriptionRejected"; + case ResponseStatus::NotCovered: return "NotCovered"; + case ResponseStatus::PartiallyCovered: return "PartiallyCovered"; + case ResponseStatus::SubscriptionUnknown: return "SubscriptionUnknown"; + case ResponseStatus::PushRejected: return "PushRejected"; + case ResponseStatus::InternalError: return "InternalError"; + case ResponseStatus::Invalid: return "Invalid"; + } + UNREACHABLE(); +} + std::string DebugPrint(TrafficImpact impact) { std::ostringstream os; diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index f7d44531c..cf5d7724f 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -223,6 +223,76 @@ enum class EventType // TODO Security*, Transport*, Weather* }; +enum class ResponseStatus +{ + /** + * The operation was successful. + */ + Ok, + + /** + * The source rejected the operation as invalid + * + * This may happen when a nonexistent operation is attempted, or an operation is attempted with + * incomplete or otherwise invalid data. + * + * @note This corresponds to TraFF status `INVALID` but was renamed here. + * `ResponseStatus::Invalid` refers to a different kind of error. + */ + InvalidOperation, + + /** + * The source rejected the subscription, e.g. because the filtered region is too large. + */ + SubscriptionRejected, + + /** + * The source does not supply data for the requested area; the request has failed. + */ + NotCovered, + + /** + * The source supplies data only for a subset of the requested area; the request was successful + * (i.e. the subscription was created or changed as requested) but the consumer should be prepared + * to receive incomplete data. + */ + PartiallyCovered, + + /** + * An operation (change, push, pull) was attempted on a subscription which the recipient did not + * recognize. On transport channels which support stable identifiers for both communication + * parties, this is also used if a consumer attempts an operation on a subscription created by + * another consumer. + */ + SubscriptionUnknown, + + /** + * The aggregator does not accept unsolicited push requests from the sensor. Reserved for future + * versions and not used as of TraFF 0.8. + */ + PushRejected, + + /** + * An internal error prevented the recipient of the request from fulfilling it. + * + * This is either translated directly from `INTERNAL_ERROR` returned from the source, or may be + * inferred from errors on the transport channel (e.g. HTTP errors). + */ + InternalError, + + /** + * An unrecognized status code. + * + * This is used for all situations where we got a response from the source, with no indication of + * an error, but could not obtain a known status code from it (e.g. XML failed to parse, did not + * contain a status code, or contained an unknown status code). + * + * @note Not to be confused with TraFF status `INVALID`, which maps to + * `ResponseStatus::InvalidOperation`. + */ + Invalid +}; + /** * @brief Represents the impact of one or more traffic events. * @@ -436,6 +506,42 @@ using TraffFeed = std::vector; * the full filter list. */ +/** + * @brief Encapsulates the response to a TraFF request. + */ +struct TraffResponse +{ + /** + * @brief The response status for the request which triggered the response. + */ + ResponseStatus m_status = ResponseStatus::Invalid; + + /** + * @brief The subscription ID which the source has assigned to the subscriber. + * + * This attribute is how the source communicates the subscription ID to a subscriber. Required for + * responses to a subscription request; some transport channels may require it for every + * subscription-related operation; forbidden otherwise. + */ + std::string m_subscriptionId; + + /** + * @brief The time in seconds after which the source will consider the subscription invalid if no + * activity occurs. + * + * Required for responses to a subscription request on some transport channels, optional on other + * channels, forbidden for other requests. + * + * If not used, the value is zero. + */ + uint32_t m_timeout = 0; + + /** + * @brief A feed of traffic messages sent as part of the response. + */ + std::optional m_feed; +}; + /** * @brief Merges the contents of one `MultiMwmColoring` into another. * @@ -457,6 +563,7 @@ std::string DebugPrint(Ramps ramps); std::string DebugPrint(RoadClass roadClass); std::string DebugPrint(EventClass eventClass); std::string DebugPrint(EventType eventType); +std::string DebugPrint(ResponseStatus status); std::string DebugPrint(TrafficImpact impact); std::string DebugPrint(Point point); std::string DebugPrint(TraffLocation location); diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 2466b5e65..93fcd0a0b 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -303,6 +303,41 @@ std::optional OptionalTimeFromXml(pugi::xml_attribute const & attribute return result; } +/** + * @brief Retrieves a response status from an attribute. + * + * @param attribute The XML attribute to retrieve. + * @param status Receives the status retrieved. + * @return `true` on success, `false` if the attribute is not set or set to an empty string. + */ +bool ResponseStatusFromXml(pugi::xml_attribute const & attribute, ResponseStatus & status) +{ + std::string statusString; + if (!StringFromXml(attribute, statusString)) + return false; + + if (statusString == "OK") + status = ResponseStatus::Ok; + else if (statusString == "INVALID") + status = ResponseStatus::InvalidOperation; + else if (statusString == "SUBSCRIPTION_REJECTED") + status = ResponseStatus::SubscriptionRejected; + else if (statusString == "NOT_COVERED") + status = ResponseStatus::NotCovered; + else if (statusString == "PARTIALLY_COVERED") + status = ResponseStatus::PartiallyCovered; + else if (statusString == "SUBSCRIPTION_UNKNOWN") + status = ResponseStatus::SubscriptionUnknown; + else if (statusString == "PUSH_REJECTED") + status = ResponseStatus::PushRejected; + else if (statusString == "INTERNAL_ERROR") + status = ResponseStatus::InternalError; + else + status = ResponseStatus::Invalid; + + return true; +} + /** * @brief Retrieves a boolean value from an attribute. * @param attribute The XML attribute to retrieve. @@ -1057,14 +1092,21 @@ void MessageToXml(TraffMessage const & message, pugi::xml_node node) } } -bool ParseTraff(pugi::xml_document const & document, - std::optional> dataSource, - TraffFeed & feed) +/** + * @brief Retrieves a TraFF feed from an XML element. + * @param node The XML element to retrieve (`feed`). + * @param dataSource The data source for coloring, see `ParseTraff()`. + * @param feed Receives the feed. + * @return `true` on success, `false` if the node does not exist or does not contain valid message data. + */ +bool FeedFromXml(pugi::xml_node const & node, + std::optional> dataSource, + TraffFeed & feed) { bool result = false; - // Select all messages elements that are direct children of the root. - auto const messages = document.document_element().select_nodes("./message"); + // Select all messages elements that are direct children of the node. + auto const messages = node.select_nodes("./message"); if (messages.empty()) return true; @@ -1085,6 +1127,13 @@ bool ParseTraff(pugi::xml_document const & document, return result; } +bool ParseTraff(pugi::xml_document const & document, + std::optional> dataSource, + TraffFeed & feed) +{ + return FeedFromXml(document.document_element(), dataSource, feed); +} + void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document) { auto root = document.append_child("feed"); @@ -1117,4 +1166,39 @@ std::string FiltersToXml(std::vector & bboxRects) mercator::XToLon(rect.maxX())); return os.str(); } + +TraffResponse ParseResponse(std::string const & responseXml) +{ + TraffResponse result; + pugi::xml_document responseDocument; + if (!responseDocument.load_string(responseXml.c_str())) + return result; + + auto const responseElement = responseDocument.document_element(); + std::string responseElementName(responseElement.name()); + + if (responseElementName != "response") + return result; + + if (!ResponseStatusFromXml(responseElement.attribute("status"), result.m_status)) + return result; + + StringFromXml(responseElement.attribute("subscription_id"), result.m_subscriptionId); + + IntegerFromXml(responseElement.attribute("timeout"), result.m_timeout); + + LOG(LDEBUG, ("Response, status:", result.m_status, "subscription ID:", result.m_subscriptionId, "timeout:", result.m_timeout)); + + if (responseElement.child("feed")) + { + TraffFeed feed; + FeedFromXml(responseElement.child("feed"), std::nullopt /* dataSource */, feed); + LOG(LDEBUG, ("Feed received, number of messages:", feed.size())); + result.m_feed = std::move(feed); + } + else + LOG(LDEBUG, ("No feed in response")); + + return result; +} } // namespace openlr diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index ad2ef1371..68c5bb8e7 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -105,4 +105,17 @@ void GenerateTraff(std::map const & message * @return A string of XML `filter` elements. */ std::string FiltersToXml(std::vector & bboxRects); + +/** + * @brief Parses the response to a TraFF request. + * + * The response must comply with TraFF 0.8. The root element must be `response`. + * + * If a parsing error occurs, the response returned will have its `m_status` member set to + * `ResponseStatus::Invalid`. + * + * @param responseXml The response, as a string in XML format. + * @return The parsed response. + */ +TraffResponse ParseResponse(std::string const & responseXml); } // namespace traffxml diff --git a/traffxml/traff_source.cpp b/traffxml/traff_source.cpp index 502d3cc95..de829fca8 100644 --- a/traffxml/traff_source.cpp +++ b/traffxml/traff_source.cpp @@ -5,6 +5,10 @@ #include "geometry/rect2d.hpp" +#include "platform/platform.hpp" + +#include +#include #include namespace traffxml { @@ -102,4 +106,202 @@ void MockTraffSource::Poll() m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval; } } + +TraffResponse HttpPost(std::string const & url, std::string data) +{ + platform::HttpClient request(url); + request.SetBodyData(data, "application/xml"); + + if (!request.RunHttpRequest() || request.ErrorCode() != 200) + { + TraffResponse result; + result.m_status = ResponseStatus::InternalError; + return result; + } + + LOG(LDEBUG, ("Got response, status", request.ErrorCode())); + + TraffResponse result = ParseResponse(request.ServerResponse()); + return result; +} + +void HttpTraffSource::Create(TraffSourceManager & manager, std::string const & url) +{ + std::unique_ptr source = std::unique_ptr(new HttpTraffSource(manager, url)); + manager.RegisterSource(std::move(source)); +} + +HttpTraffSource::HttpTraffSource(TraffSourceManager & manager, std::string const & url) + : TraffSource(manager) + , m_url(url) +{} + +void HttpTraffSource::Subscribe(std::set & mwms) +{ + std::string data = "\n\n" + + GetMwmFilters(mwms) + + "\n" + + ""; + LOG(LDEBUG, ("Sending request:\n", data)); + + threads::SimpleThread thread([this, data]() { + // TODO sometimes the request gets sent (and processed) twice + TraffResponse response = HttpPost(m_url, data); + OnSubscribeResponse(response); + return; + }); + thread.detach(); +} + +void HttpTraffSource::OnFeedReceived(TraffFeed & feed) +{ + m_lastResponseTime = std::chrono::steady_clock::now(); + m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval; + m_lastAvailability = Availability::IsAvailable; + m_manager.ReceiveFeed(feed); +} + +void HttpTraffSource::OnSubscribeResponse(TraffResponse & response) +{ + if (response.m_status == ResponseStatus::Ok + || response.m_status == ResponseStatus::PartiallyCovered) + { + if (response.m_subscriptionId.empty()) + LOG(LWARNING, ("Server replied with", response.m_status, "but subscription ID is empty; ignoring")); + else + { + { + std::lock_guard lock(m_mutex); + m_subscriptionId = response.m_subscriptionId; + // TODO timeout + } + if (response.m_feed && !response.m_feed.value().empty()) + OnFeedReceived(response.m_feed.value()); + else + Poll(); + } + } + else + LOG(LWARNING, ("Subscribe request failed:", response.m_status)); +} + +void HttpTraffSource::ChangeSubscription(std::set & mwms) +{ + std::string data = "\n" + + "\n" + + GetMwmFilters(mwms) + + "\n" + + ""; + LOG(LDEBUG, ("Sending request:\n", data)); + + threads::SimpleThread thread([this, data]() { + TraffResponse response = HttpPost(m_url, data); + OnChangeSubscriptionResponse(response); + return; + }); + thread.detach(); +} + +void HttpTraffSource::OnChangeSubscriptionResponse(TraffResponse & response) +{ + if (response.m_status == ResponseStatus::Ok + || response.m_status == ResponseStatus::PartiallyCovered) + { + if (response.m_feed && !response.m_feed.value().empty()) + OnFeedReceived(response.m_feed.value()); + else + Poll(); + } + else if (response.m_status == ResponseStatus::SubscriptionUnknown) + { + LOG(LWARNING, ("Change Subscription returned", response.m_status, " – removing subscription", m_subscriptionId)); + { + std::lock_guard lock(m_mutex); + m_subscriptionId.clear(); + } + } + else + LOG(LWARNING, ("Change Subscription request failed:", response.m_status)); +} + +void HttpTraffSource::Unsubscribe() +{ + std::string data; + { + std::lock_guard lock(m_mutex); + + if (m_subscriptionId.empty()) + return; + data = ""; + } + + LOG(LDEBUG, ("Sending request:\n", data)); + + threads::SimpleThread thread([this, data]() { + TraffResponse response = HttpPost(m_url, data); + OnUnsubscribeResponse(response); + return; + }); + thread.detach(); +} + +void HttpTraffSource::OnUnsubscribeResponse(TraffResponse & response) +{ + if (response.m_status != ResponseStatus::Ok + && response.m_status != ResponseStatus::SubscriptionUnknown) + { + LOG(LWARNING, ("Unsubscribe returned", response.m_status, " – removing subscription")); + } + { + std::lock_guard lock(m_mutex); + m_subscriptionId.clear(); + } +} + +bool HttpTraffSource::IsPollNeeded() +{ + // TODO revisit logic + return m_nextRequestTime.load() <= std::chrono::steady_clock::now(); +} + +void HttpTraffSource::Poll() +{ + std::string data; + { + std::lock_guard lock(m_mutex); + + if (m_subscriptionId.empty()) + return; + data = ""; + } + + LOG(LDEBUG, ("Sending request:\n", data)); + + threads::SimpleThread thread([this, data]() { + // TODO sometimes the request gets sent (and processed) twice + TraffResponse response = HttpPost(m_url, data); + OnPollResponse(response); + return; + }); + thread.detach(); +} + +void HttpTraffSource::OnPollResponse(TraffResponse & response) +{ + if (response.m_status == ResponseStatus::Ok) + { + if (response.m_feed && !response.m_feed.value().empty()) + OnFeedReceived(response.m_feed.value()); + } + else if (response.m_status == ResponseStatus::SubscriptionUnknown) + { + LOG(LWARNING, ("Poll returned", response.m_status, " – removing subscription", m_subscriptionId)); + { + std::lock_guard lock(m_mutex); + m_subscriptionId.clear(); + } + } + else + LOG(LWARNING, ("Poll returned", response.m_status)); +} } // namespace traffxml diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index 8497c6a5f..840d1b5d9 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -2,8 +2,12 @@ #include "traffxml/traff_model.hpp" +#include "base/thread.hpp" + #include "indexer/mwm_set.hpp" +#include "platform/http_client.hpp" + #include #include #include @@ -423,4 +427,115 @@ class MockTraffSource : public TraffSource */ static auto constexpr m_updateInterval = std::chrono::minutes(5); }; + +/** + * @brief A TraFF source backed by a HTTP[S] server. + */ +class HttpTraffSource : public TraffSource +{ +public: + /** + * @brief Creates a new `HttpTraffSource` instance and registers it with the traffic manager. + * + * @param manager The traffic manager to register the new instance with + */ + static void Create(TraffSourceManager & manager, std::string const & url); + + /** + * @brief Subscribes to a traffic service. + * + * @param mwms The MWMs for which data is needed. + */ + virtual void Subscribe(std::set & mwms) override; + + /** + * @brief Changes an existing traffic subscription. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void ChangeSubscription(std::set & mwms) override; + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + */ + virtual void Unsubscribe() override; + + /** + * @brief Whether this source should be polled. + * + * Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and + * poll the source only if the result is true. + * + * @todo Document how the result is calculated. For example: + * This implementation uses `m_nextRequestTime` to determine when the next poll is due. When a + * feed is received, `m_nextRequestTime` is set to a point in time 5 minutes in the future. As + * long as `m_nextRequestTime` is in the future, this method returns false. + * + * @return true if the source should be polled, false if not. + */ + virtual bool IsPollNeeded() override; + + /** + * @brief Polls the traffic service for updates. + */ + virtual void Poll() override; + +protected: + /** + * @brief Constructs a new `HttpTraffSource`. + * @param manager The `TrafficSourceManager` instance to register the source with. + * @param url The URL for the TraFF service API. + */ + HttpTraffSource(TraffSourceManager & manager, std::string const & url); + +private: + /** + * @brief Processes a TraFF feed. + * @param feed The feed. + */ + void OnFeedReceived(TraffFeed & feed); + + /** + * @brief Processes the response to a subscribe request. + * @param response The response to the subscribe operation. + */ + void OnSubscribeResponse(TraffResponse & response); + + /** + * @brief Processes the response to a change subscription request. + * @param response The response to the change subscription operation. + */ + void OnChangeSubscriptionResponse(TraffResponse & response); + + /** + * @brief Processes the response to an unsubscribe request. + * @param response The response to the unsubscribe operation. + */ + void OnUnsubscribeResponse(TraffResponse & response); + + /** + * @brief Processes the response to a poll request. + * @param response The response to the poll operation. + */ + void OnPollResponse(TraffResponse & response); + + /** + * @brief Event loop for the worker thread. + * + * This method runs an event loop, which blocks until woken up. When woken up, it processes the + * request (subscribe, change subscription, poll or unsubscribe) and its result, then blocks + * again until woken up for the next request. + */ + void ThreadRoutine(); + + /** + * @brief The update interval, 5 minutes. + */ + static auto constexpr m_updateInterval = std::chrono::minutes(5); + + /** + * @brief The URL for the TraFF service. + */ + const std::string m_url; +}; } // namespace traffxml From 93a1f9d1a615221df1cd0b660b4bb2211cc5c2a9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 21 Jul 2025 22:35:48 +0300 Subject: [PATCH 124/252] [traffxml] Fix bug when storing point distance attribute Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 93fcd0a0b..be5b146af 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -551,7 +551,7 @@ void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNo { auto node = parentNode.append_child(name); if (point.m_distance) - node.append_attribute("distance").set_value(point.m_junctionName.value()); + node.append_attribute("distance").set_value(point.m_distance.value()); if (point.m_junctionName) node.append_attribute("junction_name").set_value(point.m_junctionName.value()); if (point.m_junctionRef) From 798affe0ef9b2542b6a9bf2bd6233d2bf5b25bf4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 21 Jul 2025 22:36:04 +0300 Subject: [PATCH 125/252] [traffxml] Documentation Signed-off-by: mvglasow --- traffxml/traff_model_xml.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/traffxml/traff_model_xml.hpp b/traffxml/traff_model_xml.hpp index 68c5bb8e7..530210002 100644 --- a/traffxml/traff_model_xml.hpp +++ b/traffxml/traff_model_xml.hpp @@ -41,6 +41,8 @@ namespace traffxml * a child of `message` and holds decoded traffic coloring. In order to parse it, `dataSource` must * be specified. If `dataSource` is `nullopt`, coloring will be ignored. It is recommended to pass * `dataSource` if, and only if, parsing an XML stream that is expected to contain traffic coloring. + * This is only expected to occur in cached data. TraFF from external sources is not expected to + * contain `mwm_coloring` elements and souch information should be ignored in feeds from outside. * * @note To pass a reference to the framework data source (assuming the `framework` is the framework * instance), use `std::cref(framework.GetDataSource())`. From d988ab33264b98dda4ba6d25a6a6546b23601635 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 21 Jul 2025 22:58:47 +0300 Subject: [PATCH 126/252] [traffic] Restore decoded segments from cache on startup Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 980350ffb..68c044743 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -435,7 +435,7 @@ bool TrafficManager::RestoreCache() traffxml::TraffFeed feedOut; bool hasDecoded = false; bool hasUndecoded = false; - if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feedIn)) + if (traffxml::ParseTraff(document, m_dataSource, feedIn)) { while (!feedIn.empty()) { From 75c7d146afded02f48bed74e1a6d40d523366f25 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 22 Jul 2025 20:30:15 +0300 Subject: [PATCH 127/252] [traffic] Unsubscribe when traffic manager is disabled Signed-off-by: mvglasow --- map/traffic_manager.cpp | 41 ++++++++++++++++++++++++++++------------- map/traffic_manager.hpp | 33 ++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 68c044743..3d8fbc920 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -149,11 +149,14 @@ void TrafficManager::SetEnabled(bool enabled) if (notifyUpdate) OnTrafficDataUpdate(); else - RecalculateSubscription(); + RecalculateSubscription(true); m_canSetMode = false; } else + { + Unsubscribe(); m_routingSession.OnTrafficInfoClear(); + } } void TrafficManager::Clear() @@ -256,7 +259,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, } } -void TrafficManager::RecalculateSubscription() +void TrafficManager::RecalculateSubscription(bool forceRenewal) { if (!IsEnabled() || m_isPaused) return; @@ -273,7 +276,10 @@ void TrafficManager::RecalculateSubscription() * If UpdateViewport() or UpdateMyPosition() had changes, they would also have updated the * routing MWMs and reset m_activeMwmsChanged. If neither of them had changes and * m_activeMwmsChanged is true, it indicates changes in route MWMs which we need to process. + * If `forceRenewal` is true, we set `m_activeMwmsChanged` to true in order to force renewal of + * all subscriptions. */ + m_activeMwmsChanged |= forceRenewal; if (m_activeMwmsChanged) { if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) @@ -490,22 +496,25 @@ void TrafficManager::ReceiveFeed(traffxml::TraffFeed feed) void TrafficManager::RegisterSource(std::unique_ptr source) { - std::set activeMwms; - + if (IsEnabled()) { - std::lock_guard lock(m_mutex); - UniteActiveMwms(activeMwms); - } + std::set activeMwms; - if (!activeMwms.empty()) - source->SubscribeOrChangeSubscription(activeMwms); + { + std::lock_guard lock(m_mutex); + UniteActiveMwms(activeMwms); + } + + if (!activeMwms.empty()) + source->SubscribeOrChangeSubscription(activeMwms); + } { std::lock_guard lock(m_trafficSourceMutex); m_trafficSources.push_back(std::move(source)); } - m_isPollNeeded = true; + m_isPollNeeded = IsEnabled(); } void TrafficManager::PurgeExpiredMessages() @@ -740,8 +749,14 @@ bool TrafficManager::WaitForRequest() LOG(LINFO, ("nothing to do for now, waiting for timeout or notification")); bool const timeout = !m_condition.wait_for(lock, kUpdateInterval, [this] { - // return true for any condition we want to process immediately - return !m_isRunning || (m_activeMwmsChanged && !IsTestMode()) || !m_feedQueue.empty(); + // return false to continue waiting, true for any condition we want to process immediately + // return immediately if we got terminated + if (!m_isRunning) + return true; + // otherwise continue waiting if we are paused or disabled + if (!IsEnabled() || m_isPaused) + return false; + return (m_activeMwmsChanged && !IsTestMode()) || !m_feedQueue.empty(); }); // check again if we got terminated while waiting (or woken up because we got terminated) @@ -948,7 +963,7 @@ void TrafficManager::Resume() return; m_isPaused = false; - RecalculateSubscription(); + RecalculateSubscription(false); } void TrafficManager::SetSimplifiedColorScheme(bool simplified) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index db753c886..db8fdad27 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -182,21 +182,6 @@ class TrafficManager final : public traffxml::TraffSourceManager void UpdateViewport(ScreenBase const & screen); void UpdateMyPosition(MyPosition const & myPosition); - /** - * @brief Recalculates the TraFF subscription area. - * - * The subscription area needs to be recalculated when the traffic manager goes from disabled to - * enabled, or when it is resumed after being paused, as the subscription area is not updated - * while the traffic manager is disabled or paused. - * - * If the subscription area has changed, this triggers a change of the active TraFF subscription. - * - * No traffic data is discarded, but sources will be polled for an update, which may turn out - * larger than usual if the traffic manager was in disabled/paused state for an extended period of - * time or the subscription area has changed. - */ - void RecalculateSubscription(); - /** * @brief Invalidates traffic information for the specified MWM. * @@ -287,6 +272,24 @@ class TrafficManager final : public traffxml::TraffSourceManager private: + /** + * @brief Recalculates the TraFF subscription area. + * + * The subscription area needs to be recalculated when the traffic manager goes from disabled to + * enabled, or when it is resumed after being paused, as the subscription area is not updated + * while the traffic manager is disabled or paused. + * + * If the subscription area has changed, or if `forceRenewal` is true, TraFF subscriptions are + * renewed by calling `SubscribeOrChangeSubscription()`. + * + * No traffic data is discarded, but sources will be polled for an update, which may turn out + * larger than usual if the traffic manager was in disabled/paused state for an extended period of + * time or the subscription area has changed. + * + * @param forceRenewal If true, renew subscriptions even if the subscription area has not changed. + */ + void RecalculateSubscription(bool forceRenewal); + /** * @brief Ensures every TraFF source has a subscription covering all currently active MWMs. * From 7283e4ecb48f0fcde9cba5c86be287b33b74a876 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 22 Jul 2025 22:28:10 +0300 Subject: [PATCH 128/252] [traffic] Read HttpTrafficSource parameters from config Signed-off-by: mvglasow --- map/framework.cpp | 35 +++++++++++++++++++++++++++++++---- map/framework.hpp | 6 ++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index ccb72ac32..2d9e9defa 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -103,6 +103,8 @@ std::string_view constexpr kAllow3dKey = "Allow3d"; std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d"; std::string_view constexpr kAllowAutoZoom = "AutoZoom"; std::string_view constexpr kTrafficEnabledKey = "TrafficEnabled"; +std::string_view constexpr kTrafficHttpEnabledKey = "TrafficHttpEnabled"; +std::string_view constexpr kTrafficHttpUrlKey = "TrafficHttpUrl"; std::string_view constexpr kTransitSchemeEnabledKey = "TransitSchemeEnabled"; std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled"; std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled"; @@ -394,15 +396,14 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) LoadMapsSync(); m_trafficManager.SetEnabled(LoadTrafficEnabled()); + if (!params.m_trafficTestMode && LoadTrafficHttpEnabled()) + // TODO handle invalid URLs + traffxml::HttpTraffSource::Create(m_trafficManager, LoadTrafficHttpUrl()); /* * MockTraffSource for debugging purposes. - * TODO Replace with a real source, parametrized and conditionally loaded, once we have one. */ //traffxml::MockTraffSource::Create(m_trafficManager); - - // For testing purposes, HttpTraffSource pointing to a hardcoded local instance - traffxml::HttpTraffSource::Create(m_trafficManager, "http://traff:8080/subscription-manager"); } Framework::~Framework() @@ -2588,6 +2589,32 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled) settings::Set(kTrafficEnabledKey, trafficEnabled); } +bool Framework::LoadTrafficHttpEnabled() +{ + bool enabled; + if (!settings::Get(kTrafficHttpEnabledKey, enabled)) + enabled = false; + return enabled; +} + +void Framework::SaveTrafficHttpEnabled(bool trafficHttpEnabled) +{ + settings::Set(kTrafficHttpEnabledKey, trafficHttpEnabled); +} + +std::string Framework::LoadTrafficHttpUrl() +{ + std::string url; + if (!settings::Get(kTrafficHttpUrlKey, url)) + url = ""; + return url; +} + +void Framework::SaveTrafficHttpUrl(std::string trafficHttpUrl) +{ + settings::Set(kTrafficHttpUrlKey, trafficHttpUrl); +} + bool Framework::LoadTrafficSimplifiedColors() { bool simplified; diff --git a/map/framework.hpp b/map/framework.hpp index 341f5f69b..96402b2cc 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -740,6 +740,12 @@ class Framework : public PositionProvider, bool LoadTrafficEnabled(); void SaveTrafficEnabled(bool trafficEnabled); + bool LoadTrafficHttpEnabled(); + void SaveTrafficHttpEnabled(bool trafficHttpEnabled); + + std::string LoadTrafficHttpUrl(); + void SaveTrafficHttpUrl(std::string trafficHttpUrl); + bool LoadTrafficSimplifiedColors(); void SaveTrafficSimplifiedColors(bool simplified); From be3792b93ae3f6a7b13d6ae06769a0e5148b37db Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 22 Jul 2025 22:55:59 +0300 Subject: [PATCH 129/252] [traffic] Remove obsolete code Signed-off-by: mvglasow --- traffic/traffic_info.cpp | 201 --------------------------------------- traffic/traffic_info.hpp | 44 --------- 2 files changed, 245 deletions(-) diff --git a/traffic/traffic_info.cpp b/traffic/traffic_info.cpp index b7b646898..0cda53799 100644 --- a/traffic/traffic_info.cpp +++ b/traffic/traffic_info.cpp @@ -61,22 +61,6 @@ bool ReadRemoteFile(string const & url, vector & contents, int & errorC return true; } -// TODO no longer needed -#ifdef traffic_dead_code -string MakeRemoteURL(string const & name, uint64_t version) -{ - if (string(TRAFFIC_DATA_BASE_URL).empty()) - return {}; - - stringstream ss; - ss << TRAFFIC_DATA_BASE_URL; - if (version != 0) - ss << version << "/"; - ss << url::UrlEncode(name) << TRAFFIC_FILE_EXTENSION; - return ss.str(); -} -#endif - char const kETag[] = "etag"; } // namespace @@ -94,51 +78,6 @@ TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t di uint8_t const TrafficInfo::kLatestKeysVersion = 0; uint8_t const TrafficInfo::kLatestValuesVersion = 0; -// TODO no longer needed -#ifdef traffic_dead_code -TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion) - : m_mwmId(mwmId) - , m_currentDataVersion(currentDataVersion) -{ - if (!mwmId.IsAlive()) - { - LOG(LWARNING, ("Attempt to create a traffic info for dead mwm.")); - return; - } - string const mwmPath = mwmId.GetInfo()->GetLocalFile().GetPath(MapFileType::Map); - try - { - FilesContainerR rcont(mwmPath); - if (rcont.IsExist(TRAFFIC_KEYS_FILE_TAG)) - { - auto reader = rcont.GetReader(TRAFFIC_KEYS_FILE_TAG); - vector buf(static_cast(reader.Size())); - reader.Read(0, buf.data(), buf.size()); - LOG(LINFO, ("Reading keys for", mwmId, "from section")); - try - { - DeserializeTrafficKeys(buf, m_keys); - } - catch (Reader::Exception const & e) - { - auto const info = mwmId.GetInfo(); - LOG(LINFO, ("Could not read traffic keys from section. MWM:", info->GetCountryName(), - "Version:", info->GetVersion())); - } - } - else - { - LOG(LINFO, ("Reading traffic keys for", mwmId, "from the web")); - ReceiveTrafficKeys(); - } - } - catch (RootException const & e) - { - LOG(LWARNING, ("Could not initialize traffic keys")); - } -} -#endif - TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring) : m_mwmId(mwmId) , m_coloring(std::move(coloring)) @@ -160,25 +99,6 @@ void TrafficInfo::SetTrafficKeysForTesting(vector const & keys) m_availability = Availability::IsAvailable; } -// TODO no longer needed -#ifdef traffic_dead_code -bool TrafficInfo::ReceiveTrafficData(string & etag) -{ - vector values; - switch (ReceiveTrafficValues(etag, values)) - { - case ServerDataStatus::New: - return UpdateTrafficData(values); - case ServerDataStatus::NotChanged: - return true; - case ServerDataStatus::NotFound: - case ServerDataStatus::Error: - return false; - } - return false; -} -#endif - SpeedGroup TrafficInfo::GetSpeedGroup(RoadSegmentId const & id) const { auto const it = m_coloring.find(id); @@ -408,95 +328,6 @@ void TrafficInfo::DeserializeTrafficValues(vector const & data, ASSERT_EQUAL(src.Size(), 0, ()); } -// TODO no longer needed -#ifdef traffic_dead_code -// todo(@m) This is a temporary method. Do not refactor it. -bool TrafficInfo::ReceiveTrafficKeys() -{ - if (!m_mwmId.IsAlive()) - return false; - auto const & info = m_mwmId.GetInfo(); - if (!info) - return false; - - string const url = MakeRemoteURL(info->GetCountryName(), info->GetVersion()); - - if (url.empty()) - return false; - - vector contents; - int errorCode; - if (!ReadRemoteFile(url + ".keys", contents, errorCode)) - return false; - if (errorCode != 200) - { - LOG(LWARNING, ("Network error when reading keys")); - return false; - } - - vector keys; - try - { - DeserializeTrafficKeys(contents, keys); - } - catch (Reader::Exception const & e) - { - LOG(LINFO, ("Could not read traffic keys received from server. MWM:", info->GetCountryName(), - "Version:", info->GetVersion())); - return false; - } - m_keys.swap(keys); - return true; -} -#endif - -// TODO no longer needed -#ifdef traffic_dead_code -TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, vector & values) -{ - if (!m_mwmId.IsAlive()) - return ServerDataStatus::Error; - - auto const & info = m_mwmId.GetInfo(); - if (!info) - return ServerDataStatus::Error; - - auto const version = info->GetVersion(); - string const url = MakeRemoteURL(info->GetCountryName(), version); - - if (url.empty()) - return ServerDataStatus::Error; - - platform::HttpClient request(url); - request.LoadHeaders(true); - request.SetRawHeader("If-None-Match", etag); - - if (!request.RunHttpRequest() || request.ErrorCode() != 200) - return ProcessFailure(request, version); - try - { - string const & response = request.ServerResponse(); - vector contents(response.cbegin(), response.cend()); - DeserializeTrafficValues(contents, values); - } - catch (Reader::Exception const & e) - { - m_availability = Availability::NoData; - LOG(LWARNING, ("Could not read traffic values received from server. MWM:", - info->GetCountryName(), "Version:", info->GetVersion())); - return ServerDataStatus::Error; - } - // Update ETag for this MWM. - auto const & headers = request.GetHeaders(); - auto const it = headers.find(kETag); - if (it != headers.end()) - etag = it->second; - - m_availability = Availability::IsAvailable; - return ServerDataStatus::New; -} -#endif - bool TrafficInfo::UpdateTrafficData(vector const & values) { m_coloring.clear(); @@ -519,38 +350,6 @@ bool TrafficInfo::UpdateTrafficData(vector const & values) return true; } -// TODO no longer needed -#ifdef traffic_dead_code -TrafficInfo::ServerDataStatus TrafficInfo::ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion) -{ - switch (request.ErrorCode()) - { - case 404: /* Not Found */ - { - int64_t version = 0; - VERIFY(strings::to_int64(request.ServerResponse().c_str(), version), ()); - - if (version > mwmVersion && version <= m_currentDataVersion) - m_availability = Availability::ExpiredData; - else if (version > m_currentDataVersion) - m_availability = Availability::ExpiredApp; - else - m_availability = Availability::NoData; - return ServerDataStatus::NotFound; - } - case 304: /* Not Modified */ - { - m_availability = Availability::IsAvailable; - return ServerDataStatus::NotChanged; - } - } - - m_availability = Availability::Unknown; - - return ServerDataStatus::Error; -} -#endif - string DebugPrint(TrafficInfo::RoadSegmentId const & id) { string const dir = diff --git a/traffic/traffic_info.hpp b/traffic/traffic_info.hpp index 61a4e3474..d7fe391f7 100644 --- a/traffic/traffic_info.hpp +++ b/traffic/traffic_info.hpp @@ -105,11 +105,6 @@ class TrafficInfo TrafficInfo() = default; -// TODO no longer needed -#ifdef traffic_dead_code - TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion); -#endif - TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring); /** @@ -120,25 +115,6 @@ class TrafficInfo static TrafficInfo BuildForTesting(Coloring && coloring); void SetTrafficKeysForTesting(std::vector const & keys); -// TODO no longer needed -#ifdef traffic_dead_code - /** - * @brief Fetches the latest traffic data from the server and updates the coloring and ETag. - * - * The url is constructed using the `mwmId` specified in the constructor. - * - * The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. - * It is one of several mechanisms that HTTP provides for web cache validation, - * which allows a client to make conditional requests. - * - * NOTE: This method must not be called on the UI thread. - * - * @param etag The entity tag - * @return True on success, false on failure. - */ - bool ReceiveTrafficData(std::string & etag); -#endif - /** * @brief Returns the latest known speed group by a feature segment's ID. * @param id The road segment ID. @@ -205,29 +181,9 @@ class TrafficInfo friend void UnitTest_TrafficInfo_UpdateTrafficData(); -// TODO no longer needed -#ifdef traffic_dead_code - // todo(@m) A temporary method. Remove it once the keys are added - // to the generator and the data is regenerated. - bool ReceiveTrafficKeys(); -#endif - -// TODO no longer needed -#ifdef traffic_dead_code - // Tries to read the values of the Coloring map from server into |values|. - // Returns result of communicating with server as ServerDataStatus. - // Otherwise, returns false and does not change m_coloring. - ServerDataStatus ReceiveTrafficValues(std::string & etag, std::vector & values); -#endif - // Updates the coloring and changes the availability status if needed. bool UpdateTrafficData(std::vector const & values); -// TODO no longer needed -#ifdef traffic_dead_code - ServerDataStatus ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion); -#endif - /** * @brief The mapping from feature segments to speed groups (see speed_groups.hpp). */ From 3b1fca01e35b125f5874e80a6f6b45e0d6286cf9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 23 Jul 2025 21:34:11 +0300 Subject: [PATCH 130/252] [traffic] API to reconfigure a running HttpTrafficSource Signed-off-by: mvglasow --- map/framework.cpp | 10 ++++++++++ map/framework.hpp | 2 ++ map/traffic_manager.cpp | 21 +++++++++++++++++++++ map/traffic_manager.hpp | 17 +++++++++++++++++ traffxml/traff_source.cpp | 21 +++++++++++++++++++++ traffxml/traff_source.hpp | 9 +++++++++ 6 files changed, 80 insertions(+) diff --git a/map/framework.cpp b/map/framework.cpp index 2d9e9defa..3f87455de 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -2589,6 +2589,11 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled) settings::Set(kTrafficEnabledKey, trafficEnabled); } +void Framework::SetTrafficHttpEnabled(bool enabled) +{ + m_trafficManager.SetHttpTraffSource(enabled, LoadTrafficHttpUrl()); +} + bool Framework::LoadTrafficHttpEnabled() { bool enabled; @@ -2602,6 +2607,11 @@ void Framework::SaveTrafficHttpEnabled(bool trafficHttpEnabled) settings::Set(kTrafficHttpEnabledKey, trafficHttpEnabled); } +void Framework::SetTrafficHttpUrl(std::string url) +{ + m_trafficManager.SetHttpTraffSource(LoadTrafficHttpEnabled(), url); +} + std::string Framework::LoadTrafficHttpUrl() { std::string url; diff --git a/map/framework.hpp b/map/framework.hpp index 96402b2cc..c70662dee 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -740,9 +740,11 @@ class Framework : public PositionProvider, bool LoadTrafficEnabled(); void SaveTrafficEnabled(bool trafficEnabled); + void SetTrafficHttpEnabled(bool enabled); bool LoadTrafficHttpEnabled(); void SaveTrafficHttpEnabled(bool trafficHttpEnabled); + void SetTrafficHttpUrl(std::string url); std::string LoadTrafficHttpUrl(); void SaveTrafficHttpUrl(std::string trafficHttpUrl); diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 3d8fbc920..1a0173890 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -923,6 +923,27 @@ bool TrafficManager::IsEnabled() const return m_state != TrafficState::Disabled; } +void TrafficManager::SetHttpTraffSource(bool enabled, std::string url) +{ + if (IsTestMode()) + return; + + { + std::lock_guard lock(m_trafficSourceMutex); + + for (auto it = m_trafficSources.begin(); it != m_trafficSources.end(); ) + if (traffxml::HttpTraffSource* httpSource = dynamic_cast(it->get())) + { + httpSource->Close(); + m_trafficSources.erase(it); + } + else + ++it; + } + if (enabled) + traffxml::HttpTraffSource::Create(*this, url); +} + bool TrafficManager::IsInvalidState() const { return m_state == TrafficState::NetworkError; diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index db8fdad27..7b4fbe08e 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -173,6 +173,23 @@ class TrafficManager final : public traffxml::TraffSourceManager */ bool IsEnabled() const; + /** + * @brief Sets the enabled state and URL for the `HttpTraffSource`. + * + * If the traffic manager is in test mode, this function is a no-op. + * + * Otherwise this function is expected to be called only if the enabled state and/or URL have + * actually changed. Setting both to the current state will remove the current source and create + * a new one with identical settings. + * + * This function currently assumes that there is never more than one `HttpTraffSource` configured + * at the same time. + * + * @param enabled Whether the HTTP TraFF source is enabled. + * @param url The URL for the TraFF API. + */ + void SetHttpTraffSource(bool enabled, std::string url); + /** * @brief Starts the traffic manager. * diff --git a/traffxml/traff_source.cpp b/traffxml/traff_source.cpp index de829fca8..2724d8136 100644 --- a/traffxml/traff_source.cpp +++ b/traffxml/traff_source.cpp @@ -136,6 +136,27 @@ HttpTraffSource::HttpTraffSource(TraffSourceManager & manager, std::string const , m_url(url) {} +void HttpTraffSource::Close() +{ + std::string data; + { + std::lock_guard lock(m_mutex); + + if (m_subscriptionId.empty()) + return; + data = ""; + m_subscriptionId.clear(); + } + + LOG(LDEBUG, ("Sending request:\n", data)); + + threads::SimpleThread thread([this, data]() { + TraffResponse response = HttpPost(m_url, data); + return; + }); + thread.detach(); +} + void HttpTraffSource::Subscribe(std::set & mwms) { std::string data = "\n\n" diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index 840d1b5d9..f38ddfef8 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -441,6 +441,15 @@ class HttpTraffSource : public TraffSource */ static void Create(TraffSourceManager & manager, std::string const & url); + /** + * @brief Prepares the HTTP traffic source for unloading. + * + * If there is still an active subscription, it unsubscribes, but without processing the result + * received from the service. Otherwise, teardown is a no-op. + */ + // TODO move this to the parent class and override it here? + void Close(); + /** * @brief Subscribes to a traffic service. * From 03d6847be357d8a9da2421f7b305eed2af430149 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 23 Jul 2025 22:32:49 +0300 Subject: [PATCH 131/252] [traffic][qt] Make HttpTraffSource configurable from Qt GUI Signed-off-by: mvglasow --- qt/preferences_dialog.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/qt/preferences_dialog.cpp b/qt/preferences_dialog.cpp index acc0fa827..6836a9dea 100644 --- a/qt/preferences_dialog.cpp +++ b/qt/preferences_dialog.cpp @@ -183,6 +183,31 @@ namespace qt } #endif + QLabel * trafficHttpUrlLabel = new QLabel("Traffic API URL:"); + + QLineEdit * trafficHttpUrlLineEdit = new QLineEdit(); + { + trafficHttpUrlLineEdit->setText(QString::fromStdString(framework.LoadTrafficHttpUrl())); + connect(trafficHttpUrlLineEdit, &QLineEdit::editingFinished, [&framework, trafficHttpUrlLineEdit]() + { + framework.SaveTrafficHttpUrl(trafficHttpUrlLineEdit->text().toStdString()); + framework.SetTrafficHttpUrl(trafficHttpUrlLineEdit->text().toStdString()); + }); + } + + QCheckBox * trafficHttpEnabledCheckBox = new QCheckBox("Enable live traffic data"); + { + trafficHttpEnabledCheckBox->setChecked(framework.LoadTrafficHttpEnabled()); + connect(trafficHttpEnabledCheckBox, &QCheckBox::stateChanged, [&framework, trafficHttpUrlLabel, trafficHttpUrlLineEdit](int i) + { + bool const enable = i > 0; + framework.SaveTrafficHttpEnabled(enable); + framework.SetTrafficHttpEnabled(enable); + trafficHttpUrlLabel->setEnabled(enable); + trafficHttpUrlLineEdit->setEnabled(enable); + }); + } + QHBoxLayout * bottomLayout = new QHBoxLayout(); { QPushButton * closeButton = new QPushButton(tr("Close")); @@ -206,6 +231,9 @@ namespace qt #ifdef BUILD_DESIGNER finalLayout->addWidget(indexRegenCheckBox); #endif + finalLayout->addWidget(trafficHttpEnabledCheckBox); + finalLayout->addWidget(trafficHttpUrlLabel); + finalLayout->addWidget(trafficHttpUrlLineEdit); finalLayout->addLayout(bottomLayout); setLayout(finalLayout); } From 61b15d623d408d2c76e16980d1cb40649109d948 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 24 Jul 2025 21:31:46 +0300 Subject: [PATCH 132/252] [traffxml] Fix compiler warnings Signed-off-by: mvglasow --- traffxml/traff_decoder.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 672d1b225..98ed7ddf2 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -102,7 +102,7 @@ class OpenLrV3TraffDecoder : public TraffDecoder * @param message The message to decode. * @param decoded Receives the decoded segments. The speed group will be `Unknown`. */ - void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override; private: /** @@ -307,7 +307,7 @@ class RoutingTraffDecoder : public TraffDecoder, * * This implementation does nothing, as `NumMwmIds` does not support removal. */ - virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {} + virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {} protected: /** @@ -348,7 +348,7 @@ class RoutingTraffDecoder : public TraffDecoder, * @param message The message to decode. * @param decoded Receives the decoded segments. The speed group will be `Unknown`. */ - void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded); + void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override; private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); From 2729d077320613b5614c0d59d2d45e1ae0063749 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 24 Jul 2025 21:32:02 +0300 Subject: [PATCH 133/252] [traffic] Fix assignment Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 1a0173890..152320bec 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -675,7 +675,7 @@ void TrafficManager::ThreadRoutine() { if (steady_clock::now() - lastPurged >= kPurgeInterval) { - lastPurged == steady_clock::now(); + lastPurged = steady_clock::now(); hasUpdates |= PurgeExpiredMessagesImpl(); } From 98796cd6f8df7f74082816535f0c4dc623c8a4ff Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 24 Jul 2025 22:19:23 +0300 Subject: [PATCH 134/252] [android] Link against traffxml Signed-off-by: mvglasow --- android/app/src/main/cpp/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 25a839155..21a0eb74f 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -110,6 +110,7 @@ target_link_libraries(${PROJECT_NAME} # icu # agg # vulkan_wrapper + traffxml # Android libs log From a25602dbe058824466e6537c6407f5ba12da1dac Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 25 Jul 2025 17:20:07 +0300 Subject: [PATCH 135/252] [traffxml] Return to features supported by Clang 20.1 for IsoTime This reverts commit 776444edc7c4730f67e8aa2fa30b983c73e01054. # Conflicts: # traffxml/traff_model.cpp --- traffxml/traff_model.cpp | 55 ++++++++++++++++++++++++---------------- traffxml/traff_model.hpp | 4 +-- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index 0add21815..cd9dbfe37 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -79,17 +79,28 @@ const std::map kEventDelayMap{ std::optional IsoTime::ParseIsoTime(std::string timeString) { /* - * We cannot use `std::chrono::from_stream` because it requires GCC 14+, and as of mid-2025, the - * supported development platform (Ubuntu 24.04) has GCC 13.2. Clang still does not support it. + * TODO this is ugly because we need to work around some compiler deficiencies. * - * As a reasonably portable workaround, we first parse the time string into its constituent values - * using a regex, then build a `sys_seconds` instance from the values and use `clock_cast` to - * convert it to a `std::chrono::time_point` instance, which is the data - * type we use internally. + * Ideally, we would be using `std::chrono::time_point` and parse the + * string using `std::chrono::from_stream`, using `%FT%T%z` for the format string. + * This works in GCC 14+ and is pleasantly liberal about the time zone format (all of +01, +0100 + * and +01:00 are parsed correctly). Alas, Ubuntu 24.04 (currently the default dev platform) comes + * with GCC 13.2, which lacks this support. Clang, the only supported compiler for Android (and, + * presumably, iOS), as of mid-2025, doesn’t support it at all. * - * Once we have proper support for `std::chrono::from_stream` in all toolchains we support, this - * function can be rewritten accordingly. In GCC 14+, using `%FT%T%z` for the format string will - * work with all known UTC offset formats (+01, +0100 and +01:00), just like the regex does. + * The workaround is therefore to use `std::chrono::time_point`, which + * exposes the same API as its `utc_clock` counterpart, making transition at a later point easy. + * In addition, however, it can be constructed from `std::time_t`, which we can generate from + * `std::tm`. Unlike the other C legacy functions, gmtime is thread-safe. + * Still not the prettiest way (as it relies on legacy C functions which are not + * thread-safe), but the best we can get until we have proper compiler support for `from_stream`. + * + * Should we have support for `std::chrono:clock_cast` but not `std::chrono::from_stream`, we + * could build a `std::chrono::sys_seconds` from the constutuent values and use + * `std::chrono::clock_cast` to convert it to a `std::chrono::time_point`, based on whatever clock + * is supported. This works on Linux (using `utc_clock`) as of mid-2025, but not on the primary + * target platforms (Android and iOS) and has therefore been left out for uniformity (and + * reproducibility of bugs). */ /* * Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher @@ -119,17 +130,17 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) if (offset_h < 0) offset_m *= -1; - const auto y = static_cast(std::stoi(iso8601Matcher[1])); - const auto mo = static_cast(std::stoi(iso8601Matcher[2])); - const auto d = static_cast(std::stoi(iso8601Matcher[3])); - const std::chrono::hours h{std::stoi(iso8601Matcher[4]) - offset_h}; - const std::chrono::minutes min{std::stoi(iso8601Matcher[5]) - offset_m}; - const std::chrono::seconds s{static_cast(std::stof(iso8601Matcher[6]) + 0.5f)}; + std::tm tm = {}; + tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900; + tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1; + tm.tm_mday = std::stoi(iso8601Matcher[3]); + tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h; + tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m; + tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f; - std::chrono::sys_seconds sys_s = std::chrono::sys_days{y/mo/d}; - sys_s = sys_s + h + min + s; + std::time_t tt = timegm(&tm); - std::chrono::time_point tp = std::chrono::clock_cast(sys_s); + std::chrono::time_point tp = std::chrono::system_clock::from_time_t(tt); IsoTime result(tp); return result; @@ -143,21 +154,21 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) IsoTime IsoTime::Now() { - return IsoTime(std::chrono::utc_clock::now()); + return IsoTime(std::chrono::system_clock::now()); } -IsoTime::IsoTime(std::chrono::time_point tp) +IsoTime::IsoTime(std::chrono::time_point tp) : m_tp(tp) {} bool IsoTime::IsPast() { - return m_tp < std::chrono::utc_clock::now(); + return m_tp < std::chrono::system_clock::now(); }\ void IsoTime::Shift(IsoTime nowRef) { - auto const offset = std::chrono::utc_clock::now() - nowRef.m_tp; + auto const offset = std::chrono::system_clock::now() - nowRef.m_tp; m_tp += offset; } diff --git a/traffxml/traff_model.hpp b/traffxml/traff_model.hpp index cf5d7724f..0cee71c26 100644 --- a/traffxml/traff_model.hpp +++ b/traffxml/traff_model.hpp @@ -87,9 +87,9 @@ class IsoTime private: friend std::string DebugPrint(IsoTime time); - IsoTime(std::chrono::time_point tp); + IsoTime(std::chrono::time_point tp); - std::chrono::time_point m_tp; + std::chrono::time_point m_tp; }; // TODO enum urgency From bebac8d8a73bf7426e680c6a2f462d662e3343e6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 25 Jul 2025 17:25:17 +0300 Subject: [PATCH 136/252] [traffxml] Remove openlr dependency, disable OpenLrV3TraffDecoder Signed-off-by: mvglasow --- qt/CMakeLists.txt | 1 - traffxml/CMakeLists.txt | 1 - traffxml/traff_assessment_tool/CMakeLists.txt | 1 - traffxml/traff_decoder.cpp | 6 ++++++ traffxml/traff_decoder.hpp | 6 ++++++ 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index e78813c1c..2e5f38b77 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -70,7 +70,6 @@ target_link_libraries(${PROJECT_NAME} qt_common map gflags::gflags - openlr traffxml ) diff --git a/traffxml/CMakeLists.txt b/traffxml/CMakeLists.txt index 834f73918..fe4fae5f9 100644 --- a/traffxml/CMakeLists.txt +++ b/traffxml/CMakeLists.txt @@ -17,7 +17,6 @@ omim_add_library(${PROJECT_NAME} ${SRC}) target_link_libraries(${PROJECT_NAME} pugixml - openlr coding ) diff --git a/traffxml/traff_assessment_tool/CMakeLists.txt b/traffxml/traff_assessment_tool/CMakeLists.txt index 5ff1c6cc9..9b252206f 100644 --- a/traffxml/traff_assessment_tool/CMakeLists.txt +++ b/traffxml/traff_assessment_tool/CMakeLists.txt @@ -22,7 +22,6 @@ omim_add_executable(${PROJECT_NAME} ${SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES AUTOUIC ON AUTOMOC ON) target_link_libraries(${PROJECT_NAME} - openlr qt_common map gflags::gflags diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 1cd522e85..8838d1689 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -7,9 +7,12 @@ #include "indexer/feature.hpp" +// Only needed for OpenlrTraffDecoder, see below +#if 0 #include "openlr/decoded_path.hpp" #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" +#endif #include "routing/async_router.hpp" #include "routing/checkpoints.hpp" @@ -236,6 +239,8 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) } } +// Disabled for now, as the OpenLR-based decoder is slow, buggy and not well suited to the task. +#if 0 OpenLrV3TraffDecoder::OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) @@ -451,6 +456,7 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf decoded[paths[i].m_path[j].GetFeatureId().m_mwmId][traffic::TrafficInfo::RoadSegmentId(fid, segment, direction)] = traffic::SpeedGroup::Unknown; } } +#endif double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 98ed7ddf2..6189aea8b 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -5,8 +5,11 @@ #include "indexer/data_source.hpp" #include "indexer/mwm_set.hpp" +// Only needed for OpenlrTraffDecoder, see below +#if 0 #include "openlr/openlr_decoder.hpp" #include "openlr/openlr_model.hpp" +#endif #include "routing/index_router.hpp" #include "routing/regions_decl.hpp" @@ -85,6 +88,8 @@ class TraffDecoder private: }; +// Disabled for now, as the OpenLR-based decoder is slow, buggy and not well suited to the task. +#if 0 /** * @brief A `TraffDecoder` implementation which internally uses the version 3 OpenLR decoder. */ @@ -170,6 +175,7 @@ class OpenLrV3TraffDecoder : public TraffDecoder */ openlr::OpenLRDecoder m_openLrDecoder; }; +#endif /** * @brief A `TraffDecoder` implementation which internally uses the routing engine. From 06f63dcb9af72da41b96ea7a9f94072c7e71fe2d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 25 Jul 2025 17:27:28 +0300 Subject: [PATCH 137/252] [traffxml] Fix compiler warning Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index be5b146af..96309127d 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -870,7 +870,7 @@ bool ColoringFromXml(pugi::xml_node const & node, DataSource const & dataSource, return false; } - uint64_t version = 0; + int64_t version = 0; if (!IntegerFromXml(node.attribute("version"), version)) { LOG(LWARNING, ("Can’t get version for country", countryName, "(skipping)")); From 958be3dee6d7bcc10e9b776e8c78643193842578 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 26 Jul 2025 16:58:40 +0300 Subject: [PATCH 138/252] [traffxml] Fix segfault when deleting the last vector element v.erase(v.end()) is incorrect and will crash on clang but works on gcc Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 8838d1689..2db984d31 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -779,7 +779,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) rsegments.erase(rsegments.begin()); while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) - rsegments.erase(rsegments.end()); + rsegments.pop_back(); if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_to) // from–at in forward direction, add last segment From 871cd73592cf387725af985e77ce0f931f3a3c00 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 27 Jul 2025 01:24:34 +0300 Subject: [PATCH 139/252] [traffic] Make traffic initialization work with LoadMapsAsync() Signed-off-by: mvglasow --- map/framework.cpp | 27 +++++++++++++++++---------- map/framework.hpp | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 3f87455de..fbb8fc2e3 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -394,16 +394,6 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps) if (loadMaps) LoadMapsSync(); - - m_trafficManager.SetEnabled(LoadTrafficEnabled()); - if (!params.m_trafficTestMode && LoadTrafficHttpEnabled()) - // TODO handle invalid URLs - traffxml::HttpTraffSource::Create(m_trafficManager, LoadTrafficHttpUrl()); - - /* - * MockTraffSource for debugging purposes. - */ - //traffxml::MockTraffSource::Create(m_trafficManager); } Framework::~Framework() @@ -423,6 +413,19 @@ Framework::~Framework() m_featuresFetcher.SetOnMapDeregisteredCallback(nullptr); } +void Framework::InitializeTraffic() +{ + m_trafficManager.SetEnabled(LoadTrafficEnabled()); + if (!m_trafficManager.IsTestMode() && LoadTrafficHttpEnabled()) + // TODO handle invalid URLs + traffxml::HttpTraffSource::Create(m_trafficManager, LoadTrafficHttpUrl()); + + /* + * MockTraffSource for debugging purposes. + */ + //traffxml::MockTraffSource::Create(m_trafficManager); +} + void Framework::ShowNode(storage::CountryId const & countryId) { StopLocationFollow(); @@ -529,6 +532,8 @@ void Framework::LoadMapsSync() LOG(LDEBUG, ("Editor initialized")); GetStorage().RestoreDownloadQueue(); + + InitializeTraffic(); } // Small copy-paste with LoadMapsSync, but I don't have a better solution. @@ -551,6 +556,8 @@ void Framework::LoadMapsAsync(std::function && callback) GetStorage().RestoreDownloadQueue(); + InitializeTraffic(); + callback(); }); }).detach(); diff --git a/map/framework.hpp b/map/framework.hpp index c70662dee..6c2e52bfc 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -230,7 +230,34 @@ class Framework : public PositionProvider, /// \note It works for group and leaf node. bool HasUnsavedEdits(storage::CountryId const & countryId); + /** + * @brief Loads maps synchronously. + * + * Maps are loaded on the calling thread. + * + * This function also performs certain initialization operations which depend on map data being + * available, such as search, traffic and the download queue. + * + * @note This function is not suitable for use on platforms which enforce restrictions on + * time-consuming or potentially blocking operations on the UI thread (as Android does). On such + * platforms, `LoadMapsAsync()` should be used instead. + */ void LoadMapsSync(); + + /** + * @brief Loads maps asynchronously. + * + * Maps are loaded on a new thread. Some operations are executed as part of a separate task which + * is posted to the GUI thread. + * + * This function also performs certain initialization operations which depend on map data being + * available, such as search, traffic and the download queue. + * + * After finishing initialization, the caller-supplied callback function is called. This function + * also runs on the GUI thread and should therefore not perform any time-consuming operations. + * + * @param callback A callback function to run at the end of initialization. + */ void LoadMapsAsync(std::function && callback); /// Registers all local map files in internal indexes. @@ -389,6 +416,16 @@ class Framework : public PositionProvider, private: std::vector GetSelectedFeatureTriangles() const; + /** + * @brief Initializes the traffic manager. + * + * This enables the traffic manager if defined in settings. If the traffic manager is not in test + * mode, all cunfigured sources are also added here. + * + * Maps must be loaded prior to calling this method. + */ + void InitializeTraffic(); + public: /// @name GPS location updates routine. void OnLocationError(location::TLocationError error); From edc15ac982a68e74ee3d248a7458d3898aa7a539 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 27 Jul 2025 16:26:23 +0300 Subject: [PATCH 140/252] [traffic][android] Make HttpTraffSource configurable in Preferences Signed-off-by: mvglasow --- .../main/cpp/app/organicmaps/util/Config.cpp | 29 ++++++++++++ .../app/organicmaps/maplayer/LayersUtils.java | 1 + .../settings/SettingsPrefsFragment.java | 44 +++++++++++++++++++ .../java/app/organicmaps/util/Config.java | 24 ++++++++++ .../src/main/res/values/donottranslate.xml | 2 + android/app/src/main/res/values/strings.xml | 8 ++++ android/app/src/main/res/xml/prefs_main.xml | 12 +++++ 7 files changed, 120 insertions(+) diff --git a/android/app/src/main/cpp/app/organicmaps/util/Config.cpp b/android/app/src/main/cpp/app/organicmaps/util/Config.cpp index 3794f7434..c934152b6 100644 --- a/android/app/src/main/cpp/app/organicmaps/util/Config.cpp +++ b/android/app/src/main/cpp/app/organicmaps/util/Config.cpp @@ -123,4 +123,33 @@ extern "C" frm()->SaveTransliteration(value); frm()->AllowTransliteration(value); } + + JNIEXPORT jboolean JNICALL + Java_app_organicmaps_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz) + { + return frm()->LoadTrafficHttpEnabled(); + } + + JNIEXPORT void JNICALL + Java_app_organicmaps_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz, + jboolean value) + { + frm()->SaveTrafficHttpEnabled(value); + frm()->SetTrafficHttpEnabled(value); + } + + JNIEXPORT jstring JNICALL + Java_app_organicmaps_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz) + { + std::string value = frm()->LoadTrafficHttpUrl(); + return jni::ToJavaString(env, value); + } + + JNIEXPORT void JNICALL + Java_app_organicmaps_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz, + jstring value) + { + frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value)); + frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value)); + } } // extern "C" diff --git a/android/app/src/main/java/app/organicmaps/maplayer/LayersUtils.java b/android/app/src/main/java/app/organicmaps/maplayer/LayersUtils.java index ad022fd86..21aca4c7f 100644 --- a/android/app/src/main/java/app/organicmaps/maplayer/LayersUtils.java +++ b/android/app/src/main/java/app/organicmaps/maplayer/LayersUtils.java @@ -11,6 +11,7 @@ public static List getAvailableLayers() availableLayers.add(Mode.OUTDOORS); availableLayers.add(Mode.ISOLINES); availableLayers.add(Mode.SUBWAY); + availableLayers.add(Mode.TRAFFIC); return availableLayers; } } diff --git a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java index 5286c26d1..edb1a5eff 100644 --- a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java @@ -9,6 +9,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -57,6 +58,8 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) initAutoDownloadPrefsCallbacks(); initLargeFontSizePrefsCallbacks(); initTransliterationPrefsCallbacks(); + initTrafficHttpEnabledPrefsCallbacks(); + initTrafficHttpUrlPrefsCallbacks(); init3dModePrefsCallbacks(); initPerspectivePrefsCallbacks(); initAutoZoomPrefsCallbacks(); @@ -84,6 +87,16 @@ private void updateMapLanguageCodeSummary() pref.setSummary(locale.getDisplayLanguage()); } + private void updateTrafficHttpUrlSummary() + { + final Preference pref = getPreference(getString(R.string.pref_traffic_http_url)); + String summary = Config.getTrafficHttpUrl(); + if (summary.length() == 0) + pref.setSummary(R.string.traffic_http_url_not_set); + else + pref.setSummary(summary); + } + private void updateRoutingSettingsPrefsSummary() { final Preference pref = getPreference(getString(R.string.prefs_routing)); @@ -111,6 +124,7 @@ public void onResume() updateVoiceInstructionsPrefsSummary(); updateRoutingSettingsPrefsSummary(); updateMapLanguageCodeSummary(); + updateTrafficHttpUrlSummary(); } @Override @@ -170,6 +184,36 @@ private void initTransliterationPrefsCallbacks() }); } + private void initTrafficHttpEnabledPrefsCallbacks() + { + final Preference pref = getPreference(getString(R.string.pref_traffic_http_enabled)); + + ((TwoStatePreference)pref).setChecked(Config.getTrafficHttpEnabled()); + pref.setOnPreferenceChangeListener((preference, newValue) -> { + final boolean oldVal = Config.getTrafficHttpEnabled(); + final boolean newVal = (Boolean) newValue; + if (oldVal != newVal) + Config.setTrafficHttpEnabled(newVal); + + return true; + }); + } + + private void initTrafficHttpUrlPrefsCallbacks() + { + final Preference pref = getPreference(getString(R.string.pref_traffic_http_url)); + + ((EditTextPreference)pref).setText(Config.getTrafficHttpUrl()); + pref.setOnPreferenceChangeListener((preference, newValue) -> { + final String oldVal = Config.getTrafficHttpUrl(); + final String newVal = (String) newValue; + if (!oldVal.equals(newVal)) + Config.setTrafficHttpUrl(newVal); + + return true; + }); + } + private void initUseMobileDataPrefsCallbacks() { final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data)); diff --git a/android/app/src/main/java/app/organicmaps/util/Config.java b/android/app/src/main/java/app/organicmaps/util/Config.java index 2f9cdbf88..908ecc7e8 100644 --- a/android/app/src/main/java/app/organicmaps/util/Config.java +++ b/android/app/src/main/java/app/organicmaps/util/Config.java @@ -354,6 +354,26 @@ public static void setTransliteration(boolean value) nativeSetTransliteration(value); } + public static boolean getTrafficHttpEnabled() + { + return nativeGetTrafficHttpEnabled(); + } + + public static void setTrafficHttpEnabled(boolean value) + { + nativeSetTrafficHttpEnabled(value); + } + + public static String getTrafficHttpUrl() + { + return nativeGetTrafficHttpUrl(); + } + + public static void setTrafficHttpUrl(String value) + { + nativeSetTrafficHttpUrl(value); + } + public static boolean isNY() { return getBool("NY"); @@ -507,4 +527,8 @@ public static void setAnnounceStreets(boolean enabled) private static native void nativeSetLargeFontsSize(boolean value); private static native boolean nativeGetTransliteration(); private static native void nativeSetTransliteration(boolean value); + private static native boolean nativeGetTrafficHttpEnabled(); + private static native void nativeSetTrafficHttpEnabled(boolean value); + private static native String nativeGetTrafficHttpUrl(); + private static native void nativeSetTrafficHttpUrl(String value); } diff --git a/android/app/src/main/res/values/donottranslate.xml b/android/app/src/main/res/values/donottranslate.xml index 976c1ecd7..ca0b7d69d 100644 --- a/android/app/src/main/res/values/donottranslate.xml +++ b/android/app/src/main/res/values/donottranslate.xml @@ -36,6 +36,8 @@ GeneralSettings Navigation Information + TrafficHttpEnabled + TrafficHttpUrl Transliteration PowerManagment KeepScreenOn diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 36eb21197..b4da42ecc 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -833,6 +833,14 @@ When enabled, the app will work on the lockscreen even when the device is locked. Map language + + Enable live traffic data + + When enabled, the app will periodically retrieve traffic information from the configured URL. + + Traffic service URL + + Not set Map data from OpenStreetMap diff --git a/android/app/src/main/res/xml/prefs_main.xml b/android/app/src/main/res/xml/prefs_main.xml index 3ce113a9f..507e2f078 100644 --- a/android/app/src/main/res/xml/prefs_main.xml +++ b/android/app/src/main/res/xml/prefs_main.xml @@ -106,6 +106,18 @@ app:singleLineTitle="false" android:persistent="false" android:order="18"/> + + Date: Sun, 27 Jul 2025 17:15:16 +0300 Subject: [PATCH 141/252] [qt] Keep traffic URL editable even when source is disabled Signed-off-by: mvglasow --- qt/preferences_dialog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qt/preferences_dialog.cpp b/qt/preferences_dialog.cpp index 6836a9dea..4e64467bf 100644 --- a/qt/preferences_dialog.cpp +++ b/qt/preferences_dialog.cpp @@ -203,8 +203,12 @@ namespace qt bool const enable = i > 0; framework.SaveTrafficHttpEnabled(enable); framework.SetTrafficHttpEnabled(enable); - trafficHttpUrlLabel->setEnabled(enable); - trafficHttpUrlLineEdit->setEnabled(enable); + /* + * Keep URL editable even when the source is disabled. Since changes are applied immediately + * (no Apply button), we want to be able to set the URL before enabling the source. + */ + //trafficHttpUrlLabel->setEnabled(enable); + //trafficHttpUrlLineEdit->setEnabled(enable); }); } From f53c794fddc8af9274af2807fbd15d7aab8c57a3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 28 Jul 2025 01:00:43 +0300 Subject: [PATCH 142/252] [traffic] Add missing qualifier Signed-off-by: mvglasow --- map/traffic_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 152320bec..566da6b74 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -134,7 +134,7 @@ void TrafficManager::SetEnabled(bool enabled) m_countryParentNameGetterFn, m_messageCache); if (!m_storage && !IsTestMode()) { - m_storage = make_unique(kTrafficXMLFileName); + m_storage = std::make_unique(kTrafficXMLFileName); notifyUpdate = RestoreCache(); m_lastStorageUpdate = steady_clock::now(); } From 0106dc3fe5874f1455f26d953724887bd15d17ac Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Jul 2025 20:39:05 +0300 Subject: [PATCH 143/252] [traffic] Remove unused constants Signed-off-by: mvglasow --- defines.hpp | 2 -- private.h | 1 - 2 files changed, 3 deletions(-) diff --git a/defines.hpp b/defines.hpp index 081f461a8..2001d9b3f 100644 --- a/defines.hpp +++ b/defines.hpp @@ -101,8 +101,6 @@ #define CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways" #define TEMP_ADDR_EXTENSION ".tempaddr" -#define TRAFFIC_FILE_EXTENSION ".traffic" - #define SKIPPED_ELEMENTS_FILE "skipped_elements.json" #define MAPCSS_MAPPING_FILE "mapcss-mapping.csv" diff --git a/private.h b/private.h index 93deb2a98..3d27cbd1f 100644 --- a/private.h +++ b/private.h @@ -8,6 +8,5 @@ #define METASERVER_URL "https://cdn-us-1.comaps.app/servers" #define DEFAULT_URLS_JSON "[ \"https://cdn-us-2.comaps.tech/\", \"https://comaps.firewall-gateway.de/\", \"https://cdn-us-1.comaps.app/\", \"https://cdn-fi-1.comaps.app/\", \"https://comaps-cdn.s3-website.cloud.ru/\" ]" #define DEFAULT_CONNECTION_CHECK_IP "151.101.195.52" // For now the IP of comaps.app (Fastly CDN) -#define TRAFFIC_DATA_BASE_URL "" #define USER_BINDING_PKCS12 "" #define USER_BINDING_PKCS12_PASSWORD "" From dde50bd0a10ee0ddd347ca0dc4d8d1b5d20d84c6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 29 Jul 2025 20:39:57 +0300 Subject: [PATCH 144/252] [traffic] Default URL Signed-off-by: mvglasow --- map/framework.cpp | 3 ++- private.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/map/framework.cpp b/map/framework.cpp index 04dd50d68..dd72d7694 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -75,6 +75,7 @@ #include "std/target_os.hpp" #include "defines.hpp" +#include "private.h" #include @@ -2628,7 +2629,7 @@ std::string Framework::LoadTrafficHttpUrl() { std::string url; if (!settings::Get(kTrafficHttpUrlKey, url)) - url = ""; + url = TRAFFIC_HTTP_URL_DEFAULT; return url; } diff --git a/private.h b/private.h index 3d27cbd1f..c94079ce0 100644 --- a/private.h +++ b/private.h @@ -8,5 +8,7 @@ #define METASERVER_URL "https://cdn-us-1.comaps.app/servers" #define DEFAULT_URLS_JSON "[ \"https://cdn-us-2.comaps.tech/\", \"https://comaps.firewall-gateway.de/\", \"https://cdn-us-1.comaps.app/\", \"https://cdn-fi-1.comaps.app/\", \"https://comaps-cdn.s3-website.cloud.ru/\" ]" #define DEFAULT_CONNECTION_CHECK_IP "151.101.195.52" // For now the IP of comaps.app (Fastly CDN) +// TODO temporary URL, replace with final one once we have a server +#define TRAFFIC_HTTP_URL_DEFAULT "http://traff:8080/subscription-manager" #define USER_BINDING_PKCS12 "" #define USER_BINDING_PKCS12_PASSWORD "" From 73a70d943e098effa2f74c300ff1db9fd3c13e7b Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Wed, 6 Aug 2025 16:54:03 +0200 Subject: [PATCH 145/252] [traffic][ios] Make HttpTraffSource configurable in Preferences Signed-off-by: Yannik Bloscheck --- iphone/Maps/Core/Settings/MWMSettings.h | 6 + iphone/Maps/Core/Settings/MWMSettings.mm | 34 +++ .../en-GB.lproj/Localizable.strings | 7 + .../en.lproj/Localizable.strings | 7 + iphone/Maps/Maps.xcodeproj/project.pbxproj | 4 + iphone/Maps/Model/Settings.swift | 22 ++ .../UI/Settings/SettingsNavigationView.swift | 38 +++ .../contents.xcworkspacedata | 3 + .../traffxml.xcodeproj/project.pbxproj | 266 ++++++++++++++++++ 9 files changed, 387 insertions(+) create mode 100644 xcode/traffxml/traffxml.xcodeproj/project.pbxproj diff --git a/iphone/Maps/Core/Settings/MWMSettings.h b/iphone/Maps/Core/Settings/MWMSettings.h index 49978d655..9a69de341 100644 --- a/iphone/Maps/Core/Settings/MWMSettings.h +++ b/iphone/Maps/Core/Settings/MWMSettings.h @@ -1,6 +1,12 @@ NS_SWIFT_NAME(SettingsBridge) @interface MWMSettings : NSObject ++ (BOOL)liveTrafficEnabled; ++ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled; + ++ (NSURL *)liveTrafficUrl; ++ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl; + + (BOOL)buildings3dViewEnabled; + (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled; diff --git a/iphone/Maps/Core/Settings/MWMSettings.mm b/iphone/Maps/Core/Settings/MWMSettings.mm index 297cb7a8a..7e18deda6 100644 --- a/iphone/Maps/Core/Settings/MWMSettings.mm +++ b/iphone/Maps/Core/Settings/MWMSettings.mm @@ -26,6 +26,40 @@ @implementation MWMSettings ++ (BOOL)liveTrafficEnabled; +{ + return GetFramework().LoadTrafficHttpEnabled(); +} + ++ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled; +{ + auto &f = GetFramework(); + f.SaveTrafficHttpEnabled(liveTrafficEnabled); + f.SetTrafficHttpEnabled(liveTrafficEnabled); +} + ++ (NSURL *)liveTrafficUrl; +{ + NSString * link = @(GetFramework().LoadTrafficHttpUrl().c_str()); + if ([link length] == 0) { + return nil; + } else { + return [NSURL URLWithString:link]; + } +} + ++ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl; +{ + auto &f = GetFramework(); + if (liveTrafficUrl == nil) { + f.SaveTrafficHttpUrl(@"".UTF8String); + f.SetTrafficHttpUrl(@"".UTF8String); + } else { + f.SaveTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String); + f.SetTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String); + } +} + + (BOOL)buildings3dViewEnabled; { bool _ = true, on = true; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index 91bb86e4e..11e15e040 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -696,6 +696,13 @@ "editor_place_doesnt_exist" = "Place does not exist"; "text_more_button" = "…more"; +/* Live traffic data */ +"traffic_http" = "Live Traffic"; +"traffic_http_enabled" = "Enable live traffic data"; +"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL."; +"traffic_http_url" = "Traffic service URL"; +"traffic_http_url_not_set" = "Not set"; + /* Phone number error message */ "error_enter_correct_phone" = "Enter a valid phone number"; "error_enter_correct_web" = "Enter a valid web address"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index 36cf58aa1..545807d65 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -714,6 +714,13 @@ "editor_place_doesnt_exist" = "Place does not exist"; "text_more_button" = "…more"; +/* Live traffic data */ +"traffic_http" = "Live Traffic"; +"traffic_http_enabled" = "Enable live traffic data"; +"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL."; +"traffic_http_url" = "Traffic service URL"; +"traffic_http_url_not_set" = "Not set"; + /* Phone number error message */ "error_enter_correct_phone" = "Enter a valid phone number"; "error_enter_correct_web" = "Enter a valid web address"; diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 1698a9be3..078df4c8b 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; }; 272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; }; 272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.swift */; }; + 2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 274720592E439FBA00C516DF /* libtraffxml.a */; }; 2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; }; 27697F742E25177600FBD913 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27697F732E25177300FBD913 /* AboutView.swift */; }; 27697F7F2E254A5500FBD913 /* CopyrightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27697F7C2E254A5000FBD913 /* CopyrightView.swift */; }; @@ -774,6 +775,7 @@ 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = ""; }; 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; + 274720592E439FBA00C516DF /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; }; 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = ""; }; 27697F732E25177300FBD913 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 27697F7C2E254A5000FBD913 /* CopyrightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyrightView.swift; sourceTree = ""; }; @@ -1813,6 +1815,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */, FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */, FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */, FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */, @@ -2046,6 +2049,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 274720592E439FBA00C516DF /* libtraffxml.a */, FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */, FA456C3B26BDC6AD00B83C20 /* Chart.framework */, FA853BF226BC5DE50026D455 /* libshaders.a */, diff --git a/iphone/Maps/Model/Settings.swift b/iphone/Maps/Model/Settings.swift index caa36e0b7..b1c4e7239 100644 --- a/iphone/Maps/Model/Settings.swift +++ b/iphone/Maps/Model/Settings.swift @@ -373,6 +373,28 @@ import Combine } + /// If live traffic data should be used + @objc static var hasLiveTraffic: Bool { + get { + return SettingsBridge.liveTrafficEnabled() + } + set { + SettingsBridge.setLiveTrafficEnabled(newValue) + } + } + + + /// The url of the live traffic data server + @objc static var liveTrafficServerUrl: URL? { + get { + return SettingsBridge.liveTrafficUrl() + } + set { + SettingsBridge.setLiveTrafficUrl(newValue) + } + } + + // MARK: Methods diff --git a/iphone/Maps/UI/Settings/SettingsNavigationView.swift b/iphone/Maps/UI/Settings/SettingsNavigationView.swift index 01096b02c..abb308849 100644 --- a/iphone/Maps/UI/Settings/SettingsNavigationView.swift +++ b/iphone/Maps/UI/Settings/SettingsNavigationView.swift @@ -44,6 +44,14 @@ struct SettingsNavigationView: View { @State var shouldAvoidMotorwaysWhileRouting: Bool = false + /// If live traffic data should be used + @State var hasLiveTraffic: Bool = false + + + /// The url of the live traffic data server + @State var liveTrafficServerUrlString: String = "" + + /// The actual view var body: some View { List { @@ -123,6 +131,24 @@ struct SettingsNavigationView: View { } header: { Text("driving_options_title") } + + Section { + Toggle(isOn: $hasLiveTraffic) { + VStack(alignment: .leading) { + Text("traffic_http_enabled") + + Text("traffic_http_enabled_description") + .font(.footnote) + .foregroundStyle(.secondary) + } + } + .tint(.accent) + + TextField("traffic_http_url", text: $liveTrafficServerUrlString, prompt: Text("traffic_http_url_not_set")) + .tint(.accent) + } header: { + Text("traffic_http") + } } .accentColor(.accent) .navigationViewStyle(StackNavigationViewStyle()) @@ -138,6 +164,8 @@ struct SettingsNavigationView: View { shouldAvoidUnpavedRoadsWhileRouting = Settings.shouldAvoidUnpavedRoadsWhileRouting shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting + hasLiveTraffic = Settings.hasLiveTraffic + liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? "" } .onChange(of: hasPerspectiveViewWhileRouting) { changedHasPerspectiveViewWhileRouting in Settings.hasPerspectiveViewWhileRouting = changedHasPerspectiveViewWhileRouting @@ -174,5 +202,15 @@ struct SettingsNavigationView: View { .onChange(of: shouldAvoidMotorwaysWhileRouting) { changedShouldAvoidMotorwaysWhileRouting in Settings.shouldAvoidMotorwaysWhileRouting = changedShouldAvoidMotorwaysWhileRouting } + .onChange(of: hasLiveTraffic) { changedHasLiveTraffic in + Settings.hasLiveTraffic = changedHasLiveTraffic + } + .onChange(of: liveTrafficServerUrlString) { changedLiveTrafficServerUrlString in + if !changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, let changedLiveTrafficServerUrl = URL(string: changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines)) { + Settings.liveTrafficServerUrl = changedLiveTrafficServerUrl + } else { + Settings.liveTrafficServerUrl = nil + } + } } } diff --git a/xcode/CoMaps.xcworkspace/contents.xcworkspacedata b/xcode/CoMaps.xcworkspace/contents.xcworkspacedata index 490f821db..82eb96a82 100644 --- a/xcode/CoMaps.xcworkspace/contents.xcworkspacedata +++ b/xcode/CoMaps.xcworkspace/contents.xcworkspacedata @@ -157,6 +157,9 @@ + + diff --git a/xcode/traffxml/traffxml.xcodeproj/project.pbxproj b/xcode/traffxml/traffxml.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3e0c759bb --- /dev/null +++ b/xcode/traffxml/traffxml.xcodeproj/project.pbxproj @@ -0,0 +1,266 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 2747204F2E439E3000C516DF /* traff_decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2747203F2E439DDE00C516DF /* traff_decoder.cpp */; }; + 274720502E439E3000C516DF /* traff_source.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274720452E439DDE00C516DF /* traff_source.cpp */; }; + 274720512E439E3000C516DF /* traff_model.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274720412E439DDE00C516DF /* traff_model.cpp */; }; + 274720522E439E3000C516DF /* traff_model_xml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274720432E439DDE00C516DF /* traff_model_xml.cpp */; }; + 274720532E439E3000C516DF /* traff_storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274720472E439DDE00C516DF /* traff_storage.cpp */; }; + 274720542E439E3000C516DF /* traff_decoder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2747203E2E439DDE00C516DF /* traff_decoder.hpp */; }; + 274720552E439E3000C516DF /* traff_model.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 274720402E439DDE00C516DF /* traff_model.hpp */; }; + 274720562E439E3000C516DF /* traff_storage.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 274720462E439DDE00C516DF /* traff_storage.hpp */; }; + 274720572E439E3000C516DF /* traff_source.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 274720442E439DDE00C516DF /* traff_source.hpp */; }; + 274720582E439E3000C516DF /* traff_model_xml.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 274720422E439DDE00C516DF /* traff_model_xml.hpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2747203E2E439DDE00C516DF /* traff_decoder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traff_decoder.hpp; sourceTree = ""; }; + 2747203F2E439DDE00C516DF /* traff_decoder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = traff_decoder.cpp; sourceTree = ""; }; + 274720402E439DDE00C516DF /* traff_model.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traff_model.hpp; sourceTree = ""; }; + 274720412E439DDE00C516DF /* traff_model.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = traff_model.cpp; sourceTree = ""; }; + 274720422E439DDE00C516DF /* traff_model_xml.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traff_model_xml.hpp; sourceTree = ""; }; + 274720432E439DDE00C516DF /* traff_model_xml.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = traff_model_xml.cpp; sourceTree = ""; }; + 274720442E439DDE00C516DF /* traff_source.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traff_source.hpp; sourceTree = ""; }; + 274720452E439DDE00C516DF /* traff_source.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = traff_source.cpp; sourceTree = ""; }; + 274720462E439DDE00C516DF /* traff_storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = traff_storage.hpp; sourceTree = ""; }; + 274720472E439DDE00C516DF /* traff_storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = traff_storage.cpp; sourceTree = ""; }; + 67BECB4A1DDA43AF00FC4E99 /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 67BECB581DDA43FB00FC4E99 /* common-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../common-debug.xcconfig"; sourceTree = ""; }; + 67BECB591DDA440100FC4E99 /* common-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "common-release.xcconfig"; path = "../common-release.xcconfig"; sourceTree = ""; }; + FAFD413F26BBE3FC00D1DE1B /* libbase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libbase.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414126BBE3FC00D1DE1B /* libcoding.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libcoding.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414326BBE88000D1DE1B /* libplatform.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libplatform.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414526BBE88700D1DE1B /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + FAFD414926BBE98B00D1DE1B /* libindexer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libindexer.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414B26BBE9BB00D1DE1B /* libicu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libicu.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414D26BBE9C300D1DE1B /* libgeometry.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libgeometry.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD414F26BBE9C800D1DE1B /* libprotobuf.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libprotobuf.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD415126BBE9E000D1DE1B /* librouting.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = librouting.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD415326BBE9E800D1DE1B /* libsuccinct.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libsuccinct.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FAFD415526BBEA5600D1DE1B /* librouting_common.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = librouting_common.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 67BECB471DDA43AF00FC4E99 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 67BECB411DDA43AF00FC4E99 = { + isa = PBXGroup; + children = ( + 67BECB581DDA43FB00FC4E99 /* common-debug.xcconfig */, + 67BECB591DDA440100FC4E99 /* common-release.xcconfig */, + 67BECB5A1DDA449C00FC4E99 /* traffxml */, + 67BECB4B1DDA43AF00FC4E99 /* Products */, + 67BECB831DDA474400FC4E99 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 67BECB4B1DDA43AF00FC4E99 /* Products */ = { + isa = PBXGroup; + children = ( + 67BECB4A1DDA43AF00FC4E99 /* libtraffxml.a */, + ); + name = Products; + sourceTree = ""; + }; + 67BECB5A1DDA449C00FC4E99 /* traffxml */ = { + isa = PBXGroup; + children = ( + 2747203E2E439DDE00C516DF /* traff_decoder.hpp */, + 2747203F2E439DDE00C516DF /* traff_decoder.cpp */, + 274720402E439DDE00C516DF /* traff_model.hpp */, + 274720412E439DDE00C516DF /* traff_model.cpp */, + 274720422E439DDE00C516DF /* traff_model_xml.hpp */, + 274720432E439DDE00C516DF /* traff_model_xml.cpp */, + 274720442E439DDE00C516DF /* traff_source.hpp */, + 274720452E439DDE00C516DF /* traff_source.cpp */, + 274720462E439DDE00C516DF /* traff_storage.hpp */, + 274720472E439DDE00C516DF /* traff_storage.cpp */, + ); + name = traffxml; + path = ../../traffxml; + sourceTree = ""; + }; + 67BECB831DDA474400FC4E99 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FAFD415526BBEA5600D1DE1B /* librouting_common.a */, + FAFD415326BBE9E800D1DE1B /* libsuccinct.a */, + FAFD415126BBE9E000D1DE1B /* librouting.a */, + FAFD414F26BBE9C800D1DE1B /* libprotobuf.a */, + FAFD414D26BBE9C300D1DE1B /* libgeometry.a */, + FAFD414B26BBE9BB00D1DE1B /* libicu.a */, + FAFD414926BBE98B00D1DE1B /* libindexer.a */, + FAFD414526BBE88700D1DE1B /* libz.tbd */, + FAFD414326BBE88000D1DE1B /* libplatform.a */, + FAFD413F26BBE3FC00D1DE1B /* libbase.a */, + FAFD414126BBE3FC00D1DE1B /* libcoding.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 67BECB481DDA43AF00FC4E99 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 274720542E439E3000C516DF /* traff_decoder.hpp in Headers */, + 274720552E439E3000C516DF /* traff_model.hpp in Headers */, + 274720562E439E3000C516DF /* traff_storage.hpp in Headers */, + 274720572E439E3000C516DF /* traff_source.hpp in Headers */, + 274720582E439E3000C516DF /* traff_model_xml.hpp in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 67BECB491DDA43AF00FC4E99 /* traffxml */ = { + isa = PBXNativeTarget; + buildConfigurationList = 67BECB551DDA43B000FC4E99 /* Build configuration list for PBXNativeTarget "traffxml" */; + buildPhases = ( + 67BECB461DDA43AF00FC4E99 /* Sources */, + 67BECB471DDA43AF00FC4E99 /* Frameworks */, + 67BECB481DDA43AF00FC4E99 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = traffxml; + productName = traffxml; + productReference = 67BECB4A1DDA43AF00FC4E99 /* libtraffxml.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 67BECB421DDA43AF00FC4E99 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1640; + TargetAttributes = { + 67BECB491DDA43AF00FC4E99 = { + CreatedOnToolsVersion = 8.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 67BECB451DDA43AF00FC4E99 /* Build configuration list for PBXProject "traffxml" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 67BECB411DDA43AF00FC4E99; + productRefGroup = 67BECB4B1DDA43AF00FC4E99 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 67BECB491DDA43AF00FC4E99 /* traffxml */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 67BECB461DDA43AF00FC4E99 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2747204F2E439E3000C516DF /* traff_decoder.cpp in Sources */, + 274720502E439E3000C516DF /* traff_source.cpp in Sources */, + 274720512E439E3000C516DF /* traff_model.cpp in Sources */, + 274720522E439E3000C516DF /* traff_model_xml.cpp in Sources */, + 274720532E439E3000C516DF /* traff_storage.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 67BECB531DDA43B000FC4E99 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67BECB581DDA43FB00FC4E99 /* common-debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 67BECB541DDA43B000FC4E99 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67BECB591DDA440100FC4E99 /* common-release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 67BECB561DDA43B000FC4E99 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(OMIM_ROOT)/3party/pugixml/pugixml/src", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 67BECB571DDA43B000FC4E99 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(OMIM_ROOT)/3party/pugixml/pugixml/src", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 67BECB451DDA43AF00FC4E99 /* Build configuration list for PBXProject "traffxml" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67BECB531DDA43B000FC4E99 /* Debug */, + 67BECB541DDA43B000FC4E99 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 67BECB551DDA43B000FC4E99 /* Build configuration list for PBXNativeTarget "traffxml" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67BECB561DDA43B000FC4E99 /* Debug */, + 67BECB571DDA43B000FC4E99 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 67BECB421DDA43AF00FC4E99 /* Project object */; +} From bd555afe6182246bef4306d522b1f959091bd080 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 10 Aug 2025 18:57:52 +0300 Subject: [PATCH 146/252] [traffxml] Remove unused HttpTraffSource::ThreadRoutine() Signed-off-by: mvglasow --- traffxml/traff_source.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index f38ddfef8..cee5cb4db 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -528,15 +528,6 @@ class HttpTraffSource : public TraffSource */ void OnPollResponse(TraffResponse & response); - /** - * @brief Event loop for the worker thread. - * - * This method runs an event loop, which blocks until woken up. When woken up, it processes the - * request (subscribe, change subscription, poll or unsubscribe) and its result, then blocks - * again until woken up for the next request. - */ - void ThreadRoutine(); - /** * @brief The update interval, 5 minutes. */ From daa147a7219fc36d5290cceddce88dd819f2144f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 10 Aug 2025 18:58:20 +0300 Subject: [PATCH 147/252] [traffxml] Provide virtual destructor for TraffSource Signed-off-by: mvglasow --- traffxml/traff_source.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index cee5cb4db..c927ed07d 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -168,6 +168,8 @@ class TraffSource Unknown }; + virtual ~TraffSource() {} + /** * @brief Ensures we have a subscription covering the MWMs indicated. * From fe737602d83a202fa87f4691617a4d9251d7041a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 10 Aug 2025 18:58:34 +0300 Subject: [PATCH 148/252] [traffxml] Documentation Signed-off-by: mvglasow --- traffxml/traff_source.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index c927ed07d..27aa836d3 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -440,6 +440,7 @@ class HttpTraffSource : public TraffSource * @brief Creates a new `HttpTraffSource` instance and registers it with the traffic manager. * * @param manager The traffic manager to register the new instance with + * @param url The URL for the TraFF service API. */ static void Create(TraffSourceManager & manager, std::string const & url); From df7d507e1b8708a497af89f6813256c82f4483a3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 12 Aug 2025 00:29:04 +0300 Subject: [PATCH 149/252] WIP: [traffic][android] Implement Android TraFF source Signed-off-by: mvglasow --- android/app/src/main/cpp/CMakeLists.txt | 3 + .../traffxml/AndroidTraffSource.cpp | 115 +++++++++ .../traffxml/AndroidTraffSource.hpp | 199 +++++++++++++++ .../app/organicmaps/traffxml/SourceImpl.cpp | 34 +++ .../organicmaps/traffxml/AndroidConsumer.java | 111 +++++++++ .../traffxml/AndroidTransport.java | 222 +++++++++++++++++ .../app/organicmaps/traffxml/SourceImpl.java | 70 ++++++ .../organicmaps/traffxml/SourceImplV0_7.java | 127 ++++++++++ .../organicmaps/traffxml/SourceImplV0_8.java | 232 ++++++++++++++++++ .../app/organicmaps/traffxml/Version.java | 18 ++ 10 files changed, 1131 insertions(+) create mode 100644 android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.cpp create mode 100644 android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.hpp create mode 100644 android/app/src/main/cpp/app/organicmaps/traffxml/SourceImpl.cpp create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/AndroidConsumer.java create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/AndroidTransport.java create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/SourceImpl.java create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_7.java create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java create mode 100644 android/app/src/main/java/app/organicmaps/traffxml/Version.java diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 82d2a7d27..acef43d39 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -17,6 +17,7 @@ set(SRC app/organicmaps/opengl/gl3stub.h app/organicmaps/platform/GuiThread.hpp app/organicmaps/platform/AndroidPlatform.hpp + app/organicmaps/traffxml/AndroidTraffSource.hpp app/organicmaps/util/Distance.hpp app/organicmaps/util/FeatureIdBuilder.hpp app/organicmaps/vulkan/android_vulkan_context_factory.hpp @@ -71,6 +72,8 @@ set(SRC app/organicmaps/platform/PThreadImpl.cpp app/organicmaps/platform/SecureStorage.cpp app/organicmaps/platform/SocketImpl.cpp + app/organicmaps/traffxml/AndroidTraffSource.cpp + app/organicmaps/traffxml/SourceImpl.cpp app/organicmaps/util/Config.cpp app/organicmaps/util/GeoUtils.cpp app/organicmaps/util/HttpClient.cpp diff --git a/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.cpp b/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.cpp new file mode 100644 index 000000000..ac2ca8109 --- /dev/null +++ b/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.cpp @@ -0,0 +1,115 @@ +#include "AndroidTraffSource.hpp" + +#include "app/organicmaps/core/jni_helper.hpp" + +namespace traffxml { +void AndroidTraffSourceV0_7::Create(TraffSourceManager & manager) +{ + std::unique_ptr source = std::unique_ptr(new AndroidTraffSourceV0_7(manager)); + manager.RegisterSource(std::move(source)); +} + +AndroidTraffSourceV0_7::AndroidTraffSourceV0_7(TraffSourceManager & manager) + : TraffSource(manager) +{ + JNIEnv * env = jni::GetEnv(); + + static jclass const implClass = jni::GetGlobalClassRef(env, "app/organicmaps/traffxml/SourceImplV0_7"); + + static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;J)V"); + + jlong nativeManager = reinterpret_cast(&manager); + + jobject implObject = env->NewObject( + implClass, implConstructor, android::Platform::Instance().GetContext(), nativeManager); + + m_implObject = env->NewGlobalRef(implObject); + + m_subscribeImpl = jni::GetMethodID(env, m_implObject, "subscribe", "(Ljava/lang/String;)V"); + m_unsubscribeImpl = jni::GetMethodID(env, m_implObject, "unsubscribe", "()V"); +} + +AndroidTraffSourceV0_7::~AndroidTraffSourceV0_7() +{ + jni::GetEnv()->DeleteGlobalRef(m_implObject); +} + +void AndroidTraffSourceV0_7::Close() +{ + Unsubscribe(); +} + +void AndroidTraffSourceV0_7::Subscribe(std::set & mwms) +{ + jni::GetEnv()->CallVoidMethod(m_implObject, m_subscribeImpl, nullptr); +} + +void AndroidTraffSourceV0_7::Unsubscribe() +{ + jni::GetEnv()->CallVoidMethod(m_implObject, m_unsubscribeImpl); +} + +void AndroidTraffSourceV0_8::Create(TraffSourceManager & manager, std::string const & packageId) +{ + std::unique_ptr source = std::unique_ptr(new AndroidTraffSourceV0_8(manager, packageId)); + manager.RegisterSource(std::move(source)); +} + +AndroidTraffSourceV0_8::AndroidTraffSourceV0_8(TraffSourceManager & manager, std::string const & packageId) + : TraffSource(manager) +{ + JNIEnv * env = jni::GetEnv(); + + static jclass const implClass = jni::GetGlobalClassRef(env, "app/organicmaps/traffxml/SourceImplV0_8"); + + static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;JLjava/lang/String;)V"); + + jlong nativeManager = reinterpret_cast(&manager); + + jobject implObject = env->NewObject( + implClass, implConstructor, android::Platform::Instance().GetContext(), nativeManager, jni::ToJavaString(env, packageId)); + + m_implObject = env->NewGlobalRef(implObject); + + m_subscribeImpl = jni::GetMethodID(env, m_implObject, "subscribe", "(Ljava/lang/String;)V"); + m_changeSubscriptionImpl = jni::GetMethodID(env, m_implObject, "changeSubscription", "(Ljava/lang/String;)V"); + m_unsubscribeImpl = jni::GetMethodID(env, m_implObject, "unsubscribe", "()V"); + + // TODO packageId (if we need that at all here) +} + +AndroidTraffSourceV0_8::~AndroidTraffSourceV0_8() +{ + jni::GetEnv()->DeleteGlobalRef(m_implObject); +} + +void AndroidTraffSourceV0_8::Close() +{ + Unsubscribe(); +} + +void AndroidTraffSourceV0_8::Subscribe(std::set & mwms) +{ + JNIEnv * env = jni::GetEnv(); + std::string data = "\n" + + GetMwmFilters(mwms) + + ""; + + env->CallVoidMethod(m_implObject, m_subscribeImpl, jni::ToJavaString(env, data)); +} + +void AndroidTraffSourceV0_8::ChangeSubscription(std::set & mwms) +{ + JNIEnv * env = jni::GetEnv(); + std::string data = "\n" + + GetMwmFilters(mwms) + + ""; + + env->CallVoidMethod(m_implObject, m_changeSubscriptionImpl, jni::ToJavaString(env, data)); +} + +void AndroidTraffSourceV0_8::Unsubscribe() +{ + jni::GetEnv()->CallVoidMethod(m_implObject, m_unsubscribeImpl); +} +} // namespace traffxml diff --git a/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.hpp b/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.hpp new file mode 100644 index 000000000..05e04c659 --- /dev/null +++ b/android/app/src/main/cpp/app/organicmaps/traffxml/AndroidTraffSource.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include "traffxml/traff_source.hpp" + +namespace traffxml +{ +/** + * @brief A TraFF source which relies on Android Binder for message delivery, using version 0.7 of the TraFF protocol. + * + * TraFF 0.7 does not support subscriptions. Messages are broadcast as the payload to a `FEED` intent. + */ +class AndroidTraffSourceV0_7 : public TraffSource +{ +public: + /** + * @brief Creates a new `AndroidTraffSourceV0_7` instance and registers it with the traffic manager. + * + * @param manager The traffic manager to register the new instance with + */ + static void Create(TraffSourceManager & manager); + + virtual ~AndroidTraffSourceV0_7() override; + + /** + * @brief Prepares the traffic source for unloading. + */ + // TODO do we need a close operation here? + // TODO move this to the parent class and override it here? + void Close(); + + /** + * @brief Subscribes to a traffic service. + * + * TraFF 0.7 does not support subscriptions. This implementation registers a broadcast receiver. + * + * @param mwms The MWMs for which data is needed (not used by this implementation). + */ + virtual void Subscribe(std::set & mwms) override; + + /** + * @brief Changes an existing traffic subscription. + * + * This implementation does nothing, as TraFF 0.7 does not support subscriptions. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void ChangeSubscription(std::set & mwms) override {}; + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + * + * TraFF 0.7 does not support subscriptions. This implementation unregisters the broadcast + * receiver which was registered by `Subscribe()`. + */ + virtual void Unsubscribe() override; + + /** + * @brief Whether this source should be polled. + * + * Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and + * poll the source only if the result is true. + * + * This implementation always returns false, as message delivery on Android uses `FEED` (push). + * + * @return true if the source should be polled, false if not. + */ + virtual bool IsPollNeeded() override { return false; }; + + /** + * @brief Polls the traffic service for updates. + * + * This implementation does nothing, as message delivery on Android uses `FEED` (push). + */ + virtual void Poll() override {}; + +protected: + /** + * @brief Constructs a new `AndroidTraffSourceV0_7`. + * @param manager The `TrafficSourceManager` instance to register the source with. + */ + AndroidTraffSourceV0_7(TraffSourceManager & manager); + +private: + // TODO “subscription” (i.e. broadcast receiver) state + + /** + * @brief The Java implementation class instance. + */ + jobject m_implObject; + + /** + * @brief The Java subscribe method. + */ + jmethodID m_subscribeImpl; + + /** + * @brief The Java unsubscribe method. + */ + jmethodID m_unsubscribeImpl; +}; + +/** + * @brief A TraFF source which relies on Android Binder for message delivery, using version 0.8 of the TraFF protocol. + * + * TraFF 0.8 supports subscriptions. Messages are announced through a `FEED` intent, whereupon the + * consumer can retrieve them from a content provider. + */ +class AndroidTraffSourceV0_8 : public TraffSource +{ +public: + /** + * @brief Creates a new `AndroidTraffSourceV0_8` instance and registers it with the traffic manager. + * + * @param manager The traffic manager to register the new instance with + * @param packageId The package ID of the app providing the TraFF source. + */ + static void Create(TraffSourceManager & manager, std::string const & packageId); + + virtual ~AndroidTraffSourceV0_8() override; + + /** + * @brief Prepares the traffic source for unloading. + * + * If there is still an active subscription, it unsubscribes, but without processing the result + * received from the service. Otherwise, teardown is a no-op. + */ + // TODO move this to the parent class and override it here? + void Close(); + + /** + * @brief Subscribes to a traffic service. + * + * @param mwms The MWMs for which data is needed. + */ + virtual void Subscribe(std::set & mwms) override; + + /** + * @brief Changes an existing traffic subscription. + * + * @param mwms The new set of MWMs for which data is needed. + */ + virtual void ChangeSubscription(std::set & mwms) override; + + /** + * @brief Unsubscribes from a traffic service we are subscribed to. + */ + virtual void Unsubscribe() override; + + /** + * @brief Whether this source should be polled. + * + * Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and + * poll the source only if the result is true. + * + * This implementation always returns false, as message delivery on Android uses `FEED` (push). + * + * @return true if the source should be polled, false if not. + */ + virtual bool IsPollNeeded() override { return false; }; + + /** + * @brief Polls the traffic service for updates. + * + * This implementation does nothing, as message delivery on Android uses `FEED` (push). + */ + virtual void Poll() override {}; + +protected: + /** + * @brief Constructs a new `AndroidTraffSourceV0_8`. + * @param manager The `TrafficSourceManager` instance to register the source with. + * @param packageId The package ID of the app providing the TraFF source. + */ + AndroidTraffSourceV0_8(TraffSourceManager & manager, std::string const & packageId); + +private: + // TODO subscription state + + /** + * @brief The Java implementation class instance. + */ + jobject m_implObject; + + /** + * @brief The Java subscribe method. + */ + jmethodID m_subscribeImpl; + + /** + * @brief The Java changeSubscription method. + */ + jmethodID m_changeSubscriptionImpl; + + /** + * @brief The Java unsubscribe method. + */ + jmethodID m_unsubscribeImpl; +}; +} // namespace traffxml diff --git a/android/app/src/main/cpp/app/organicmaps/traffxml/SourceImpl.cpp b/android/app/src/main/cpp/app/organicmaps/traffxml/SourceImpl.cpp new file mode 100644 index 000000000..45f89b2f5 --- /dev/null +++ b/android/app/src/main/cpp/app/organicmaps/traffxml/SourceImpl.cpp @@ -0,0 +1,34 @@ +// TODO which of the two do we need? (jni_helper includes jni) +//#include +#include "app/organicmaps/core/jni_helper.hpp" + +#include "traffxml/traff_source.hpp" +#include "traffxml/traff_model_xml.hpp" + +#include + +extern "C" +{ + JNIEXPORT void JNICALL + Java_app_organicmaps_traffxml_SourceImpl_onFeedReceivedImpl(JNIEnv * env, jclass thiz, jlong nativeManager, jstring feed) + { + std::string feedStd = jni::ToNativeString(env, feed); + pugi::xml_document document; + traffxml::TraffFeed parsedFeed; + + if (!document.load_string(feedStd.c_str())) + { + LOG(LWARNING, ("Feed is not a well-formed XML document")); + return; + } + + if (!traffxml::ParseTraff(document, std::nullopt, parsedFeed)) + { + LOG(LWARNING, ("Feed is not a valid TraFF feed")); + return; + } + + traffxml::TraffSourceManager & manager = *reinterpret_cast(nativeManager); + manager.ReceiveFeed(parsedFeed); + } +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/AndroidConsumer.java b/android/app/src/main/java/app/organicmaps/traffxml/AndroidConsumer.java new file mode 100644 index 000000000..1b8b19878 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/AndroidConsumer.java @@ -0,0 +1,111 @@ +/* + * Copyright © 2017–2020 traffxml.org. + * + * Relicensed to CoMaps by the original author. + */ + +package app.organicmaps.traffxml; + +import java.util.List; + +import app.organicmaps.traffxml.Version; +import app.organicmaps.traffxml.AndroidTransport; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentFilter.MalformedMimeTypeException; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; + +public class AndroidConsumer { + /** + * Creates an Intent filter which matches the Intents a TraFF consumer needs to receive. + * + *

Different filters are available for consumers implementing different versions of the TraFF + * specification. + * + * @param version The version of the TraFF specification (one of the constants in {@link org.traffxml.traff.Version}) + * + * @return An intent filter matching the necessary Intents + */ + public static IntentFilter createIntentFilter(int version) { + IntentFilter res = new IntentFilter(); + switch (version) { + case Version.V0_7: + res.addAction(AndroidTransport.ACTION_TRAFF_PUSH); + break; + case Version.V0_8: + res.addAction(AndroidTransport.ACTION_TRAFF_PUSH); + res.addDataScheme(AndroidTransport.CONTENT_SCHEMA); + try { + res.addDataType(AndroidTransport.MIME_TYPE_TRAFF); + } catch (MalformedMimeTypeException e) { + // as long as the constant is a well-formed MIME type, this exception never gets thrown + e.printStackTrace(); + } + break; + default: + throw new IllegalArgumentException("Invalid version code: " + version); + } + return res; + } + + /** + * Sends a TraFF intent to a source. + * + *

This encapsulates most of the low-level Android handling. + * + *

If the recipient specified in {@code packageName} declares multiple receivers for the intent in its + * manifest, a separate intent will be delivered to each of them. The intent will not be delivered to + * receivers registered at runtime. + * + *

All intents are sent as explicit ordered broadcasts. This means two things: + * + *

Any app which declares a matching receiver in its manifest will be woken up to process the intent. + * This works even with certain Android 7 builds which restrict intent delivery to apps which are not + * currently running. + * + *

It is safe for the recipient to unconditionally set result data. If the recipient does not set + * result data, the result will have a result code of + * {@link org.traffxml.transport.android.AndroidTransport#RESULT_INTERNAL_ERROR}, no data and no extras. + * + * @param context The context + * @param action The intent action. + * @param data The intent data (for TraFF, this is the content provider URI), or null + * @param extras The extras for the intent + * @param packageName The package name for the intent recipient, or null to deliver the intent to all matching receivers + * @param receiverPermission A permission which the recipient must hold, or null if not required + * @param resultReceiver A BroadcastReceiver which will receive the result for the intent + */ + public static void sendTraffIntent(Context context, String action, Uri data, Bundle extras, String packageName, + String receiverPermission, BroadcastReceiver resultReceiver) { + Intent outIntent = new Intent(action); + PackageManager pm = context.getPackageManager(); + List receivers = pm.queryBroadcastReceivers(outIntent, 0); + if (receivers != null) + for (ResolveInfo receiver : receivers) { + if ((packageName != null) && !packageName.equals(receiver.activityInfo.applicationInfo.packageName)) + continue; + ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName, + receiver.activityInfo.name); + outIntent = new Intent(action); + if (data != null) + outIntent.setData(data); + if (extras != null) + outIntent.putExtras(extras); + outIntent.setComponent(cn); + context.sendOrderedBroadcast (outIntent, + receiverPermission, + resultReceiver, + null, // scheduler, + AndroidTransport.RESULT_INTERNAL_ERROR, // initialCode, + null, // initialData, + null); + } + } +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/AndroidTransport.java b/android/app/src/main/java/app/organicmaps/traffxml/AndroidTransport.java new file mode 100644 index 000000000..abb69d965 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/AndroidTransport.java @@ -0,0 +1,222 @@ +/* + * Copyright © 2019–2020 traffxml.org. + * + * Relicensed to CoMaps by the original author. + */ + +package app.organicmaps.traffxml; + +public class AndroidTransport { + /** + * Intent to poll a peer for its capabilities. + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + */ + public static final String ACTION_TRAFF_GET_CAPABILITIES = "org.traffxml.traff.GET_CAPABILITIES"; + + /** + * Intent to send a heartbeat to a peer. + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + */ + public static final String ACTION_TRAFF_HEARTBEAT = "org.traffxml.traff.GET_HEARTBEAT"; + + /** + * Intent to poll a source for information. + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + * + *

Polling is a legacy feature on Android and deprecated in TraFF 0.8 (rather than polling, TraFF 0.8 + * applications query the content provider). Therefore, poll operations are subscriptionless, and the + * source should either reply with all messages it currently holds, or ignore the request. + */ + @Deprecated + public static final String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL"; + + /** + * Intent for a push feed. + * + *

This is a broadcast intent. It can be used in different forms: + * + *

As of TraFF 0.8, it must be sent as an explicit broadcast and include the + * {@link #EXTRA_SUBSCRIPTION_ID} extra. The intent data must be a URI to the content provider from which + * the messages can be retrieved. The {@link #EXTRA_FEED} extra is not supported. The feed is part of a + * subscription and will contain only changes over feeds sent previously as part of the same + * subscription. + * + *

Legacy applications omit the {@link #EXTRA_SUBSCRIPTION_ID} extra and may send it as an implicit + * broadcast. If an application supports both legacy transport and TraFF 0.8 or later, it must include + * the {@link #EXTRA_PACKAGE} extra. The feed is sent in the {@link #EXTRA_FEED} extra, as legacy + * applications may not support content providers. If sent as a response to a subscriptionless poll, the + * source should include all messages it holds, else the set of messages included is at the discretion of + * the source. + * + *

Future applications may reintroduce unsolicited push operations for certain scenarios. + */ + public static final String ACTION_TRAFF_PUSH = "org.traffxml.traff.FEED"; + + /** + * Intent for a subscription request. + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + * + *

The filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra. + * + *

The sender must indicate its package name in the {@link #EXTRA_PACKAGE} extra. + */ + public static final String ACTION_TRAFF_SUBSCRIBE = "org.traffxml.traff.SUBSCRIBE"; + + /** + * Intent for a subscription change request, + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + * + *

This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between + * the calling consumer and the source which receives the broadcast. + * + *

The new filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra. + */ + public static final String ACTION_TRAFF_SUBSCRIPTION_CHANGE = "org.traffxml.traff.SUBSCRIPTION_CHANGE"; + + /** + * Intent for an unsubscribe request, + * + *

This is a broadcast intent and must be sent as an explicit broadcast. + * + *

This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between + * the calling consumer and the source which receives the broadcast. It signals that the consumer is no + * longer interested in receiving messages related to that subscription, and that the source should stop + * sending updates. Unsubscribing from a nonexistent subscription is a no-op. + */ + public static final String ACTION_TRAFF_UNSUBSCRIBE = "org.traffxml.traff.UNSUBSCRIBE"; + + /** + * Name for the column which holds the message data. + */ + public static final String COLUMN_DATA = "data"; + + /** + * Schema for TraFF content URIs. + */ + public static final String CONTENT_SCHEMA = "content"; + + /** + * String representations of TraFF result codes + */ + public static final String[] ERROR_STRINGS = { + "unknown (0)", + "invalid request (1)", + "subscription rejected by the source (2)", + "requested area not covered (3)", + "requested area partially covered (4)", + "subscription ID not recognized by the source (5)", + "unknown (6)", + "source reported an internal error (7)" + }; + + /** + * Extra which contains the capabilities of the peer. + * + *

This is a String extra. It contains a {@code capabilities} XML element. + */ + public static final String EXTRA_CAPABILITIES = "capabilities"; + + /** + * Extra which contains a TraFF feed. + * + *

This is a String extra. It contains a {@code feed} XML element. + * + *

The sender should be careful to keep the size of this extra low, as Android has a 1 MByte limit on all + * pending Binder transactions. However, there is no feedback to the sender about the capacity still + * available, or whether a request exceeds that limit. Therefore, senders should keep the size if each + * feed significantly below that limit. If necessary, they should split up a feed into multiple smaller + * ones and send them with a delay in between. + * + *

This mechanism is deprecated since TraFF 0.8 and peers are no longer required to support it. Peers + * which support TraFF 0.8 must rely on content providers for message transport. + */ + @Deprecated + public static final String EXTRA_FEED = "feed"; + + /** + * Extra which contains a filter list. + * + *

This is a String extra. It contains a {@code filter_list} XML element. + */ + public static final String EXTRA_FILTER_LIST = "filter_list"; + + /** + * Extra which contains the package name of the app sending it. + * + *

This is a String extra. + */ + public static final String EXTRA_PACKAGE = "package"; + + /** + * Extra which contains a subscription ID. + * + *

This is a String extra. + */ + public static final String EXTRA_SUBSCRIPTION_ID = "subscription_id"; + + /** + * Extra which contains the timeout duration for a subscription. + * + *

This is an integer extra. + */ + public static final String EXTRA_TIMEOUT = "timeout"; + + /** + * The MIME type for TraFF content providers. + */ + public static final String MIME_TYPE_TRAFF = "vnd.android.cursor.dir/org.traffxml.message"; + + /** + * The operation completed successfully. + */ + public static final int RESULT_OK = -1; + + /** + * An internal error prevented the recipient from fulfilling the request. + */ + public static final int RESULT_INTERNAL_ERROR = 7; + + /** + * A nonexistent operation was attempted, or an operation was attempted with incomplete or otherwise + * invalid data. + */ + public static final int RESULT_INVALID = 1; + + /** + * The subscription was rejected, and no messages will be sent. + */ + public static final int RESULT_SUBSCRIPTION_REJECTED = 2; + + /** + * The subscription was rejected because the source will never provide messages matching the selection. + */ + public static final int RESULT_NOT_COVERED = 3; + + /** + * The subscription was accepted but the source can only provide messages for parts of the selection. + */ + public static final int RESULT_PARTIALLY_COVERED = 4; + + /** + * The request failed because it refers to a subscription which does not exist between the source and + * consumer involved. + */ + public static final int RESULT_SUBSCRIPTION_UNKNOWN = 5; + + /** + * The request failed because the aggregator does not accept unsolicited push requests from the sensor. + */ + public static final int RESULT_PUSH_REJECTED = 6; + + public static String formatTraffError(int code) { + if ((code < 0) || (code >= ERROR_STRINGS.length)) + return String.format("unknown (%d)", code); + else + return ERROR_STRINGS[code]; + } +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/SourceImpl.java b/android/app/src/main/java/app/organicmaps/traffxml/SourceImpl.java new file mode 100644 index 000000000..9ef6e55e8 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/SourceImpl.java @@ -0,0 +1,70 @@ +package app.organicmaps.traffxml; + +import android.content.BroadcastReceiver; +import android.content.Context; + +/** + * Abstract superclass for TraFF source implementations. + */ +public abstract class SourceImpl extends BroadcastReceiver +{ + /** + * Creates a new instance. + * + * @param context The application context + */ + public SourceImpl(Context context, long nativeManager) + { + super(); + this.context = context; + this.nativeManager = nativeManager; + } + + protected Context context; + + /** + * The native `TraffSourceManager` instance. + */ + protected long nativeManager; + + /** + * Subscribes to a traffic source. + * + * @param filterList The filter list in XML format + */ + public abstract void subscribe(String filterList); + + /** + * Changes an existing traffic subscription. + * + * @param filterList The filter list in XML format + */ + public abstract void changeSubscription(String filterList); + + /** + * Unsubscribes from a traffic source we are subscribed to. + */ + public abstract void unsubscribe(); + + /** + * Forwards a newly received TraFF feed to the traffic module for processing. + * + * Called when a TraFF feed is received. This is a wrapper around {@link #onFeedReceivedImpl(long, String)}. + * + * @param feed The TraFF feed + */ + protected void onFeedReceived(String feed) + { + onFeedReceivedImpl(nativeManager, feed); + } + + /** + * Forwards a newly received TraFF feed to the traffic module for processing. + * + * Called when a TraFF feed is received. + * + * @param nativeManager The native `TraffSourceManager` instance + * @param feed The TraFF feed + */ + protected static native void onFeedReceivedImpl(long nativeManager, String feed); +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_7.java b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_7.java new file mode 100644 index 000000000..981e2affc --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_7.java @@ -0,0 +1,127 @@ +package app.organicmaps.traffxml; + +import java.util.ArrayList; +import java.util.List; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import app.organicmaps.util.log.Logger; + +/** + * Implementation for a TraFF 0.7 source. + */ +public class SourceImplV0_7 extends SourceImpl +{ + private PackageManager pm; + + /** + * Creates a new instance. + * + * @param context The application context + */ + public SourceImplV0_7(Context context, long nativeManager) + { + super(context, nativeManager); + // TODO Auto-generated constructor stub + } + + /** + * Subscribes to a traffic source. + * + * @param filterList The filter list in XML format + */ + @Override + public void subscribe(String filterList) + { + IntentFilter traffFilter07 = new IntentFilter(); + traffFilter07.addAction(AndroidTransport.ACTION_TRAFF_PUSH); + + this.context.registerReceiver(this, traffFilter07); + + // Broadcast a poll intent to all TraFF 0.7-only receivers + Intent outIntent = new Intent(AndroidTransport.ACTION_TRAFF_POLL); + pm = this.context.getPackageManager(); + List receivers07 = pm.queryBroadcastReceivers(outIntent, 0); + List receivers08 = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0); + if (receivers07 != null) + { + /* + * Get receivers which support only TraFF 0.7 and poll them. + * If there are no TraFF 0.7 sources at the moment, we register the receiver nonetheless. + * That way, if any new sources are added during the session, we get any messages they send. + */ + if (receivers08 != null) + receivers07.removeAll(receivers08); + for (ResolveInfo receiver : receivers07) + { + ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName, + receiver.activityInfo.name); + outIntent = new Intent(AndroidTransport.ACTION_TRAFF_POLL); + outIntent.setComponent(cn); + this.context.sendBroadcast(outIntent, Manifest.permission.ACCESS_COARSE_LOCATION); + } + } + } + + /** + * Changes an existing traffic subscription. + * + * This implementation does nothing, as TraFF 0.7 does not support subscriptions. + * + * @param filterList The filter list in XML format + */ + @Override + public void changeSubscription(String filterList) + { + // NOP + } + + /** + * Unsubscribes from a traffic source we are subscribed to. + */ + @Override + public void unsubscribe() + { + this.context.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) + { + if (intent == null) + return; + + if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_PUSH)) + { + /* 0.7 feed */ + String packageName = intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE); + /* + * If the feed comes from a TraFF 0.8+ source, skip it (this may happen with “bilingual” + * TraFF 0.7/0.8 sources). That ensures the only way to get information from such sources is + * through a TraFF 0.8 subscription. Fetching the list from scratch each time ensures that + * apps installed during runtime get considered.) + */ + if (packageName != null) + { + for (ResolveInfo info : pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0)) + if (packageName.equals(info.resolvePackageName)) + return; + } + String feed = intent.getStringExtra(AndroidTransport.EXTRA_FEED); + if (feed == null) + { + Logger.w(this.getClass().getSimpleName(), "empty feed, ignoring"); + } + else + { + onFeedReceived(feed); + } + } + } + +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java new file mode 100644 index 000000000..4729643c8 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java @@ -0,0 +1,232 @@ +package app.organicmaps.traffxml; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentFilter.MalformedMimeTypeException; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import app.organicmaps.util.log.Logger; + +/** + * Implementation for a TraFF 0.8 source. + */ +public class SourceImplV0_8 extends SourceImpl +{ + + private String packageName; + private String subscriptionId = null; + + /** + * Creates a new instance. + * + * @param context The application context + * @param packageName The package name for the source + */ + public SourceImplV0_8(Context context, long nativeManager, String packageName) + { + super(context, nativeManager); + this.packageName = packageName; + } + + /** + * Subscribes to a traffic source. + * + * @param filterList The filter list in XML format + */ + @Override + public void subscribe(String filterList) + { + IntentFilter filter = new IntentFilter(); + filter.addAction(AndroidTransport.ACTION_TRAFF_PUSH); + filter.addDataScheme(AndroidTransport.CONTENT_SCHEMA); + try + { + filter.addDataType(AndroidTransport.MIME_TYPE_TRAFF); + } + catch (MalformedMimeTypeException e) + { + // as long as the constant is a well-formed MIME type, this exception never gets thrown + // TODO revisit logging + e.printStackTrace(); + } + + context.registerReceiver(this, filter); + } + + /** + * Changes an existing traffic subscription. + * + * @param filterList The filter list in XML format + */ + @Override + public void changeSubscription(String filterList) + { + Bundle extras = new Bundle(); + extras.putString(AndroidTransport.EXTRA_SUBSCRIPTION_ID, subscriptionId); + extras.putString(AndroidTransport.EXTRA_FILTER_LIST, filterList); + AndroidConsumer.sendTraffIntent(context, AndroidTransport.ACTION_TRAFF_SUBSCRIPTION_CHANGE, null, + extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this); + } + + /** + * Unsubscribes from a traffic source we are subscribed to. + */ + @Override + public void unsubscribe() + { + Bundle extras = new Bundle(); + extras.putString(AndroidTransport.EXTRA_SUBSCRIPTION_ID, subscriptionId); + AndroidConsumer.sendTraffIntent(this.context, AndroidTransport.ACTION_TRAFF_UNSUBSCRIBE, null, + extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this); + + this.context.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) + { + if (intent == null) + return; + + if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_PUSH)) + { + Uri uri = intent.getData(); + if (uri != null) + { + /* 0.8 feed */ + String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID); + if (subscriptionId.equals(this.subscriptionId)) + fetchMessages(context, uri); + } + else + { + Logger.w(this.getClass().getSimpleName(), "no URI in feed, ignoring"); + } // uri != null + } else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_SUBSCRIBE)) { + if (this.getResultCode() != AndroidTransport.RESULT_OK) { + Bundle extras = this.getResultExtras(true); + if (extras != null) + Logger.e(this.getClass().getSimpleName(), String.format("subscription to %s failed, %s", + extras.getString(AndroidTransport.EXTRA_PACKAGE), AndroidTransport.formatTraffError(this.getResultCode()))); + else + Logger.e(this.getClass().getSimpleName(), String.format("subscription failed, %s", + AndroidTransport.formatTraffError(this.getResultCode()))); + return; + } + Bundle extras = this.getResultExtras(true); + String data = this.getResultData(); + String packageName = extras.getString(AndroidTransport.EXTRA_PACKAGE); + if (!this.packageName.equals(packageName)) + return; + String subscriptionId = extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID); + if (subscriptionId == null) { + Logger.e(this.getClass().getSimpleName(), + String.format("subscription to %s failed: no subscription ID returned", packageName)); + return; + } else if (packageName == null) { + Logger.e(this.getClass().getSimpleName(), "subscription failed: no package name"); + return; + } else if (data == null) { + Logger.w(this.getClass().getSimpleName(), + String.format("subscription to %s successful (ID: %s) but no content URI was supplied. " + + "This is an issue with the source and may result in delayed message retrieval.", + packageName, subscriptionId)); + this.subscriptionId = subscriptionId; + return; + } + Logger.d(this.getClass().getSimpleName(), + "subscription to " + packageName + " successful, ID: " + subscriptionId); + this.subscriptionId = subscriptionId; + fetchMessages(context, Uri.parse(data)); + } else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_SUBSCRIPTION_CHANGE)) { + if (this.getResultCode() != AndroidTransport.RESULT_OK) { + Bundle extras = this.getResultExtras(true); + if (extras != null) + Logger.e(this.getClass().getSimpleName(), + String.format("subscription change for %s failed: %s", + extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID), + AndroidTransport.formatTraffError(this.getResultCode()))); + else + Logger.e(this.getClass().getSimpleName(), + String.format("subscription change failed: %s", + AndroidTransport.formatTraffError(this.getResultCode()))); + return; + } + Bundle extras = intent.getExtras(); + String data = this.getResultData(); + String subscriptionId = extras.getString(AndroidTransport.EXTRA_SUBSCRIPTION_ID); + if (subscriptionId == null) { + Logger.w(this.getClass().getSimpleName(), + "subscription change successful but the source did not specify the subscription ID. " + + "This is an issue with the source and may result in delayed message retrieval. " + + "URI: " + data); + return; + } else if (!subscriptionId.equals(this.subscriptionId)) { + return; + } else if (data == null) { + Logger.w(this.getClass().getSimpleName(), + String.format("subscription change for %s successful but no content URI was supplied. " + + "This is an issue with the source and may result in delayed message retrieval.", + subscriptionId)); + return; + } + Logger.d(this.getClass().getSimpleName(), + "subscription change for " + subscriptionId + " successful"); + fetchMessages(context, Uri.parse(data)); + } else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_UNSUBSCRIBE)) { + String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID); + if (subscriptionId.equals(this.subscriptionId)) + this.subscriptionId = null; + // TODO is there anything to do here? (Comment below is from Navit) + /* + * If we ever unsubscribe for reasons other than that we are shutting down or got a feed for + * a subscription we don’t recognize, or if we start keeping a persistent list of + * subscriptions, we need to delete the subscription from our list. Until then, there is + * nothing to do here: either the subscription isn’t in the list, or we are about to shut + * down and the whole list is about to get discarded. + */ + } else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_HEARTBEAT)) { + String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID); + if (subscriptionId.equals(this.subscriptionId)) { + Logger.d(this.getClass().getSimpleName(), + String.format("got a heartbeat from %s for subscription %s; sending result", + intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE), subscriptionId)); + this.setResult(AndroidTransport.RESULT_OK, null, null); + } + } // intent.getAction() + // TODO Auto-generated method stub + + } + + /** + * Fetches TraFF messages from a content provider. + * + * @param context The context to use for the content resolver + * @param uri The content provider URI + */ + private void fetchMessages(Context context, Uri uri) { + try { + Cursor cursor = context.getContentResolver().query(uri, new String[] {AndroidTransport.COLUMN_DATA}, null, null, null); + if (cursor == null) + return; + if (cursor.getCount() < 1) { + cursor.close(); + return; + } + StringBuilder builder = new StringBuilder("\n"); + while (cursor.moveToNext()) + builder.append(cursor.getString(cursor.getColumnIndex(AndroidTransport.COLUMN_DATA))).append("\n"); + builder.append(""); + cursor.close(); + onFeedReceived(builder.toString()); + } catch (Exception e) { + Logger.w(this.getClass().getSimpleName(), + String.format("Unable to fetch messages from %s", uri.toString()), e); + e.printStackTrace(); + } + } + +} diff --git a/android/app/src/main/java/app/organicmaps/traffxml/Version.java b/android/app/src/main/java/app/organicmaps/traffxml/Version.java new file mode 100644 index 000000000..8f3587ea0 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/traffxml/Version.java @@ -0,0 +1,18 @@ +/* + * Copyright © 2019–2020 traffxml.org. + * + * Relicensed to CoMaps by the original author. + */ + +package app.organicmaps.traffxml; + +/** + * Constants for versions. + */ +public class Version { + /** Version 0.7: introduced transport on Android. */ + public static final int V0_7 = 7; + + /** Version 0.8: introduced subscriptions and HTTP transport. */ + public static final int V0_8 = 8; +} From 167f0b8377544f8c8874a1276d00fcea3c6ef579 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 12 Aug 2025 19:02:46 +0300 Subject: [PATCH 150/252] Add traffic.xml to .gitignore Signed-off-by: mvglasow --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f3b1d23d8..390e52bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ data/colors_design.txt data/patterns_design.txt data/bookmarks data/edits.xml +data/traffic.xml data/World.mwm data/WorldCoasts.mwm From 3a6f21dbd1958613467114c05ce27bb4c8962f32 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 15 Aug 2025 13:22:51 +0300 Subject: [PATCH 151/252] [traffic][android] Implement Android TraFF source Signed-off-by: mvglasow --- android/app/src/main/AndroidManifest.xml | 15 +++ .../main/cpp/app/organicmaps/Framework.hpp | 2 + .../cpp/app/organicmaps/sdk/OrganicMaps.cpp | 25 ++++- .../main/cpp/app/organicmaps/util/Config.cpp | 41 ++++++++ .../settings/SettingsPrefsFragment.java | 95 +++++++++++++++++++ .../organicmaps/traffxml/SourceImplV0_8.java | 8 ++ .../java/app/organicmaps/util/Config.java | 44 +++++++++ .../src/main/res/values/donottranslate.xml | 3 + android/app/src/main/res/values/strings.xml | 11 +++ android/app/src/main/res/xml/prefs_main.xml | 42 +++++--- map/traffic_manager.cpp | 11 +++ map/traffic_manager.hpp | 16 ++++ 12 files changed, 299 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 718e423b3..1ed8d7833 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -62,6 +62,21 @@ + + + + + + + + + + + + + + + CallVoidMethod(*onComplete, methodId); + jmethodID const runId = jni::GetMethodID(env, *onComplete, "run", "()V"); + env->CallVoidMethod(*onComplete, runId); + + ASSERT(g_framework, ("g_framework must be non-null")); + + /* + * Add traffic sources for Android. + */ + jclass configClass = env->FindClass("app/organicmaps/util/Config"); + jmethodID const getTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass, + "getTrafficLegacyEnabled", "()Z"); + jmethodID const applyTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass, + "applyTrafficLegacyEnabled", "(Z)V"); + jmethodID const getTrafficAppsId = jni::GetStaticMethodID(env, configClass, + "getTrafficApps", "()[Ljava/lang/String;"); + jmethodID const applyTrafficAppsId = jni::GetStaticMethodID(env, configClass, + "applyTrafficApps", "([Ljava/lang/String;)V"); + + env->CallStaticVoidMethod(configClass, applyTrafficLegacyEnabledId, + env->CallStaticBooleanMethod(configClass, getTrafficLegacyEnabledId)); + env->CallStaticVoidMethod(configClass, applyTrafficAppsId, + (jobjectArray)env->CallStaticObjectMethod(configClass, getTrafficAppsId)); }); } } diff --git a/android/app/src/main/cpp/app/organicmaps/util/Config.cpp b/android/app/src/main/cpp/app/organicmaps/util/Config.cpp index c934152b6..f0e3a4beb 100644 --- a/android/app/src/main/cpp/app/organicmaps/util/Config.cpp +++ b/android/app/src/main/cpp/app/organicmaps/util/Config.cpp @@ -152,4 +152,45 @@ extern "C" frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value)); frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value)); } + + JNIEXPORT void JNICALL + Java_app_organicmaps_util_Config_applyTrafficLegacyEnabled(JNIEnv * env, jclass thiz, + jboolean value) + { + TrafficManager & tm = g_framework->GetTrafficManager(); + tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) { + if (traffxml::AndroidTraffSourceV0_7* traffSource = dynamic_cast(source)) + { + traffSource->Close(); + return true; + } + else + return false; + }); + if (value) + traffxml::AndroidTraffSourceV0_7::Create(tm); + } + + JNIEXPORT void JNICALL + Java_app_organicmaps_util_Config_applyTrafficApps(JNIEnv * env, jclass thiz, jobjectArray value) + { + jsize valueLen = env->GetArrayLength(value); + TrafficManager & tm = g_framework->GetTrafficManager(); + tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) { + if (traffxml::AndroidTraffSourceV0_8* traffSource = dynamic_cast(source)) + { + traffSource->Close(); + return true; + } + else + return false; + }); + for (jsize i = 0; i < valueLen; i++) + { + jstring jAppId = (jstring)env->GetObjectArrayElement(value, i); + std::string appId = jni::ToNativeString(env, jAppId); + traffxml::AndroidTraffSourceV0_8::Create(tm, appId); + env->DeleteLocalRef(jAppId); + } + } } // extern "C" diff --git a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java index 1d7e3a2b7..3e50ea039 100644 --- a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java @@ -3,8 +3,11 @@ import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE; import android.annotation.SuppressLint; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.view.View; @@ -13,6 +16,7 @@ import androidx.annotation.StringRes; import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; +import androidx.preference.MultiSelectListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.TwoStatePreference; @@ -38,6 +42,8 @@ import app.organicmaps.util.Utils; import app.organicmaps.util.log.LogsManager; import app.organicmaps.sdk.search.SearchRecents; +import app.organicmaps.traffxml.AndroidTransport; + import com.google.android.material.dialog.MaterialAlertDialogBuilder; import java.util.ArrayList; @@ -46,6 +52,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.Set; public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener { @@ -69,6 +76,8 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) initTransliterationPrefsCallbacks(); initTrafficHttpEnabledPrefsCallbacks(); initTrafficHttpUrlPrefsCallbacks(); + initTrafficAppsPrefs(); + initTrafficLegacyEnabledPrefsCallbacks(); init3dModePrefsCallbacks(); initPerspectivePrefsCallbacks(); initAutoZoomPrefsCallbacks(); @@ -154,6 +163,36 @@ private void updateTrafficHttpUrlSummary() pref.setSummary(summary); } + private void updateTrafficAppsSummary() + { + final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps)); + /* + * If the preference is disabled, it has not been initialized. This is the case if no TraFF + * apps were found. The code below would crash when trying to access the entries, and there + * is no need to update the summary if the setting cannot be changed. + */ + if (!pref.isEnabled()) + return; + String[] apps = Config.getTrafficApps(); + if (apps.length == 0) + pref.setSummary(R.string.traffic_apps_none_selected); + else + { + String summary = ""; + for (int i = 0; i < apps.length; i++) + { + if (i > 0) + summary = summary + ", "; + int index = pref.findIndexOfValue(apps[i]); + if (i >= 0) + summary = summary + pref.getEntries()[index]; + else + summary = summary + apps[i]; + } + pref.setSummary(summary); + } + } + private void updateRoutingSettingsPrefsSummary() { final Preference pref = getPreference(getString(R.string.prefs_routing)); @@ -182,6 +221,7 @@ public void onResume() updateRoutingSettingsPrefsSummary(); updateMapLanguageCodeSummary(); updateTrafficHttpUrlSummary(); + updateTrafficAppsSummary(); } @Override @@ -271,6 +311,61 @@ private void initTrafficHttpUrlPrefsCallbacks() }); } + private void initTrafficAppsPrefs() + { + final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps)); + + PackageManager pm = getContext().getPackageManager(); + List receivers = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0); + + if (receivers == null || receivers.isEmpty()) + { + pref.setSummary(R.string.traffic_apps_not_available); + pref.setEnabled(false); + return; + } + + pref.setEnabled(true); + + List entryList = new ArrayList<>(receivers.size()); + List valueList = new ArrayList<>(receivers.size()); + + for (ResolveInfo receiver : receivers) + { + // friendly name + entryList.add(receiver.loadLabel(pm).toString()); + // actual value (we just need the package name, broadcasts are sent to any receiver in the package) + valueList.add(receiver.activityInfo.applicationInfo.packageName); + } + + pref.setEntries(entryList.toArray(new CharSequence[0])); + pref.setEntryValues(valueList.toArray(new CharSequence[0])); + + pref.setOnPreferenceChangeListener((preference, newValue) -> { + // newValue is a Set, each item is a package ID + String[] apps = ((Set)newValue).toArray(new String[0]); + Config.setTrafficApps(apps); + updateTrafficAppsSummary(); + + return true; + }); + } + + private void initTrafficLegacyEnabledPrefsCallbacks() + { + final Preference pref = getPreference(getString(R.string.pref_traffic_legacy_enabled)); + + ((TwoStatePreference)pref).setChecked(Config.getTrafficLegacyEnabled()); + pref.setOnPreferenceChangeListener((preference, newValue) -> { + final boolean oldVal = Config.getTrafficLegacyEnabled(); + final boolean newVal = (Boolean) newValue; + if (oldVal != newVal) + Config.setTrafficLegacyEnabled(newVal); + + return true; + }); + } + private void initUseMobileDataPrefsCallbacks() { final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data)); diff --git a/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java index 4729643c8..753875ec1 100644 --- a/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java +++ b/android/app/src/main/java/app/organicmaps/traffxml/SourceImplV0_8.java @@ -54,6 +54,12 @@ public void subscribe(String filterList) } context.registerReceiver(this, filter); + + Bundle extras = new Bundle(); + extras.putString(AndroidTransport.EXTRA_PACKAGE, context.getPackageName()); + extras.putString(AndroidTransport.EXTRA_FILTER_LIST, filterList); + AndroidConsumer.sendTraffIntent(context, AndroidTransport.ACTION_TRAFF_SUBSCRIBE, null, + extras, packageName, Manifest.permission.ACCESS_COARSE_LOCATION, this); } /** @@ -114,6 +120,8 @@ public void onReceive(Context context, Intent intent) else Logger.e(this.getClass().getSimpleName(), String.format("subscription failed, %s", AndroidTransport.formatTraffError(this.getResultCode()))); + if (this.getResultCode() == AndroidTransport.RESULT_INTERNAL_ERROR) + Logger.e(this.getClass().getSimpleName(), "Make sure the TraFF source app has at least coarse location permission, even when running in background"); return; } Bundle extras = this.getResultExtras(true); diff --git a/android/app/src/main/java/app/organicmaps/util/Config.java b/android/app/src/main/java/app/organicmaps/util/Config.java index 418ebb7b2..06fcb6c85 100644 --- a/android/app/src/main/java/app/organicmaps/util/Config.java +++ b/android/app/src/main/java/app/organicmaps/util/Config.java @@ -55,6 +55,16 @@ public final class Config * True if the first start animation has been seen. */ private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen"; + + /** + * Whether feeds from legacy TraFF applications (TraFF 0.7, Android transport) are enabled. + */ + private static final String KEY_TRAFFIC_LEGACY_ENABLED = "TrafficLegacyEnabled"; + + /** + * TraFF (0.8+) applications from which to request traffic data. + */ + private static final String KEY_TRAFFIC_APPS = "TrafficApps"; private Config() {} @@ -338,6 +348,38 @@ public static void setTrafficHttpUrl(String value) { nativeSetTrafficHttpUrl(value); } + + public static String[] getTrafficApps() + { + String appString = getString(KEY_TRAFFIC_APPS, ""); + if (appString.length() == 0) + return new String[0]; + return appString.split(","); + } + + public static void setTrafficApps(String[] value) + { + String valueString = ""; + for (int i = 0; i < value.length; i++) + { + valueString = valueString + value[i]; + if ((i + 1) < value.length) + valueString = valueString + ","; + } + setString(KEY_TRAFFIC_APPS, valueString); + applyTrafficApps(value); + } + + public static boolean getTrafficLegacyEnabled() + { + return getBool(KEY_TRAFFIC_LEGACY_ENABLED, false); + } + + public static void setTrafficLegacyEnabled(boolean value) + { + setBool(KEY_TRAFFIC_LEGACY_ENABLED, value); + applyTrafficLegacyEnabled(value); + } public static boolean isNY() { @@ -496,4 +538,6 @@ public static void setAnnounceStreets(boolean enabled) private static native void nativeSetTrafficHttpEnabled(boolean value); private static native String nativeGetTrafficHttpUrl(); private static native void nativeSetTrafficHttpUrl(String value); + private static native void applyTrafficApps(String[] value); + private static native void applyTrafficLegacyEnabled(boolean value); } diff --git a/android/app/src/main/res/values/donottranslate.xml b/android/app/src/main/res/values/donottranslate.xml index c6294b18f..046304210 100644 --- a/android/app/src/main/res/values/donottranslate.xml +++ b/android/app/src/main/res/values/donottranslate.xml @@ -35,8 +35,11 @@ GeneralSettings Navigation Information + Traffic TrafficHttpEnabled TrafficHttpUrl + TrafficApps + TrafficLegacyEnabled Transliteration PowerManagment KeepScreenOn diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f26902f99..e28573b37 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -234,6 +234,7 @@ Information Navigation + Traffic information Zoom buttons Display on the map @@ -838,6 +839,16 @@ Traffic service URL Not set + + Use data from TraFF applications + + No apps installed + + No apps salected + + Use data from legacy TraFF applications + + When enabled, the app will receive and process traffic data from legacy TraFF applications. Map data from OpenStreetMap diff --git a/android/app/src/main/res/xml/prefs_main.xml b/android/app/src/main/res/xml/prefs_main.xml index 625a86e74..421f7a4a1 100644 --- a/android/app/src/main/res/xml/prefs_main.xml +++ b/android/app/src/main/res/xml/prefs_main.xml @@ -119,18 +119,6 @@ app:singleLineTitle="false" android:persistent="false" android:order="19"/> - - + + + + + + + &pred) +{ + std::lock_guard lock(m_trafficSourceMutex); + + for (auto it = m_trafficSources.begin(); it != m_trafficSources.end(); ) + if (pred(it->get())) + m_trafficSources.erase(it); + else + ++it; +} + bool TrafficManager::IsInvalidState() const { return m_state == TrafficState::NetworkError; diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 7b4fbe08e..6e14bb4c7 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -190,6 +190,22 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void SetHttpTraffSource(bool enabled, std::string url); + /** + * @brief Removes all `TraffSource` instances which satisfy a predicate. + * + * This method iterates over all currently configured `TraffSource` instances and calls the + * caller-suppplied predicate function `pred` on each of them. If `pred` returns true, the source + * is removed, else it is kept. + * + * @todo For now, `pred` deliberately takes a non-const argument so we can do cleanup inside + * `pred`. If we manage to move any such cleanup into the destructor of the `TraffSource` subclass + * and get rid of any `Close()` methods in subclasses (which is preferable for other reasons as + * well), the argument can be made const. + * + * @param pred The predicate function, see description. + */ + void RemoveTraffSourceIf(const std::function& pred); + /** * @brief Starts the traffic manager. * From 01476d3dc788a09fe59416ecc068f647c2b396e6 Mon Sep 17 00:00:00 2001 From: Eivind Samseth Date: Fri, 15 Aug 2025 21:44:44 +0200 Subject: [PATCH 152/252] Standard format changes --- traffxml/traff_model.cpp | 18 +++++++++++++++--- traffxml/traff_model_xml.cpp | 25 ++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/traffxml/traff_model.cpp b/traffxml/traff_model.cpp index cd9dbfe37..299b03813 100644 --- a/traffxml/traff_model.cpp +++ b/traffxml/traff_model.cpp @@ -2,6 +2,7 @@ #include "base/logging.hpp" +#include #include using namespace std; @@ -174,7 +175,14 @@ void IsoTime::Shift(IsoTime nowRef) std::string IsoTime::ToString() const { - return std::format("{0:%F}T{0:%T}{0:%Ez}", time_point_cast(m_tp)); + auto const tp_seconds = time_point_cast(m_tp); + auto const time_t = std::chrono::system_clock::to_time_t(tp_seconds); + std::tm tm = *std::gmtime(&time_t); + + std::ostringstream ss; + ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S"); + ss << "Z"; + return ss.str(); } bool IsoTime::operator< (IsoTime & rhs) @@ -346,7 +354,9 @@ std::string DebugPrint(IsoTime time) std::ostringstream os; //os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z"); // %FT%T%z - os << std::format("{0:%F} {0:%T} {0:%z}", time.m_tp); + auto const time_t = std::chrono::system_clock::to_time_t(time.m_tp); + std::tm tm = *std::gmtime(&time_t); + os << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC"); return os.str(); } @@ -542,7 +552,9 @@ std::string DebugPrint(TraffEvent event) os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", "; os << "q_duration: " << (event.m_qDurationMins - ? std::format("{:1d}:{:02d}", event.m_qDurationMins.value() / 60, event.m_qDurationMins.value() % 60) + ? (std::to_string(event.m_qDurationMins.value() / 60) + ":" + + (event.m_qDurationMins.value() % 60 < 10 ? "0" : "") + + std::to_string(event.m_qDurationMins.value() % 60)) : "nullopt") << ", "; // TODO other quantifiers diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 96309127d..553b2965e 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -4,6 +4,7 @@ #include "base/logging.hpp" #include +#include #include #include #include @@ -557,7 +558,10 @@ void PointToXml(Point const & point, std::string name, pugi::xml_node & parentNo if (point.m_junctionRef) node.append_attribute("junction_ref").set_value(point.m_junctionRef.value()); - node.text() = std::format("{0:+.5f} {1:+.5f}", point.m_coordinates.m_lat, point.m_coordinates.m_lon); + std::ostringstream coord_ss; + coord_ss << std::fixed << std::setprecision(5) + << std::showpos << point.m_coordinates.m_lat << " " << point.m_coordinates.m_lon; + node.text() = coord_ss.str().c_str(); } /** @@ -761,7 +765,15 @@ void EventToXml(TraffEvent const & event, pugi::xml_node & node) node.append_attribute("probability").set_value(event.m_probability.value()); if (event.m_qDurationMins) - node.append_attribute("q_duration").set_value(std::format("{:%H:%M}", std::chrono::minutes(event.m_qDurationMins.value()))); + { + auto mins = event.m_qDurationMins.value(); + auto hours = mins / 60; + auto remaining_mins = mins % 60; + std::ostringstream duration_ss; + duration_ss << std::setfill('0') << std::setw(2) << hours << ":" + << std::setw(2) << remaining_mins; + node.append_attribute("q_duration").set_value(duration_ss.str().c_str()); + } // TODO other quantifiers (not yet implemented in struct) @@ -1159,11 +1171,10 @@ std::string FiltersToXml(std::vector & bboxRects) { std::ostringstream os; for (auto rect : bboxRects) - os << std::format("\n", - mercator::YToLat(rect.minY()), - mercator::XToLon(rect.minX()), - mercator::YToLat(rect.maxY()), - mercator::XToLon(rect.maxX())); + os << "\n"; return os.str(); } From 57f55f1022ccc72c3a0fa0c24c267192e9b7732a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 16 Aug 2025 19:52:03 +0300 Subject: [PATCH 153/252] [traffxml] Fix attribute names in output Signed-off-by: mvglasow --- traffxml/traff_model_xml.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 553b2965e..50e022064 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -635,10 +635,10 @@ void LocationToXml(TraffLocation const & location, pugi::xml_node & node) node.append_attribute("origin").set_value(location.m_origin.value()); EnumToXml(location.m_ramps, "ramps", node, kRampsMap); if (location.m_roadClass) - EnumToXml(location.m_roadClass.value(), "roadClass", node, kRoadClassMap); + EnumToXml(location.m_roadClass.value(), "road_class", node, kRoadClassMap); // TODO roadIsUrban (disabled for now) if (location.m_roadRef) - node.append_attribute("roadRef").set_value(location.m_roadRef.value()); + node.append_attribute("road_ref").set_value(location.m_roadRef.value()); if (location.m_roadName) node.append_attribute("roadName").set_value(location.m_roadName.value()); if (location.m_territory) From 6963637b1b37a94afda50f79abbd285a95df2ae0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 16 Aug 2025 19:59:28 +0300 Subject: [PATCH 154/252] [traffxml] Fix compiler warnings Signed-off-by: mvglasow --- traffxml/traff_decoder.cpp | 3 +++ traffxml/traff_decoder.hpp | 2 ++ traffxml/traff_model_xml.cpp | 5 +++-- traffxml/traff_source.hpp | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/traffxml/traff_decoder.cpp b/traffxml/traff_decoder.cpp index 2db984d31..10fe48756 100644 --- a/traffxml/traff_decoder.cpp +++ b/traffxml/traff_decoder.cpp @@ -29,6 +29,8 @@ namespace traffxml { +// Only needed for OpenlrTraffDecoder, see below +#if 0 // Number of worker threads for the OpenLR decoder /* * TODO how to determine the best number of worker threads? @@ -37,6 +39,7 @@ namespace traffxml * Otherwise there is little to be gained, as we decode messages one at a time. */ auto constexpr kNumDecoderThreads = 1; +#endif // Timeout for the router in seconds, used by RoutingTraffDecoder // TODO set to a sensible value diff --git a/traffxml/traff_decoder.hpp b/traffxml/traff_decoder.hpp index 6189aea8b..5cfe044a9 100644 --- a/traffxml/traff_decoder.hpp +++ b/traffxml/traff_decoder.hpp @@ -41,6 +41,8 @@ class TraffDecoder const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); + virtual ~TraffDecoder() {} + /** * @brief Decodes a single message to its segments and their speed groups. * diff --git a/traffxml/traff_model_xml.cpp b/traffxml/traff_model_xml.cpp index 50e022064..bc31b346b 100644 --- a/traffxml/traff_model_xml.cpp +++ b/traffxml/traff_model_xml.cpp @@ -826,8 +826,9 @@ bool SegmentFromXml(pugi::xml_node const & node, std::map & coloring) { uint32_t fid; - uint16_t idx; - uint8_t dir; + // initialize to get rid of compiler warnings (which are false alerts) + uint16_t idx = 0; + uint8_t dir = 0; if (IntegerFromXml(node.attribute("fid"), fid) && IntegerFromXml(node.attribute("idx"), idx) && IntegerFromXml(node.attribute("dir"), dir)) diff --git a/traffxml/traff_source.hpp b/traffxml/traff_source.hpp index 27aa836d3..b93d15a86 100644 --- a/traffxml/traff_source.hpp +++ b/traffxml/traff_source.hpp @@ -26,6 +26,8 @@ class TraffSource; class TraffSourceManager { public: + virtual ~TraffSourceManager() {} + /** * @brief Retrieves all currently active MWMs. * From 16708aae7f0f31c9764013a2fbff81b285b748c4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 17 Aug 2025 17:52:29 +0300 Subject: [PATCH 155/252] [traff_assessment_tool] Rename TrafficMode to TrafficModel Signed-off-by: mvglasow --- traffxml/traff_assessment_tool/CMakeLists.txt | 4 +- traffxml/traff_assessment_tool/mainwindow.cpp | 28 +++++----- traffxml/traff_assessment_tool/mainwindow.hpp | 4 +- .../{traffic_mode.cpp => traffic_model.cpp} | 56 +++++++++---------- .../{traffic_mode.hpp => traffic_model.hpp} | 6 +- 5 files changed, 49 insertions(+), 49 deletions(-) rename traffxml/traff_assessment_tool/{traffic_mode.cpp => traffic_model.cpp} (89%) rename traffxml/traff_assessment_tool/{traffic_mode.hpp => traffic_model.hpp} (95%) diff --git a/traffxml/traff_assessment_tool/CMakeLists.txt b/traffxml/traff_assessment_tool/CMakeLists.txt index 9b252206f..833b68dd2 100644 --- a/traffxml/traff_assessment_tool/CMakeLists.txt +++ b/traffxml/traff_assessment_tool/CMakeLists.txt @@ -8,8 +8,8 @@ set(SRC map_widget.hpp points_controller_delegate_base.hpp traffic_drawer_delegate_base.hpp - traffic_mode.cpp - traffic_mode.hpp + traffic_model.cpp + traffic_model.hpp traffic_panel.cpp traffic_panel.hpp trafficmodeinitdlg.cpp diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 3d551a6b5..9741f021c 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -274,10 +274,10 @@ MainWindow::MainWindow(Framework & framework) fileMenu->addSeparator(); #ifdef openlr_obsolete - m_goldifyMatchedPathAction = fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficMode->GoldifyMatchedPath(); }); + m_goldifyMatchedPathAction = fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficModel->GoldifyMatchedPath(); }); m_startEditingAction = fileMenu->addAction("Edit", QKeySequence("Ctrl+E"), [this] { - m_trafficMode->StartBuildingPath(); + m_trafficModel->StartBuildingPath(); m_mapWidget->SetMode(MapWidget::Mode::TrafficMarkup); m_commitPathAction->setEnabled(true /* enabled */); m_cancelPathAction->setEnabled(true /* enabled */); @@ -285,19 +285,19 @@ MainWindow::MainWindow(Framework & framework) m_commitPathAction = fileMenu->addAction("Accept path", QKeySequence("Ctrl+A"), [this] { - m_trafficMode->CommitPath(); + m_trafficModel->CommitPath(); m_mapWidget->SetMode(MapWidget::Mode::Normal); }); m_cancelPathAction = fileMenu->addAction("Revert path", QKeySequence("Ctrl+R"), [this] { - m_trafficMode->RollBackPath(); + m_trafficModel->RollBackPath(); m_mapWidget->SetMode(MapWidget::Mode::Normal); }); m_ignorePathAction = fileMenu->addAction("Ignore path", QKeySequence("Ctrl+I"), [this] { - m_trafficMode->IgnorePath(); + m_trafficModel->IgnorePath(); m_mapWidget->SetMode(MapWidget::Mode::Normal); }); @@ -313,22 +313,22 @@ MainWindow::MainWindow(Framework & framework) void MainWindow::CreateTrafficPanel(std::string const & dataFilePath) { - m_trafficMode = new TrafficMode(dataFilePath, + m_trafficModel = new TrafficModel(dataFilePath, m_framework.GetDataSource(), std::make_unique(m_framework), std::make_unique(m_framework)); connect(m_mapWidget, &MapWidget::TrafficMarkupClick, - m_trafficMode, &TrafficMode::OnClick); - connect(m_trafficMode, &TrafficMode::EditingStopped, + m_trafficModel, &TrafficModel::OnClick); + connect(m_trafficModel, &TrafficModel::EditingStopped, this, &MainWindow::OnPathEditingStop); - connect(m_trafficMode, &TrafficMode::SegmentSelected, + connect(m_trafficModel, &TrafficModel::SegmentSelected, [](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); }); m_docWidget = new QDockWidget(tr("Routes"), this); addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget); - m_docWidget->setWidget(new TrafficPanel(m_trafficMode, m_docWidget)); + m_docWidget->setWidget(new TrafficPanel(m_trafficModel, m_docWidget)); m_docWidget->adjustSize(); m_docWidget->setMinimumWidth(400); @@ -341,8 +341,8 @@ void MainWindow::DestroyTrafficPanel() delete m_docWidget; m_docWidget = nullptr; - delete m_trafficMode; - m_trafficMode = nullptr; + delete m_trafficModel; + m_trafficModel = nullptr; m_mapWidget->SetMode(MapWidget::Mode::Normal); } @@ -392,7 +392,7 @@ void MainWindow::OnOpenTrafficSample() { CreateTrafficPanel(dlg.GetDataFilePath()); } - catch (TrafficModeError const & e) + catch (TrafficModelError const & e) { QMessageBox::critical(this, "Data loading error", QString("Can't load data file.")); LOG(LERROR, (e.Msg())); @@ -453,7 +453,7 @@ void MainWindow::OnSaveTrafficSample() document.save_file(fileName.toStdString().data(), " " /* indent */); #ifdef openlr_obsolete - if (!m_trafficMode->SaveSampleAs(fileName.toStdString())) + if (!m_trafficModel->SaveSampleAs(fileName.toStdString())) { QMessageBox::critical( this, "Saving error", diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp index 02fa4803f..e3c460df4 100644 --- a/traffxml/traff_assessment_tool/mainwindow.hpp +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -12,7 +12,7 @@ class QHBoxLayout; namespace traffxml { class MapWidget; -class TrafficMode; +class TrafficModel; class WebView; } @@ -56,7 +56,7 @@ class MainWindow : public QMainWindow Framework & m_framework; - traffxml::TrafficMode * m_trafficMode = nullptr; + traffxml::TrafficModel * m_trafficModel = nullptr; QDockWidget * m_docWidget = nullptr; #ifdef openlr_obsolete diff --git a/traffxml/traff_assessment_tool/traffic_mode.cpp b/traffxml/traff_assessment_tool/traffic_model.cpp similarity index 89% rename from traffxml/traff_assessment_tool/traffic_mode.cpp rename to traffxml/traff_assessment_tool/traffic_model.cpp index f0e5511ea..12cb8c9d2 100644 --- a/traffxml/traff_assessment_tool/traffic_mode.cpp +++ b/traffxml/traff_assessment_tool/traffic_model.cpp @@ -1,4 +1,4 @@ -#include "traffic_mode.hpp" +#include "traffic_model.hpp" #ifdef openlr_obsolete #include "openlr/openlr_model_xml.hpp" @@ -99,8 +99,8 @@ void RoadPointCandidate::SetActivePoint(FeatureID const & fid) } // namespace impl #endif -// TrafficMode ------------------------------------------------------------------------------------- -TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & dataSource, +// TrafficModel ------------------------------------------------------------------------------------- +TrafficModel::TrafficModel(std::string const & dataFileName, DataSource const & dataSource, std::unique_ptr drawerDelegate, std::unique_ptr pointsDelegate, QObject * parent) @@ -112,7 +112,7 @@ TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & da // TODO(mgsergio): Collect stat how many segments of each kind were parsed. pugi::xml_document doc; if (!doc.load_file(dataFileName.data())) - MYTHROW(TrafficModeError, ("Can't load file:", strerror(errno))); + MYTHROW(TrafficModelError, ("Can't load file:", strerror(errno))); // Save root node without children. { @@ -141,7 +141,7 @@ TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & da // TODO(mgsergio): Unify error handling interface of openlr_xml_mode and decoded_path parsers. auto const partnerSegmentXML = xmlSegment.child("reportSegments"); if (!openlr::SegmentFromXML(partnerSegmentXML, segment)) - MYTHROW(TrafficModeError, ("An error occurred while parsing: can't parse segment")); + MYTHROW(TrafficModelError, ("An error occurred while parsing: can't parse segment")); if (auto const route = xmlSegment.child("Route")) openlr::PathFromXML(route, m_dataSource, matchedPath); @@ -182,7 +182,7 @@ TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & da } catch (openlr::DecodedPathLoadError const & e) { - MYTHROW(TrafficModeError, ("An exception occurred while parsing", dataFileName, e.Msg())); + MYTHROW(TrafficModelError, ("An exception occurred while parsing", dataFileName, e.Msg())); } #endif @@ -190,7 +190,7 @@ TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & da } // TODO(mgsergio): Check if a path was committed, or commit it. -bool TrafficMode::SaveSampleAs(std::string const & fileName) const +bool TrafficModel::SaveSampleAs(std::string const & fileName) const { CHECK(!fileName.empty(), ("Can't save to an empty file.")); @@ -230,7 +230,7 @@ bool TrafficMode::SaveSampleAs(std::string const & fileName) const return true; } -int TrafficMode::rowCount(const QModelIndex & parent) const +int TrafficModel::rowCount(const QModelIndex & parent) const { #ifdef openlr_obsolete return static_cast(m_segments.size()); @@ -240,9 +240,9 @@ int TrafficMode::rowCount(const QModelIndex & parent) const #endif } -int TrafficMode::columnCount(const QModelIndex & parent) const { return 4; } +int TrafficModel::columnCount(const QModelIndex & parent) const { return 4; } -QVariant TrafficMode::data(const QModelIndex & index, int role) const +QVariant TrafficModel::data(const QModelIndex & index, int role) const { if (!index.isValid()) return QVariant(); @@ -270,7 +270,7 @@ QVariant TrafficMode::data(const QModelIndex & index, int role) const return QVariant(); } -QVariant TrafficMode::headerData(int section, Qt::Orientation orientation, +QVariant TrafficModel::headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const { if (orientation != Qt::Horizontal && role != Qt::DisplayRole) @@ -286,7 +286,7 @@ QVariant TrafficMode::headerData(int section, Qt::Orientation orientation, UNREACHABLE(); } -void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection const &) +void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelection const &) { #ifdef openlr_obsolete ASSERT(!selected.empty(), ()); @@ -314,7 +314,7 @@ void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection #endif } -Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const +Qt::ItemFlags TrafficModel::flags(QModelIndex const & index) const { if (!index.isValid()) return Qt::ItemIsEnabled; @@ -323,7 +323,7 @@ Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const } #ifdef openlr_obsolete -void TrafficMode::GoldifyMatchedPath() +void TrafficModel::GoldifyMatchedPath() { if (!m_currentSegment->HasMatchedPath()) { @@ -340,7 +340,7 @@ void TrafficMode::GoldifyMatchedPath() m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); } -void TrafficMode::StartBuildingPath() +void TrafficModel::StartBuildingPath() { if (!StartBuildingPathChecks()) return; @@ -352,7 +352,7 @@ void TrafficMode::StartBuildingPath() m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport()); } -void TrafficMode::PushPoint(m2::PointD const & coord, std::vector const & points) +void TrafficModel::PushPoint(m2::PointD const & coord, std::vector const & points) { impl::RoadPointCandidate point(points, coord); if (!m_goldenPath.empty()) @@ -360,18 +360,18 @@ void TrafficMode::PushPoint(m2::PointD const & coord, std::vector m_goldenPath.push_back(point); } -void TrafficMode::PopPoint() +void TrafficModel::PopPoint() { CHECK(!m_goldenPath.empty(), ("Attempt to pop point from an empty path.")); m_goldenPath.pop_back(); } -void TrafficMode::CommitPath() +void TrafficModel::CommitPath() { CHECK(m_currentSegment, ("No segments selected")); if (!m_buildingPath) - MYTHROW(TrafficModeError, ("Path building is not started")); + MYTHROW(TrafficModelError, ("Path building is not started")); SCOPE_GUARD(guard, [this] { emit EditingStopped(); }); @@ -412,7 +412,7 @@ void TrafficMode::CommitPath() m_goldenPath.clear(); } -void TrafficMode::RollBackPath() +void TrafficModel::RollBackPath() { CHECK(m_currentSegment, ("No segments selected")); CHECK(m_buildingPath, ("No path building is in progress.")); @@ -429,7 +429,7 @@ void TrafficMode::RollBackPath() emit EditingStopped(); } -void TrafficMode::IgnorePath() +void TrafficModel::IgnorePath() { CHECK(m_currentSegment, ("No segments selected")); @@ -453,23 +453,23 @@ void TrafficMode::IgnorePath() emit EditingStopped(); } -size_t TrafficMode::GetPointsCount() const +size_t TrafficModel::GetPointsCount() const { return m_goldenPath.size(); } -m2::PointD const & TrafficMode::GetPoint(size_t const index) const +m2::PointD const & TrafficModel::GetPoint(size_t const index) const { return m_goldenPath[index].GetCoordinate(); } -m2::PointD const & TrafficMode::GetLastPoint() const +m2::PointD const & TrafficModel::GetLastPoint() const { CHECK(!m_goldenPath.empty(), ("Attempt to get point from an empty path.")); return m_goldenPath.back().GetCoordinate(); } -std::vector TrafficMode::GetGoldenPathPoints() const +std::vector TrafficModel::GetGoldenPathPoints() const { std::vector coordinates; for (auto const & roadPoint : m_goldenPath) @@ -478,7 +478,7 @@ std::vector TrafficMode::GetGoldenPathPoints() const } // TODO(mgsergio): Draw the first point when the path size is 1. -void TrafficMode::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button) +void TrafficModel::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button) { if (!m_buildingPath) return; @@ -555,12 +555,12 @@ void TrafficMode::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const butto } } -bool TrafficMode::StartBuildingPathChecks() const +bool TrafficModel::StartBuildingPathChecks() const { CHECK(m_currentSegment, ("A segment should be selected before path building is started.")); if (m_buildingPath) - MYTHROW(TrafficModeError, ("Path building already in progress.")); + MYTHROW(TrafficModelError, ("Path building already in progress.")); if (m_currentSegment->HasGoldenPath()) { diff --git a/traffxml/traff_assessment_tool/traffic_mode.hpp b/traffxml/traff_assessment_tool/traffic_model.hpp similarity index 95% rename from traffxml/traff_assessment_tool/traffic_mode.hpp rename to traffxml/traff_assessment_tool/traffic_model.hpp index edf31be11..eeccca3e7 100644 --- a/traffxml/traff_assessment_tool/traffic_mode.hpp +++ b/traffxml/traff_assessment_tool/traffic_model.hpp @@ -26,7 +26,7 @@ class QItemSelection; class Selection; -DECLARE_EXCEPTION(TrafficModeError, RootException); +DECLARE_EXCEPTION(TrafficModelError, RootException); namespace traffxml { @@ -62,13 +62,13 @@ class RoadPointCandidate /// This class is used to map sample ids to real data /// and change sample evaluations. -class TrafficMode : public QAbstractTableModel +class TrafficModel : public QAbstractTableModel { Q_OBJECT public: // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything. - TrafficMode(std::string const & dataFileName, DataSource const & dataSource, + TrafficModel(std::string const & dataFileName, DataSource const & dataSource, std::unique_ptr drawerDelegate, std::unique_ptr pointsDelegate, QObject * parent = Q_NULLPTR); From 2663eda82090dfeed4612862b14fad6f59d0fe59 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 19 Aug 2025 00:16:17 +0300 Subject: [PATCH 156/252] [traff_assessment_tool] Basic traffic message panel Signed-off-by: mvglasow --- map/traffic_manager.cpp | 8 + map/traffic_manager.hpp | 17 + traffxml/traff_assessment_tool/mainwindow.cpp | 62 ++-- traffxml/traff_assessment_tool/mainwindow.hpp | 4 +- .../traff_assessment_tool/traffic_model.cpp | 303 +++++++++++++----- .../traff_assessment_tool/traffic_model.hpp | 9 +- .../traff_assessment_tool/traffic_panel.cpp | 16 +- 7 files changed, 301 insertions(+), 118 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index ca85b7437..5b143933b 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -170,6 +170,11 @@ void TrafficManager::Clear() OnTrafficDataUpdate(); } +void TrafficManager::SetTrafficUpdateCallbackFn(TrafficUpdateCallbackFn && fn) +{ + m_trafficUpdateCallbackFn = std::move(fn); +} + void TrafficManager::SetDrapeEngine(ref_ptr engine) { m_drapeEngine.Set(engine); @@ -815,6 +820,9 @@ void TrafficManager::OnTrafficDataUpdate() m_lastStorageUpdate = steady_clock::now(); } + if (m_trafficUpdateCallbackFn) + m_trafficUpdateCallbackFn.value()(feedQueueEmpty); + if (!notifyDrape && !notifyObserver) return; diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 6e14bb4c7..8c24ec2f2 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -42,6 +42,7 @@ class TrafficManager final : public traffxml::TraffSourceManager public: using CountryInfoGetterFn = std::function; using CountryParentNameGetterFn = std::function; + using TrafficUpdateCallbackFn = std::function; /** * @brief Global state of traffic information. @@ -303,6 +304,15 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void Clear(); + /** + * @brief Registers a callback function which gets called on traffic updates. + * + * Intended for testing. + * + * @param fn The callback function. + */ + void SetTrafficUpdateCallbackFn(TrafficUpdateCallbackFn && fn); + private: /** @@ -682,6 +692,13 @@ class TrafficManager final : public traffxml::TraffSourceManager * thread). */ std::map m_allMwmColoring; + + /** + * @brief Callback function which gets called on traffic updates. + * + * Intended for testing. + */ + std::optional m_trafficUpdateCallbackFn; }; extern std::string DebugPrint(TrafficManager::TrafficState state); diff --git a/traffxml/traff_assessment_tool/mainwindow.cpp b/traffxml/traff_assessment_tool/mainwindow.cpp index 9741f021c..3bb37d106 100644 --- a/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/traffxml/traff_assessment_tool/mainwindow.cpp @@ -311,35 +311,42 @@ MainWindow::MainWindow(Framework & framework) #endif } -void MainWindow::CreateTrafficPanel(std::string const & dataFilePath) +void MainWindow::CreateTrafficPanel() { - m_trafficModel = new TrafficModel(dataFilePath, - m_framework.GetDataSource(), - std::make_unique(m_framework), - std::make_unique(m_framework)); - - connect(m_mapWidget, &MapWidget::TrafficMarkupClick, - m_trafficModel, &TrafficModel::OnClick); - connect(m_trafficModel, &TrafficModel::EditingStopped, - this, &MainWindow::OnPathEditingStop); - connect(m_trafficModel, &TrafficModel::SegmentSelected, - [](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); }); - - m_docWidget = new QDockWidget(tr("Routes"), this); - addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget); - - m_docWidget->setWidget(new TrafficPanel(m_trafficModel, m_docWidget)); - - m_docWidget->adjustSize(); - m_docWidget->setMinimumWidth(400); - m_docWidget->show(); + if (!m_trafficModel) + { + // TODO simplify the call, everything depends on m_framework + m_trafficModel = new TrafficModel(m_framework, m_framework.GetDataSource(), + std::make_unique(m_framework), + std::make_unique(m_framework)); + + connect(m_mapWidget, &MapWidget::TrafficMarkupClick, + m_trafficModel, &TrafficModel::OnClick); + connect(m_trafficModel, &TrafficModel::EditingStopped, + this, &MainWindow::OnPathEditingStop); + connect(m_trafficModel, &TrafficModel::SegmentSelected, + [](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); }); + } + + if (!m_dockWidget) + { + m_dockWidget = new QDockWidget(tr("Messages"), this); + addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_dockWidget); + + m_dockWidget->setWidget(new TrafficPanel(m_trafficModel, m_dockWidget)); + + m_dockWidget->adjustSize(); + m_dockWidget->setMinimumWidth(400); + } + m_dockWidget->show(); } void MainWindow::DestroyTrafficPanel() { - removeDockWidget(m_docWidget); - delete m_docWidget; - m_docWidget = nullptr; + LOG(LINFO, ("enter")); + removeDockWidget(m_dockWidget); + delete m_dockWidget; + m_dockWidget = nullptr; delete m_trafficModel; m_trafficModel = nullptr; @@ -386,19 +393,16 @@ void MainWindow::OnOpenTrafficSample() return; } -// TODO create traffic panel with TraFF messages -#if 0 try { - CreateTrafficPanel(dlg.GetDataFilePath()); + CreateTrafficPanel(); } catch (TrafficModelError const & e) { - QMessageBox::critical(this, "Data loading error", QString("Can't load data file.")); + QMessageBox::critical(this, "Data loading error", QString("Can't create traffic panel.")); LOG(LERROR, (e.Msg())); return; } -#endif #ifdef openlr_obsolete m_goldifyMatchedPathAction->setEnabled(true /* enabled */); diff --git a/traffxml/traff_assessment_tool/mainwindow.hpp b/traffxml/traff_assessment_tool/mainwindow.hpp index e3c460df4..c6b964098 100644 --- a/traffxml/traff_assessment_tool/mainwindow.hpp +++ b/traffxml/traff_assessment_tool/mainwindow.hpp @@ -33,7 +33,7 @@ class MainWindow : public QMainWindow explicit MainWindow(Framework & framework); private: - void CreateTrafficPanel(std::string const & dataFilePath); + void CreateTrafficPanel(); void DestroyTrafficPanel(); /** @@ -57,7 +57,7 @@ class MainWindow : public QMainWindow Framework & m_framework; traffxml::TrafficModel * m_trafficModel = nullptr; - QDockWidget * m_docWidget = nullptr; + QDockWidget * m_dockWidget = nullptr; #ifdef openlr_obsolete QAction * m_goldifyMatchedPathAction = nullptr; diff --git a/traffxml/traff_assessment_tool/traffic_model.cpp b/traffxml/traff_assessment_tool/traffic_model.cpp index 12cb8c9d2..ec1493f8d 100644 --- a/traffxml/traff_assessment_tool/traffic_model.cpp +++ b/traffxml/traff_assessment_tool/traffic_model.cpp @@ -99,94 +99,223 @@ void RoadPointCandidate::SetActivePoint(FeatureID const & fid) } // namespace impl #endif -// TrafficModel ------------------------------------------------------------------------------------- -TrafficModel::TrafficModel(std::string const & dataFileName, DataSource const & dataSource, - std::unique_ptr drawerDelegate, - std::unique_ptr pointsDelegate, - QObject * parent) - : QAbstractTableModel(parent) - , m_dataSource(dataSource) - , m_drawerDelegate(std::move(drawerDelegate)) - , m_pointsDelegate(std::move(pointsDelegate)) +QVariant GetCountryAndRoadRef(TraffMessage const & message) { - // TODO(mgsergio): Collect stat how many segments of each kind were parsed. - pugi::xml_document doc; - if (!doc.load_file(dataFileName.data())) - MYTHROW(TrafficModelError, ("Can't load file:", strerror(errno))); - - // Save root node without children. + std::string result = ""; + if (message.m_location) { - auto const root = doc.document_element(); - auto node = m_template.append_child(root.name()); - for (auto const & attr : root.attributes()) - node.append_copy(attr); + if (message.m_location.value().m_country) + result += message.m_location.value().m_country.value(); + if (message.m_location.value().m_roadRef) + { + if (!result.empty()) + result += '\n'; + result += message.m_location.value().m_roadRef.value(); + } } + return QString::fromStdString(result); +} - // Select all Segment elements that are direct children of the root. - auto const segments = doc.document_element().select_nodes("./Segment"); - -#ifdef openlr_obsolete - try +/** + * @brief Returns a descriptive text for a point. + * + * The result is the junction name (if any), followed by the junction number or kilometric point (if any). + * + * @param point + * @return + */ +std::string GetPointDetail(Point const & point) +{ + std::string result = point.m_junctionName ? point.m_junctionName.value() : ""; + std::string junctionRefOrKmp = point.m_junctionRef ? point.m_junctionRef.value() : + point.m_distance ? std::format("km {0:.0f}", point.m_distance.value()) : ""; + if (!junctionRefOrKmp.empty()) { - for (auto const & xpathNode : segments) - { - auto const xmlSegment = xpathNode.node(); + if (result.empty()) + result = junctionRefOrKmp; + else + result += " (" + junctionRefOrKmp + ")"; + } + return result; +} - openlr::Path matchedPath; - openlr::Path fakePath; - openlr::Path goldenPath; +/** + * @brief Returns a descriptive text for the location on the road. + * + * The result is one or two junctions, with an arrow to indicate direction where applicable. + * + * @param location + * @return + */ +std::string GetLocationDetail(TraffLocation const & location) +{ + if (location.m_at) + return GetPointDetail(location.m_at.value()); - openlr::LinearSegment segment; + std::string nameFrom = location.m_from ? GetPointDetail(location.m_from.value()) : ""; + std::string nameTo = location.m_from ? GetPointDetail(location.m_to.value()) : ""; - // TODO(mgsergio): Unify error handling interface of openlr_xml_mode and decoded_path parsers. - auto const partnerSegmentXML = xmlSegment.child("reportSegments"); - if (!openlr::SegmentFromXML(partnerSegmentXML, segment)) - MYTHROW(TrafficModelError, ("An error occurred while parsing: can't parse segment")); + if (nameFrom == nameTo) + return nameFrom; + else if (!nameFrom.empty()) + { + if (nameTo.empty()) + return nameFrom + ((location.m_directionality == Directionality::OneDirection) ? " →" : " ↔"); + else + return nameFrom + + ((location.m_directionality == Directionality::OneDirection) ? " → " : " ↔ ") + + nameTo; + } + else if (!nameTo.empty()) + return ((location.m_directionality == Directionality::OneDirection) ? "→ " : "↔ ") + nameTo; + else if (location.m_via) + return GetPointDetail(location.m_via.value()); + return ""; +} - if (auto const route = xmlSegment.child("Route")) - openlr::PathFromXML(route, m_dataSource, matchedPath); - if (auto const route = xmlSegment.child("FakeRoute")) - openlr::PathFromXML(route, m_dataSource, fakePath); - if (auto const route = xmlSegment.child("GoldenRoute")) - openlr::PathFromXML(route, m_dataSource, goldenPath); +/** + * @brief Returns a description for the events in the message. + * + * @param message + * @return + */ +std::string GetEventText(TraffMessage const & message) +{ + std::string result = ""; + for (auto const & event : message.m_events) + { + if (!result.empty()) + result += ", "; + result += DebugPrint(event.m_type); + // TODO quantifiers (format "q = {}") + // TODO supplementary information (not in struct yet) + if (event.m_length) + result += std::format(" for {0:d} m", event.m_length.value()); + if (event.m_speed) + result += std::format(", speed {0:d} km/h", event.m_speed.value()); + } + return result; +} - uint32_t positiveOffsetM = 0; - uint32_t negativeOffsetM = 0; - if (auto const reportSegmentLRC = partnerSegmentXML.child("ReportSegmentLRC")) +QVariant GetDescription(TraffMessage const & message) +{ + std::string result = ""; + if (message.m_cancellation) + { + result = "Cancellation"; + } + else + { + if (message.m_location) + { + std::string direction = ""; + if (message.m_location.value().m_directionality == Directionality::BothDirections) + direction = "both directions"; + else { - if (auto const method = reportSegmentLRC.child("method")) + // TODO determine bearing and convert it to a string (northbound, southeastbound etc.) + /* + std::optional loc1 = message.m_location.value().m_from; + std::optional loc2 = message.m_location.value().m_to; + if (loc2 && !loc1) + loc1 = message.m_location.value().m_at; + else if (loc1 && !loc2) + loc2 = message.m_location.value().m_at; + if (loc1 && loc2) { - if (auto const locationReference = method.child("olr:locationReference")) - { - if (auto const optionLinearLocationReference = locationReference - .child("olr:optionLinearLocationReference")) - { - if (auto const positiveOffset = optionLinearLocationReference.child("olr:positiveOffset")) - positiveOffsetM = UintValueFromXML(positiveOffset); - - if (auto const negativeOffset = optionLinearLocationReference.child("olr:negativeOffset")) - negativeOffsetM = UintValueFromXML(negativeOffset); - } - } + ms::LatLon c1 = loc1.value().m_coordinates; + ms::LatLon c2 = loc2.value().m_coordinates; + // TODO figure out bearing (as string) } + */ } + if (message.m_location.value().m_roadName) + { + // roadName, town, direction + if (message.m_location.value().m_town) + result += message.m_location.value().m_town.value() + ", "; + // TODO territory? + result += message.m_location.value().m_roadName.value(); + if (!direction.empty()) + result += ", " + direction; + } + else if (message.m_location.value().m_origin && message.m_location.value().m_destination) + // origin–destination with arrow + result += message.m_location.value().m_origin.value() + + ((message.m_location.value().m_directionality == Directionality::BothDirections) ? " ↔ " : " → ") + + message.m_location.value().m_destination.value(); + else if (!message.m_location.value().m_origin && !message.m_location.value().m_destination) + // direction, if available + result += direction; + else if (message.m_location.value().m_directionality == Directionality::OneDirection) + { + // unidirectional, one endpoint; replacce the other with direction + if (message.m_location.value().m_origin) + result += message.m_location.value().m_origin.value() + " → " + direction; + else + result += direction + " → " + message.m_location.value().m_destination.value(); + } + else + // direction, if available (no meaningful way to use origin or destination) + result += direction; - m_segments.emplace_back(segment, positiveOffsetM, negativeOffsetM, matchedPath, fakePath, - goldenPath, partnerSegmentXML); - if (auto const status = xmlSegment.child("Ignored")) + auto locationDetail = GetLocationDetail(message.m_location.value()); + if (!locationDetail.empty()) { - if (status.text().as_bool()) - m_segments.back().Ignore(); + if (!result.empty()) + result += "\n"; + result += locationDetail; } } + auto eventText = GetEventText(message); + if (!eventText.empty()) + { + if (!result.empty()) + result += "\n"; + result += eventText; + } + // TODO start/end date } - catch (openlr::DecodedPathLoadError const & e) - { - MYTHROW(TrafficModelError, ("An exception occurred while parsing", dataFileName, e.Msg())); - } -#endif + if (!result.empty()) + result += "\n"; + result += message.m_id.substr(0, message.m_id.find(':')); + result += "\t" + DebugPrint(message.m_updateTime); + // add an extra line to get bigger rows (default is too small) + result += "\n"; + return QString::fromStdString(result); +} + +// TrafficModel ------------------------------------------------------------------------------------- +TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, + std::unique_ptr drawerDelegate, // TODO do we need that? + std::unique_ptr pointsDelegate, // TODO do we need that? + QObject * parent) + : QAbstractTableModel(parent) + , m_dataSource(dataSource) + , m_drawerDelegate(std::move(drawerDelegate)) + , m_pointsDelegate(std::move(pointsDelegate)) +{ + framework.GetTrafficManager().SetTrafficUpdateCallbackFn([this, &framework](bool final) { + /* + * If final is true, this indicates the queue has been emptied and no further updates are + * imminent. Such updates should always be processed. If final is false, we can optimize by + * selectively skipping updates. + */ + GetPlatform().RunTask(Platform::Thread::Gui, [this, &framework]() + { + beginResetModel(); + auto const messageCache = framework.GetTrafficManager().GetMessageCache(); + m_messages.clear(); + m_messages.reserve(messageCache.size()); - LOG(LINFO, (segments.size(), "segments are loaded.")); + for (auto & entry : messageCache) + m_messages.push_back(std::move(entry.second)); + + endResetModel(); + + LOG(LINFO, ("Messages:", m_messages.size())); + }); + }); } // TODO(mgsergio): Check if a path was committed, or commit it. @@ -232,15 +361,10 @@ bool TrafficModel::SaveSampleAs(std::string const & fileName) const int TrafficModel::rowCount(const QModelIndex & parent) const { -#ifdef openlr_obsolete - return static_cast(m_segments.size()); -#else - // TODO return a meaningful value - return 0; -#endif + return static_cast(m_messages.size()); } -int TrafficModel::columnCount(const QModelIndex & parent) const { return 4; } +int TrafficModel::columnCount(const QModelIndex & parent) const { return 2; } QVariant TrafficModel::data(const QModelIndex & index, int role) const { @@ -266,24 +390,39 @@ QVariant TrafficModel::data(const QModelIndex & index, int role) const if (index.column() == 3) return m_segments[index.row()].GetNegativeOffset(); #endif + switch (index.column()) + { + case 0: + return GetCountryAndRoadRef(m_messages[index.row()]); + case 1: + return GetDescription(m_messages[index.row()]); + default: + return QVariant(); + } - return QVariant(); + UNREACHABLE(); } QVariant TrafficModel::headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const { - if (orientation != Qt::Horizontal && role != Qt::DisplayRole) + /* + * Qt seems buggy here. Initially, we seem to get called with Qt::Vertical for a horizontal + * header, i.e. a row of column headers, and Qt::Horizontal for a vertical header (column of row + * headers). Using the intuitively correct value will result in incorrect behavior and a lot of + * head-scratching if you use just one type of header. + * However, this (presumed) bug does not seem to be consistent, as updates call us with + * Qt::Vertical and a row number (which can be beyond the number of columns). + */ + if (orientation == Qt::Horizontal && role != Qt::DisplayRole) return QVariant(); switch (section) { - case 0: return "Segment id"; break; - case 1: return "Status code"; break; - case 2: return "Positive offset (Meters)"; break; - case 3: return "Negative offset (Meters)"; break; + case 0: return "Road ref"; break; + case 1: return "Description"; break; } - UNREACHABLE(); + return QVariant(); } void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelection const &) diff --git a/traffxml/traff_assessment_tool/traffic_model.hpp b/traffxml/traff_assessment_tool/traffic_model.hpp index eeccca3e7..7fa37ee75 100644 --- a/traffxml/traff_assessment_tool/traffic_model.hpp +++ b/traffxml/traff_assessment_tool/traffic_model.hpp @@ -9,6 +9,7 @@ #ifdef openlr_obsolete #include "openlr/decoded_path.hpp" #endif +#include "traffxml/traff_model.hpp" #include "indexer/data_source.hpp" @@ -68,7 +69,7 @@ class TrafficModel : public QAbstractTableModel public: // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything. - TrafficModel(std::string const & dataFileName, DataSource const & dataSource, + TrafficModel(Framework & framework, DataSource const & dataSource, std::unique_ptr drawerDelegate, std::unique_ptr pointsDelegate, QObject * parent = Q_NULLPTR); @@ -126,6 +127,12 @@ public slots: // Non-owning pointer to an element of m_segments. SegmentCorrespondence * m_currentSegment = nullptr; #endif + std::vector m_messages; + + /** + * Non-owning pointer to an element of m_messages. + */ + TraffMessage * m_message = nullptr; std::unique_ptr m_drawerDelegate; std::unique_ptr m_pointsDelegate; diff --git a/traffxml/traff_assessment_tool/traffic_panel.cpp b/traffxml/traff_assessment_tool/traffic_panel.cpp index 89030d16b..caeb2670d 100644 --- a/traffxml/traff_assessment_tool/traffic_panel.cpp +++ b/traffxml/traff_assessment_tool/traffic_panel.cpp @@ -67,15 +67,23 @@ void TrafficPanel::CreateTable(QAbstractItemModel * trafficModel) m_table->setShowGrid(false); m_table->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); m_table->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); - m_table->verticalHeader()->setVisible(false); - m_table->horizontalHeader()->setVisible(true); - m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - m_table->setModel(trafficModel); m_table->setItemDelegate(new ComboBoxDelegate()); + // the model must be set before we can set dimensions and headers + m_table->verticalHeader()->setVisible(false); + m_table->horizontalHeader()->setVisible(true); + //m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_table->setColumnWidth(0, 80); + m_table->setColumnWidth(1, 300); + connect(m_table->selectionModel(), SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)), trafficModel, SLOT(OnItemSelected(QItemSelection const &, QItemSelection const &))); + connect(trafficModel, &QAbstractItemModel::modelReset, + m_table, [this]() { + m_table->resizeRowsToContents(); + //m_table->resizeColumnsToContents(); + }); } } // namespace traffxml From 1d42d3b43121991fb0f394aad29d871a6383ec6d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 19 Aug 2025 20:03:04 +0300 Subject: [PATCH 157/252] [traff_assessment_tool] On click, zoom to message and show reference points Signed-off-by: mvglasow --- .../traff_assessment_tool/traffic_model.cpp | 53 +++++++++++++------ .../traff_assessment_tool/traffic_model.hpp | 1 + 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/traffxml/traff_assessment_tool/traffic_model.cpp b/traffxml/traff_assessment_tool/traffic_model.cpp index ec1493f8d..e9f563af2 100644 --- a/traffxml/traff_assessment_tool/traffic_model.cpp +++ b/traffxml/traff_assessment_tool/traffic_model.cpp @@ -4,8 +4,12 @@ #include "openlr/openlr_model_xml.hpp" #endif +#include "drape_frontend/drape_api.hpp" + #include "indexer/data_source.hpp" +#include "map/framework.hpp" + #include "base/assert.hpp" #include "base/scope_guard.hpp" @@ -291,6 +295,7 @@ TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, std::unique_ptr pointsDelegate, // TODO do we need that? QObject * parent) : QAbstractTableModel(parent) + , m_framework(framework) , m_dataSource(dataSource) , m_drawerDelegate(std::move(drawerDelegate)) , m_pointsDelegate(std::move(pointsDelegate)) @@ -427,30 +432,44 @@ QVariant TrafficModel::headerData(int section, Qt::Orientation orientation, void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelection const &) { -#ifdef openlr_obsolete ASSERT(!selected.empty(), ()); - ASSERT(!m_segments.empty(), ()); + ASSERT(!m_messages.empty(), ()); auto const row = selected.front().top(); - CHECK_LESS(static_cast(row), m_segments.size(), ()); - m_currentSegment = &m_segments[row]; + CHECK_LESS(static_cast(row), m_messages.size(), ()); + auto message = &m_messages[row]; + if (!message->m_location) + return; - auto const & partnerSegment = m_currentSegment->GetPartnerSegment(); - auto const & partnerSegmentPoints = partnerSegment.GetMercatorPoints(); - auto const & viewportCenter = partnerSegmentPoints.front(); + m2::RectD rect; + std::vector points; + + for (auto & coords : { + message->m_location.value().m_from, + message->m_location.value().m_at, + message->m_location.value().m_via, + message->m_location.value().m_notVia, + message->m_location.value().m_to + }) + if (coords) + { + auto point = mercator::FromLatLon(coords.value().m_coordinates); + rect.Add(point); + points.push_back(point); + } - m_drawerDelegate->ClearAllPaths(); - // TODO(mgsergio): Use a better way to set viewport and scale. - m_drawerDelegate->SetViewportCenter(viewportCenter); - m_drawerDelegate->DrawEncodedSegment(partnerSegmentPoints); - if (m_currentSegment->HasMatchedPath()) - m_drawerDelegate->DrawDecodedSegments(GetPoints(m_currentSegment->GetMatchedPath())); - if (m_currentSegment->HasGoldenPath()) - m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); + if (rect.IsValid()) + { + rect.Scale(1.5); + m_framework.ShowRect(rect, 15 /* maxScale */, true /* animation */, true /* useVisibleViewport */); + } - emit SegmentSelected(static_cast(partnerSegment.m_segmentId)); -#endif + auto editSession = m_framework.GetBookmarkManager().GetEditSession(); + editSession.ClearGroup(UserMark::Type::DEBUG_MARK); + editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true); + for (auto const & p : points) + editSession.CreateUserMark(p); } Qt::ItemFlags TrafficModel::flags(QModelIndex const & index) const diff --git a/traffxml/traff_assessment_tool/traffic_model.hpp b/traffxml/traff_assessment_tool/traffic_model.hpp index 7fa37ee75..7093173d5 100644 --- a/traffxml/traff_assessment_tool/traffic_model.hpp +++ b/traffxml/traff_assessment_tool/traffic_model.hpp @@ -121,6 +121,7 @@ public slots: bool StartBuildingPathChecks() const; #endif + Framework & m_framework; DataSource const & m_dataSource; #ifdef openlr_obsolete std::vector m_segments; From d46c0fec76f8d430c6feab254525a8d794a7eadc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 24 Aug 2025 22:15:30 +0300 Subject: [PATCH 158/252] [traffic] Fix endless loop in TrafficManager::Invalidate() Signed-off-by: mvglasow --- map/traffic_manager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 5b143933b..f365967bd 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -307,7 +307,10 @@ void TrafficManager::Invalidate(MwmSet::MwmId const & mwmId) for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) { if (!it->second.m_location) + { + it++; continue; + } bool isInvalid = false; From ef806cf18a33fd6be59ebf1dda4772637237e5c8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 28 Aug 2025 21:10:42 +0300 Subject: [PATCH 159/252] Synchronize map updates with traffic manager Signed-off-by: mvglasow --- map/framework.cpp | 14 ++++---- map/traffic_manager.cpp | 72 +++++++++++++++++++---------------------- map/traffic_manager.hpp | 16 +++++++++ 3 files changed, 57 insertions(+), 45 deletions(-) diff --git a/map/framework.cpp b/map/framework.cpp index 3ae4a917b..a3b37c201 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -443,13 +443,13 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, m2::RectD rect = mercator::Bounds::FullRect(); if (localFile && localFile->OnDisk(MapFileType::Map)) - { - auto const res = RegisterMap(*localFile); - MwmSet::MwmId const & id = res.first; - if (id.IsAlive()) - rect = id.GetInfo()->m_bordersRect; - m_trafficManager.Invalidate(id); - } + m_trafficManager.RunSynchronized([this, localFile, &rect](){ + auto const res = RegisterMap(*localFile); + MwmSet::MwmId const & id = res.first; + if (id.IsAlive()) + rect = id.GetInfo()->m_bordersRect; + m_trafficManager.Invalidate(id); + }); m_transitManager.Invalidate(); m_isolinesManager.Invalidate(); diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index f365967bd..d3214d83c 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -301,53 +301,49 @@ void TrafficManager::Invalidate(MwmSet::MwmId const & mwmId) auto const mwmRect = mwmId.GetInfo()->m_bordersRect; // m2::RectD traffxml::TraffFeed invalidated; + for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) { - std::lock_guard lock(m_mutex); - - for (auto it = m_messageCache.begin(); it != m_messageCache.end(); ) + if (!it->second.m_location) { - if (!it->second.m_location) - { - it++; - continue; - } - - bool isInvalid = false; + it++; + continue; + } - // invalidate if decoded location uses a previous version of the MWM - for (auto const & [decodedMwmId, coloring] : it->second.m_decoded) - if (decodedMwmId.GetInfo()->GetCountryName() == mwmId.GetInfo()->GetCountryName()) - isInvalid = true; + bool isInvalid = false; - // invalidate if bounding rect of reference points intersects with bounding rect of MWM - if (!isInvalid) - { - m2::RectD locationRect; - for (auto const & point : {it->second.m_location.value().m_from, - it->second.m_location.value().m_via, - it->second.m_location.value().m_at, - it->second.m_location.value().m_to}) - if (point) - locationRect.Add(mercator::FromLatLon(point.value().m_coordinates)); - isInvalid = locationRect.IsIntersect(mwmRect); - } + // invalidate if decoded location uses a previous version of the MWM + for (auto const & [decodedMwmId, coloring] : it->second.m_decoded) + if (decodedMwmId.GetInfo()->GetCountryName() == mwmId.GetInfo()->GetCountryName()) + isInvalid = true; - if (isInvalid) - { - traffxml::TraffMessage message(it->second); - message.m_decoded.clear(); - invalidated.push_back(message); - it = m_messageCache.erase(it); - } - else - ++it; + // invalidate if bounding rect of reference points intersects with bounding rect of MWM + if (!isInvalid) + { + m2::RectD locationRect; + for (auto const & point : {it->second.m_location.value().m_from, + it->second.m_location.value().m_via, + it->second.m_location.value().m_at, + it->second.m_location.value().m_to}) + if (point) + locationRect.Add(mercator::FromLatLon(point.value().m_coordinates)); + isInvalid = locationRect.IsIntersect(mwmRect); } - if (!invalidated.empty()) + if (isInvalid) { - m_feedQueue.insert(m_feedQueue.begin(), invalidated); - m_condition.notify_one(); + traffxml::TraffMessage message(it->second); + message.m_decoded.clear(); + invalidated.push_back(message); + it = m_messageCache.erase(it); } + else + ++it; + } + + if (!invalidated.empty()) + { + m_feedQueue.insert(m_feedQueue.begin(), invalidated); + m_condition.notify_one(); } } diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 8c24ec2f2..82b816faf 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -224,6 +224,9 @@ class TrafficManager final : public traffxml::TraffSourceManager * for these locations are discarded and decoded again, ensuring they are based on the new MWM. * The TraFF messages themselves remain unchanged. * + * This method must either be called from a lambda function passed to `RunSynchronized()`, + * or the caller must explicitly lock the private `m_mutex` prior to calling this method. + * * @param mwmId The newly addded MWM. */ void Invalidate(MwmSet::MwmId const & mwmId); @@ -313,6 +316,19 @@ class TrafficManager final : public traffxml::TraffSourceManager */ void SetTrafficUpdateCallbackFn(TrafficUpdateCallbackFn && fn); + /** + * @brief Runs a function guarded by the traffic manager mutex. + * + * This locks `m_mutex`, then runs `f` and releases the mutex. + * + * @param f + */ + void RunSynchronized(std::function f) + { + std::lock_guard lock(m_mutex); + f(); + } + private: /** From ac87e3c58547654ff32c9dc06c16865b258483b4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 29 Aug 2025 17:49:15 +0300 Subject: [PATCH 160/252] [traffic] Handle MWM removal during traffic update Signed-off-by: mvglasow --- map/traffic_manager.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index d3214d83c..954686f9a 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -844,9 +844,9 @@ void TrafficManager::OnTrafficDataUpdate() /* * Much of this code is copied and pasted together from old MWM code, with some minor adaptations: * - * ForEachActiveMwm and the assertion (not the rest of the body) is from RequestTrafficData() - * (now RequestTrafficSubscription()), modification: cycle over all MWMs (active or not). - * trafficCache lookup is original code. + * ForEachMwm (not the body) is from RequestTrafficData() (now RequestTrafficSubscription()), + * modification: cycle over all MWMs (active or not). + * Handling dead MWMs and traffic cache lookup is original code. * TrafficInfo construction is taken fron ThreadRoutine(), with modifications (different constructor). * The remainder of the loop is from OnTrafficDataResponse(traffic::TrafficInfo &&), with some modifications * (removed CacheEntry logic; deciding whether to notify a component and managing timestamps is original code). @@ -860,8 +860,14 @@ void TrafficManager::OnTrafficDataUpdate() MwmSet::MwmId const mwmId(info); - ASSERT(mwmId.IsAlive(), ()); auto tcit = m_allMwmColoring.find(mwmId); + if (!mwmId.IsAlive()) + { + // MWM was deleted or replaced during decoding + if (tcit != m_allMwmColoring.end()) + m_allMwmColoring.erase(tcit); + return; + } if (tcit != m_allMwmColoring.end()) { traffic::TrafficInfo::Coloring coloring = tcit->second; From 3ed31a575f3069f37c4681e17b704628fee2edb6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 29 Aug 2025 22:12:33 +0300 Subject: [PATCH 161/252] [traffic] Inhibit perodic traffic updates during route calculation Signed-off-by: mvglasow --- map/traffic_manager.cpp | 18 ++++++++++++++++-- map/traffic_manager.hpp | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/map/traffic_manager.cpp b/map/traffic_manager.cpp index 954686f9a..4270e6791 100644 --- a/map/traffic_manager.cpp +++ b/map/traffic_manager.cpp @@ -205,6 +205,10 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, // TODO assert we’re running on the UI thread LOG(LINFO, ("Routing session state changed from", previous, "to", current)); LOG(LINFO, ("Running on thread", std::this_thread::get_id())); + + m_observerInhibited = ((current == routing::SessionState::RouteBuilding) + || (current == routing::SessionState::RouteRebuilding)); + /* * Filter based on session state (see routing_callbacks.hpp for states and transitions). * @@ -796,11 +800,21 @@ void TrafficManager::OnTrafficDataUpdate() { auto const currentTime = steady_clock::now(); auto const drapeAge = currentTime - m_lastDrapeUpdate; - auto const observerAge = currentTime - m_lastObserverUpdate; auto const storageAge = currentTime - m_lastStorageUpdate; notifyDrape = (drapeAge >= kDrapeUpdateInterval); - notifyObserver = (observerAge >= kObserverUpdateInterval); updateStorage = (storageAge >= kStorageUpdateInterval); + if (!m_observerInhibited) + { + /* + * To avoid resetting the route over and over again while building, inhibit periodic updates + * to the router while a route is being built. Periodic updates will resume once the route is + * fully built. During route calculation, traffic updates are sent only if the queue is empty. + * TODO test this with TMC, where messages arrive one by one, or in short bursts, and the + * queue may run empty multiple times while a route is being calculated. + */ + auto const observerAge = currentTime - m_lastObserverUpdate; + notifyObserver = (observerAge >= kObserverUpdateInterval); + } } if (!m_storage || IsTestMode()) diff --git a/map/traffic_manager.hpp b/map/traffic_manager.hpp index 82b816faf..a425e2539 100644 --- a/map/traffic_manager.hpp +++ b/map/traffic_manager.hpp @@ -654,6 +654,19 @@ class TrafficManager final : public traffxml::TraffSourceManager */ std::chrono::time_point m_lastObserverUpdate; + /** + * @brief Whether updates to the observer are currently inhibited. + * + * Updates are inhibited while a route calculation is in progress. In this state, the observer + * receives traffic updates only if the queue has run empty, not if nore locations are waiting + * to be decoded. + * + * Inhibtiting the observer is necessary as traffic updates during route calculation will cause + * it to restart from scratch. Once the route has been calculated, updates will trigger a + * recalculation, which is much faster (seconds or less). + */ + bool m_observerInhibited = false; + /** * @brief When the cache file was last updated. */ From 0713e2232897139087070a7839b2c150caee468e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 1 Sep 2025 22:02:45 +0300 Subject: [PATCH 162/252] [traffic] Set default traffic URL to dev server Signed-off-by: mvglasow --- private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/private.h b/private.h index d2a93edd8..c5ee53908 100644 --- a/private.h +++ b/private.h @@ -8,7 +8,7 @@ #define METASERVER_URL "https://cdn-us-1.comaps.app/servers" #define DEFAULT_URLS_JSON "[ \"https://cdn-us-2.comaps.tech/\", \"https://comaps.firewall-gateway.de/\", \"https://cdn-fi-1.comaps.app/\", \"https://comaps-cdn.s3-website.cloud.ru/\" ]" #define DEFAULT_CONNECTION_CHECK_IP "151.101.195.52" // For now the IP of comaps.app (Fastly CDN) -// TODO temporary URL, replace with final one once we have a server -#define TRAFFIC_HTTP_URL_DEFAULT "http://traff:8080/subscription-manager" +// TODO development server, replace with live instance (once we have one) before merging into main +#define TRAFFIC_HTTP_URL_DEFAULT "https://api.dev.traffxml.org" #define USER_BINDING_PKCS12 "" #define USER_BINDING_PKCS12_PASSWORD "" From 48e8f32e01530f57154a909ecc0d3c1574623a16 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 12 Sep 2025 18:46:50 +0300 Subject: [PATCH 163/252] [traff_assessment_tool] Use different marker colors for reference points Also fixes bug with DebugMarkPoint no longer rendering after the last merge Signed-off-by: mvglasow --- .../traff_assessment_tool/traffic_model.cpp | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/libs/traffxml/traff_assessment_tool/traffic_model.cpp b/libs/traffxml/traff_assessment_tool/traffic_model.cpp index e9f563af2..1b31d6cfa 100644 --- a/libs/traffxml/traff_assessment_tool/traffic_model.cpp +++ b/libs/traffxml/traff_assessment_tool/traffic_model.cpp @@ -18,6 +18,13 @@ namespace traffxml { + +constexpr static dp::Color kColorFrom(0x309302ff); +constexpr static dp::Color kColorAt(0x1a5ec1ff); +constexpr static dp::Color kColorVia(0xf19721ff); +constexpr static dp::Color kColorNotVia(0x8c5678ff); +constexpr static dp::Color kColorTo(0xe42300ff); + namespace { void RemovePointFromPull(m2::PointD const & toBeRemoved, std::vector & pool) @@ -443,20 +450,24 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio return; m2::RectD rect; - std::vector points; - - for (auto & coords : { - message->m_location.value().m_from, - message->m_location.value().m_at, - message->m_location.value().m_via, - message->m_location.value().m_notVia, - message->m_location.value().m_to + + auto editSession = m_framework.GetBookmarkManager().GetEditSession(); + editSession.ClearGroup(UserMark::Type::COLORED); + editSession.SetIsVisible(UserMark::Type::COLORED, true); + + for (auto & [coords, color] : { + std::pair{message->m_location.value().m_from, kColorFrom}, + std::pair{message->m_location.value().m_at, kColorAt}, + std::pair{message->m_location.value().m_via, kColorVia}, + std::pair{message->m_location.value().m_notVia, kColorNotVia}, + std::pair{message->m_location.value().m_to, kColorTo} }) if (coords) { auto point = mercator::FromLatLon(coords.value().m_coordinates); rect.Add(point); - points.push_back(point); + auto mark = editSession.CreateUserMark(point); + mark->SetColor(color); } if (rect.IsValid()) @@ -464,12 +475,6 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio rect.Scale(1.5); m_framework.ShowRect(rect, 15 /* maxScale */, true /* animation */, true /* useVisibleViewport */); } - - auto editSession = m_framework.GetBookmarkManager().GetEditSession(); - editSession.ClearGroup(UserMark::Type::DEBUG_MARK); - editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true); - for (auto const & p : points) - editSession.CreateUserMark(p); } Qt::ItemFlags TrafficModel::flags(QModelIndex const & index) const From 5ba708caff81d818b3b3c9a29c71f396bfcce45c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 12 Sep 2025 19:06:21 +0300 Subject: [PATCH 164/252] [map] Documentation Signed-off-by: mvglasow --- libs/map/user_mark.hpp | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/libs/map/user_mark.hpp b/libs/map/user_mark.hpp index a9ff23a9b..71fbced5d 100644 --- a/libs/map/user_mark.hpp +++ b/libs/map/user_mark.hpp @@ -36,20 +36,61 @@ class UserMark : public df::UserPointMark RoadWarningFirstFerry, }; + /** + * @brief User mark types. + * + * `UserMark` subclasses are assigned a value from this enum. + */ enum Type : uint32_t { + /** + * `Bookmark` + */ BOOKMARK, // Should always be the first one + /** + * `ApiMarkPoint` + */ API, + /** + * `SearchMarkPoint` + */ SEARCH, + /** + * `StaticMarkPoint` + */ STATIC, + /** + * `RouteMarkPoint` + */ ROUTING, + /** + * `SpeedCameraMark` + */ SPEED_CAM, + /** + * `RoadWarningMark` + */ ROAD_WARNING, + /** + * `TransitMark` + */ TRANSIT, LOCAL_ADS, + /** + * `TrackInfoMark` + */ TRACK_INFO, + /** + * `TrackSelectionMark` + */ TRACK_SELECTION, + /** + * `DebugMarkPoint` + */ DEBUG_MARK, // Plain "DEBUG" results in a name collision. + /** + * `ColoredMarkPoint` + */ COLORED, USER_MARK_TYPES_COUNT, USER_MARK_TYPES_COUNT_MAX = 1000, @@ -133,6 +174,9 @@ class MyPositionMarkPoint : public StaticMarkPoint bool m_hasPosition = false; }; +/** + * @brief A mark in the shape of a dot. + */ class DebugMarkPoint : public UserMark { public: @@ -141,6 +185,9 @@ class DebugMarkPoint : public UserMark drape_ptr GetSymbolNames() const override; }; +/** + * @brief A mark in the shape of a dot, of caller-defined color and radius. + */ class ColoredMarkPoint : public UserMark { public: From e7bde9aa058852e5c4f81e9ddcefbc9adca5aef6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 12 Sep 2025 21:27:21 +0300 Subject: [PATCH 165/252] [openlr_decoder] Fix faulty merge Signed-off-by: mvglasow --- tools/openlr/openlr_decoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/openlr/openlr_decoder.cpp b/tools/openlr/openlr_decoder.cpp index 491c78126..8c1f0786b 100644 --- a/tools/openlr/openlr_decoder.cpp +++ b/tools/openlr/openlr_decoder.cpp @@ -436,7 +436,7 @@ bool OpenLRDecoder::SegmentsFilter::Matches(LinearSegment const & segment) const // OpenLRDecoder ----------------------------------------------------------------------------- OpenLRDecoder::OpenLRDecoder(DataSource & dataSource, CountryParentNameGetter const & countryParentNameGetter) - : m_dataSources(dataSources) + : m_dataSource(dataSource) , m_countryParentNameGetter(countryParentNameGetter) {} From 07c75e627e1de681ded4950afe87839f9d18ae51 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 12 Sep 2025 21:40:35 +0300 Subject: [PATCH 166/252] [traff_assessment_tool] Move to tools Signed-off-by: mvglasow --- libs/traffxml/CMakeLists.txt | 4 ---- tools/CMakeLists.txt | 1 + .../traff_assessment_tool/CMakeLists.txt | 0 .../traff_assessment_tool/Info.plist | 0 .../traff_assessment_tool/main.cpp | 0 .../traff_assessment_tool/mainwindow.cpp | 12 ++++++------ .../traff_assessment_tool/mainwindow.hpp | 0 .../traff_assessment_tool/map_widget.cpp | 2 +- .../traff_assessment_tool/map_widget.hpp | 0 .../points_controller_delegate_base.hpp | 0 .../traffic_drawer_delegate_base.hpp | 0 .../traff_assessment_tool/traffic_model.cpp | 0 .../traff_assessment_tool/traffic_model.hpp | 0 .../traff_assessment_tool/traffic_panel.cpp | 2 +- .../traff_assessment_tool/traffic_panel.hpp | 0 .../traff_assessment_tool/trafficmodeinitdlg.cpp | 2 +- .../traff_assessment_tool/trafficmodeinitdlg.h | 0 .../traff_assessment_tool/trafficmodeinitdlg.ui | 0 18 files changed, 10 insertions(+), 13 deletions(-) rename {libs/traffxml => tools}/traff_assessment_tool/CMakeLists.txt (100%) rename {libs/traffxml => tools}/traff_assessment_tool/Info.plist (100%) rename {libs/traffxml => tools}/traff_assessment_tool/main.cpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/mainwindow.cpp (97%) rename {libs/traffxml => tools}/traff_assessment_tool/mainwindow.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/map_widget.cpp (90%) rename {libs/traffxml => tools}/traff_assessment_tool/map_widget.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/points_controller_delegate_base.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/traffic_drawer_delegate_base.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/traffic_model.cpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/traffic_model.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/traffic_panel.cpp (98%) rename {libs/traffxml => tools}/traff_assessment_tool/traffic_panel.hpp (100%) rename {libs/traffxml => tools}/traff_assessment_tool/trafficmodeinitdlg.cpp (96%) rename {libs/traffxml => tools}/traff_assessment_tool/trafficmodeinitdlg.h (100%) rename {libs/traffxml => tools}/traff_assessment_tool/trafficmodeinitdlg.ui (100%) diff --git a/libs/traffxml/CMakeLists.txt b/libs/traffxml/CMakeLists.txt index fe4fae5f9..937805518 100644 --- a/libs/traffxml/CMakeLists.txt +++ b/libs/traffxml/CMakeLists.txt @@ -19,7 +19,3 @@ target_link_libraries(${PROJECT_NAME} pugixml coding ) - -if (NOT SKIP_QT_GUI) - omim_add_tool_subdirectory(traff_assessment_tool) -endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6a8e916f0..a2492d55e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -7,5 +7,6 @@ omim_add_tool_subdirectory(topography_generator) omim_add_tool_subdirectory(track_generator) if (NOT SKIP_QT_GUI) omim_add_tool_subdirectory(skin_generator) + omim_add_tool_subdirectory(traff_assessment_tool) endif() diff --git a/libs/traffxml/traff_assessment_tool/CMakeLists.txt b/tools/traff_assessment_tool/CMakeLists.txt similarity index 100% rename from libs/traffxml/traff_assessment_tool/CMakeLists.txt rename to tools/traff_assessment_tool/CMakeLists.txt diff --git a/libs/traffxml/traff_assessment_tool/Info.plist b/tools/traff_assessment_tool/Info.plist similarity index 100% rename from libs/traffxml/traff_assessment_tool/Info.plist rename to tools/traff_assessment_tool/Info.plist diff --git a/libs/traffxml/traff_assessment_tool/main.cpp b/tools/traff_assessment_tool/main.cpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/main.cpp rename to tools/traff_assessment_tool/main.cpp diff --git a/libs/traffxml/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp similarity index 97% rename from libs/traffxml/traff_assessment_tool/mainwindow.cpp rename to tools/traff_assessment_tool/mainwindow.cpp index 3bb37d106..9a4d4682b 100644 --- a/libs/traffxml/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -1,10 +1,10 @@ -#include "traffxml/traff_assessment_tool/mainwindow.hpp" +#include "traff_assessment_tool/mainwindow.hpp" -#include "traffxml/traff_assessment_tool/map_widget.hpp" -#include "traffxml/traff_assessment_tool/points_controller_delegate_base.hpp" -#include "traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp" -#include "traffxml/traff_assessment_tool/traffic_panel.hpp" -#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h" +#include "traff_assessment_tool/map_widget.hpp" +#include "traff_assessment_tool/points_controller_delegate_base.hpp" +#include "traff_assessment_tool/traffic_drawer_delegate_base.hpp" +#include "traff_assessment_tool/traffic_panel.hpp" +#include "traff_assessment_tool/trafficmodeinitdlg.h" #include "map/framework.hpp" diff --git a/libs/traffxml/traff_assessment_tool/mainwindow.hpp b/tools/traff_assessment_tool/mainwindow.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/mainwindow.hpp rename to tools/traff_assessment_tool/mainwindow.hpp diff --git a/libs/traffxml/traff_assessment_tool/map_widget.cpp b/tools/traff_assessment_tool/map_widget.cpp similarity index 90% rename from libs/traffxml/traff_assessment_tool/map_widget.cpp rename to tools/traff_assessment_tool/map_widget.cpp index 97232c054..59601ae88 100644 --- a/libs/traffxml/traff_assessment_tool/map_widget.cpp +++ b/tools/traff_assessment_tool/map_widget.cpp @@ -1,4 +1,4 @@ -#include "traffxml/traff_assessment_tool/map_widget.hpp" +#include "traff_assessment_tool/map_widget.hpp" #include "qt/qt_common/helpers.hpp" diff --git a/libs/traffxml/traff_assessment_tool/map_widget.hpp b/tools/traff_assessment_tool/map_widget.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/map_widget.hpp rename to tools/traff_assessment_tool/map_widget.hpp diff --git a/libs/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp b/tools/traff_assessment_tool/points_controller_delegate_base.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/points_controller_delegate_base.hpp rename to tools/traff_assessment_tool/points_controller_delegate_base.hpp diff --git a/libs/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp b/tools/traff_assessment_tool/traffic_drawer_delegate_base.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/traffic_drawer_delegate_base.hpp rename to tools/traff_assessment_tool/traffic_drawer_delegate_base.hpp diff --git a/libs/traffxml/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/traffic_model.cpp rename to tools/traff_assessment_tool/traffic_model.cpp diff --git a/libs/traffxml/traff_assessment_tool/traffic_model.hpp b/tools/traff_assessment_tool/traffic_model.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/traffic_model.hpp rename to tools/traff_assessment_tool/traffic_model.hpp diff --git a/libs/traffxml/traff_assessment_tool/traffic_panel.cpp b/tools/traff_assessment_tool/traffic_panel.cpp similarity index 98% rename from libs/traffxml/traff_assessment_tool/traffic_panel.cpp rename to tools/traff_assessment_tool/traffic_panel.cpp index caeb2670d..377e1a13e 100644 --- a/libs/traffxml/traff_assessment_tool/traffic_panel.cpp +++ b/tools/traff_assessment_tool/traffic_panel.cpp @@ -1,4 +1,4 @@ -#include "traffxml/traff_assessment_tool/traffic_panel.hpp" +#include "traff_assessment_tool/traffic_panel.hpp" #include #include diff --git a/libs/traffxml/traff_assessment_tool/traffic_panel.hpp b/tools/traff_assessment_tool/traffic_panel.hpp similarity index 100% rename from libs/traffxml/traff_assessment_tool/traffic_panel.hpp rename to tools/traff_assessment_tool/traffic_panel.hpp diff --git a/libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp b/tools/traff_assessment_tool/trafficmodeinitdlg.cpp similarity index 96% rename from libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp rename to tools/traff_assessment_tool/trafficmodeinitdlg.cpp index e6eab99cf..b017b4179 100644 --- a/libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.cpp +++ b/tools/traff_assessment_tool/trafficmodeinitdlg.cpp @@ -1,4 +1,4 @@ -#include "traffxml/traff_assessment_tool/trafficmodeinitdlg.h" +#include "traff_assessment_tool/trafficmodeinitdlg.h" #include "ui_trafficmodeinitdlg.h" #include "platform/settings.hpp" diff --git a/libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.h b/tools/traff_assessment_tool/trafficmodeinitdlg.h similarity index 100% rename from libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.h rename to tools/traff_assessment_tool/trafficmodeinitdlg.h diff --git a/libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui b/tools/traff_assessment_tool/trafficmodeinitdlg.ui similarity index 100% rename from libs/traffxml/traff_assessment_tool/trafficmodeinitdlg.ui rename to tools/traff_assessment_tool/trafficmodeinitdlg.ui From d6eacd7364f80f3e7df2ddc26fe70e5a0ac4ae4b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 26 Sep 2025 22:53:08 +0300 Subject: [PATCH 167/252] [drape] Correctly render traffic at zoom 20 Signed-off-by: mvglasow --- libs/drape_frontend/rule_drawer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/drape_frontend/rule_drawer.cpp b/libs/drape_frontend/rule_drawer.cpp index 298053ffe..c45274b41 100644 --- a/libs/drape_frontend/rule_drawer.cpp +++ b/libs/drape_frontend/rule_drawer.cpp @@ -38,9 +38,9 @@ namespace { // The first zoom level in kAverageSegmentsCount. int constexpr kFirstZoomInAverageSegments = 10; -std::array const kAverageSegmentsCount = { - // 10 11 12 13 14 15 16 17 18 19 - 10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500}; +std::array const kAverageSegmentsCount = { + // 10 11 12 13 14 15 16 17 18 19 20 + 10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500, 500}; double constexpr kMetersPerLevel = 3.0; From ba2d653a30451551c268378b8ce1cb372349c925 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 28 Sep 2025 13:55:34 +0300 Subject: [PATCH 168/252] [3party/protobuf] Fix faulty merge Signed-off-by: mvglasow --- 3party/protobuf/protobuf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3party/protobuf/protobuf b/3party/protobuf/protobuf index a6189acd1..9442c12e8 160000 --- a/3party/protobuf/protobuf +++ b/3party/protobuf/protobuf @@ -1 +1 @@ -Subproject commit a6189acd18b00611c1dc7042299ad75486f08a1a +Subproject commit 9442c12e866b56369f1c53abea1261259c924a19 From 30b2df89cd76630c79837899947dc17033dba2a4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 9 Oct 2025 20:44:33 +0300 Subject: [PATCH 169/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/directions_engine.hpp | 15 ++++++++++++--- libs/routing/routing_helpers.hpp | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libs/routing/directions_engine.hpp b/libs/routing/directions_engine.hpp index c7dc61840..c5babdb30 100644 --- a/libs/routing/directions_engine.hpp +++ b/libs/routing/directions_engine.hpp @@ -33,9 +33,18 @@ class DirectionsEngine // @TODO(bykoianko) Method Generate() should fill // vector instead of corresponding arguments. - /// \brief Generates all args which are passed by reference. - /// \param path is points of the route. It should not be empty. - /// \returns true if fields passed by reference are filled correctly and false otherwise. + /** + * @brief Calculates segments from a path on a route graph. + * + * Segments are calculated from `graph` (the route graph) and `path` (points on the route); each + * pair of consecutive points becomes a segment. + * + * @param graph The route graph + * @param path The route path, an ordered list of points on the route + * @param cancellable + * @param routeSegments Receives the list of segments + * @return true on successful completion, false if cancelled or an error occurred + */ bool Generate(IndexRoadGraph const & graph, std::vector const & path, base::Cancellable const & cancellable, std::vector & routeSegments); void Clear(); diff --git a/libs/routing/routing_helpers.hpp b/libs/routing/routing_helpers.hpp index 154a1c7c7..ab825759c 100644 --- a/libs/routing/routing_helpers.hpp +++ b/libs/routing/routing_helpers.hpp @@ -61,6 +61,26 @@ bool IsRoad(Types const & types) void FillSegmentInfo(std::vector const & times, std::vector & routeSegments); +/** + * @brief Constructs or reconstructs a route. + * + * This function populates `route` with segments and geometry. Segments are calculated from `graph` + * (the route graph) and `path` (points on the route); each pair of consecutive points becomes a + * segment. The actual calculation is delegated to `engine` and can be influenced by passing a + * different directions engine. Segment information is then enriched with the length of each segment + * (calculated directly) and the estimated travel time specified in `times`. Geometry is calculated + * from `path` by extracting latitude and longitude from each item. + * + * The number of items in `times` must be equal to the number of segments, or the number of items in + * `points` minus 1. The items of `times` are travel times from start, therefore no value can be + * less than the previous one. + * + * @param engine The directions engine + * @param graph The route graph + * @param path The route path, an ordered list of points on the route + * @param times Travel times (from start) for each segment + * @param route The route + */ void ReconstructRoute(DirectionsEngine & engine, IndexRoadGraph const & graph, base::Cancellable const & cancellable, std::vector const & path, std::vector const & times, Route & route); From bf6cf27f8c4373c19de3441d9b83b652216c39f0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 11 Oct 2025 14:47:41 +0300 Subject: [PATCH 170/252] [traff_assessment_tool] Clear markers when updating TraFF data Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index 1b31d6cfa..68e2d0dc1 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -325,6 +325,11 @@ TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, endResetModel(); + // clear markers + auto editSession = m_framework.GetBookmarkManager().GetEditSession(); + editSession.ClearGroup(UserMark::Type::COLORED); + editSession.SetIsVisible(UserMark::Type::COLORED, false); + LOG(LINFO, ("Messages:", m_messages.size())); }); }); From 9e06ec815ed9088c0b131e06bdbe74c5905baa0b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 11 Oct 2025 15:08:17 +0300 Subject: [PATCH 171/252] [traff_assessment_tool] Handle invalid selections gracefully, clear marks Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index 68e2d0dc1..a0f9b1640 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -449,15 +449,24 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio auto const row = selected.front().top(); - CHECK_LESS(static_cast(row), m_messages.size(), ()); + auto editSession = m_framework.GetBookmarkManager().GetEditSession(); + editSession.ClearGroup(UserMark::Type::COLORED); + + if (static_cast(row) >= m_messages.size()) + { + editSession.SetIsVisible(UserMark::Type::COLORED, false); + return; + } + auto message = &m_messages[row]; if (!message->m_location) + { + editSession.SetIsVisible(UserMark::Type::COLORED, false); return; + } m2::RectD rect; - auto editSession = m_framework.GetBookmarkManager().GetEditSession(); - editSession.ClearGroup(UserMark::Type::COLORED); editSession.SetIsVisible(UserMark::Type::COLORED, true); for (auto & [coords, color] : { From d098ecae157845b9a3255b85f3e7104c70f31d29 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 11 Oct 2025 19:02:43 +0300 Subject: [PATCH 172/252] [traff_assessment_tool] Ensure traffic layer is enabled Signed-off-by: mvglasow --- tools/traff_assessment_tool/mainwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index 9a4d4682b..dcd95845d 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -273,6 +273,8 @@ MainWindow::MainWindow(Framework & framework) fileMenu->addSeparator(); + m_framework.GetTrafficManager().SetEnabled(true); + #ifdef openlr_obsolete m_goldifyMatchedPathAction = fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficModel->GoldifyMatchedPath(); }); m_startEditingAction = fileMenu->addAction("Edit", QKeySequence("Ctrl+E"), From 2ed9bc1880ea816bdc46558f4daad4bee4f55143 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 12 Oct 2025 16:39:39 +0300 Subject: [PATCH 173/252] [traffic] Properly process message replacement Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 7 +++++ libs/traffxml/traff_decoder.cpp | 46 +++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index c74d36488..523f8adaa 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -639,6 +639,13 @@ void TrafficManager::DecodeFirstMessage() // store message in cache m_messageCache.insert_or_assign(message.m_id, message); + + for (auto & replaced : message.m_replaces) + { + auto it = m_messageCache.find(replaced); + if (it != m_messageCache.cend()) + m_messageCache.erase(it); + } } /* * TODO detect if we can do a quick update: diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index e09277500..cf3527650 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -210,29 +210,43 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) return; traffxml::MultiMwmColoring decoded; + bool isDecoded = false; - auto it = m_messageCache.find(message.m_id); - if ((it != m_messageCache.end()) - && !it->second.m_decoded.empty() - && (it->second.m_location == message.m_location)) + std::vector ids = message.m_replaces; + ids.insert(ids.begin(), message.m_id); + + for (auto & id : ids) { - // cache already has a message with reusable location + auto it = m_messageCache.find(id); + if ((it != m_messageCache.end()) + && !it->second.m_decoded.empty() + && (it->second.m_location == message.m_location)) + { + // cache already has a message with reusable location - LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); + LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); - std::optional cachedImpact = it->second.GetTrafficImpact(); - if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) - { - LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); + std::optional cachedImpact = it->second.GetTrafficImpact(); + if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) + { + LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); - // same impact, m_decoded can be reused altogether - message.m_decoded = it->second.m_decoded; - return; + // same impact, m_decoded can be reused altogether + message.m_decoded = it->second.m_decoded; + return; + } + else if (!isDecoded) + { + /* + * populate only on first occurrence but continue searching, we might find a matching + * location with matching impact + */ + decoded = it->second.m_decoded; + isDecoded = true; + } } - else - decoded = it->second.m_decoded; } - else + if (!isDecoded) DecodeLocation(message, decoded); if (impact) From c9f50cdc72cc6a4d294c5b6af28921aebf0562b5 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Tue, 14 Oct 2025 10:54:37 +0200 Subject: [PATCH 174/252] [ios] Enabled traffic button Signed-off-by: Yannik Bloscheck --- .../en-GB.lproj/Localizable.strings | 1 + .../en.lproj/Localizable.strings | 1 + .../Menu/Cells/BottomMenuLayersCell.swift | 20 +++++++++++++++++++ .../Menu/Cells/BottomMenuLayersCell.xib | 20 ++++++++++++++----- .../traffxml.xcodeproj/project.pbxproj | 2 +- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings index 9d6942b46..b5d5cfd58 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/Localizable.strings @@ -811,6 +811,7 @@ "privacy_policy" = "Privacy policy"; "terms_of_use" = "Terms of use"; "button_layer_subway" = "Metro"; +"button_layer_traffic" = "Traffic"; "layers_title" = "Map Styles and Layers"; "subway_data_unavailable" = "Metro map is unavailable"; "title_error_downloading_bookmarks" = "An error occurred"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings index 5a5f26267..840e642c6 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/Localizable.strings @@ -833,6 +833,7 @@ "privacy_policy" = "Privacy policy"; "terms_of_use" = "Terms of use"; "button_layer_subway" = "Subway"; +"button_layer_traffic" = "Traffic"; "layers_title" = "Map Styles and Layers"; "subway_data_unavailable" = "Subway map is unavailable"; "title_error_downloading_bookmarks" = "An error occurred"; diff --git a/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.swift b/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.swift index 704445eda..460b228a6 100644 --- a/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.swift +++ b/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.swift @@ -18,6 +18,11 @@ class BottomMenuLayersCell: UITableViewCell { updateOutdoorButton() } } + @IBOutlet private var trafficButton: BottomMenuLayerButton! { + didSet { + updateTrafficButton() + } + } var onClose: (()->())? @@ -32,6 +37,7 @@ class BottomMenuLayersCell: UITableViewCell { outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor")) isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines")) subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway")) + trafficButton.setupWith(image: UIImage(resource: .btnMenuTraffic), text: L("button_layer_traffic")) } deinit { @@ -56,6 +62,11 @@ class BottomMenuLayersCell: UITableViewCell { let enabled = MapOverlayManager.outdoorEnabled() outdoorButton.setStyleAndApply(styleFor(enabled)) } + + private func updateTrafficButton() { + let enabled = MapOverlayManager.trafficEnabled() + trafficButton.setStyleAndApply(styleFor(enabled)) + } @IBAction func onCloseButtonPressed(_ sender: Any) { onClose?() @@ -75,6 +86,11 @@ class BottomMenuLayersCell: UITableViewCell { let enable = !MapOverlayManager.outdoorEnabled() MapOverlayManager.setOutdoorEnabled(enable) } + + @IBAction func onTrafficButton(_ sender: Any) { + let enable = !MapOverlayManager.trafficEnabled() + MapOverlayManager.setTrafficEnabled(enable) + } } extension BottomMenuLayersCell: MapOverlayManagerObserver { @@ -89,6 +105,10 @@ extension BottomMenuLayersCell: MapOverlayManagerObserver { func onOutdoorStateUpdated() { updateOutdoorButton() } + + func onTrafficStateUpdated() { + updateTrafficButton() + } } private extension BottomMenuLayersCell { diff --git a/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.xib b/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.xib index 2261721f3..cc71a35e5 100644 --- a/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.xib +++ b/iphone/Maps/UI/BottomMenu/Menu/Cells/BottomMenuLayersCell.xib @@ -1,9 +1,9 @@ - + - + @@ -81,26 +81,35 @@ - + - + - + + + + + + + + + + @@ -144,6 +153,7 @@ + diff --git a/xcode/traffxml/traffxml.xcodeproj/project.pbxproj b/xcode/traffxml/traffxml.xcodeproj/project.pbxproj index 3e0c759bb..6f731cdd9 100644 --- a/xcode/traffxml/traffxml.xcodeproj/project.pbxproj +++ b/xcode/traffxml/traffxml.xcodeproj/project.pbxproj @@ -93,7 +93,7 @@ 274720472E439DDE00C516DF /* traff_storage.cpp */, ); name = traffxml; - path = ../../traffxml; + path = ../../libs/traffxml; sourceTree = ""; }; 67BECB831DDA474400FC4E99 /* Frameworks */ = { From f07c8d66d862689beeacf4a2b7c26251a0d0ec93 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 14 Oct 2025 21:41:22 +0300 Subject: [PATCH 175/252] [traffic] Calculate segment weight based on road ref Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 174 ++++++++++++++++++++++++++++++-- libs/traffxml/traff_decoder.hpp | 37 +++++++ 2 files changed, 205 insertions(+), 6 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index cf3527650..d4288bd5d 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -6,6 +6,7 @@ #include "geometry/mercator.hpp" #include "indexer/feature.hpp" +#include "indexer/road_shields_parser.hpp" // Only needed for OpenlrTraffDecoder, see below #if 0 @@ -27,8 +28,17 @@ #include "traffic/traffic_cache.hpp" +#include + namespace traffxml { +enum class RefParserState +{ + Whitespace, + Alpha, + Numeric +}; + // Only needed for OpenlrTraffDecoder, see below #if 0 // Number of worker threads for the OpenLR decoder @@ -54,13 +64,13 @@ auto constexpr kOneMpSInKmpH = 3.6; /* * Penalty factor for using a fake segment to get to a nearby road. - * Maximum penalty for roads is currently 16 (4 for ramps * 4 for road type), offroad penalty is - * twice the maximum road penalty. We might need to increase that, since offroad penalty applies to - * direct distance whereas road penalty applies to roads, which can be up to around 3 times the - * direct distance (theoretically unlimited). That would imply multiplying maximum road penalty by - * more than 3 (e.g. 4). + * Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref), offroad + * penalty is twice the maximum road penalty. We might need to increase that, since offroad penalty + * applies to direct distance whereas road penalty applies to roads, which can be up to around 3 + * times the direct distance (theoretically unlimited). That would imply multiplying maximum road + * penalty by more than 3 (e.g. 4). */ -auto constexpr kOffroadPenalty = 32; +auto constexpr kOffroadPenalty = 128; /* * Penalty factor for non-matching attributes @@ -475,6 +485,67 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf } #endif +double RoutingTraffDecoder::TraffEstimator::GetRoadRefPenalty(std::string & ref) const +{ + // skip parsing if ref is empty + if (ref.empty()) + { + if (m_decoder.m_roadRef.empty()) + return 1; + else if (!m_decoder.m_roadRef.empty()) + return kAttributePenalty; + } + + // TODO does caching results per ref improve performance? + + std::vector r = ParseRef(ref); + + size_t matches = 0; + + if (m_decoder.m_roadRef.empty() && r.empty()) + return 1; + else if (m_decoder.m_roadRef.empty() || r.empty()) + return kAttributePenalty; + + // work on a copy of `m_decoder.m_roadRef` + std::vector l = m_decoder.m_roadRef; + + if ((l.size() > 1) && (r.size() > 1) && (l.front() == r.front())) + { + /* + * Discard generic prefixes, which are often used to denote the road class. + * This will turn `A1` and `A2` into `1` and `2`, causing them to be treated as a mismatch, + * not a partial match. + */ + l.erase(l.begin()); + r.erase(r.begin()); + } + + // for both sides, count items matched by the other side + for (auto & litem : l) + for (auto ritem : r) + if (litem == ritem) + { + matches++; + break; + } + + for (auto ritem : r) + for (auto & litem : l) + if (litem == ritem) + { + matches++; + break; + } + + if (matches == 0) + return kAttributePenalty; + else if (matches == (l.size() + r.size())) + return 1; + else + return kReducedAttributePenalty; +} + double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { // Adds 2 minutes penalty for U-turn. The value is quite arbitrary @@ -517,6 +588,13 @@ double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, return result; } +/* + * Currently, the attribute penalty (kAttributePenalty or kReducedAttributePenalty) can be applied + * up to 3 times: + * - ramp attribute mismatch + * - road class mismatch + * - road ref mismatch + */ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const { double result = road.GetDistance(segment.GetSegmentIdx()); @@ -545,6 +623,28 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c result *= kAttributePenalty; } + if (!m_decoder.m_roadRef.empty()) + { + auto const countryFile = m_decoder.m_numMwmIds->GetFile(segment.GetMwmId()); + auto const mwmId = m_decoder.m_dataSource.GetMwmIdByCountryFile(countryFile); + FeaturesLoaderGuard g(m_decoder.m_dataSource, mwmId); + auto f = g.GetOriginalFeatureByIndex(segment.GetFeatureId()); + auto refs = ftypes::GetRoadShieldsNames(*f); + + auto penalty = kAttributePenalty; + + for (auto & ref : refs) + { + auto newPenalty = GetRoadRefPenalty(ref); + if (newPenalty < penalty) + penalty = newPenalty; + if (penalty == 1) + break; + } + + result *= penalty; + } + return result; } @@ -865,6 +965,11 @@ void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traff m_message = message; + if (m_message.value().m_location.value().m_roadRef) + m_roadRef = ParseRef(m_message.value().m_location.value().m_roadRef.value()); + else + m_roadRef.clear(); + int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1; for (int dir = 0; dir < dirs; dir++) DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */); @@ -966,4 +1071,61 @@ bool IsRamp(routing::HighwayType highwayType) return false; } } + +std::vector ParseRef(std::string & ref) +{ + std::vector res; + std::string curr = ""; + RefParserState state = RefParserState::Whitespace; + + for (size_t i = 0; i < ref.size(); i++) + { + // TODO this list of delimiters might not be exhaustive + if ((ref[i] <= 0x20) || (ref[i] == ',') || (ref[i] == '-') || (ref[i] == '.') || (ref[i] == '/')) + { + // whitespace + if (state != RefParserState::Whitespace) + { + if (state == RefParserState::Alpha) + boost::to_lower(curr); + res.push_back(curr); + curr = ""; + } + state = RefParserState::Whitespace; + } + /* + * TODO adapt this to other number systems as well. + * Roman numerals (or any use of letters as numbers) are a stupid idea. If they are at least + * properly delimited, they are treated as a letter group, which sort of works for comparison. + * However, `IVbis` will be treated as one group, whereas `IV bis` will be treated as two. + */ + else if ((ref[i] >= '0') && (ref[i] <= '9')) + { + // numeric + if (state == RefParserState::Alpha) + { + boost::to_lower(curr); + res.push_back(curr); + curr = ""; + } + curr += ref[i]; + state = RefParserState::Numeric; + } + // anything that is not a delimiter or a digit (as per the above rules) is considered a letter + else + { + // alpha + if (state == RefParserState::Numeric) + { + res.push_back(curr); + curr = ""; + } + curr += ref[i]; + state = RefParserState::Alpha; + } + } + if (!curr.empty()) + res.push_back(curr); + return res; +} } // namespace traffxml diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 82e232e5a..faf5c51cb 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -295,6 +295,20 @@ class RoutingTraffDecoder : public TraffDecoder, */ double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const override; double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; + + /** + * @brief Determines the penalty factor based on how two reference numbers match. + * + * Rules are subject to change. + * + * @param ref The reference number of the current segment, compared against `m_roadRef`. + * + * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` + * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for + * a partial match (unclear whether both refs refer to the same object). + */ + double GetRoadRefPenalty(std::string & ref) const; + double GetUTurnPenalty(Purpose /* purpose */) const override; double GetTurnPenalty(Purpose purpose, double angle, routing::RoadGeometry const & from_road, routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override; @@ -378,6 +392,11 @@ class RoutingTraffDecoder : public TraffDecoder, std::shared_ptr m_numMwmIds = std::make_shared(); std::unique_ptr m_router; std::optional m_message = std::nullopt; + + /** + * @brief The road ref of `m_message`, parsed with `ParseRef()` + */ + std::vector m_roadRef; }; /** @@ -389,4 +408,22 @@ using DefaultTraffDecoder = RoutingTraffDecoder; traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType); double GetRoadClassPenalty(traffxml::RoadClass lhs, traffxml::RoadClass rhs); bool IsRamp(routing::HighwayType highwayType); + +/** + * @brief Breaks down a ref into groups for comparison. + * + * The result of this function can be used to determine if two reference numbers match partially + * (such as `A4`, `A4bis` and `A4.1`). + * + * Implementation details may change; currently the following applies: + * + * A whitespace character (or sequence of whitespace characters), or a switch between letters and + * digits, starts a new group. + * + * Letters are converted to lowercase. + * + * For example, each of `A42`, `A 42` and `-a42` would be broken down into `a, 42`, whereas `A4.2` + * would be broken down into `a, 4, 2`. + */ +std::vector ParseRef(std::string & ref); } // namespace traffxml From 091d510ba1704fac49e600fdd18768baeba1bf67 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 14 Oct 2025 21:52:12 +0300 Subject: [PATCH 176/252] [traffic] Simplify ferry landing penalty in TraFF decoder Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index d4288bd5d..aea01e2fa 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -565,14 +565,9 @@ double RoutingTraffDecoder::TraffEstimator::GetTurnPenalty(Purpose /* purpose */ return 0.0; } -double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose purpose) const +double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose /* purpose */) const { - switch (purpose) - { - case Purpose::Weight: return 20 * 60; // seconds - case Purpose::ETA: return 20 * 60; // seconds - } - UNREACHABLE(); + return 20 * 60; // seconds } double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, From 7fe5823140da3bf5e68e6196187ea47a38d5a8e0 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 17 Oct 2025 21:09:54 +0300 Subject: [PATCH 177/252] [traffic] Optimize offroad penalty in decoder Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index aea01e2fa..ad2683356 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -64,13 +64,22 @@ auto constexpr kOneMpSInKmpH = 3.6; /* * Penalty factor for using a fake segment to get to a nearby road. - * Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref), offroad - * penalty is twice the maximum road penalty. We might need to increase that, since offroad penalty - * applies to direct distance whereas road penalty applies to roads, which can be up to around 3 - * times the direct distance (theoretically unlimited). That would imply multiplying maximum road - * penalty by more than 3 (e.g. 4). + * Offroad penalty applies to direct distance whereas road penalty applies to roads, which can be up + * to around 3 times the direct distance (theoretically unlimited). Therefore, a factor of 3–4 times + * the penalty of a well-matched road may be needed to avoid competing with the correct route. + * On the other hand, a very high offroad penalty would give preference to a poorly matched route + * over a well-matched one if it is closer to the reference points. + * Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref). + * A well-matched road may still have a penalty of around 4 (twice the reduced attribute penalty, or + * once the full attribute penalty). + * A “wrong” road may also just have a penalty of 4 (e.g. road name mismatch, but road class and + * ramp type match). + * A value of 16 has worked well for the DE-B2R-SendlingSued-Passauerstrasse test case. The + * DE-A10-Werder-GrossKreutz or DE-A115-PotsdamDrewitz-Nuthetal test cases still resolve incorrectly + * with an offroad penalty of 8 and even 2, presumably because the correct endpoints are not + * connected by fake segments. */ -auto constexpr kOffroadPenalty = 128; +auto constexpr kOffroadPenalty = 16; /* * Penalty factor for non-matching attributes From 38dbad0f7e945ff7c9285ea4e7bf5acfb0986309 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 18 Oct 2025 15:24:00 +0300 Subject: [PATCH 178/252] [traffic] Add test cases Signed-off-by: mvglasow --- .../traff/DE-A10-Werder-GrossKreutz.xml | 17 +++++++++++++++++ .../traff/DE-A115-PotsdamDrewitz-Nuthetal.xml | 17 +++++++++++++++++ .../DE-B2R-SendlingSued-Passauerstrasse.xml | 18 ++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 data/test_data/traff/DE-A10-Werder-GrossKreutz.xml create mode 100644 data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml create mode 100644 data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml diff --git a/data/test_data/traff/DE-A10-Werder-GrossKreutz.xml b/data/test_data/traff/DE-A10-Werder-GrossKreutz.xml new file mode 100644 index 000000000..6ea4707b8 --- /dev/null +++ b/data/test_data/traff/DE-A10-Werder-GrossKreutz.xml @@ -0,0 +1,17 @@ + + + + + + +52.334801 +12.814650 + +52.393700 +12.835000 + + + + + + + diff --git a/data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml b/data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml new file mode 100644 index 000000000..3d1c93472 --- /dev/null +++ b/data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml @@ -0,0 +1,17 @@ + + + + + + +52.352650 +13.140700 + +52.300201 +13.083500 + + + + + + + diff --git a/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml b/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml new file mode 100644 index 000000000..72eb3e515 --- /dev/null +++ b/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml @@ -0,0 +1,18 @@ + + + + + + +48.110901 +11.518500 + +48.110649 +11.534150 + + + + + + + From a3d1ed83c35e64f4ce16a0af56c55c3c3b27cb54 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 18 Oct 2025 16:34:32 +0300 Subject: [PATCH 179/252] [routing][traffic] Different routing options in navigation and decoder mode Signed-off-by: mvglasow --- libs/routing/index_router.cpp | 7 ++++++- libs/routing/index_router.hpp | 8 ++++++++ libs/traffxml/traff_decoder.cpp | 5 +++++ libs/traffxml/traff_decoder.hpp | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp index 643dd61be..c67d9dee0 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -1091,10 +1091,15 @@ RouterResultCode IndexRouter::AdjustRoute(Checkpoints const & checkpoints, m2::P return RouterResultCode::NoError; } +RoutingOptions IndexRouter::GetRoutingOptions() +{ + return RoutingOptions::LoadCarOptionsFromSettings(); +} + unique_ptr IndexRouter::MakeWorldGraph() { // Use saved routing options for all types (car, bicycle, pedestrian). - RoutingOptions const routingOptions = RoutingOptions::LoadCarOptionsFromSettings(); + RoutingOptions const routingOptions = GetRoutingOptions(); /// @DebugNote // Add avoid roads here for debug purpose. // routingOptions.Add(RoutingOptions::Road::Motorway); diff --git a/libs/routing/index_router.hpp b/libs/routing/index_router.hpp index 1dc6d88f9..4fbcd078f 100644 --- a/libs/routing/index_router.hpp +++ b/libs/routing/index_router.hpp @@ -175,6 +175,14 @@ class IndexRouter : public IRouter */ virtual Mode GetMode() { return Mode::Navigation; } + /** + * @brief Returns current routing options. + * + * In this class, the routing options are the one set in the GUI. Subclasses may override this + * method to provide different routing options. + */ + virtual RoutingOptions GetRoutingOptions(); + private: RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, RouterDelegate const & delegate, std::shared_ptr const & progress, diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index ad2683356..1c3b82a17 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -674,6 +674,11 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons //, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource)) // TODO we don’t need directions, can we disable that? {} +routing::RoutingOptions RoutingTraffDecoder::DecoderRouter::GetRoutingOptions() +{ + return routing::RoutingOptions(); +} + RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache) diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index faf5c51cb..c728f8309 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -266,6 +266,13 @@ class RoutingTraffDecoder : public TraffDecoder, */ IndexRouter::Mode GetMode() { return IndexRouter::Mode::Decoding; } + /** + * @brief Returns current routing options. + * + * For traffic decoding purposes, all roads are allowed. + */ + routing::RoutingOptions GetRoutingOptions() override; + private: }; From 4e624bd04b0b75d22e9b1f1bd1f33a61d8e3e9f8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 19 Oct 2025 17:45:25 +0300 Subject: [PATCH 180/252] [traffic] Comment out unused argument Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 2 +- libs/traffxml/traff_decoder.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 1c3b82a17..dc58135e9 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -599,7 +599,7 @@ double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, * - road class mismatch * - road ref mismatch */ -double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const +double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const { double result = road.GetDistance(segment.GetSegmentIdx()); diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index c728f8309..e1e50f379 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -301,7 +301,7 @@ class RoutingTraffDecoder : public TraffDecoder, * @return Travel time in seconds. */ double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const override; - double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose purpose) const override; + double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const override; /** * @brief Determines the penalty factor based on how two reference numbers match. @@ -317,9 +317,9 @@ class RoutingTraffDecoder : public TraffDecoder, double GetRoadRefPenalty(std::string & ref) const; double GetUTurnPenalty(Purpose /* purpose */) const override; - double GetTurnPenalty(Purpose purpose, double angle, routing::RoadGeometry const & from_road, + double GetTurnPenalty(Purpose /* purpose */, double angle, routing::RoadGeometry const & from_road, routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override; - double GetFerryLandingPenalty(Purpose purpose) const override; + double GetFerryLandingPenalty(Purpose /* purpose */) const override; private: RoutingTraffDecoder & m_decoder; From 3a457408840e1520a1db9b57f62c7755f9202d8c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 19 Oct 2025 17:45:35 +0300 Subject: [PATCH 181/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/route.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index fecec1066..301dabca7 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -39,8 +39,21 @@ namespace routing using SubrouteUid = uint64_t; SubrouteUid constexpr kInvalidSubrouteId = std::numeric_limits::max(); -/// \brief The route is composed of one or several subroutes. Every subroute is composed of segments. -/// For every Segment is kept some attributes in the structure SegmentInfo. +/** + * @brief A segment of the route. + * + * The route is composed of one or several subroutes. Every subroute is composed of segments. + * + * For every Segment, some attributes are kept in the `SegmentInfo` structure. + * + * @todo the statement regarding `SegmentInfo` seems to be outdated, is `SegmentInfo` a prececessor + * of `RouteSegment`? + * + * Segment data which is actually related to a point, such as junction, distance and time, refer to + * the end which is closer to the end of the route. For the first segment, distance and time are the + * length and travel time of the segment itself. For the last segment, distance and time are the + * length and travel time of the entore route, and the junction is the finish point. + */ class RouteSegment final { public: From 32d1a3a36e639f4ec936555915487e7f149b949a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 19 Oct 2025 17:59:48 +0300 Subject: [PATCH 182/252] [traffic] Eliminate any ETA calculations, always return weight Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 5 +---- libs/traffxml/traff_decoder.hpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index dc58135e9..7a0a4d29e 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -580,11 +580,8 @@ double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose /* pu } double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, - Purpose purpose) const + Purpose /* purpose */) const { - if (purpose == Purpose::ETA) - return 0.0; - double result = ms::DistanceOnEarth(from, to); result *= kOffroadPenalty; diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index e1e50f379..dbe1ab4bb 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -300,7 +300,7 @@ class RoutingTraffDecoder : public TraffDecoder, * @param purpose The purpose for which the result is to be used. * @return Travel time in seconds. */ - double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const override; + double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const override; double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const override; /** From a313526aedfbf8842b58a44978cd93e5a8b6c92e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 19 Oct 2025 18:02:04 +0300 Subject: [PATCH 183/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/route.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index 301dabca7..886fe8bba 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -171,8 +171,23 @@ class RouteSegment final turns::TurnItem const & GetTurn() const { return m_turn; } void ClearTurnLanes() { m_turn.m_lanes.clear(); } + /** + * @brief Returns distance from the beginning of the route in meters. + * + * Distance is measured up to the end of the current segment, i.e. including the segment. For the + * first segment, this is identical to the length of the segment; for the last segment, it is + * identical to the length of the entire route. + */ double GetDistFromBeginningMeters() const { return m_distFromBeginningMeters; } double GetDistFromBeginningMerc() const { return m_distFromBeginningMerc; } + + /** + * @brief Returns travel time from the beginning of the route. + * + * Travel time is the ETA from the beginning of the route to the end of the current segment, i.e. + * including the segment. For the first segment, this is identical to the travel time along the + * segment; for the last segment, it is identical to the travel time along the entire route. + */ double GetTimeFromBeginningSec() const { return m_timeFromBeginningS; } bool HasTransitInfo() const { return m_transitInfo.HasTransitInfo(); } From c1340a9941ad0b6a40fa6cdcb964240bc4a13629 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 19 Oct 2025 22:39:06 +0300 Subject: [PATCH 184/252] [traffic] Truncate locations which overshoot their endpoints Signed-off-by: mvglasow --- .../traff/LT-A5-Kaunas-Eastbound.xml | 23 +++++ libs/traffxml/traff_decoder.cpp | 97 ++++++++++++++++++- libs/traffxml/traff_decoder.hpp | 59 +++++++++++ 3 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 data/test_data/traff/LT-A5-Kaunas-Eastbound.xml diff --git a/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml b/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml new file mode 100644 index 000000000..d700afd4c --- /dev/null +++ b/data/test_data/traff/LT-A5-Kaunas-Eastbound.xml @@ -0,0 +1,23 @@ + + + + + + 54.939945 23.879789 + 54.940094 23.881950 + + + + + + + + diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 7a0a4d29e..55cf13ad7 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -815,6 +815,63 @@ void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded decoded[mwmId][traffic::TrafficInfo::RoadSegmentId(fid, sid, direction)] = traffic::SpeedGroup::Unknown; } +void RoutingTraffDecoder::TruncateRoute(std::vector & rsegments, + routing::Checkpoints const & checkpoints) +{ + double const endWeight = rsegments.back().GetTimeFromBeginningSec(); + + // erase leading and trailing fake segments + while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.erase(rsegments.begin()); + while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + rsegments.pop_back(); + + if (rsegments.size() < 2) + return; + + // Index of first segment to keep, or number of segments to truncate at start. + size_t start = 0; + // Cost saved by omitting all segments prior to `start`. + double startSaving = 0; + + // Index of last segment to keep. + size_t end = rsegments.size() - 1; + // Cost saved by omitting the last `end` segments. + double endSaving = 0; + + TruncateStart(rsegments, checkpoints, start, startSaving); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + + /* + * If start <= end, we can truncate both ends at the same time. + * Else, the segments to truncate overlap. In this case, first truncate where the saving is bigger, + * then recalculate the other end and truncate it as well. + */ + if (start <= end) + { + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + } + else if (startSaving > endSaving) + { + // truncate start, then recalculate and truncate end + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + end = rsegments.size() - 1; + endSaving = 0; + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + } + else + { + // truncate end, then recalculate and truncate start + rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); + start = 0; + startSaving = 0; + TruncateStart(rsegments, checkpoints, start, startSaving); + rsegments.erase(rsegments.begin(), rsegments.begin() + start); + } +} + void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded, bool backwards) { @@ -909,11 +966,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { std::vector rsegments(route->GetRouteSegments()); - // erase leading and trailing fake segments - while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) - rsegments.erase(rsegments.begin()); - while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) - rsegments.pop_back(); + TruncateRoute(rsegments, checkpoints); if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_to) // from–at in forward direction, add last segment @@ -1134,4 +1187,38 @@ std::vector ParseRef(std::string & ref) res.push_back(curr); return res; } + +void TruncateStart(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & start, double & startSaving) +{ + for (size_t i = 0; i < rsegments.size(); i++) + { + double newStartSaving = rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(checkpoints.GetStart(), rsegments[i].GetJunction().GetPoint()) + * kOffroadPenalty); + if (newStartSaving > startSaving) + { + start = i + 1; // add 1 because we are ditching this segment and keeping the next one + startSaving = newStartSaving; + } + } +} + +void TruncateEnd(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & end, double & endSaving, double const endWeight) +{ + for (size_t i = 0; i < rsegments.size(); i++) + { + double newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(rsegments[i].GetJunction().GetPoint(), checkpoints.GetFinish()) + * kOffroadPenalty); + if (newEndSaving > endSaving) + { + end = i; + endSaving = newEndSaving; + } + } +} } // namespace traffxml diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index dbe1ab4bb..2d31abb08 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -382,6 +382,30 @@ class RoutingTraffDecoder : public TraffDecoder, */ void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override; + /** + * @brief Truncates the route so its endpoints best match the reference points. + * + * Leading and trailing fake segments are discarded. + * + * When building the graph, the router creates fake segments to the nearest roads. These are not + * necessarily the best for location decoding, which may result in “heads” or “tails” being added + * to the decoded location. This function attempts to detect and remove them. + * + * To do this, it iterates over the nodes (taken from `rsegments`) and determines if any of them + * is a better start/end candidate. This is done by calculating the cost of leaping between the + * node and the corresponding checkpoint; if this is cheaper than the stretch of route bypassed + * in this way, the node becomes a candidate for the corresponding endpoint. The higher the cost + * saving, the better the candidate. + * + * After identifying the best candidate for each endpoint, segments outside these nodes are + * discarded. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + */ + void TruncateRoute(std::vector & rsegments, + routing::Checkpoints const & checkpoints); + private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); @@ -433,4 +457,39 @@ bool IsRamp(routing::HighwayType highwayType); * would be broken down into `a, 4, 2`. */ std::vector ParseRef(std::string & ref); + +/** + * @brief Calculates the segments to truncate at the start of the route. + * + * The route is not actually truncated by this function. + * + * `start` and `startSaving` should be 0 when calling this function. After it returns, these values + * will indicate the first segment to keep and the cost saved by truncating everything before. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + * @param start Index of the first segment to keep + * @param startSaving Cost saved by truncating + */ +void TruncateStart(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & start, double & startSaving); + +/** + * @brief Calculates the segments to truncate at the start of the route. + * + * The route is not actually truncated by this function. + * + * `end` should be `rsegments.size() - 1` and `endSaving` should be 0 when calling this function. + * After it returns, these values will indicate the last segment to keep and the cost saved by + * truncating everything after. + * + * @param rsegments The segments of the route + * @param checkpoints The reference points (at least two) + * @param end Index of the last segment to keep + * @param endSaving Cost saved by truncating + */ +void TruncateEnd(std::vector & rsegments, + routing::Checkpoints const & checkpoints, + size_t & end, double & endSaving, double const endWeight); } // namespace traffxml From dda13b8d3d8890b546603a1417b38c0435765a1d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 20 Oct 2025 22:38:29 +0300 Subject: [PATCH 185/252] [traffic] Penalize turns near endpoints This improves decoding quality on urban multi-carriageway roads. Signed-off-by: mvglasow --- ...DE-B2R-LerchenauerStrasse-Petueltunnel.xml | 16 ++++ .../DE-B2R-SendlingSued-Passauerstrasse.xml | 1 + .../DE-GM4-UrbanDualCarriagewayEndpoint.xml | 22 ++++++ libs/traffxml/traff_decoder.cpp | 77 +++++++++++++++++-- 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 data/test_data/traff/DE-B2R-LerchenauerStrasse-Petueltunnel.xml create mode 100644 data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml diff --git a/data/test_data/traff/DE-B2R-LerchenauerStrasse-Petueltunnel.xml b/data/test_data/traff/DE-B2R-LerchenauerStrasse-Petueltunnel.xml new file mode 100644 index 000000000..f8ac9beb7 --- /dev/null +++ b/data/test_data/traff/DE-B2R-LerchenauerStrasse-Petueltunnel.xml @@ -0,0 +1,16 @@ + + + + + +48.176102 +11.558100 + +48.178001 +11.572800 + + + + + + + diff --git a/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml b/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml index 72eb3e515..50df83776 100644 --- a/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml +++ b/data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml @@ -3,6 +3,7 @@ A combination of reference points on the opposite carriageway, high offroad cost, allowing any road to be used and a low penalty results in a completely incorrect location (through a residential area rather than along the opposite carriageway). + Test case for turn penalty (eastern end) if the above is resolved. --> diff --git a/data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml b/data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml new file mode 100644 index 000000000..5340bdd5e --- /dev/null +++ b/data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml @@ -0,0 +1,22 @@ + + + + + +48.167301 +11.586200 + +48.164200 +11.586500 + + + + + + + + + + + diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 55cf13ad7..97480ec79 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -91,6 +91,21 @@ auto constexpr kAttributePenalty = 4; */ auto constexpr kReducedAttributePenalty = 2; +/* + * Maximum distance in meters from location endpoint at which a turn penalty is applied + */ +auto constexpr kTurnPenaltyMaxDist = 100.0; + +/* + * Minimum angle in degrees at which turn penalty is applied + */ +auto constexpr kTurnPenaltyMinAngle = 65.0; + +/* + * Minimum angle in degrees at which the full turn penalty is applied + */ +auto constexpr kTurnPenaltyFullAngle = 90.0; + /* * Invalid feature ID. * Borrowed from indexer/feature_decl.hpp. @@ -563,15 +578,67 @@ double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose * return 2 * 60; // seconds } -double RoutingTraffDecoder::TraffEstimator::GetTurnPenalty(Purpose /* purpose */, double /* angle */, - routing::RoadGeometry const & /* from_road */, - routing::RoadGeometry const & /* to_road */, - bool /* is_left_hand_traffic */) const +double RoutingTraffDecoder::TraffEstimator::GetTurnPenalty(Purpose /* purpose */, double angle, + routing::RoadGeometry const & from_road, + routing::RoadGeometry const & to_road, + bool is_left_hand_traffic) const { /* * TODO determine if turn penalties make sense for the traffic decoder, else leave them out. + * `angle` seems to be in degrees, right is negative + * Turn is at the first or last point of the roads involved, compare points to find out. + */ + // Flip sign for left-hand traffic, so a positive angle always means a turn across traffic + if (is_left_hand_traffic) + angle *= -1; + + // We only penalize sharp turns (above kTurnPenaltyMinAngle) across traffic + if (angle < kTurnPenaltyMinAngle) + return 0.0; + + /* + * Identify coordinates of location endpoints and of the turn, and establish distance between the + * turn and the nearest endpoint. */ - return 0.0; + ms::LatLon from = m_decoder.m_message.value().m_location.value().m_from + ? m_decoder.m_message.value().m_location.value().m_from.value().m_coordinates + : m_decoder.m_message.value().m_location.value().m_at.value().m_coordinates; + ms::LatLon to = m_decoder.m_message.value().m_location.value().m_to + ? m_decoder.m_message.value().m_location.value().m_to.value().m_coordinates + : m_decoder.m_message.value().m_location.value().m_at.value().m_coordinates; + + // Upper boundary for distance (approximately earth circumference) + double dist = 4.0e+7; + + for (auto & fromPoint : { from_road.GetPoint(0), from_road.GetPoint(from_road.GetPointsCount() - 1) }) + for (auto & toPoint : { to_road.GetPoint(0), to_road.GetPoint(to_road.GetPointsCount() - 1) }) + if (fromPoint == toPoint) + for (auto & endpoint : { from, to }) + { + auto newdist = ms::DistanceOnEarth(fromPoint, endpoint); + if (newdist < dist) + dist = newdist; + } + + // We only penalize turns close to an endpoint + if (dist > kTurnPenaltyMaxDist) + return 0.0; + + /* + * The penalty depends on the distance between the turn point and the nearest endpoint: the + * shorter the distance, the greater the penalty. This is obtained by subtracting the distance + * from `kTurnPenaltyMaxDist`. + * + * Above `kTurnPenaltyFullAngle`, the full turn penalty applies, i.e. the distance-based value is + * multiplied with `kAttributePenalty`. + * + * Between `kTurnPenaltyMinAngle` and `kTurnPenaltyFullAngle`, the penalty proportionally + * increases from 0 to the full value. + */ + double result = (kTurnPenaltyMaxDist - dist) * kAttributePenalty; + if (angle < kTurnPenaltyFullAngle) + result *= (angle - kTurnPenaltyMinAngle) / (kTurnPenaltyFullAngle - kTurnPenaltyMinAngle); + return result; } double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose /* purpose */) const From 6a694c5d3e8bb78a63afac321e2a1230f5c69497 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 20 Oct 2025 22:47:26 +0300 Subject: [PATCH 186/252] [traffic] Documentation and comment cleanup Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 5 ----- libs/traffxml/traff_decoder.hpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 97480ec79..c30693733 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -583,11 +583,6 @@ double RoutingTraffDecoder::TraffEstimator::GetTurnPenalty(Purpose /* purpose */ routing::RoadGeometry const & to_road, bool is_left_hand_traffic) const { - /* - * TODO determine if turn penalties make sense for the traffic decoder, else leave them out. - * `angle` seems to be in degrees, right is negative - * Turn is at the first or last point of the roads involved, compare points to find out. - */ // Flip sign for left-hand traffic, so a positive angle always means a turn across traffic if (is_left_hand_traffic) angle *= -1; diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 2d31abb08..c32ade1b7 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -317,6 +317,19 @@ class RoutingTraffDecoder : public TraffDecoder, double GetRoadRefPenalty(std::string & ref) const; double GetUTurnPenalty(Purpose /* purpose */) const override; + + /** + * @brief Determines the penalty factor for making a turn. + * + * The turn is at the first or last points of `from_road` and `to_road` and can be determined + * by comparing the endpoints of `from_road` and `to_road` for a match. + * + * @param purpose The purpose for which the penalty is calculated, ignored by this implementation + * @param angle The angle in degrees (negative values indicate a right turn) + * @param from_road The road (segment between two junctions) before the turn + * @param to_road The road (segment between two junctions) after the turn + * @param is_left_hand_traffic True for left-hand traffic, false for right-hand traffic + */ double GetTurnPenalty(Purpose /* purpose */, double angle, routing::RoadGeometry const & from_road, routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override; double GetFerryLandingPenalty(Purpose /* purpose */) const override; From 1b74062447f441c0c4cc5e85649e7f91c8b291dc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 20 Oct 2025 23:44:48 +0300 Subject: [PATCH 187/252] [traff_assessment_tool] Show animation while feed is loading Signed-off-by: mvglasow --- tools/traff_assessment_tool/mainwindow.cpp | 14 ++++++++++++-- tools/traff_assessment_tool/mainwindow.hpp | 3 +++ tools/traff_assessment_tool/traffic_model.cpp | 7 +++++++ tools/traff_assessment_tool/traffic_model.hpp | 3 +++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index dcd95845d..0be4db79b 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -317,10 +317,11 @@ void MainWindow::CreateTrafficPanel() { if (!m_trafficModel) { - // TODO simplify the call, everything depends on m_framework + // TODO simplify the call, almost everything depends on m_framework m_trafficModel = new TrafficModel(m_framework, m_framework.GetDataSource(), std::make_unique(m_framework), - std::make_unique(m_framework)); + std::make_unique(m_framework), + *this); connect(m_mapWidget, &MapWidget::TrafficMarkupClick, m_trafficModel, &TrafficModel::OnClick); @@ -339,7 +340,13 @@ void MainWindow::CreateTrafficPanel() m_dockWidget->adjustSize(); m_dockWidget->setMinimumWidth(400); + + m_progressBar = new QProgressBar(m_dockWidget); + m_progressBar->setMinimum(0); + m_progressBar->setMaximum(0); + } + m_dockWidget->setTitleBarWidget(m_progressBar); m_dockWidget->show(); } @@ -350,6 +357,9 @@ void MainWindow::DestroyTrafficPanel() delete m_dockWidget; m_dockWidget = nullptr; + delete m_progressBar; + m_progressBar = nullptr; + delete m_trafficModel; m_trafficModel = nullptr; diff --git a/tools/traff_assessment_tool/mainwindow.hpp b/tools/traff_assessment_tool/mainwindow.hpp index c6b964098..a00995dc8 100644 --- a/tools/traff_assessment_tool/mainwindow.hpp +++ b/tools/traff_assessment_tool/mainwindow.hpp @@ -5,6 +5,7 @@ #include #include +#include class Framework; class QHBoxLayout; @@ -31,6 +32,7 @@ class MainWindow : public QMainWindow public: explicit MainWindow(Framework & framework); + QDockWidget * GetDockWidget() { return m_dockWidget; } private: void CreateTrafficPanel(); @@ -58,6 +60,7 @@ class MainWindow : public QMainWindow traffxml::TrafficModel * m_trafficModel = nullptr; QDockWidget * m_dockWidget = nullptr; + QProgressBar * m_progressBar = nullptr; #ifdef openlr_obsolete QAction * m_goldifyMatchedPathAction = nullptr; diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index a0f9b1640..f1bfceb18 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -13,6 +13,7 @@ #include "base/assert.hpp" #include "base/scope_guard.hpp" +#include #include #include @@ -300,12 +301,14 @@ QVariant GetDescription(TraffMessage const & message) TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, std::unique_ptr drawerDelegate, // TODO do we need that? std::unique_ptr pointsDelegate, // TODO do we need that? + MainWindow & mainWindow, QObject * parent) : QAbstractTableModel(parent) , m_framework(framework) , m_dataSource(dataSource) , m_drawerDelegate(std::move(drawerDelegate)) , m_pointsDelegate(std::move(pointsDelegate)) + , m_mainWindow(mainWindow) { framework.GetTrafficManager().SetTrafficUpdateCallbackFn([this, &framework](bool final) { /* @@ -330,6 +333,10 @@ TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, editSession.ClearGroup(UserMark::Type::COLORED); editSession.SetIsVisible(UserMark::Type::COLORED, false); + // restore QDockWidget title + if (m_mainWindow.GetDockWidget()) + m_mainWindow.GetDockWidget()->setTitleBarWidget(nullptr); + LOG(LINFO, ("Messages:", m_messages.size())); }); }); diff --git a/tools/traff_assessment_tool/traffic_model.hpp b/tools/traff_assessment_tool/traffic_model.hpp index 7093173d5..7d3387e99 100644 --- a/tools/traff_assessment_tool/traffic_model.hpp +++ b/tools/traff_assessment_tool/traffic_model.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mainwindow.hpp" #include "points_controller_delegate_base.hpp" #ifdef openlr_obsolete #include "segment_correspondence.hpp" @@ -72,6 +73,7 @@ class TrafficModel : public QAbstractTableModel TrafficModel(Framework & framework, DataSource const & dataSource, std::unique_ptr drawerDelegate, std::unique_ptr pointsDelegate, + MainWindow & mainWindow, QObject * parent = Q_NULLPTR); bool SaveSampleAs(std::string const & fileName) const; @@ -123,6 +125,7 @@ public slots: Framework & m_framework; DataSource const & m_dataSource; + MainWindow & m_mainWindow; #ifdef openlr_obsolete std::vector m_segments; // Non-owning pointer to an element of m_segments. From 800cc0641ba6b85521e5cdf42a9e777b5023086c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 20 Oct 2025 23:55:29 +0300 Subject: [PATCH 188/252] [traff_assessment_tool] Refactor TrafficModel constructor Signed-off-by: mvglasow --- tools/traff_assessment_tool/mainwindow.cpp | 6 +----- tools/traff_assessment_tool/traffic_model.cpp | 10 ++++------ tools/traff_assessment_tool/traffic_model.hpp | 4 +--- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index 0be4db79b..3b2819042 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -317,11 +317,7 @@ void MainWindow::CreateTrafficPanel() { if (!m_trafficModel) { - // TODO simplify the call, almost everything depends on m_framework - m_trafficModel = new TrafficModel(m_framework, m_framework.GetDataSource(), - std::make_unique(m_framework), - std::make_unique(m_framework), - *this); + m_trafficModel = new TrafficModel(m_framework, *this); connect(m_mapWidget, &MapWidget::TrafficMarkupClick, m_trafficModel, &TrafficModel::OnClick); diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index f1bfceb18..df297384e 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -298,16 +298,14 @@ QVariant GetDescription(TraffMessage const & message) } // TrafficModel ------------------------------------------------------------------------------------- -TrafficModel::TrafficModel(Framework & framework, DataSource const & dataSource, - std::unique_ptr drawerDelegate, // TODO do we need that? - std::unique_ptr pointsDelegate, // TODO do we need that? +TrafficModel::TrafficModel(Framework & framework, MainWindow & mainWindow, QObject * parent) : QAbstractTableModel(parent) , m_framework(framework) - , m_dataSource(dataSource) - , m_drawerDelegate(std::move(drawerDelegate)) - , m_pointsDelegate(std::move(pointsDelegate)) + , m_dataSource(framework.GetDataSource()) + , m_drawerDelegate(std::make_unique(framework)) + , m_pointsDelegate(std::make_unique(framework)) , m_mainWindow(mainWindow) { framework.GetTrafficManager().SetTrafficUpdateCallbackFn([this, &framework](bool final) { diff --git a/tools/traff_assessment_tool/traffic_model.hpp b/tools/traff_assessment_tool/traffic_model.hpp index 7d3387e99..c2dcf24a7 100644 --- a/tools/traff_assessment_tool/traffic_model.hpp +++ b/tools/traff_assessment_tool/traffic_model.hpp @@ -70,9 +70,7 @@ class TrafficModel : public QAbstractTableModel public: // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything. - TrafficModel(Framework & framework, DataSource const & dataSource, - std::unique_ptr drawerDelegate, - std::unique_ptr pointsDelegate, + TrafficModel(Framework & framework, MainWindow & mainWindow, QObject * parent = Q_NULLPTR); From ae23afd72ef5cd2f9899f6149f64569ad8d41cda Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 21 Oct 2025 00:04:34 +0300 Subject: [PATCH 189/252] [traff_assessment_tool] Hide progress bar only on final update Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index df297384e..75f6e6eec 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -314,7 +314,7 @@ TrafficModel::TrafficModel(Framework & framework, * imminent. Such updates should always be processed. If final is false, we can optimize by * selectively skipping updates. */ - GetPlatform().RunTask(Platform::Thread::Gui, [this, &framework]() + GetPlatform().RunTask(Platform::Thread::Gui, [this, &framework, final]() { beginResetModel(); auto const messageCache = framework.GetTrafficManager().GetMessageCache(); @@ -332,7 +332,7 @@ TrafficModel::TrafficModel(Framework & framework, editSession.SetIsVisible(UserMark::Type::COLORED, false); // restore QDockWidget title - if (m_mainWindow.GetDockWidget()) + if (final && m_mainWindow.GetDockWidget()) m_mainWindow.GetDockWidget()->setTitleBarWidget(nullptr); LOG(LINFO, ("Messages:", m_messages.size())); From 3c9eeb9a7538de96011eea5175fa4a8946062f84 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 21 Oct 2025 21:13:26 +0300 Subject: [PATCH 190/252] [traff_assessment_tool] Introduce status line for traffic panel Signed-off-by: mvglasow --- tools/traff_assessment_tool/mainwindow.cpp | 17 +++++--------- tools/traff_assessment_tool/mainwindow.hpp | 6 ++--- tools/traff_assessment_tool/traffic_model.cpp | 11 ++++----- tools/traff_assessment_tool/traffic_model.hpp | 7 +++--- tools/traff_assessment_tool/traffic_panel.cpp | 23 +++++++++++++++++++ tools/traff_assessment_tool/traffic_panel.hpp | 5 ++++ 6 files changed, 45 insertions(+), 24 deletions(-) diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index 3b2819042..780506880 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -3,6 +3,7 @@ #include "traff_assessment_tool/map_widget.hpp" #include "traff_assessment_tool/points_controller_delegate_base.hpp" #include "traff_assessment_tool/traffic_drawer_delegate_base.hpp" +#include "traff_assessment_tool/traffic_model.hpp" #include "traff_assessment_tool/traffic_panel.hpp" #include "traff_assessment_tool/trafficmodeinitdlg.h" @@ -317,7 +318,7 @@ void MainWindow::CreateTrafficPanel() { if (!m_trafficModel) { - m_trafficModel = new TrafficModel(m_framework, *this); + m_trafficModel = new TrafficModel(m_framework); connect(m_mapWidget, &MapWidget::TrafficMarkupClick, m_trafficModel, &TrafficModel::OnClick); @@ -332,17 +333,14 @@ void MainWindow::CreateTrafficPanel() m_dockWidget = new QDockWidget(tr("Messages"), this); addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_dockWidget); - m_dockWidget->setWidget(new TrafficPanel(m_trafficModel, m_dockWidget)); + m_trafficPanel = new TrafficPanel(m_trafficModel, m_dockWidget); + m_dockWidget->setWidget(m_trafficPanel); + m_trafficModel->SetTrafficPanel(m_trafficPanel); m_dockWidget->adjustSize(); m_dockWidget->setMinimumWidth(400); - - m_progressBar = new QProgressBar(m_dockWidget); - m_progressBar->setMinimum(0); - m_progressBar->setMaximum(0); - } - m_dockWidget->setTitleBarWidget(m_progressBar); + m_trafficPanel->SetStatus(true); m_dockWidget->show(); } @@ -353,9 +351,6 @@ void MainWindow::DestroyTrafficPanel() delete m_dockWidget; m_dockWidget = nullptr; - delete m_progressBar; - m_progressBar = nullptr; - delete m_trafficModel; m_trafficModel = nullptr; diff --git a/tools/traff_assessment_tool/mainwindow.hpp b/tools/traff_assessment_tool/mainwindow.hpp index a00995dc8..9f06ee66a 100644 --- a/tools/traff_assessment_tool/mainwindow.hpp +++ b/tools/traff_assessment_tool/mainwindow.hpp @@ -2,10 +2,11 @@ #include "base/string_utils.hpp" +#include "traff_assessment_tool/traffic_panel.hpp" + #include #include -#include class Framework; class QHBoxLayout; @@ -32,7 +33,6 @@ class MainWindow : public QMainWindow public: explicit MainWindow(Framework & framework); - QDockWidget * GetDockWidget() { return m_dockWidget; } private: void CreateTrafficPanel(); @@ -60,7 +60,7 @@ class MainWindow : public QMainWindow traffxml::TrafficModel * m_trafficModel = nullptr; QDockWidget * m_dockWidget = nullptr; - QProgressBar * m_progressBar = nullptr; + TrafficPanel * m_trafficPanel = nullptr; #ifdef openlr_obsolete QAction * m_goldifyMatchedPathAction = nullptr; diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index 75f6e6eec..d2de8f1a3 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -13,7 +13,6 @@ #include "base/assert.hpp" #include "base/scope_guard.hpp" -#include #include #include @@ -299,14 +298,12 @@ QVariant GetDescription(TraffMessage const & message) // TrafficModel ------------------------------------------------------------------------------------- TrafficModel::TrafficModel(Framework & framework, - MainWindow & mainWindow, - QObject * parent) + QObject * parent) : QAbstractTableModel(parent) , m_framework(framework) , m_dataSource(framework.GetDataSource()) , m_drawerDelegate(std::make_unique(framework)) , m_pointsDelegate(std::make_unique(framework)) - , m_mainWindow(mainWindow) { framework.GetTrafficManager().SetTrafficUpdateCallbackFn([this, &framework](bool final) { /* @@ -331,9 +328,9 @@ TrafficModel::TrafficModel(Framework & framework, editSession.ClearGroup(UserMark::Type::COLORED); editSession.SetIsVisible(UserMark::Type::COLORED, false); - // restore QDockWidget title - if (final && m_mainWindow.GetDockWidget()) - m_mainWindow.GetDockWidget()->setTitleBarWidget(nullptr); + // restore status bar + if (final) + m_trafficPanel->SetStatus(false, m_messages.size()); LOG(LINFO, ("Messages:", m_messages.size())); }); diff --git a/tools/traff_assessment_tool/traffic_model.hpp b/tools/traff_assessment_tool/traffic_model.hpp index c2dcf24a7..9450ecb76 100644 --- a/tools/traff_assessment_tool/traffic_model.hpp +++ b/tools/traff_assessment_tool/traffic_model.hpp @@ -6,6 +6,7 @@ #include "segment_correspondence.hpp" #endif #include "traffic_drawer_delegate_base.hpp" +#include "traffic_panel.hpp" #ifdef openlr_obsolete #include "openlr/decoded_path.hpp" @@ -71,10 +72,10 @@ class TrafficModel : public QAbstractTableModel public: // TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything. TrafficModel(Framework & framework, - MainWindow & mainWindow, - QObject * parent = Q_NULLPTR); + QObject * parent = Q_NULLPTR); bool SaveSampleAs(std::string const & fileName) const; + void SetTrafficPanel(TrafficPanel * panel) { m_trafficPanel = panel; } int rowCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE; @@ -123,7 +124,7 @@ public slots: Framework & m_framework; DataSource const & m_dataSource; - MainWindow & m_mainWindow; + TrafficPanel * m_trafficPanel = nullptr; #ifdef openlr_obsolete std::vector m_segments; // Non-owning pointer to an element of m_segments. diff --git a/tools/traff_assessment_tool/traffic_panel.cpp b/tools/traff_assessment_tool/traffic_panel.cpp index 377e1a13e..483a306b6 100644 --- a/tools/traff_assessment_tool/traffic_panel.cpp +++ b/tools/traff_assessment_tool/traffic_panel.cpp @@ -52,6 +52,13 @@ TrafficPanel::TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent) auto * layout = new QVBoxLayout(); layout->addWidget(m_table); + m_progressBar = new QProgressBar(); + m_progressBar->setMinimum(0); + m_progressBar->setMaximum(0); + layout->addWidget(m_progressBar); + m_status = new QLabel("0 messages"); + layout->addWidget(m_status); + m_progressBar->hide(); setLayout(layout); // Select first segment by default; @@ -59,6 +66,22 @@ TrafficPanel::TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent) m_table->selectionModel()->select(index, QItemSelectionModel::Select); } +void TrafficPanel::SetStatus(bool inProgress, std::optional messageCount) +{ + if (inProgress) + { + m_status->hide(); + m_progressBar->show(); + } + else + { + if (messageCount) + m_status->setText(QString("Messages: %1").arg(messageCount.value())); + m_progressBar->hide(); + m_status->show(); + } +} + void TrafficPanel::CreateTable(QAbstractItemModel * trafficModel) { m_table = new QTableView(); diff --git a/tools/traff_assessment_tool/traffic_panel.hpp b/tools/traff_assessment_tool/traffic_panel.hpp index 27ab4d033..6c279cdae 100644 --- a/tools/traff_assessment_tool/traffic_panel.hpp +++ b/tools/traff_assessment_tool/traffic_panel.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include class QAbstractItemModel; @@ -34,6 +36,7 @@ class TrafficPanel : public QWidget public: explicit TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent); + void SetStatus(bool inProgress, std::optional messageCount = std::nullopt); private: void CreateTable(QAbstractItemModel * trafficModel); @@ -46,5 +49,7 @@ public slots: private: QTableView * m_table = Q_NULLPTR; + QProgressBar * m_progressBar = Q_NULLPTR; + QLabel * m_status = Q_NULLPTR; }; } // namespace traffxml From 0dc62c47dd9b1a09b7a8d299c3e6b1fd30fbcca3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 24 Oct 2025 21:44:38 +0300 Subject: [PATCH 191/252] [traff_assessment_tool] Show decoding time Signed-off-by: mvglasow --- tools/traff_assessment_tool/CMakeLists.txt | 2 + tools/traff_assessment_tool/mainwindow.cpp | 2 + .../traff_assessment_tool/resumable_timer.cpp | 35 ++++++++ .../traff_assessment_tool/resumable_timer.hpp | 81 +++++++++++++++++++ tools/traff_assessment_tool/traffic_panel.cpp | 3 +- tools/traff_assessment_tool/traffic_panel.hpp | 4 + 6 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tools/traff_assessment_tool/resumable_timer.cpp create mode 100644 tools/traff_assessment_tool/resumable_timer.hpp diff --git a/tools/traff_assessment_tool/CMakeLists.txt b/tools/traff_assessment_tool/CMakeLists.txt index 833b68dd2..a5ee6f868 100644 --- a/tools/traff_assessment_tool/CMakeLists.txt +++ b/tools/traff_assessment_tool/CMakeLists.txt @@ -7,6 +7,8 @@ set(SRC map_widget.cpp map_widget.hpp points_controller_delegate_base.hpp + resumable_timer.cpp + resumable_timer.hpp traffic_drawer_delegate_base.hpp traffic_model.cpp traffic_model.hpp diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index 780506880..86aebee57 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -341,6 +341,7 @@ void MainWindow::CreateTrafficPanel() m_dockWidget->setMinimumWidth(400); } m_trafficPanel->SetStatus(true); + m_trafficPanel->GetTimer().Resume(); m_dockWidget->show(); } @@ -423,6 +424,7 @@ void MainWindow::OnPurgeExpiredMessages() void MainWindow::OnClearCache() { + m_trafficPanel->GetTimer().Reset(); m_framework.GetTrafficManager().Clear(); } diff --git a/tools/traff_assessment_tool/resumable_timer.cpp b/tools/traff_assessment_tool/resumable_timer.cpp new file mode 100644 index 000000000..a3e93d4d1 --- /dev/null +++ b/tools/traff_assessment_tool/resumable_timer.cpp @@ -0,0 +1,35 @@ +#include "traff_assessment_tool/resumable_timer.hpp" + +namespace base +{ +std::chrono::steady_clock::duration ResumableTimer::TimeElapsed() const +{ + if (m_isRunning) + return std::chrono::steady_clock::now() - m_startTime; + else + return m_prevTimeElapsed; +} + +void ResumableTimer::Pause() +{ + if (!m_isRunning) + return; + m_prevTimeElapsed = TimeElapsed(); + m_isRunning = false; +} + +void ResumableTimer::Resume() +{ + if (m_isRunning) + return; + m_startTime = std::chrono::steady_clock::now() - m_prevTimeElapsed; + m_prevTimeElapsed = std::chrono::steady_clock::duration::zero(); + m_isRunning = true; +} + +void ResumableTimer::Reset() +{ + m_startTime = std::chrono::steady_clock::now(); + m_prevTimeElapsed = std::chrono::steady_clock::duration::zero(); +} +} // namespace base diff --git a/tools/traff_assessment_tool/resumable_timer.hpp b/tools/traff_assessment_tool/resumable_timer.hpp new file mode 100644 index 000000000..f09a46ab3 --- /dev/null +++ b/tools/traff_assessment_tool/resumable_timer.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include + +namespace base +{ +/** + * @brief A timer which can be paused and resumed. + * + * On creation, the timer is in paused state. + * + * Elapsed time can be queried in any state (running or paused). In running state, it will return + * the currently elapsed time, and the result will increase with each subsequent call. In paused + * state, it will return the time elapsed when the timer was last paused, and the result is stable + * between calls as long as the timer remains paused. + */ +class ResumableTimer +{ +public: + std::chrono::steady_clock::duration TimeElapsed() const; + + template + Duration TimeElapsedAs() const + { + return std::chrono::duration_cast(TimeElapsed()); + } + + double ElapsedSeconds() const { return TimeElapsedAs>().count(); } + uint64_t ElapsedMilliseconds() const { return TimeElapsedAs().count(); } + uint64_t ElapsedNanoseconds() const { return TimeElapsedAs().count(); } + + /** + * @brief Pauses the timer, i.e. freezes its current value and stops it from advancing further. + * + * Pausing an already paused timer is a no-op. + */ + void Pause(); + + /** + * @brief Resumes the timer, i.e. causes it to advance further from its previous value. + * + * Resuming a running timer is a no-op. + */ + void Resume(); + + /** + * @brief Resets the timer to zero. + * + * Resetting the timer will not change its paused/running state. That is, resetting a running + * timer will cause it to count upward from zero immediately, whereas resetting a paused timer + * will set its value to zero and keep it there until resumed. + */ + void Reset(); +private: + /** + * @brief The time at which the timer was last started. + * + * Valid only if the timer is running. + * + * When first starting a timer after it has been created or reset, this value is set to current + * time. When resuming a timer, this value us set to current time minus `m_prevTimeElapsed`, so + * that the timer value for a currently running timer is always the difference between this value + * and current time. + */ + std::chrono::steady_clock::time_point m_startTime; + + /** + * @brief Whether the timer is currently running. + */ + bool m_isRunning = false; + + /** + * @brief The time elapsed when the timer was last paused. + * + * Valid only if the timer is paused. + * + * Initially zero, reset to zero when the timer is reset or resumed. + */ + std::chrono::steady_clock::duration m_prevTimeElapsed = std::chrono::steady_clock::duration::zero(); +}; +} // namespace base diff --git a/tools/traff_assessment_tool/traffic_panel.cpp b/tools/traff_assessment_tool/traffic_panel.cpp index 483a306b6..c72898b6e 100644 --- a/tools/traff_assessment_tool/traffic_panel.cpp +++ b/tools/traff_assessment_tool/traffic_panel.cpp @@ -75,8 +75,9 @@ void TrafficPanel::SetStatus(bool inProgress, std::optional messageCount } else { + GetTimer().Pause(); if (messageCount) - m_status->setText(QString("Messages: %1").arg(messageCount.value())); + m_status->setText(QString("Messages: %1\tDecoded in %2 s").arg(messageCount.value()).arg(GetTimer().ElapsedSeconds())); m_progressBar->hide(); m_status->show(); } diff --git a/tools/traff_assessment_tool/traffic_panel.hpp b/tools/traff_assessment_tool/traffic_panel.hpp index 6c279cdae..a568d504a 100644 --- a/tools/traff_assessment_tool/traffic_panel.hpp +++ b/tools/traff_assessment_tool/traffic_panel.hpp @@ -1,5 +1,7 @@ #pragma once +#include "traff_assessment_tool/resumable_timer.hpp" + #include #include #include @@ -36,6 +38,7 @@ class TrafficPanel : public QWidget public: explicit TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent); + base::ResumableTimer & GetTimer() { return m_timer; } void SetStatus(bool inProgress, std::optional messageCount = std::nullopt); private: @@ -50,6 +53,7 @@ public slots: private: QTableView * m_table = Q_NULLPTR; QProgressBar * m_progressBar = Q_NULLPTR; + base::ResumableTimer m_timer; QLabel * m_status = Q_NULLPTR; }; } // namespace traffxml From 46d363ae24697958023a993b253aaf20f66a3783 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 25 Oct 2025 13:35:05 +0300 Subject: [PATCH 192/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/route.hpp | 44 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index 886fe8bba..1aa35d3dc 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -82,16 +82,44 @@ class RouteSegment final uint8_t m_maxSpeedKmPH = 0; }; + /** + * @brief Holds structured information about a road. + * + * `m_ref` and `m_name` refer to the road itself. + * + * This structure is fully populated only for the first segment after a junction. For other + * segments, members may be left at their default values (empty string or false). + */ struct RoadNameInfo { - // This is for street/road. |m_ref| |m_name|. - std::string m_name; // E.g "Johnson Ave.". - std::string m_destination_ref; // Number of next road, e.g. "CA 85", Sometimes "CA 85 South". Usually match |m_ref| - // of next main road. - // This is for 1st segment of link after junction. Exit |junction_ref| to |m_destination_ref| for |m_destination|. - std::string m_junction_ref; // Number of junction e.g. "398B". - std::string m_destination; // E.g. "Cupertino". - std::string m_ref; // Number of street/road e.g. "CA 85". + /** + * @brief The name of the road, e.g. “Johnson Ave”. + */ + std::string m_name; + /** + * @brief The number of the next road. + * + * This usually matches `m_ref` of the naxt main road, e.g. “CA 85”, sometimes “CA 85 South”. + */ + std::string m_destination_ref; + /** + * @brief The junction number, e.g. “398B”. + * + * This is used for the first link segment after a junction (exit `junction_ref` to + * `m_destination_ref` for `m_destination`). + */ + std::string m_junction_ref; + /** + * @brief The destination of the road, e.g. “Cupertino”. + */ + std::string m_destination; + /** + * @brief The number of the road, e.g. “CA85”. + */ + std::string m_ref; + /** + * @brief Whether the road is of a link type. + */ bool m_isLink = false; RoadNameInfo() = default; From 9065f45b216f5be3858b33d682c8e325a3ec293c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 25 Oct 2025 15:09:28 +0300 Subject: [PATCH 193/252] [traffic] Exclude roundabouts from decoded locations (with exceptions) Signed-off-by: mvglasow --- .../test_data/traff/DE-RoundaboutEndpoint.xml | 58 +++++++++++++++++++ libs/routing/directions_engine.cpp | 1 + libs/routing/route.hpp | 4 ++ libs/traffxml/traff_decoder.cpp | 39 +++++++++++++ 4 files changed, 102 insertions(+) create mode 100755 data/test_data/traff/DE-RoundaboutEndpoint.xml diff --git a/data/test_data/traff/DE-RoundaboutEndpoint.xml b/data/test_data/traff/DE-RoundaboutEndpoint.xml new file mode 100755 index 000000000..616834699 --- /dev/null +++ b/data/test_data/traff/DE-RoundaboutEndpoint.xml @@ -0,0 +1,58 @@ + + + + + +48.661098 +7.936800 + +48.683701 +7.916600 + + + + + + + + + + + +49.822948 +7.906900 + +49.803650 +7.901450 + + + + + + + + + + + +49.239750 +8.222300 + +49.253448 +8.268100 + + + + + + + + + + + +50.372398 +8.038500 + +50.370850 +8.004050 + + + + + + + + + + diff --git a/libs/routing/directions_engine.cpp b/libs/routing/directions_engine.cpp index 95555632f..d5bc87a24 100644 --- a/libs/routing/directions_engine.cpp +++ b/libs/routing/directions_engine.cpp @@ -85,6 +85,7 @@ void DirectionsEngine::LoadPathAttributes(FeatureID const & featureId, LoadedPat pathSegment.m_isOneWay = m_onewayChecker(types); pathSegment.m_roadNameInfo.m_isLink = pathSegment.m_isLink; + pathSegment.m_roadNameInfo.m_onRoundabout = pathSegment.m_onRoundabout; pathSegment.m_roadNameInfo.m_junction_ref = ft->GetMetadata(feature::Metadata::FMD_JUNCTION_REF); pathSegment.m_roadNameInfo.m_destination_ref = ft->GetMetadata(feature::Metadata::FMD_DESTINATION_REF); pathSegment.m_roadNameInfo.m_destination = ft->GetMetadata(feature::Metadata::FMD_DESTINATION); diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index 1aa35d3dc..44d9cd17e 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -121,6 +121,10 @@ class RouteSegment final * @brief Whether the road is of a link type. */ bool m_isLink = false; + /** + * @brief Whether the road is part of a roundabout. + */ + bool m_onRoundabout = false; RoadNameInfo() = default; RoadNameInfo(std::string name) : m_name(std::move(name)) {} diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index c30693733..add330f9e 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -1030,6 +1030,32 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa TruncateRoute(rsegments, checkpoints); + /* + * `m_onRoundabout` is set only for the first segment after the junction. In order to identify + * all roundabout segments, cache the last segment with `m_onRoundabout` set. Any subsequent + * segment with the same MWM and feature ID is also a roundabout segment. + */ + routing::Segment lastRoundaboutSegment; + + /* + * We usually discard roundabouts, unless the location is a point (`at` point set) or the entire + * decoded location is a roundabout. + */ + bool keepRoundabouts = true; + + if (!message.m_location.value().m_at) + for (auto & rsegment : rsegments) + { + if (rsegment.GetRoadNameInfo().m_onRoundabout) + lastRoundaboutSegment = rsegment.GetSegment(); + else if ((rsegment.GetSegment().GetMwmId() != lastRoundaboutSegment.GetMwmId()) + || (rsegment.GetSegment().GetFeatureId() != lastRoundaboutSegment.GetFeatureId())) + { + keepRoundabouts = false; + break; + } + } + if (!backwards && message.m_location.value().m_at && !message.m_location.value().m_to) // from–at in forward direction, add last segment AddDecodedSegment(decoded, rsegments.back().GetSegment()); @@ -1070,6 +1096,19 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { routing::Segment & segment = rsegment.GetSegment(); + // Skip roundabouts to avoid side effects on crossing roads + if (!keepRoundabouts) + { + if (rsegment.GetRoadNameInfo().m_onRoundabout) + { + lastRoundaboutSegment = segment; + continue; + } + else if ((segment.GetMwmId() == lastRoundaboutSegment.GetMwmId()) + && (segment.GetFeatureId() == lastRoundaboutSegment.GetFeatureId())) + continue; + } + // If we have more than two checkpoints, fake segments can occur in the middle, skip them. if (segment.GetMwmId() == routing::kFakeNumMwmId) continue; From 3ec32e441586e97bcc9e20861531d908959a5a11 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 25 Oct 2025 15:12:05 +0300 Subject: [PATCH 194/252] [traffic] Use references for loop, not copies Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index add330f9e..c8ab1af3f 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -1075,7 +1075,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa routing::RouteSegment & closestRSegment = rsegments.front(); double closestDist = ms::DistanceOnEarth(at, mercator::ToLatLon(closestRSegment.GetJunction().GetPoint())); - for (auto rsegment : rsegments) + for (auto & rsegment : rsegments) { // If we have more than two checkpoints, fake segments can occur in the middle, skip them. if (rsegment.GetSegment().GetMwmId() == routing::kFakeNumMwmId) @@ -1092,7 +1092,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa } else // from–[via]–to, add all real segments - for (auto rsegment : rsegments) + for (auto & rsegment : rsegments) { routing::Segment & segment = rsegment.GetSegment(); From d88ed01bc11d608d8ed8a8acf49e30882a1e5f95 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 26 Oct 2025 13:15:07 +0200 Subject: [PATCH 195/252] [traffic] Update comment on offroad penalty Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index c8ab1af3f..1b84077dd 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -74,10 +74,10 @@ auto constexpr kOneMpSInKmpH = 3.6; * once the full attribute penalty). * A “wrong” road may also just have a penalty of 4 (e.g. road name mismatch, but road class and * ramp type match). - * A value of 16 has worked well for the DE-B2R-SendlingSued-Passauerstrasse test case. The - * DE-A10-Werder-GrossKreutz or DE-A115-PotsdamDrewitz-Nuthetal test cases still resolve incorrectly - * with an offroad penalty of 8 and even 2, presumably because the correct endpoints are not - * connected by fake segments. + * A value of 16 has worked well for the DE-B2R-SendlingSued-Passauerstrasse test case. (The + * DE-A10-Werder-GrossKreutz or DE-A115-PotsdamDrewitz-Nuthetal test cases gave incorrect results + * due to lack of fake segments, which was fixed through truncation and now works correctly even + * with an offroad penalty of 128.) */ auto constexpr kOffroadPenalty = 16; From fbc150fae2b642b42b035bd4e0a3e6c6d922f1c4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 26 Oct 2025 18:23:10 +0200 Subject: [PATCH 196/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/route.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index 44d9cd17e..deb04aa58 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -87,8 +87,9 @@ class RouteSegment final * * `m_ref` and `m_name` refer to the road itself. * - * This structure is fully populated only for the first segment after a junction. For other - * segments, members may be left at their default values (empty string or false). + * This structure is only populated for the first segment of a feature (segment index is either 0, + * or one less than the segment count of the feature, or the segment is the first segment of the + * route which is not a fake segment). For subsequent segments of the same feature, it is empty. */ struct RoadNameInfo { From d47aa0905303f1e8e732b5db11fb7d5ba7da8c8b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 27 Oct 2025 22:50:05 +0200 Subject: [PATCH 197/252] [traffxml] Process fuzziness attribute in location Signed-off-by: mvglasow --- libs/traffxml/traff_model.cpp | 15 ++++++++++++++- libs/traffxml/traff_model.hpp | 11 +++++++++-- libs/traffxml/traff_model_xml.cpp | 17 +++++++++++------ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/libs/traffxml/traff_model.cpp b/libs/traffxml/traff_model.cpp index 299b03813..f7b4ba483 100644 --- a/libs/traffxml/traff_model.cpp +++ b/libs/traffxml/traff_model.cpp @@ -370,6 +370,19 @@ std::string DebugPrint(Directionality directionality) UNREACHABLE(); } +std::string DebugPrint(Fuzziness fuzziness) +{ + switch (fuzziness) + { + case Fuzziness::LowRes: return "LowRes"; + case Fuzziness::MediumRes: return "MediumRes"; + case Fuzziness::EndUnknown: return "EndUnknown"; + case Fuzziness::StartUnknown: return "StartUnknown"; + case Fuzziness::ExtentUnknown: return "ExtentUnknown"; + } + UNREACHABLE(); +} + std::string DebugPrint(Ramps ramps) { switch (ramps) @@ -526,7 +539,7 @@ std::string DebugPrint(TraffLocation location) os << "via: " << (location.m_via ? DebugPrint(location.m_via.value()) : "nullopt") << ", "; os << "to: " << (location.m_to ? DebugPrint(location.m_to.value()) : "nullopt") << ", "; os << "notVia: " << (location.m_notVia ? DebugPrint(location.m_notVia.value()) : "nullopt") << ", "; - // TODO fuzziness (not yet implemented in struct) + os << "fuzziness: " << (location.m_fuzziness ? DebugPrint(location.m_fuzziness.value()) : "nullopt") << ", "; os << "country: " << location.m_country.value_or("nullopt") << ", "; os << "territory: " << location.m_territory.value_or("nullopt") << ", "; os << "town: " << location.m_town.value_or("nullopt") << ", "; diff --git a/libs/traffxml/traff_model.hpp b/libs/traffxml/traff_model.hpp index 0cee71c26..616f8b31a 100644 --- a/libs/traffxml/traff_model.hpp +++ b/libs/traffxml/traff_model.hpp @@ -100,7 +100,14 @@ enum class Directionality BothDirections }; -// TODO enum fuzziness +enum class Fuzziness +{ + LowRes, + MediumRes, + EndUnknown, + StartUnknown, + ExtentUnknown +}; enum class Ramps { @@ -383,7 +390,7 @@ struct TraffLocation std::optional m_destination; std::optional m_direction; Directionality m_directionality = Directionality::BothDirections; - // TODO std::optional m_fuzziness; + std::optional m_fuzziness; std::optional m_origin; Ramps m_ramps = Ramps::None; std::optional m_roadClass; diff --git a/libs/traffxml/traff_model_xml.cpp b/libs/traffxml/traff_model_xml.cpp index bc31b346b..243956921 100644 --- a/libs/traffxml/traff_model_xml.cpp +++ b/libs/traffxml/traff_model_xml.cpp @@ -36,6 +36,14 @@ const boost::bimap kDirectionalityMap = MakeBimap kFuzzinessMap = MakeBimap({ + {"LOW_RES", Fuzziness::LowRes}, + {"MEDIUM_RES", Fuzziness::MediumRes}, + {"END_UNKNOWN", Fuzziness::EndUnknown}, + {"START_UNKNOWN", Fuzziness::StartUnknown}, + {"EXTENT_UNKNOWN", Fuzziness::ExtentUnknown} +}); + const boost::bimap kRampsMap = MakeBimap({ {"ALL_RAMPS", Ramps::All}, {"ENTRY_RAMP", Ramps::Entry}, @@ -597,9 +605,7 @@ bool LocationFromXml(pugi::xml_node const & node, TraffLocation & location) location.m_direction = OptionalStringFromXml(node.attribute("direction")); EnumFromXml(node.attribute("directionality"), location.m_directionality, kDirectionalityMap); - - // TODO fuzziness (not yet implemented in struct) - + location.m_fuzziness = OptionalEnumFromXml(node.attribute("fuzziness"), kFuzzinessMap); location.m_origin = OptionalStringFromXml(node.attribute("origin")); EnumFromXml(node.attribute("ramps"), location.m_ramps, kRampsMap); location.m_roadClass = OptionalEnumFromXml(node.attribute("road_class"), kRoadClassMap); @@ -628,9 +634,8 @@ void LocationToXml(TraffLocation const & location, pugi::xml_node & node) if (location.m_direction) node.append_attribute("direction").set_value(location.m_direction.value()); EnumToXml(location.m_directionality, "directionality", node, kDirectionalityMap); - - // TODO fuzziness (not yet implemented in struct) - + if (location.m_fuzziness) + EnumToXml(location.m_fuzziness.value(), "fuzziness", node, kFuzzinessMap); if (location.m_origin) node.append_attribute("origin").set_value(location.m_origin.value()); EnumToXml(location.m_ramps, "ramps", node, kRampsMap); From 207d6c833d1f004a5be42b3f3d82cd8728eec78d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 30 Oct 2025 21:50:19 +0200 Subject: [PATCH 198/252] [traffic] Pass toJunctions parameter to TruncateRoute() Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 18 ++++++++++-------- libs/traffxml/traff_decoder.hpp | 9 ++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 1b84077dd..0d1b39eb5 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -878,7 +878,7 @@ void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded } void RoutingTraffDecoder::TruncateRoute(std::vector & rsegments, - routing::Checkpoints const & checkpoints) + routing::Checkpoints const & checkpoints, bool toJunctions) { double const endWeight = rsegments.back().GetTimeFromBeginningSec(); @@ -901,8 +901,8 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse // Cost saved by omitting the last `end` segments. double endSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving); - TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + TruncateStart(rsegments, checkpoints, start, startSaving, toJunctions); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, toJunctions); /* * If start <= end, we can truncate both ends at the same time. @@ -920,7 +920,7 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse rsegments.erase(rsegments.begin(), rsegments.begin() + start); end = rsegments.size() - 1; endSaving = 0; - TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, toJunctions); rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); } else @@ -929,7 +929,7 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); start = 0; startSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving); + TruncateStart(rsegments, checkpoints, start, startSaving, toJunctions); rsegments.erase(rsegments.begin(), rsegments.begin() + start); } } @@ -1028,7 +1028,9 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { std::vector rsegments(route->GetRouteSegments()); - TruncateRoute(rsegments, checkpoints); + TruncateRoute(rsegments, checkpoints, + message.m_location.value().m_fuzziness + && (message.m_location.value().m_fuzziness.value() == traffxml::Fuzziness::LowRes)); /* * `m_onRoundabout` is set only for the first segment after the junction. In order to identify @@ -1291,7 +1293,7 @@ std::vector ParseRef(std::string & ref) void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving) + size_t & start, double & startSaving, bool toJunction) { for (size_t i = 0; i < rsegments.size(); i++) { @@ -1308,7 +1310,7 @@ void TruncateStart(std::vector & rsegments, void TruncateEnd(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & end, double & endSaving, double const endWeight) + size_t & end, double & endSaving, double const endWeight, bool toJunction) { for (size_t i = 0; i < rsegments.size(); i++) { diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index c32ade1b7..753abec7c 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -415,9 +415,10 @@ class RoutingTraffDecoder : public TraffDecoder, * * @param rsegments The segments of the route * @param checkpoints The reference points (at least two) + * @param toJunctions Whether the truncated route should begin and end at a junction */ void TruncateRoute(std::vector & rsegments, - routing::Checkpoints const & checkpoints); + routing::Checkpoints const & checkpoints, bool toJunctions); private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); @@ -483,10 +484,11 @@ std::vector ParseRef(std::string & ref); * @param checkpoints The reference points (at least two) * @param start Index of the first segment to keep * @param startSaving Cost saved by truncating + * @param toJunction Whether the truncated route should start at a junction */ void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving); + size_t & start, double & startSaving, bool toJunction); /** * @brief Calculates the segments to truncate at the start of the route. @@ -501,8 +503,9 @@ void TruncateStart(std::vector & rsegments, * @param checkpoints The reference points (at least two) * @param end Index of the last segment to keep * @param endSaving Cost saved by truncating + * @param toJunction Whether the truncated route should end at a junction */ void TruncateEnd(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & end, double & endSaving, double const endWeight); + size_t & end, double & endSaving, double const endWeight, bool ToJunction); } // namespace traffxml From c0fd4057980794e73f56021bbc9c749f73353bbc Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 1 Nov 2025 13:13:10 +0200 Subject: [PATCH 199/252] [traffic] Refactor methods for penalty calculation Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 201 +++++++++++++++++--------------- libs/traffxml/traff_decoder.hpp | 72 +++++++++--- 2 files changed, 165 insertions(+), 108 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 0d1b39eb5..f67130775 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -509,67 +509,6 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf } #endif -double RoutingTraffDecoder::TraffEstimator::GetRoadRefPenalty(std::string & ref) const -{ - // skip parsing if ref is empty - if (ref.empty()) - { - if (m_decoder.m_roadRef.empty()) - return 1; - else if (!m_decoder.m_roadRef.empty()) - return kAttributePenalty; - } - - // TODO does caching results per ref improve performance? - - std::vector r = ParseRef(ref); - - size_t matches = 0; - - if (m_decoder.m_roadRef.empty() && r.empty()) - return 1; - else if (m_decoder.m_roadRef.empty() || r.empty()) - return kAttributePenalty; - - // work on a copy of `m_decoder.m_roadRef` - std::vector l = m_decoder.m_roadRef; - - if ((l.size() > 1) && (r.size() > 1) && (l.front() == r.front())) - { - /* - * Discard generic prefixes, which are often used to denote the road class. - * This will turn `A1` and `A2` into `1` and `2`, causing them to be treated as a mismatch, - * not a partial match. - */ - l.erase(l.begin()); - r.erase(r.begin()); - } - - // for both sides, count items matched by the other side - for (auto & litem : l) - for (auto ritem : r) - if (litem == ritem) - { - matches++; - break; - } - - for (auto ritem : r) - for (auto & litem : l) - if (litem == ritem) - { - matches++; - break; - } - - if (matches == 0) - return kAttributePenalty; - else if (matches == (l.size() + r.size())) - return 1; - else - return kReducedAttributePenalty; -} - double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { // Adds 2 minutes penalty for U-turn. The value is quite arbitrary @@ -665,26 +604,9 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c if (!m_decoder.m_message || !m_decoder.m_message.value().m_location.value().m_roadClass) return result; - std::optional highwayType = road.GetHighwayType(); - - if (highwayType) - { - if (IsRamp(highwayType.value()) != (m_decoder.m_message.value().m_location.value().m_ramps != Ramps::None)) - // if one is a ramp and the other is not, treat it as a mismatch - result *= kAttributePenalty; - if (m_decoder.m_message.value().m_location.value().m_roadClass) - // if the message specifies a road class, penalize mismatches - result *= GetRoadClassPenalty(m_decoder.m_message.value().m_location.value().m_roadClass.value(), - GetRoadClass(highwayType.value())); - } - else // road has no highway class - { - // we can’t determine if it is a ramp, penalize for mismatch - result *= kAttributePenalty; - if (m_decoder.m_message.value().m_location.value().m_roadClass) - // we can’t determine if the road matches the required road class, treat it as mismatch - result *= kAttributePenalty; - } + result *= GetHighwayTypePenalty(road.GetHighwayType(), + m_decoder.m_message.value().m_location.value().m_roadClass, + m_decoder.m_message.value().m_location.value().m_ramps); if (!m_decoder.m_roadRef.empty()) { @@ -694,18 +616,7 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c auto f = g.GetOriginalFeatureByIndex(segment.GetFeatureId()); auto refs = ftypes::GetRoadShieldsNames(*f); - auto penalty = kAttributePenalty; - - for (auto & ref : refs) - { - auto newPenalty = GetRoadRefPenalty(ref); - if (newPenalty < penalty) - penalty = newPenalty; - if (penalty == 1) - break; - } - - result *= penalty; + result *= m_decoder.GetRoadRefPenalty(refs); } return result; @@ -747,6 +658,108 @@ RoutingTraffDecoder::RoutingTraffDecoder(DataSource & dataSource, CountryInfoGet InitRouter(); } +double RoutingTraffDecoder::GetHighwayTypePenalty(std::optional highwayType, + std::optional roadClass, + Ramps ramps) +{ + double result = 1.0; + if (highwayType) + { + if (IsRamp(highwayType.value()) != (ramps != Ramps::None)) + // if one is a ramp and the other is not, treat it as a mismatch + result *= kAttributePenalty; + if (roadClass) + // if the message specifies a road class, penalize mismatches + result *= GetRoadClassPenalty(roadClass.value(), GetRoadClass(highwayType.value())); + } + else // road has no highway class + { + // we can’t determine if it is a ramp, penalize for mismatch + result *= kAttributePenalty; + if (roadClass) + // we can’t determine if the road matches the required road class, treat it as mismatch + result *= kAttributePenalty; + } + return result; +} + +double RoutingTraffDecoder::GetRoadRefPenalty(std::vector & refs) const +{ + double result = kAttributePenalty; + + for (auto & ref : refs) + { + auto newResult = GetRoadRefPenalty(ref); + if (newResult < result) + result = newResult; + if (result == 1) + break; + } + + return result; +} + +double RoutingTraffDecoder::GetRoadRefPenalty(std::string const & ref) const +{ + // skip parsing if ref is empty + if (ref.empty()) + { + if (m_roadRef.empty()) + return 1; + else if (!m_roadRef.empty()) + return kAttributePenalty; + } + + // TODO does caching results per ref improve performance? + + std::vector r = ParseRef(ref); + + size_t matches = 0; + + if (m_roadRef.empty() && r.empty()) + return 1; + else if (m_roadRef.empty() || r.empty()) + return kAttributePenalty; + + // work on a copy of `m_decoder.m_roadRef` + std::vector l = m_roadRef; + + if ((l.size() > 1) && (r.size() > 1) && (l.front() == r.front())) + { + /* + * Discard generic prefixes, which are often used to denote the road class. + * This will turn `A1` and `A2` into `1` and `2`, causing them to be treated as a mismatch, + * not a partial match. + */ + l.erase(l.begin()); + r.erase(r.begin()); + } + + // for both sides, count items matched by the other side + for (auto & litem : l) + for (auto ritem : r) + if (litem == ritem) + { + matches++; + break; + } + + for (auto ritem : r) + for (auto & litem : l) + if (litem == ritem) + { + matches++; + break; + } + + if (matches == 0) + return kAttributePenalty; + else if (matches == (l.size() + r.size())) + return 1; + else + return kReducedAttributePenalty; +} + void RoutingTraffDecoder::OnMapRegistered(platform::LocalCountryFile const & localFile) { std::lock_guard lock(m_mutex); @@ -1234,7 +1247,7 @@ bool IsRamp(routing::HighwayType highwayType) } } -std::vector ParseRef(std::string & ref) +std::vector ParseRef(std::string const & ref) { std::vector res; std::string curr = ""; diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 753abec7c..1a3ec0dbd 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -303,19 +303,6 @@ class RoutingTraffDecoder : public TraffDecoder, double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const override; double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const override; - /** - * @brief Determines the penalty factor based on how two reference numbers match. - * - * Rules are subject to change. - * - * @param ref The reference number of the current segment, compared against `m_roadRef`. - * - * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` - * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for - * a partial match (unclear whether both refs refer to the same object). - */ - double GetRoadRefPenalty(std::string & ref) const; - double GetUTurnPenalty(Purpose /* purpose */) const override; /** @@ -354,6 +341,63 @@ class RoutingTraffDecoder : public TraffDecoder, */ virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {} + /** + * @brief Determines the penalty factor bases on how highway attributes match. + * + * This compares the highway type of the candidate feature (as retrieved from OSM) against the + * road class and ramps attributes of the location. + * + * Rules are subject to change but principles are: + * + * Penalties for ramp mismatch and road class mismatch are applied consecutively, thus the maximum + * penalty is `kAttributePenalty ^ 2`. + * + * If ramps mismatch (location specifies a ramp but candidate is not a ramp, or vice versa), the + * penalty is `kAttributePenalty`. + * + * If road classes are similar, the penalty is `kReducedAttributePenalty`. For a complete + * mismatch, the penalty is `kAttributePenalty`. + * + * @param highwayType The OSM highway type of the candidate feature. + * @param roadClass The TraFF road class of the location. + * @param ramps The ramps atribute of the TraFF location. + * + * @return 1 for a perfect match (same road class and ramp type), up to `kAttributePenalty ^ 2` + * for a mismatch. + */ + static double GetHighwayTypePenalty(std::optional highwayType, + std::optional roadClass, + Ramps ramps); + + /** + * @brief Determines the penalty factor based on how two reference numbers match. + * + * Rules are subject to change. + * + * This method takes a vector as an argument, compares each element and returns the penalty for + * the best match. + * + * @param refs A vector of reference numbers of the current segment, compared against `m_roadRef`. + * + * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` + * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for + * a partial match (unclear whether both refs refer to the same object). + */ + double GetRoadRefPenalty(std::vector & refs) const; + + /** + * @brief Determines the penalty factor based on how two reference numbers match. + * + * Rules are subject to change. + * + * @param ref The reference number of the current segment, compared against `m_roadRef`. + * + * @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty` + * for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for + * a partial match (unclear whether both refs refer to the same object). + */ + double GetRoadRefPenalty(std::string const & ref) const; + protected: /** * @brief Initializes the router. @@ -470,7 +514,7 @@ bool IsRamp(routing::HighwayType highwayType); * For example, each of `A42`, `A 42` and `-a42` would be broken down into `a, 42`, whereas `A4.2` * would be broken down into `a, 4, 2`. */ -std::vector ParseRef(std::string & ref); +std::vector ParseRef(std::string const & ref); /** * @brief Calculates the segments to truncate at the start of the route. From 2b867a64a0f0b1bbbb9295658ac36130b25ebd38 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 2 Nov 2025 11:14:38 +0200 Subject: [PATCH 200/252] [traffic] Reduce weight for fake segments involving junctions Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 162 +++++++++++++++++++++++++++++++- libs/traffxml/traff_decoder.hpp | 58 ++++++++++++ 2 files changed, 217 insertions(+), 3 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index f67130775..0865d09e1 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -21,7 +21,9 @@ #include "routing/maxspeeds.hpp" #include "routing/route.hpp" #include "routing/router_delegate.hpp" +#include "routing/routing_helpers.hpp" +#include "routing_common/car_model.hpp" #include "routing_common/maxspeed_conversion.hpp" #include "storage/routing_helpers.hpp" @@ -91,6 +93,11 @@ auto constexpr kAttributePenalty = 4; */ auto constexpr kReducedAttributePenalty = 2; +/* + * Radius around reference point in which to search for junctions + */ +auto constexpr kJunctionPointRadius = 500.0; + /* * Maximum distance in meters from location endpoint at which a turn penalty is applied */ @@ -583,11 +590,65 @@ double RoutingTraffDecoder::TraffEstimator::GetFerryLandingPenalty(Purpose /* pu double RoutingTraffDecoder::TraffEstimator::CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const { - double result = ms::DistanceOnEarth(from, to); + /* + * Usage of this method is not quite clear. For some locations this method never gets called. + * There is also no clear pattern in which of the two arguments is the reference point and which + * is part of a segment. Either reference point can appear as either argument for either direction, + * nothing to infer from a particular reference point appearing in a particular argument. + */ - result *= kOffroadPenalty; + double defaultWeight = ms::DistanceOnEarth(from, to) * kOffroadPenalty; - return result; + /* + * Retrieves offroad weight from the junctions map supplied, if found, or default. + * + * Bugs: Due to back-and-forth conversion of `roadPoint` from Mercator to WGS84 and back, it may + * no longer match its counterpart in `junctions` (near-miss). + * + * Tests showed very few actual matches. Extending this logic to return near-matches did return + * some more, but still relatively few. This may be due to the way fake segments are chosen. + * + * refPoint: point from TraFF location + * roadPoint: point on segment + * junctions: known junctions for `refPoint` + * + * Returns: reduced offroad weight from table, or default offroad weight if not found + */ + auto const getOffroadFromJunction = [defaultWeight](ms::LatLon const & refPoint, + ms::LatLon const & roadPoint, + std::map const & junctions) + { + m2::PointD m2RoadPoint = mercator::FromLatLon(roadPoint); + auto it = junctions.find(m2RoadPoint); + if (it != junctions.end()) + return it->second; + // TODO this is likely an inefficient way to return near-matches + for (auto & [point, weight] : junctions) + if (m2RoadPoint.EqualDxDy(point, kMwmPointAccuracy)) + return weight; + return defaultWeight; + }; + + /* + * If one of from/to is a reference point and the other is in the corresponding junction map, + * return the weight from the map + */ + if (m_decoder.m_message.value().m_location.value().m_from) + { + if (m_decoder.m_message.value().m_location.value().m_from.value().m_coordinates == from) + return getOffroadFromJunction(from, to, m_decoder.m_startJunctions); + else if (m_decoder.m_message.value().m_location.value().m_from.value().m_coordinates == to) + return getOffroadFromJunction(to, from, m_decoder.m_startJunctions); + } + if (m_decoder.m_message.value().m_location.value().m_to) + { + if (m_decoder.m_message.value().m_location.value().m_to.value().m_coordinates == from) + return getOffroadFromJunction(from, to, m_decoder.m_endJunctions); + else if (m_decoder.m_message.value().m_location.value().m_to.value().m_coordinates == to) + return getOffroadFromJunction(to, from, m_decoder.m_endJunctions); + } + + return defaultWeight; } /* @@ -1145,11 +1206,106 @@ void RoutingTraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traff else m_roadRef.clear(); + GetJunctionPointCandidates(); + int dirs = (message.m_location.value().m_directionality == Directionality::BothDirections) ? 2 : 1; for (int dir = 0; dir < dirs; dir++) DecodeLocationDirection(message, decoded, dir == 0 ? false : true /* backwards */); m_message = std::nullopt; + m_roadRef.clear(); +} + +void RoutingTraffDecoder::GetJunctionPointCandidates() +{ + m_startJunctions.clear(); + m_endJunctions.clear(); + + if (m_message.value().m_location.value().m_fuzziness + && (m_message.value().m_location.value().m_fuzziness.value() == traffxml::Fuzziness::LowRes)) + { + if (m_message.value().m_location.value().m_from) + GetJunctionPointCandidates(m_message.value().m_location.value().m_from.value(), m_startJunctions); + if (m_message.value().m_location.value().m_to) + GetJunctionPointCandidates(m_message.value().m_location.value().m_to.value(), m_endJunctions); + } +} + +void RoutingTraffDecoder::GetJunctionPointCandidates(Point const & point, + std::map & junctions) +{ + m2::PointD const m2Point = mercator::FromLatLon(point.m_coordinates); + std::map pointCandidates; + auto const selectCandidates = [&m2Point, &pointCandidates, this](FeatureType & ft) + { + ft.ParseGeometry(FeatureType::BEST_GEOMETRY); + if (ft.GetGeomType() != feature::GeomType::Line || !routing::IsRoad(feature::TypesHolder(ft))) + return; + + for (auto i : {size_t(0), ft.GetPointsCount() - 1}) + { + double weight = mercator::DistanceOnEarth(m2Point, ft.GetPoint(i)); + + // TODO make junction point radius dependent on distance between reference points + if (weight > kJunctionPointRadius) + continue; + + weight *= GetHighwayTypePenalty(routing::CarModel::AllLimitsInstance().GetHighwayType(feature::TypesHolder(ft)), + m_message.value().m_location.value().m_roadClass, + m_message.value().m_location.value().m_ramps); + + auto refs = ftypes::GetRoadShieldsNames(ft); + weight *= GetRoadRefPenalty(refs); + + /* + * Store candidate point and weight (unless we already have a lower weight). + * These are points read directly from the map, so we should be able to work with true matches + * (according to tests, near-matches are rare and the one we examined was close to the + * tolerance limit, so it could have been accidental). + */ + auto it = pointCandidates.find(ft.GetPoint(i)); + if (it == pointCandidates.end()) + it = pointCandidates.insert(std::make_pair(ft.GetPoint(i), JunctionCandidateInfo(weight))).first; + else if (weight < it->second.m_weight) + it->second.m_weight = weight; + + // check oneway attribute and increase appropriate segment count + if (!ftypes::IsOneWayChecker::Instance()(ft)) + it->second.m_twoWaySegments++; + else if (i == 0) + it->second.m_segmentsOut++; + else + it->second.m_segmentsIn++; + } + }; + + m_dataSource.ForEachInRect(selectCandidates, mercator::RectByCenterXYAndSizeInMeters(m2Point, kJunctionPointRadius), + scales::GetUpperScale()); + + /* + * Cycle through point candidates and see if they are really junctions. A point is a junction if + * it can be left through more than one segment, other than the one through which it was reached, + * or reached through more than one segment, other than the one through which it will be left. + * Junctions are added to `junctions`, other points are skipped. + * Bug: may fail to catch duplicate ways at MWM boundaries + */ + for (auto & [candidatePoint, candidateInfo] : pointCandidates) + { + if (candidateInfo.m_segmentsIn > 0) + candidateInfo.m_segmentsIn--; + else if (candidateInfo.m_twoWaySegments > 0) + candidateInfo.m_twoWaySegments--; + + if (candidateInfo.m_segmentsOut > 0) + candidateInfo.m_segmentsOut--; + else if (candidateInfo.m_twoWaySegments > 0) + candidateInfo.m_twoWaySegments--; + + if ((candidateInfo.m_segmentsIn > 0) + || (candidateInfo.m_segmentsOut > 0) + || (candidateInfo.m_twoWaySegments > 0)) + junctions.insert(std::make_pair(candidatePoint, candidateInfo.m_weight)); + } } traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType) diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 1a3ec0dbd..58515a02c 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -325,6 +325,18 @@ class RoutingTraffDecoder : public TraffDecoder, RoutingTraffDecoder & m_decoder; }; + struct JunctionCandidateInfo + { + JunctionCandidateInfo(double weight) + : m_weight(weight) + {} + + double m_weight; + size_t m_segmentsIn = 0; + size_t m_segmentsOut = 0; + size_t m_twoWaySegments = 0; + }; + RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, const CountryParentNameGetterFn & countryParentNameGetter, std::map & messageCache); @@ -467,6 +479,36 @@ class RoutingTraffDecoder : public TraffDecoder, private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); + /** + * @brief Populates the list of candidates for junction points. + * + * If the location has a fuzziness of `LowRes`, the map is searched for candidates around the + * `from` and `to` points, which are taken from the `m_location` member of `m_message`. The weight + * for each candidate is calculated based on its distance from the reference point and the match + * between the attributes of the segment and the location. Since junction points are part of + * multiple segments, the best match wins. Candidates and their weight are stored in + * `m_startJunctions` and `m_endJunctions`. + * + * If the location’s fuzziness attribute is empty or does not equal `LowRes`, `m_startJunctions` + * and `m_endJunctions` are cleared. + */ + void GetJunctionPointCandidates(); + + /** + * @brief Populates a list of candidates for junction points. + * + * Implementation for `GetJunctionPointCandidates()`. The map is searched for candidates around + * `point`. The weight for each candidate is calculated based on its distance from `point` and + * the match between the attributes of the segment and the location of `m_message`. Since junction + * points are part of multiple segments, the best match wins. Candidates and their weight are + * stored in `junctions`. + * + * @param point The reference point + * @param junctions Receives a list of junction candidates with their weight + */ + void GetJunctionPointCandidates(Point const & point, + std::map & junctions); + /** * @brief Mutex for access to shared members. * @@ -482,6 +524,22 @@ class RoutingTraffDecoder : public TraffDecoder, std::unique_ptr m_router; std::optional m_message = std::nullopt; + /** + * @brief Junction points near start of location, with their associated offroad weight. + * + * If the list is empty, no junction alignment at the `from` point will be done and decoding + * relies solely on point coordinates. + */ + std::map m_startJunctions; + + /** + * @brief Junction points near end of location, with their associated offroad weight. + * + * If the list is empty, no junction alignment at the `to` point will be done and decoding + * relies solely on point coordinates. + */ + std::map m_endJunctions; + /** * @brief The road ref of `m_message`, parsed with `ParseRef()` */ From 05f6dfad7b67026bfdfa0f2067d54d7925ad4210 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 2 Nov 2025 21:42:21 +0200 Subject: [PATCH 201/252] [traffic] Dyamically determine radius in which to search for junctions Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 37 ++++++++++++++++++++++++++++----- libs/traffxml/traff_decoder.hpp | 10 +++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 0865d09e1..d3426ed3a 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -94,9 +94,15 @@ auto constexpr kAttributePenalty = 4; auto constexpr kReducedAttributePenalty = 2; /* - * Radius around reference point in which to search for junctions + * Lower boundary for radius around endpoint in which to search for junctions, in meters + * (unless the lower boundary exceeds half the distance between endpoints) */ -auto constexpr kJunctionPointRadius = 500.0; +auto constexpr kJunctionRadiusMin = 300.0; + +/* + * Upper boundary for radius around endpoint in which to search for junctions, in meters + */ +auto constexpr kJunctionRadiusMax = 500.0; /* * Maximum distance in meters from location endpoint at which a turn penalty is applied @@ -1224,6 +1230,28 @@ void RoutingTraffDecoder::GetJunctionPointCandidates() if (m_message.value().m_location.value().m_fuzziness && (m_message.value().m_location.value().m_fuzziness.value() == traffxml::Fuzziness::LowRes)) { + /* + * Identify coordinates of location endpoints and of the turn, and determine distance. + */ + ms::LatLon from = m_message.value().m_location.value().m_from + ? m_message.value().m_location.value().m_from.value().m_coordinates + : m_message.value().m_location.value().m_at.value().m_coordinates; + ms::LatLon to = m_message.value().m_location.value().m_to + ? m_message.value().m_location.value().m_to.value().m_coordinates + : m_message.value().m_location.value().m_at.value().m_coordinates; + + auto dist = ms::DistanceOnEarth(from, to); + + m_junctionRadius = dist / 3.0f; + if (m_junctionRadius > kJunctionRadiusMax) + m_junctionRadius = kJunctionRadiusMax; + else if (m_junctionRadius < kJunctionRadiusMin) + { + m_junctionRadius = dist / 2.0f; + if (m_junctionRadius > kJunctionRadiusMin) + m_junctionRadius = kJunctionRadiusMin; + } + if (m_message.value().m_location.value().m_from) GetJunctionPointCandidates(m_message.value().m_location.value().m_from.value(), m_startJunctions); if (m_message.value().m_location.value().m_to) @@ -1246,8 +1274,7 @@ void RoutingTraffDecoder::GetJunctionPointCandidates(Point const & point, { double weight = mercator::DistanceOnEarth(m2Point, ft.GetPoint(i)); - // TODO make junction point radius dependent on distance between reference points - if (weight > kJunctionPointRadius) + if (weight > m_junctionRadius) continue; weight *= GetHighwayTypePenalty(routing::CarModel::AllLimitsInstance().GetHighwayType(feature::TypesHolder(ft)), @@ -1279,7 +1306,7 @@ void RoutingTraffDecoder::GetJunctionPointCandidates(Point const & point, } }; - m_dataSource.ForEachInRect(selectCandidates, mercator::RectByCenterXYAndSizeInMeters(m2Point, kJunctionPointRadius), + m_dataSource.ForEachInRect(selectCandidates, mercator::RectByCenterXYAndSizeInMeters(m2Point, m_junctionRadius), scales::GetUpperScale()); /* diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 58515a02c..086e3b879 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -540,6 +540,16 @@ class RoutingTraffDecoder : public TraffDecoder, */ std::map m_endJunctions; + /** + * @brief Radius around reference points in which to search for junctions. + * + * Determined dynamically, based on distance between reference points. + * Maximum distance is never more than half the distance between endpoints. + * It should be between `kJunctionRadiusMin` and `kJunctionRadiusMax`, and as close as possible to + * 1/3 the distance. + */ + double m_junctionRadius; + /** * @brief The road ref of `m_message`, parsed with `ParseRef()` */ From af20e2b42bfedca743a5c6b1eeefcb13dfc301cb Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 3 Nov 2025 20:16:53 +0200 Subject: [PATCH 202/252] [traffic] Truncate decoded location to nearby junctions Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 81 ++++++++++++++++++++++++++------- libs/traffxml/traff_decoder.hpp | 14 +++--- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index d3426ed3a..e1b82e330 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -958,7 +958,7 @@ void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded } void RoutingTraffDecoder::TruncateRoute(std::vector & rsegments, - routing::Checkpoints const & checkpoints, bool toJunctions) + routing::Checkpoints const & checkpoints, bool backwards) { double const endWeight = rsegments.back().GetTimeFromBeginningSec(); @@ -981,8 +981,10 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse // Cost saved by omitting the last `end` segments. double endSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving, toJunctions); - TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, toJunctions); + TruncateStart(rsegments, checkpoints, start, startSaving, + backwards ? m_endJunctions : m_startJunctions); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, + backwards ? m_startJunctions : m_endJunctions); /* * If start <= end, we can truncate both ends at the same time. @@ -1000,7 +1002,8 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse rsegments.erase(rsegments.begin(), rsegments.begin() + start); end = rsegments.size() - 1; endSaving = 0; - TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, toJunctions); + TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, + backwards ? m_startJunctions : m_endJunctions); rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); } else @@ -1009,7 +1012,8 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); start = 0; startSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving, toJunctions); + TruncateStart(rsegments, checkpoints, start, startSaving, + backwards ? m_endJunctions : m_startJunctions); rsegments.erase(rsegments.begin(), rsegments.begin() + start); } } @@ -1108,9 +1112,7 @@ void RoutingTraffDecoder::DecodeLocationDirection(traffxml::TraffMessage & messa { std::vector rsegments(route->GetRouteSegments()); - TruncateRoute(rsegments, checkpoints, - message.m_location.value().m_fuzziness - && (message.m_location.value().m_fuzziness.value() == traffxml::Fuzziness::LowRes)); + TruncateRoute(rsegments, checkpoints, backwards); /* * `m_onRoundabout` is set only for the first segment after the junction. In order to identify @@ -1489,13 +1491,38 @@ std::vector ParseRef(std::string const & ref) void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving, bool toJunction) + size_t & start, double & startSaving, + std::map const & junctions) { + if (rsegments.empty()) + return; + for (size_t i = 0; i < rsegments.size(); i++) { - double newStartSaving = rsegments[i].GetTimeFromBeginningSec() - - (mercator::DistanceOnEarth(checkpoints.GetStart(), rsegments[i].GetJunction().GetPoint()) - * kOffroadPenalty); + double newStartSaving = 0; + /* + * Examine end point of segment: for a junction, take weight from table; else calculate it as + * direct distance multiplied with offroad penalty; calculate saving based on that. + */ + auto it = junctions.find(rsegments[i].GetJunction().GetPoint()); + if (it != junctions.end()) + newStartSaving = rsegments[i].GetTimeFromBeginningSec() - it->second; + else + { + bool matched = false; + // TODO this is likely an inefficient way to return near-matches + for (auto & [point, weight] : junctions) + if (rsegments[i].GetJunction().GetPoint().EqualDxDy(point, kMwmPointAccuracy)) + { + newStartSaving = rsegments[i].GetTimeFromBeginningSec() - weight; + matched = true; + break; + } + if (!matched) + newStartSaving = rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(checkpoints.GetStart(), rsegments[i].GetJunction().GetPoint()) + * kOffroadPenalty); + } if (newStartSaving > startSaving) { start = i + 1; // add 1 because we are ditching this segment and keeping the next one @@ -1506,13 +1533,35 @@ void TruncateStart(std::vector & rsegments, void TruncateEnd(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & end, double & endSaving, double const endWeight, bool toJunction) + size_t & end, double & endSaving, double const endWeight, + std::map const & junctions) { for (size_t i = 0; i < rsegments.size(); i++) { - double newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() - - (mercator::DistanceOnEarth(rsegments[i].GetJunction().GetPoint(), checkpoints.GetFinish()) - * kOffroadPenalty); + double newEndSaving = 0; + /* + * Examine end point of segment: for a junction, take weight from table; else calculate it as + * direct distance multiplied with offroad penalty; calculate saving based on that. + */ + auto it = junctions.find(rsegments[i].GetJunction().GetPoint()); + if (it != junctions.end()) + newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() - it->second; + else + { + bool matched = false; + // TODO this is likely an inefficient way to return near-matches + for (auto & [point, weight] : junctions) + if (rsegments[i].GetJunction().GetPoint().EqualDxDy(point, kMwmPointAccuracy)) + { + newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() - weight; + matched = true; + break; + } + if (!matched) + newEndSaving = endWeight - rsegments[i].GetTimeFromBeginningSec() + - (mercator::DistanceOnEarth(rsegments[i].GetJunction().GetPoint(), checkpoints.GetFinish()) + * kOffroadPenalty); + } if (newEndSaving > endSaving) { end = i; diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 086e3b879..9e5869aa6 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -471,10 +471,10 @@ class RoutingTraffDecoder : public TraffDecoder, * * @param rsegments The segments of the route * @param checkpoints The reference points (at least two) - * @param toJunctions Whether the truncated route should begin and end at a junction + * @param backwards True when decoding the backward direction, false when decodign the forward direction. */ void TruncateRoute(std::vector & rsegments, - routing::Checkpoints const & checkpoints, bool toJunctions); + routing::Checkpoints const & checkpoints, bool backwards); private: static void LogCode(routing::RouterResultCode code, double const elapsedSec); @@ -596,11 +596,12 @@ std::vector ParseRef(std::string const & ref); * @param checkpoints The reference points (at least two) * @param start Index of the first segment to keep * @param startSaving Cost saved by truncating - * @param toJunction Whether the truncated route should start at a junction + * @param junctions Junctions with the weight of their leap segment */ void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving, bool toJunction); + size_t & start, double & startSaving, + std::map const & junctions); /** * @brief Calculates the segments to truncate at the start of the route. @@ -615,9 +616,10 @@ void TruncateStart(std::vector & rsegments, * @param checkpoints The reference points (at least two) * @param end Index of the last segment to keep * @param endSaving Cost saved by truncating - * @param toJunction Whether the truncated route should end at a junction + * @param junctions Junctions with the weight of their leap segment */ void TruncateEnd(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & end, double & endSaving, double const endWeight, bool ToJunction); + size_t & end, double & endSaving, double const endWeight, + std::map const & junctions); } // namespace traffxml From 615f57c604f9b7c24b35aa9a5e6c0f9971938195 Mon Sep 17 00:00:00 2001 From: x7z4w Date: Mon, 3 Nov 2025 15:32:26 +0000 Subject: [PATCH 203/252] [traffxml] Faster MessageFromXml Signed-off-by: x7z4w --- libs/traffxml/traff_model.cpp | 16 +++++++++------- libs/traffxml/traff_model_xml.cpp | 14 +++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/libs/traffxml/traff_model.cpp b/libs/traffxml/traff_model.cpp index f7b4ba483..1a083a55a 100644 --- a/libs/traffxml/traff_model.cpp +++ b/libs/traffxml/traff_model.cpp @@ -3,13 +3,15 @@ #include "base/logging.hpp" #include -#include +#include + +#include using namespace std; namespace traffxml { -const std::map kEventSpeedGroupMap{ +const std::unordered_map kEventSpeedGroupMap{ // TODO Activity*, Authority*, Carpool* (not in enum yet) {EventType::CongestionHeavyTraffic, traffic::SpeedGroup::G4}, {EventType::CongestionLongQueue, traffic::SpeedGroup::G0}, @@ -54,7 +56,7 @@ const std::map kEventSpeedGroupMap{ // none of the currently define events imply an explicit maxspeed #if 0 -const std::map kEventMaxspeedMap{ +const std::unordered_map kEventMaxspeedMap{ // TODO Activity*, Authority*, Carpool* (not in enum yet) // TODO Construction* (not in enum yet) // TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet) @@ -63,7 +65,7 @@ const std::map kEventMaxspeedMap{ }; #endif -const std::map kEventDelayMap{ +const std::unordered_map kEventDelayMap{ // TODO Activity*, Authority*, Carpool* (not in enum yet) // TODO Construction* (not in enum yet) //{EventType::DelayDelay, }, // mapped to speed group @@ -121,10 +123,10 @@ std::optional IsoTime::ParseIsoTime(std::string timeString) * 11: :00 (UTC offset, minutes, prefixed with separator) * 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified) */ - std::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(\\.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); + static boost::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(\\.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?"); - std::smatch iso8601Matcher; - if (std::regex_search(timeString, iso8601Matcher, iso8601Regex)) + boost::smatch iso8601Matcher; + if (boost::regex_search(timeString, iso8601Matcher, iso8601Regex)) { int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0; int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0; diff --git a/libs/traffxml/traff_model_xml.cpp b/libs/traffxml/traff_model_xml.cpp index 243956921..0a6643d9c 100644 --- a/libs/traffxml/traff_model_xml.cpp +++ b/libs/traffxml/traff_model_xml.cpp @@ -6,11 +6,11 @@ #include #include #include -#include #include #include #include +#include #include @@ -504,9 +504,9 @@ bool LatLonFromXml(pugi::xml_node const & node, ms::LatLon & latLon) std::string string; if (StringFromXml(node, string)) { - std::regex latLonRegex("([+-]?[0-9]*\\.?[0-9]*)\\s+([+-]?[0-9]*\\.?[0-9]*)"); - std::smatch latLonMatcher; - if (std::regex_search(string, latLonMatcher, latLonRegex) && latLonMatcher[1].matched && latLonMatcher[2].matched) + static boost::regex latLonRegex("([+-]?[0-9]*\\.?[0-9]*)\\s+([+-]?[0-9]*\\.?[0-9]*)"); + boost::smatch latLonMatcher; + if (boost::regex_search(string, latLonMatcher, latLonRegex) && latLonMatcher[1].matched && latLonMatcher[2].matched) { try { @@ -689,10 +689,10 @@ std::optional OptionalDurationFromXml(pugi::xml_attribute const & attr * 1 h * 30 min */ - std::regex durationRegex("(([0-9]+):([0-9]{2}))|(([0-9]+) *h)|(([0-9]+) *min)"); - std::smatch durationMatcher; + static boost::regex durationRegex("(([0-9]+):([0-9]{2}))|(([0-9]+) *h)|(([0-9]+) *min)"); + boost::smatch durationMatcher; - if (std::regex_search(durationString, durationMatcher, durationRegex)) + if (boost::regex_search(durationString, durationMatcher, durationRegex)) { if (!durationMatcher.str(2).empty() && !durationMatcher.str(3).empty()) return std::stoi(durationMatcher[2]) * 60 + std::stoi(durationMatcher[3]); From d6818786f7e2d309bcca2049d5109319f86d661f Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 4 Nov 2025 20:33:08 +0200 Subject: [PATCH 204/252] [traffic] Vary junction search radius depending on road class Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index e1b82e330..07dba8986 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -94,15 +94,19 @@ auto constexpr kAttributePenalty = 4; auto constexpr kReducedAttributePenalty = 2; /* - * Lower boundary for radius around endpoint in which to search for junctions, in meters - * (unless the lower boundary exceeds half the distance between endpoints) + * Lower and upper boundary for radius around endpoint in which to search for junctions, depending + * on the road class. Boundaries are in meters. If the lower boundary exceeds half the distance + * between endpoints, the latter is used instead. */ -auto constexpr kJunctionRadiusMin = 300.0; - -/* - * Upper boundary for radius around endpoint in which to search for junctions, in meters - */ -auto constexpr kJunctionRadiusMax = 500.0; +const std::unordered_map, std::array> kJunctionRadiusBoundaries{ + { RoadClass::Motorway, { {300.0, 500.0} } }, + { RoadClass::Trunk, { {300.0, 500.0} } }, + { RoadClass::Primary, { {200.0, 300.0} } }, + { RoadClass::Secondary, { {200.0, 300.0} } }, + { RoadClass::Tertiary, { {200.0, 300.0} } }, + { RoadClass::Other, { {200.0, 300.0} } }, + { std::nullopt, { {300.0, 500.0} } } +}; /* * Maximum distance in meters from location endpoint at which a turn penalty is applied @@ -1244,14 +1248,16 @@ void RoutingTraffDecoder::GetJunctionPointCandidates() auto dist = ms::DistanceOnEarth(from, to); + auto bounds = kJunctionRadiusBoundaries.at(m_message.value().m_location.value().m_roadClass); + m_junctionRadius = dist / 3.0f; - if (m_junctionRadius > kJunctionRadiusMax) - m_junctionRadius = kJunctionRadiusMax; - else if (m_junctionRadius < kJunctionRadiusMin) + if (m_junctionRadius > bounds[1]) + m_junctionRadius = bounds[1]; + else if (m_junctionRadius < bounds[0]) { m_junctionRadius = dist / 2.0f; - if (m_junctionRadius > kJunctionRadiusMin) - m_junctionRadius = kJunctionRadiusMin; + if (m_junctionRadius > bounds[0]) + m_junctionRadius = bounds[0]; } if (m_message.value().m_location.value().m_from) From d82b545e30fa93a38a31420e79c69fc3e5cea895 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 6 Nov 2025 21:09:26 +0200 Subject: [PATCH 205/252] [traffic][android] Move Java source files to android/sdk Signed-off-by: mvglasow --- .../main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java | 0 .../main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java | 0 .../src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java | 0 .../main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java | 0 .../main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java | 0 .../src/main/java/app/organicmaps/sdk/traffxml/Version.java | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java (100%) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java (100%) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java (100%) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java (100%) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java (100%) rename android/{app => sdk}/src/main/java/app/organicmaps/sdk/traffxml/Version.java (100%) diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/AndroidConsumer.java diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/AndroidTransport.java diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImpl.java diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_7.java diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/SourceImplV0_8.java diff --git a/android/app/src/main/java/app/organicmaps/sdk/traffxml/Version.java b/android/sdk/src/main/java/app/organicmaps/sdk/traffxml/Version.java similarity index 100% rename from android/app/src/main/java/app/organicmaps/sdk/traffxml/Version.java rename to android/sdk/src/main/java/app/organicmaps/sdk/traffxml/Version.java From e87727c168fc7a9e8079c455d71b750f7414f21a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 6 Nov 2025 22:29:06 +0200 Subject: [PATCH 206/252] [traffic] Add missing override, silence warning Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 9e5869aa6..92a5a8219 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -264,7 +264,7 @@ class RoutingTraffDecoder : public TraffDecoder, * but tries to find a route with the existing maps, or exits without a route. When snapping * endpoints to edges, it considers all edges within the given radius, fenced off or not. */ - IndexRouter::Mode GetMode() { return IndexRouter::Mode::Decoding; } + IndexRouter::Mode GetMode() override { return IndexRouter::Mode::Decoding; } /** * @brief Returns current routing options. From 40fbea11e712c80d04ef548fef2f0c9abe40649c Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 7 Nov 2025 00:41:30 +0200 Subject: [PATCH 207/252] [traffic][android] Keep Proguard from deleting TraFF classes Signed-off-by: mvglasow --- android/app/proguard-rules.pro | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 19d264243..473e1d5a9 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -28,3 +28,8 @@ # R8 crypts the source line numbers in all log messages. # https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926 -dontoptimize + +# Keep classes for Android TraFF support +-keep class app.organicmaps.sdk.traffxml.SourceImplV0_7 { *; } +-keep class app.organicmaps.sdk.traffxml.SourceImplV0_8 { *; } + From f7882636cd59243932837535dbf360aee5f44759 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 8 Nov 2025 16:28:34 +0200 Subject: [PATCH 208/252] [traffic] Documentation Signed-off-by: mvglasow --- libs/routing/routing_callbacks.hpp | 53 ++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/libs/routing/routing_callbacks.hpp b/libs/routing/routing_callbacks.hpp index 7314849e5..fb97035d3 100644 --- a/libs/routing/routing_callbacks.hpp +++ b/libs/routing/routing_callbacks.hpp @@ -45,16 +45,49 @@ enum class RouterResultCode enum class SessionState { - NoValidRoute, // No valid route: no route after application launching or the route was removed. - RouteBuilding, // We requested a route and wait when it will be built. User may be following - // the previous route. - RouteNotStarted, // Route is built but the user isn't on it. - OnRoute, // User follows the route. - RouteNeedRebuild, // User left the route. - RouteFinished, // Destination point is reached but the session isn't closed. - RouteNoFollowing, // Route is built but following mode has been disabled. - RouteRebuilding, // We requested a route rebuild and wait when it will be rebuilt. - // User may following the previous route. + /** + * No valid route: no route after application launching, or the route was removed. + * + * This is the initial state at launch; in order to get out of it, a destination must be set AND + * the current location must be obtained. + */ + NoValidRoute, + + /** + * We have requested a route and are waiting for it to be built. User may be following the previous route. + */ + RouteBuilding, + + /** + * Route is built but the user isn't on it. + */ + RouteNotStarted, + + /** + * User is following the route. + */ + OnRoute, + + /** + * User has left the route. + */ + RouteNeedRebuild, + + /** + * Destination point has been reached but the session isn’t closed yet. + */ + RouteFinished, + + /** + * Route has been built but following mode has been disabled. + */ + RouteNoFollowing, + + /** + * We have requested a route rebuild and are waiting for it to be rebuilt. User may be following + * the previous route. + */ + RouteRebuilding, }; /* From 1d87e0e987f85dba8a42ecc27d6b05197055a0ca Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 8 Nov 2025 16:32:28 +0200 Subject: [PATCH 209/252] [traffic] Cache routing session state, introduce IsObserverInhibited() Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 5 ++--- libs/map/traffic_manager.hpp | 35 ++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index 523f8adaa..3b757f55f 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -206,8 +206,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, LOG(LINFO, ("Routing session state changed from", previous, "to", current)); LOG(LINFO, ("Running on thread", std::this_thread::get_id())); - m_observerInhibited = ((current == routing::SessionState::RouteBuilding) - || (current == routing::SessionState::RouteRebuilding)); + m_routingSessionState = current; /* * Filter based on session state (see routing_callbacks.hpp for states and transitions). @@ -806,7 +805,7 @@ void TrafficManager::OnTrafficDataUpdate() auto const storageAge = currentTime - m_lastStorageUpdate; notifyDrape = (drapeAge >= kDrapeUpdateInterval); updateStorage = (storageAge >= kStorageUpdateInterval); - if (!m_observerInhibited) + if (!IsObserverInhibited()) { /* * To avoid resetting the route over and over again while building, inhibit periodic updates diff --git a/libs/map/traffic_manager.hpp b/libs/map/traffic_manager.hpp index ad1443653..c99ae5bd4 100644 --- a/libs/map/traffic_manager.hpp +++ b/libs/map/traffic_manager.hpp @@ -533,6 +533,20 @@ class TrafficManager final : public traffxml::TraffSourceManager std::for_each(activeMwms.begin(), activeMwms.end(), std::forward(f)); } + /** + * @brief Whether updates to the observer are currently inhibited. + * + * Updates are inhibited while a route calculation is in progress. In this state, the observer + * receives traffic updates only if the queue has run empty, not if nore locations are waiting + * to be decoded. + * + * Inhibtiting the observer is necessary as traffic updates during route calculation will cause + * it to restart from scratch. Once the route has been calculated, updates will trigger a + * recalculation, which is much faster (seconds or less). + */ + bool IsObserverInhibited() const { return (m_routingSessionState == routing::SessionState::RouteBuilding) + || (m_routingSessionState == routing::SessionState::RouteRebuilding); } + DataSource & m_dataSource; CountryInfoGetterFn m_countryInfoGetterFn; CountryParentNameGetterFn m_countryParentNameGetterFn; @@ -546,6 +560,14 @@ class TrafficManager final : public traffxml::TraffSourceManager */ routing::RoutingSession & m_routingSession; + /** + * @brief Cached state of the routing session. + * + * `m_routingSession` methods which query the state may only be called from the GUI thread, + * therefore we are caching this value when we get notified of a change. + */ + routing::SessionState m_routingSessionState = routing::SessionState::NoValidRoute; + df::DrapeEngineSafePtr m_drapeEngine; std::atomic m_currentDataVersion; @@ -651,19 +673,6 @@ class TrafficManager final : public traffxml::TraffSourceManager */ std::chrono::time_point m_lastObserverUpdate; - /** - * @brief Whether updates to the observer are currently inhibited. - * - * Updates are inhibited while a route calculation is in progress. In this state, the observer - * receives traffic updates only if the queue has run empty, not if nore locations are waiting - * to be decoded. - * - * Inhibtiting the observer is necessary as traffic updates during route calculation will cause - * it to restart from scratch. Once the route has been calculated, updates will trigger a - * recalculation, which is much faster (seconds or less). - */ - bool m_observerInhibited = false; - /** * @brief When the cache file was last updated. */ From a9700156db422e63bd2774f8e40db4616cd44f3e Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 8 Nov 2025 17:49:46 +0200 Subject: [PATCH 210/252] [traffic] Keep polling and processing messages while routing Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 14 +++++++------- libs/map/traffic_manager.hpp | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index 3b757f55f..fd0f83ba4 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -259,7 +259,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, std::swap(mwms, m_activeRoutingMwms); if ((m_activeDrapeMwms.empty() && m_activePositionMwms.empty() && m_activeRoutingMwms.empty()) - || !IsEnabled() || IsInvalidState() || m_isPaused) + || !IsEnabled() || IsInvalidState() || IsPausedAndNotRouting()) return; m_condition.notify_one(); @@ -269,7 +269,7 @@ void TrafficManager::OnChangeRoutingSessionState(routing::SessionState previous, void TrafficManager::RecalculateSubscription(bool forceRenewal) { - if (!IsEnabled() || m_isPaused) + if (!IsEnabled() || IsPausedAndNotRouting()) return; if (m_currentModelView.second) @@ -367,7 +367,7 @@ void TrafficManager::UpdateActiveMwms(m2::RectD const & rect, std::vector Date: Sat, 8 Nov 2025 22:28:01 +0200 Subject: [PATCH 211/252] [traffic] Decode nearby messages first (based on position and viewport) Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 91 ++++++++++++++++++++++++++++++++++-- libs/map/traffic_manager.hpp | 14 ++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index fd0f83ba4..aaafc688b 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -8,6 +8,7 @@ #include "indexer/ftypes_matcher.hpp" #include "indexer/scales.hpp" +#include "geometry/distance_on_sphere.hpp" #include "geometry/mercator.hpp" #include "platform/platform.hpp" @@ -52,6 +53,12 @@ auto constexpr kStorageUpdateInterval = minutes(1); * File name at which traffic data is persisted. */ auto constexpr kTrafficXMLFileName = "traffic.xml"; + +/** + * Threshold by which viewport or user position can change without requiring the nessage queue to + * be resorted. + */ +auto constexpr kPositionThreshold = 1000.0; } // namespace TrafficManager::TrafficManager(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter, @@ -381,6 +388,13 @@ void TrafficManager::UpdateMyPosition(MyPosition const & myPosition) double const kSquareSideM = 5000.0; m_currentPosition = {myPosition, true /* initialized */}; + if (!m_currentPositionLazy.second + || (mercator::DistanceOnEarth(m_currentPositionLazy.first.m_position, myPosition.m_position) > kPositionThreshold)) + { + m_isFeedQueueSortInvalid = true; + m_currentPositionLazy = m_currentPosition; + } + if (!IsEnabled() || IsInvalidState() || IsPausedAndNotRouting()) return; @@ -393,6 +407,13 @@ void TrafficManager::UpdateViewport(ScreenBase const & screen) { m_currentModelView = {screen, true /* initialized */}; + if (!m_currentModelViewLazy.second + || (mercator::DistanceOnEarth(m_currentModelViewLazy.first.ClipRect().Center(), screen.ClipRect().Center()) > kPositionThreshold)) + { + m_isFeedQueueSortInvalid = true; + m_currentModelViewLazy = m_currentModelView; + } + if (!IsEnabled() || IsInvalidState() || IsPausedAndNotRouting()) return; @@ -493,6 +514,7 @@ void TrafficManager::ReceiveFeed(traffxml::TraffFeed feed) { std::lock_guard lock(m_mutex); m_feedQueue.push_back(feed); + m_isFeedQueueSortInvalid = true; } m_condition.notify_one(); } @@ -588,12 +610,27 @@ void TrafficManager::ConsolidateFeedQueue() } } } - // remove empty feeds - for (auto it = m_feedQueue.begin(); it != m_feedQueue.end(); ) - if (it->empty()) - it = m_feedQueue.erase(it); + // remove empty feeds from the beginning of the queue + while (!m_feedQueue.empty() && m_feedQueue.front().empty()) + m_feedQueue.erase(m_feedQueue.begin()); + // merge everything into the first vector + for (size_t i = 1; i < m_feedQueue.size(); i++) + { + if (m_feedQueue[i].empty()) + continue; + m_feedQueue[0].insert(m_feedQueue[0].end(), + std::make_move_iterator(m_feedQueue[i].begin()), + std::make_move_iterator(m_feedQueue[i].end())); + m_feedQueue[i].clear(); + } + // remove empty feeds (any feeds after the first one are empty at this point) + if (!m_feedQueue.empty()) + { + if (!m_feedQueue.front().empty()) + m_feedQueue.resize(1); else - ++it; + m_feedQueue.clear(); + } } void TrafficManager::DecodeFirstMessage() @@ -608,6 +645,50 @@ void TrafficManager::DecodeFirstMessage() // if we have no more feeds, return (nothing to do) if (m_feedQueue.empty()) return; + if (m_isFeedQueueSortInvalid + && (m_currentPositionLazy.second || m_currentModelViewLazy.second)) + { + std::sort(m_feedQueue.front().begin(), m_feedQueue.front().end(), + [this](const traffxml::TraffMessage & a, const traffxml::TraffMessage & b){ + // return a < b + // cancellations before others + if (a.m_cancellation) + return !b.m_cancellation; + // sort by shortest distance between reference point and position/viewport + std::vector locations; + if (m_currentPositionLazy.second) + locations.push_back(mercator::ToLatLon(m_currentPositionLazy.first.m_position)); + if (m_currentModelViewLazy.second) + locations.push_back(mercator::ToLatLon(m_currentModelViewLazy.first.ClipRect().Center())); + double aDist = 4.5e+7; + double bDist = 4.5e+7; + for (auto const & [message, dist] : { std::pair{a, std::ref(aDist)}, {b, std::ref(bDist)} }) + { + // messages without location first + if (!message.m_location) + { + dist.get() = 0; + continue; + } + // for simplification, we are skipping the via point + for (auto const & point : { message.m_location.value().m_from, + message.m_location.value().m_at, + message.m_location.value().m_to }) + { + if (!point) + continue; + for (auto const & location : locations) + { + auto newdist = ms::DistanceOnEarth(point.value().m_coordinates, location); + if (newdist < dist.get()) + dist.get() = newdist; + } + } + } + return aDist < bDist; + }); + m_isFeedQueueSortInvalid = false; + } // retrieve the first message from the first feed, remove it from the feed std::swap(message, m_feedQueue.front().front()); m_feedQueue.front().erase(m_feedQueue.front().begin()); diff --git a/libs/map/traffic_manager.hpp b/libs/map/traffic_manager.hpp index 0fb39a76d..31ce2a4a2 100644 --- a/libs/map/traffic_manager.hpp +++ b/libs/map/traffic_manager.hpp @@ -586,8 +586,14 @@ class TrafficManager final : public traffxml::TraffSourceManager std::atomic m_currentDataVersion; // These fields have a flag of their initialization. + /* + * The lazy ones get updated only if they are not initialized, or if their new position is more + * than a certain distance from the previously stored one. + */ std::pair m_currentPosition = {MyPosition(), false}; + std::pair m_currentPositionLazy = m_currentPosition; std::pair m_currentModelView = {ScreenBase(), false}; + std::pair m_currentModelViewLazy = m_currentModelView; /** * The mode in which the traffic manager is running. @@ -708,6 +714,14 @@ class TrafficManager final : public traffxml::TraffSourceManager */ std::vector m_feedQueue; + /** + * @brief Whether the feed queue needs to be resorted. + * + * Resorting is needed when a new feed is added, or the current position or the viewport center + * has changed by more than a certain threshold. + */ + std::atomic m_isFeedQueueSortInvalid = false; + /** * @brief Cache of all currently active TraFF messages. * From e6937b211a1a79b8a3dbaf11f9630c77e59e3be7 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 8 Nov 2025 23:03:32 +0200 Subject: [PATCH 212/252] [traffic] Optimize comparison for cancellation messages Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index aaafc688b..9bdcdbd14 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -654,6 +654,8 @@ void TrafficManager::DecodeFirstMessage() // cancellations before others if (a.m_cancellation) return !b.m_cancellation; + else if (b.m_cancellation) + return false; // sort by shortest distance between reference point and position/viewport std::vector locations; if (m_currentPositionLazy.second) From cd1201cb027f3d9ce9477d36cd61bb579a89d787 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 8 Nov 2025 23:45:45 +0200 Subject: [PATCH 213/252] [traffic] Prioritize message which are updates with an unchanged location Signed-off-by: mvglasow --- libs/map/traffic_manager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/map/traffic_manager.cpp b/libs/map/traffic_manager.cpp index 9bdcdbd14..46a3e394c 100644 --- a/libs/map/traffic_manager.cpp +++ b/libs/map/traffic_manager.cpp @@ -651,11 +651,17 @@ void TrafficManager::DecodeFirstMessage() std::sort(m_feedQueue.front().begin(), m_feedQueue.front().end(), [this](const traffxml::TraffMessage & a, const traffxml::TraffMessage & b){ // return a < b - // cancellations before others + // put messages first which decode quickly: cancellations, and updates with same location if (a.m_cancellation) return !b.m_cancellation; else if (b.m_cancellation) return false; + auto aIt = m_messageCache.find(a.m_id); + auto bIt = m_messageCache.find(b.m_id); + if ((aIt != m_messageCache.end()) && (aIt->second.m_location == a.m_location)) + return !((bIt != m_messageCache.end()) && (bIt->second.m_location == b.m_location)); + else if ((bIt != m_messageCache.end()) && (bIt->second.m_location == b.m_location)) + return false; // sort by shortest distance between reference point and position/viewport std::vector locations; if (m_currentPositionLazy.second) From 2b7ee4ba44dba1bae462c3379e707fe067a51a0b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 9 Nov 2025 00:49:47 +0200 Subject: [PATCH 214/252] [traff_assessment_tool] Show location fuzziness in list Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index d2de8f1a3..1dfbf0a8f 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -123,6 +123,32 @@ QVariant GetCountryAndRoadRef(TraffMessage const & message) result += '\n'; result += message.m_location.value().m_roadRef.value(); } + if (message.m_location.value().m_fuzziness) + { + if (!result.empty()) + result += '\n'; + switch (message.m_location.value().m_fuzziness.value()) + { + case traffxml::Fuzziness::LowRes: + result += "LowRes"; + break; + case traffxml::Fuzziness::MediumRes: + result += "MediumRes"; + break; + case traffxml::Fuzziness::EndUnknown: + result += "EndUnknown"; + break; + case traffxml::Fuzziness::StartUnknown: + result += "StartUnknown"; + break; + case traffxml::Fuzziness::ExtentUnknown: + result += "ExtentUnknown"; + break; + default: + result += "(invalid)"; + break; + } + } } return QString::fromStdString(result); } From 90fdf0ed726fabac933789b65de57679e3087509 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 9 Nov 2025 23:50:46 +0200 Subject: [PATCH 215/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/route.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/routing/route.hpp b/libs/routing/route.hpp index deb04aa58..f67b3e91e 100644 --- a/libs/routing/route.hpp +++ b/libs/routing/route.hpp @@ -210,6 +210,9 @@ class RouteSegment final * Distance is measured up to the end of the current segment, i.e. including the segment. For the * first segment, this is identical to the length of the segment; for the last segment, it is * identical to the length of the entire route. + * + * Note that the first and last real (non-fake) segment on the route may not report the actual + * length between their endpoints, but only the part which is also part of the route. */ double GetDistFromBeginningMeters() const { return m_distFromBeginningMeters; } double GetDistFromBeginningMerc() const { return m_distFromBeginningMerc; } @@ -220,6 +223,10 @@ class RouteSegment final * Travel time is the ETA from the beginning of the route to the end of the current segment, i.e. * including the segment. For the first segment, this is identical to the travel time along the * segment; for the last segment, it is identical to the travel time along the entire route. + * + * Note that the first and last real (non-fake) segment on the route may report time based not + * upon the actual length between their endpoints, but upon the part which is also part of the + * route. */ double GetTimeFromBeginningSec() const { return m_timeFromBeginningS; } From 67a3866529cfe262fba4a669d5375e3d09a43ebe Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 10 Nov 2025 21:55:52 +0200 Subject: [PATCH 216/252] [geometry] Documentation Signed-off-by: mvglasow --- libs/geometry/parametrized_segment.hpp | 33 ++++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/libs/geometry/parametrized_segment.hpp b/libs/geometry/parametrized_segment.hpp index e71af2e28..5bb32d0f3 100644 --- a/libs/geometry/parametrized_segment.hpp +++ b/libs/geometry/parametrized_segment.hpp @@ -9,17 +9,20 @@ namespace m2 { -// This class holds a parametrization of the -// line segment between two points p0 and p1. -// The parametrization is of the form -// p(t) = p0 + t * dir. -// Other conditions: -// dir is the normalized (p1 - p0) vector. -// length(dir) = 1. -// p(0) = p0. -// p(T) = p1 with T = length(p1 - p0). -// -// The points with t in [0, T] are the points of the segment. +/** + * @brief This class holds a parametrization of the line segment between two points `p0` and `p1`. + * + * The parametrization is of the form + * `p(t) = p0 + t * dir`. + * + * Other conditions: + * * `dir` is the normalized `(p1 - p0)` vector. + * * `length(dir) = 1`. + * * `p(0) = p0`. + * * `p(T) = p1` with `T = length(p1 - p0)`. + * + * The points with `t` in `[0, T]` are the points of the segment. + */ template class ParametrizedSegment { @@ -36,7 +39,9 @@ class ParametrizedSegment m_d = m_d / m_length; } - // Returns the squared (euclidean) distance from the segment to |p|. + /** + * @brief Returns the squared (euclidean) distance from the segment to `p`. + */ double SquaredDistanceToPoint(Point const & p) const { m2::PointD const diff(p - m_p0); @@ -52,7 +57,9 @@ class ParametrizedSegment return math::Pow2(CrossProduct(diff, m_d)); } - // Returns the point of the segment that is closest to |p|. + /** + * @brief Returns the point of the segment that is closest to `p`. + */ m2::PointD ClosestPointTo(Point const & p) const { m2::PointD const diff(p - m_p0); From 5c2cedb19ad0a41db6dd68efa59b63aace66d1f7 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 10 Nov 2025 23:32:53 +0200 Subject: [PATCH 217/252] [traffxml] Documentation Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 92a5a8219..2dc4b12c9 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -616,6 +616,7 @@ void TruncateStart(std::vector & rsegments, * @param checkpoints The reference points (at least two) * @param end Index of the last segment to keep * @param endSaving Cost saved by truncating + * @param endWeight Total weight of the route, including trailing fake segments * @param junctions Junctions with the weight of their leap segment */ void TruncateEnd(std::vector & rsegments, From 8fcf00b0cac1d856929643586ba57ee9706c2d87 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 11 Nov 2025 00:56:48 +0200 Subject: [PATCH 218/252] [routing] In decoder mode, snap to segment endpoints (no partial segments) Signed-off-by: mvglasow --- libs/geometry/parametrized_segment.hpp | 14 +++++++++++- libs/routing/fake_ending.cpp | 14 +++++++----- libs/routing/fake_ending.hpp | 8 ++++--- libs/routing/features_road_graph.cpp | 5 +++-- libs/routing/features_road_graph.hpp | 3 ++- libs/routing/index_router.cpp | 9 ++++---- libs/routing/index_router.hpp | 4 ++-- libs/routing/nearest_edge_finder.cpp | 8 ++++--- libs/routing/nearest_edge_finder.hpp | 8 ++++++- libs/routing/road_graph.hpp | 2 +- libs/traffxml/traff_decoder.cpp | 31 +++++++++++++++++++++++--- libs/traffxml/traff_decoder.hpp | 3 ++- 12 files changed, 80 insertions(+), 29 deletions(-) diff --git a/libs/geometry/parametrized_segment.hpp b/libs/geometry/parametrized_segment.hpp index 5bb32d0f3..8d5a79669 100644 --- a/libs/geometry/parametrized_segment.hpp +++ b/libs/geometry/parametrized_segment.hpp @@ -1,5 +1,6 @@ #pragma once +#include "geometry/mercator.hpp" #include "geometry/point2d.hpp" #include "base/math.hpp" @@ -59,9 +60,20 @@ class ParametrizedSegment /** * @brief Returns the point of the segment that is closest to `p`. + * + * @param p The checkpoint + * @param snapToEnds If true, the result is the endpoint of the segment which is closest to `p` */ - m2::PointD ClosestPointTo(Point const & p) const + m2::PointD ClosestPointTo(Point const & p, bool snapToEnds = false) const { + if (snapToEnds) + { + if (mercator::DistanceOnEarth(p, m_p0) < mercator::DistanceOnEarth(p, m_p1)) + return m_p0; + else + return m_p1; + } + m2::PointD const diff(p - m_p0); double const t = DotProduct(m_d, diff); diff --git a/libs/routing/fake_ending.cpp b/libs/routing/fake_ending.cpp index 683bad7e3..4a32f439f 100644 --- a/libs/routing/fake_ending.cpp +++ b/libs/routing/fake_ending.cpp @@ -18,12 +18,12 @@ using namespace routing; using namespace std; LatLonWithAltitude CalcProjectionToSegment(LatLonWithAltitude const & begin, LatLonWithAltitude const & end, - m2::PointD const & point) + m2::PointD const & point, bool snapToEnds) { m2::ParametrizedSegment segment(mercator::FromLatLon(begin.GetLatLon()), mercator::FromLatLon(end.GetLatLon())); - auto const projectedPoint = segment.ClosestPointTo(point); + auto const projectedPoint = segment.ClosestPointTo(point, snapToEnds); auto const distBeginToEnd = ms::DistanceOnEarth(begin.GetLatLon(), end.GetLatLon()); auto const projectedLatLon = mercator::ToLatLon(projectedPoint); @@ -45,7 +45,8 @@ bool Projection::operator==(Projection const & other) const tie(other.m_segment, other.m_isOneWay, other.m_segmentFront, other.m_segmentBack, other.m_junction); } -FakeEnding MakeFakeEnding(vector const & segments, m2::PointD const & point, WorldGraph & graph) +FakeEnding MakeFakeEnding(vector const & segments, m2::PointD const & point, + WorldGraph & graph, bool snapToEnds) { FakeEnding ending; double averageAltitude = 0.0; @@ -57,7 +58,7 @@ FakeEnding MakeFakeEnding(vector const & segments, m2::PointD const & p bool const oneWay = graph.IsOneWay(segment.GetMwmId(), segment.GetFeatureId()); auto const & frontJunction = graph.GetJunction(segment, true /* front */); auto const & backJunction = graph.GetJunction(segment, false /* front */); - auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point); + auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point, snapToEnds); ending.m_projections.emplace_back(segment, oneWay, frontJunction, backJunction, projectedJunction); @@ -69,13 +70,14 @@ FakeEnding MakeFakeEnding(vector const & segments, m2::PointD const & p return ending; } -FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph) +FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph, + bool snapToEnds) { auto const & road = graph.GetRoadGeometry(segment.GetFeatureId()); bool const oneWay = road.IsOneWay(); auto const & frontJunction = road.GetJunction(segment.GetPointId(true /* front */)); auto const & backJunction = road.GetJunction(segment.GetPointId(false /* front */)); - auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point); + auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point, snapToEnds); FakeEnding ending; ending.m_originJunction = LatLonWithAltitude(mercator::ToLatLon(point), projectedJunction.GetAltitude()); diff --git a/libs/routing/fake_ending.hpp b/libs/routing/fake_ending.hpp index f12215603..5cc59a7d0 100644 --- a/libs/routing/fake_ending.hpp +++ b/libs/routing/fake_ending.hpp @@ -40,9 +40,11 @@ struct FakeEnding final std::vector m_projections; }; -FakeEnding MakeFakeEnding(std::vector const & segments, m2::PointD const & point, WorldGraph & graph); -FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph); +FakeEnding MakeFakeEnding(std::vector const & segments, m2::PointD const & point, + WorldGraph & graph, bool snapToEnds = false); +FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph, + bool snapToEnds = false); LatLonWithAltitude CalcProjectionToSegment(LatLonWithAltitude const & begin, LatLonWithAltitude const & end, - m2::PointD const & point); + m2::PointD const & point, bool snapToEnds = false); } // namespace routing diff --git a/libs/routing/features_road_graph.cpp b/libs/routing/features_road_graph.cpp index 13c894983..f0bd2ec08 100644 --- a/libs/routing/features_road_graph.cpp +++ b/libs/routing/features_road_graph.cpp @@ -143,9 +143,10 @@ void FeaturesRoadGraphBase::ForEachFeatureClosestToCross(m2::PointD const & cros } void FeaturesRoadGraphBase::FindClosestEdges(m2::RectD const & rect, uint32_t count, - vector> & vicinities) const + vector> & vicinities, + bool snapToEnds) const { - NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */); + NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */, snapToEnds); m_dataSource.ForEachStreet([&](FeatureType & ft) { diff --git a/libs/routing/features_road_graph.hpp b/libs/routing/features_road_graph.hpp index 712236bad..fed223351 100644 --- a/libs/routing/features_road_graph.hpp +++ b/libs/routing/features_road_graph.hpp @@ -85,7 +85,8 @@ class FeaturesRoadGraphBase : public IRoadGraph /// @{ void ForEachFeatureClosestToCross(m2::PointD const & cross, ICrossEdgesLoader & edgesLoader) const override; void FindClosestEdges(m2::RectD const & rect, uint32_t count, - std::vector> & vicinities) const override; + std::vector> & vicinities, + bool snapToEnds = false) const override; std::vector FindRoads(m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const override; void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const override; diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp index c67d9dee0..9415fe9bf 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -314,7 +314,7 @@ bool IndexRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin std::vector candidates; uint32_t const count = direction.IsAlmostZero() ? 1 : 4; - m_roadGraph.FindClosestEdges(rect, count, candidates); + m_roadGraph.FindClosestEdges(rect, count, candidates, (GetMode() == Mode::Decoding)); if (candidates.empty()) return false; @@ -1146,10 +1146,10 @@ int IndexRouter::PointsOnEdgesSnapping::Snap(m2::PointD const & start, m2::Point // One of startEnding or finishEnding will be empty here. if (startEnding.m_projections.empty()) - startEnding = MakeFakeEnding(m_startSegments, start, m_graph); + startEnding = MakeFakeEnding(m_startSegments, start, m_graph, (m_router.GetMode() == Mode::Decoding)); if (finishEnding.m_projections.empty()) - finishEnding = MakeFakeEnding(finishSegments, finish, m_graph); + finishEnding = MakeFakeEnding(finishSegments, finish, m_graph, (m_router.GetMode() == Mode::Decoding)); return 0; } @@ -1231,12 +1231,11 @@ bool IndexRouter::PointsOnEdgesSnapping::IsFencedOff(m2::PointD const & point, E return false; } -// static void IndexRouter::PointsOnEdgesSnapping::RoadsToNearestEdges(m2::PointD const & point, vector const & roads, IsEdgeProjGood const & isGood, vector & edgeProj) { - NearestEdgeFinder finder(point, isGood); + NearestEdgeFinder finder(point, isGood, (m_router.GetMode() == Mode::Decoding)); for (auto const & road : roads) finder.AddInformationSource(road); diff --git a/libs/routing/index_router.hpp b/libs/routing/index_router.hpp index 4fbcd078f..ae19f2564 100644 --- a/libs/routing/index_router.hpp +++ b/libs/routing/index_router.hpp @@ -247,8 +247,8 @@ class IndexRouter : public IRouter static bool IsFencedOff(m2::PointD const & point, EdgeProjectionT const & edgeProjection, std::vector const & fences); - static void RoadsToNearestEdges(m2::PointD const & point, std::vector const & roads, - IsEdgeProjGood const & isGood, std::vector & edgeProj); + void RoadsToNearestEdges(m2::PointD const & point, std::vector const & roads, + IsEdgeProjGood const & isGood, std::vector & edgeProj); Segment GetSegmentByEdge(Edge const & edge) const; diff --git a/libs/routing/nearest_edge_finder.cpp b/libs/routing/nearest_edge_finder.cpp index f7c0a41fa..964127823 100644 --- a/libs/routing/nearest_edge_finder.cpp +++ b/libs/routing/nearest_edge_finder.cpp @@ -9,9 +9,11 @@ namespace routing { using namespace std; -NearestEdgeFinder::NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood) +NearestEdgeFinder::NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood, + bool snapToEnds) : m_point(point) , m_isEdgeProjGood(isEdgeProjGood) + , m_snapToEnds(snapToEnds) {} void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & roadInfo) @@ -28,7 +30,7 @@ void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & ro { m2::ParametrizedSegment segment(junctions[i - 1].GetPoint(), junctions[i].GetPoint()); - m2::PointD const closestPoint = segment.ClosestPointTo(m_point); + m2::PointD const closestPoint = segment.ClosestPointTo(m_point, m_snapToEnds); double const squaredDist = m_point.SquaredLength(closestPoint); if (squaredDist < res.m_squaredDist) @@ -48,7 +50,7 @@ void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & ro geometry::Altitude const startAlt = segStart.GetAltitude(); geometry::Altitude const endAlt = segEnd.GetAltitude(); m2::ParametrizedSegment segment(junctions[idx - 1].GetPoint(), junctions[idx].GetPoint()); - m2::PointD const closestPoint = segment.ClosestPointTo(m_point); + m2::PointD const closestPoint = segment.ClosestPointTo(m_point, m_snapToEnds); double const segLenM = mercator::DistanceOnEarth(segStart.GetPoint(), segEnd.GetPoint()); geometry::Altitude projPointAlt = geometry::kDefaultAltitudeMeters; diff --git a/libs/routing/nearest_edge_finder.hpp b/libs/routing/nearest_edge_finder.hpp index bcdf428f2..c7546d713 100644 --- a/libs/routing/nearest_edge_finder.hpp +++ b/libs/routing/nearest_edge_finder.hpp @@ -27,7 +27,12 @@ using IsEdgeProjGood = std::function & res) const; m2::PointD const m_point; + bool m_snapToEnds; std::vector m_candidates; IsEdgeProjGood m_isEdgeProjGood; }; diff --git a/libs/routing/road_graph.hpp b/libs/routing/road_graph.hpp index 4258d8ba8..5bbdfa588 100644 --- a/libs/routing/road_graph.hpp +++ b/libs/routing/road_graph.hpp @@ -263,7 +263,7 @@ class IRoadGraph : public RoadGraphBase /// then returns empty array. using EdgeProjectionT = std::pair; virtual void FindClosestEdges(m2::RectD const & /*rect*/, uint32_t /*count*/, - std::vector & /*vicinities*/) const + std::vector & /*vicinities*/, bool snapToEnds) const {} /// \returns Vector of pairs FeatureID and corresponding RoadInfo for road features diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 07dba8986..622d86be8 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -964,11 +964,15 @@ void RoutingTraffDecoder::AddDecodedSegment(traffxml::MultiMwmColoring & decoded void RoutingTraffDecoder::TruncateRoute(std::vector & rsegments, routing::Checkpoints const & checkpoints, bool backwards) { + double startWeight = 0; double const endWeight = rsegments.back().GetTimeFromBeginningSec(); // erase leading and trailing fake segments while(!rsegments.empty() && rsegments.front().GetSegment().GetMwmId() == routing::kFakeNumMwmId) + { + startWeight = rsegments.front().GetTimeFromBeginningSec(); rsegments.erase(rsegments.begin()); + } while(!rsegments.empty() && rsegments.back().GetSegment().GetMwmId() == routing::kFakeNumMwmId) rsegments.pop_back(); @@ -985,7 +989,7 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse // Cost saved by omitting the last `end` segments. double endSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving, + TruncateStart(rsegments, checkpoints, start, startSaving, startWeight, backwards ? m_endJunctions : m_startJunctions); TruncateEnd(rsegments, checkpoints, end, endSaving, endWeight, backwards ? m_startJunctions : m_endJunctions); @@ -1016,7 +1020,7 @@ void RoutingTraffDecoder::TruncateRoute(std::vector & rse rsegments.erase(rsegments.begin() + end + 1, rsegments.end()); start = 0; startSaving = 0; - TruncateStart(rsegments, checkpoints, start, startSaving, + TruncateStart(rsegments, checkpoints, start, startSaving, startWeight, backwards ? m_endJunctions : m_startJunctions); rsegments.erase(rsegments.begin(), rsegments.begin() + start); } @@ -1497,7 +1501,7 @@ std::vector ParseRef(std::string const & ref) void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving, + size_t & start, double & startSaving, double const startWeight, std::map const & junctions) { if (rsegments.empty()) @@ -1535,6 +1539,16 @@ void TruncateStart(std::vector & rsegments, startSaving = newStartSaving; } } + /* + * The router may return a route that starts and ends with a partial segment. In decoder mode, + * we don’t allow starting or ending a segment at an intermediate point but use the nearest + * segment endpoint instead. However, this may leave us with a zero-length segment, which we + * need to remove so we don’t overshoot the reference point. + */ + // TODO not if we have an `at` point + if ((start == 0) && (rsegments.size() > 1) + && (rsegments[0].GetTimeFromBeginningSec() == startWeight)) + start = 1; } void TruncateEnd(std::vector & rsegments, @@ -1574,5 +1588,16 @@ void TruncateEnd(std::vector & rsegments, endSaving = newEndSaving; } } + /* + * The router may return a route that starts and ends with a partial segment. In decoder mode, + * we don’t allow starting or ending a segment at an intermediate point but use the nearest + * segment endpoint instead. However, this may leave us with a zero-length segment, which we + * need to remove so we don’t overshoot the reference point. + */ + // TODO not if we have an `at` point + if ((end == (rsegments.size() - 1)) && (rsegments.size() > 1) + && (rsegments[rsegments.size() - 1].GetTimeFromBeginningSec() + == rsegments[rsegments.size() - 2].GetTimeFromBeginningSec())) + end--; } } // namespace traffxml diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 2dc4b12c9..4ccbe382d 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -596,11 +596,12 @@ std::vector ParseRef(std::string const & ref); * @param checkpoints The reference points (at least two) * @param start Index of the first segment to keep * @param startSaving Cost saved by truncating + * @param startWeight Weight of the fake segments up to the first real segment * @param junctions Junctions with the weight of their leap segment */ void TruncateStart(std::vector & rsegments, routing::Checkpoints const & checkpoints, - size_t & start, double & startSaving, + size_t & start, double & startSaving, double const startWeight, std::map const & junctions); /** From 859b89e1279558e428e87b450d3e176679f772af Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 11 Nov 2025 21:41:25 +0200 Subject: [PATCH 219/252] [traff_assessment_tool] Make OK the default button in open dialog Signed-off-by: mvglasow --- tools/traff_assessment_tool/trafficmodeinitdlg.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/traff_assessment_tool/trafficmodeinitdlg.ui b/tools/traff_assessment_tool/trafficmodeinitdlg.ui index a7821ff6a..441d771c2 100644 --- a/tools/traff_assessment_tool/trafficmodeinitdlg.ui +++ b/tools/traff_assessment_tool/trafficmodeinitdlg.ui @@ -51,6 +51,9 @@ Ok + + true + From 5a26b72431655461ca8bfd72e1d59f79e958af57 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 12 Nov 2025 20:20:01 +0200 Subject: [PATCH 220/252] [traff_assessment_tool] Do not show progress bar on loading an empty feed Signed-off-by: mvglasow --- tools/traff_assessment_tool/mainwindow.cpp | 13 +++++++++---- tools/traff_assessment_tool/mainwindow.hpp | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tools/traff_assessment_tool/mainwindow.cpp b/tools/traff_assessment_tool/mainwindow.cpp index 86aebee57..ecd91568d 100644 --- a/tools/traff_assessment_tool/mainwindow.cpp +++ b/tools/traff_assessment_tool/mainwindow.cpp @@ -314,7 +314,7 @@ MainWindow::MainWindow(Framework & framework) #endif } -void MainWindow::CreateTrafficPanel() +void MainWindow::CreateTrafficPanel(bool hasMessages) { if (!m_trafficModel) { @@ -340,8 +340,11 @@ void MainWindow::CreateTrafficPanel() m_dockWidget->adjustSize(); m_dockWidget->setMinimumWidth(400); } - m_trafficPanel->SetStatus(true); - m_trafficPanel->GetTimer().Resume(); + if (hasMessages) + { + m_trafficPanel->SetStatus(true); + m_trafficPanel->GetTimer().Resume(); + } m_dockWidget->show(); } @@ -378,8 +381,10 @@ void MainWindow::OnOpenTrafficSample() std::setlocale(LC_ALL, "en_US.UTF-8"); traffxml::TraffFeed feed; traffxml::TraffFeed shiftedFeed; + bool hasMessages = false; if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed)) { + hasMessages = !feed.empty(); for (auto message : feed) { // `ShiftTimestamps()` will not change the message in `feed`, therefore construct a new feed @@ -399,7 +404,7 @@ void MainWindow::OnOpenTrafficSample() try { - CreateTrafficPanel(); + CreateTrafficPanel(hasMessages); } catch (TrafficModelError const & e) { diff --git a/tools/traff_assessment_tool/mainwindow.hpp b/tools/traff_assessment_tool/mainwindow.hpp index 9f06ee66a..2e6440ce3 100644 --- a/tools/traff_assessment_tool/mainwindow.hpp +++ b/tools/traff_assessment_tool/mainwindow.hpp @@ -35,7 +35,7 @@ class MainWindow : public QMainWindow explicit MainWindow(Framework & framework); private: - void CreateTrafficPanel(); + void CreateTrafficPanel(bool hasMessages); void DestroyTrafficPanel(); /** From 9d4801886e4d25707fa7f2ccb0da946b45fbcb95 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 12 Nov 2025 21:34:23 +0200 Subject: [PATCH 221/252] [traffxml] Tweak junction search radius for lower road classes Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 622d86be8..e2353e0ea 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -102,9 +102,9 @@ const std::unordered_map, std::array> kJunct { RoadClass::Motorway, { {300.0, 500.0} } }, { RoadClass::Trunk, { {300.0, 500.0} } }, { RoadClass::Primary, { {200.0, 300.0} } }, - { RoadClass::Secondary, { {200.0, 300.0} } }, - { RoadClass::Tertiary, { {200.0, 300.0} } }, - { RoadClass::Other, { {200.0, 300.0} } }, + { RoadClass::Secondary, { {150.0, 200.0} } }, + { RoadClass::Tertiary, { {0.0, 150.0} } }, + { RoadClass::Other, { {0.0, 150.0} } }, { std::nullopt, { {300.0, 500.0} } } }; From d4c002851bc14ecc78932a2e8489f118bbcd5f1b Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 13 Nov 2025 21:16:45 +0200 Subject: [PATCH 222/252] [traffic] Ignore access flags when decoding closure events Fixes decoding of closures which are mapped in OSM as access restrictions Signed-off-by: mvglasow --- libs/routing/edge_estimator.hpp | 12 ++++++++++++ libs/routing/index_graph.hpp | 3 +++ libs/traffxml/traff_decoder.cpp | 19 +++++++++++++------ libs/traffxml/traff_decoder.hpp | 17 +++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/libs/routing/edge_estimator.hpp b/libs/routing/edge_estimator.hpp index 9d8daa854..e0149dc60 100644 --- a/libs/routing/edge_estimator.hpp +++ b/libs/routing/edge_estimator.hpp @@ -144,6 +144,18 @@ class EdgeEstimator */ virtual double GetFerryLandingPenalty(Purpose purpose) const = 0; + /** + * @brief Whether access restrictions are ignored. + * + * A return value of false indicates that access restrictions should be observed, which is the + * default behavior for a routing use case. If true, it indicates that routing should ignore + * access restrictions. This is needed to resolve traffic message locations; it could also be + * used e.g. for emergency vehicle use cases. + * + * This implementation always returns false. + */ + virtual bool IsAccessIgnored() { return false; } + /** * @brief Creates an `EdgeEstimator` based on maximum speeds. * diff --git a/libs/routing/index_graph.hpp b/libs/routing/index_graph.hpp index 881956d63..8a56b4ce8 100644 --- a/libs/routing/index_graph.hpp +++ b/libs/routing/index_graph.hpp @@ -217,6 +217,9 @@ template bool IndexGraph::IsAccessNoForSure(AccessPositionType const & accessPositionType, RouteWeight const & weight, bool useAccessConditional) const { + if (m_estimator->IsAccessIgnored()) + return false; + auto const [accessType, confidence] = useAccessConditional ? m_roadAccess.GetAccess(accessPositionType, weight) : m_roadAccess.GetAccessWithoutConditional(accessPositionType); diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index e2353e0ea..70070a69f 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -252,12 +252,12 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) if (!message.m_location) return; // Decode events into consolidated traffic impact - std::optional impact = message.GetTrafficImpact(); + m_trafficImpact = message.GetTrafficImpact(); - LOG(LINFO, (" Impact: ", impact)); + LOG(LINFO, (" Impact: ", m_trafficImpact)); // Skip further processing if there is no impact - if (!impact) + if (!m_trafficImpact) return; traffxml::MultiMwmColoring decoded; @@ -278,7 +278,7 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) LOG(LINFO, (" Location for message", message.m_id, "can be reused from cache")); std::optional cachedImpact = it->second.GetTrafficImpact(); - if (cachedImpact.has_value() && cachedImpact.value() == impact.value()) + if (cachedImpact.has_value() && cachedImpact.value() == m_trafficImpact.value()) { LOG(LINFO, (" Impact for message", message.m_id, "unchanged, reusing cached coloring")); @@ -300,10 +300,11 @@ void TraffDecoder::DecodeMessage(traffxml::TraffMessage & message) if (!isDecoded) DecodeLocation(message, decoded); - if (impact) + if (m_trafficImpact) { - ApplyTrafficImpact(impact.value(), decoded); + ApplyTrafficImpact(m_trafficImpact.value(), decoded); std::swap(message.m_decoded, decoded); + m_trafficImpact = std::nullopt; } } @@ -693,6 +694,12 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c return result; } +bool RoutingTraffDecoder::TraffEstimator::IsAccessIgnored() +{ + ASSERT(m_decoder.m_trafficImpact, ("Traffic impact for current message is not set")); + return (m_decoder.m_trafficImpact.value().m_speedGroup == traffic::SpeedGroup::TempBlock); +} + RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn, routing::TCountryFileFn const & countryFileFn, routing::CountryRectFn const & countryRectFn, diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 4ccbe382d..08db50870 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -88,6 +88,11 @@ class TraffDecoder */ std::map & m_messageCache; + /** + * @brief Consolidated traffic impact of the message currently being decoded + */ + std::optional m_trafficImpact; + private: }; @@ -321,6 +326,18 @@ class RoutingTraffDecoder : public TraffDecoder, routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override; double GetFerryLandingPenalty(Purpose /* purpose */) const override; + /** + * @brief Whether access restrictions are ignored. + * + * A return value of false indicates that access restrictions should be observed, which is the + * default behavior for a routing use case. If true, it indicates that routing should ignore + * access restrictions. This is needed to resolve traffic message locations; it could also be + * used e.g. for emergency vehicle use cases. + * + * This implementation may return true or false, depending on the location being decoded. + */ + bool IsAccessIgnored() override; + private: RoutingTraffDecoder & m_decoder; }; From 48a3e5d4b06fb995f099b9d847aeb489c3b400c4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 18 Nov 2025 00:00:30 +0200 Subject: [PATCH 223/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/features_road_graph.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libs/routing/features_road_graph.hpp b/libs/routing/features_road_graph.hpp index fed223351..9a323ab3b 100644 --- a/libs/routing/features_road_graph.hpp +++ b/libs/routing/features_road_graph.hpp @@ -84,9 +84,21 @@ class FeaturesRoadGraphBase : public IRoadGraph /// @name IRoadGraph overrides /// @{ void ForEachFeatureClosestToCross(m2::PointD const & cross, ICrossEdgesLoader & edgesLoader) const override; + + /** + * @brief Finds the closest edges to a reference point within a given distance. + * + * @param rect A rectangle. Its center is the reference point; the search distance is expressed + * through the height and width. + * @param count The number of results to return. + * @param vicinities Receives the results. + * @param snapToEnds If true, the projection point (the point on the edge closest to the reference + * point) is constrained to one of the edge endpoints; if false, it can be anywhere on the edge. + */ void FindClosestEdges(m2::RectD const & rect, uint32_t count, std::vector> & vicinities, bool snapToEnds = false) const override; + std::vector FindRoads(m2::RectD const & rect, IsGoodFeatureFn const & isGoodFeature) const override; void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const override; From cd64d14830ba92e2eb55965e2476343a15ba410a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 22 Nov 2025 14:45:40 +0200 Subject: [PATCH 224/252] [traffic] Remove obsolete code Signed-off-by: mvglasow --- libs/traffic/traffic_info.cpp | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/libs/traffic/traffic_info.cpp b/libs/traffic/traffic_info.cpp index d2579d5bc..a036851bd 100644 --- a/libs/traffic/traffic_info.cpp +++ b/libs/traffic/traffic_info.cpp @@ -33,36 +33,6 @@ namespace traffic { using namespace std; -namespace -{ -bool ReadRemoteFile(string const & url, vector & contents, int & errorCode) -{ - platform::HttpClient request(url); - if (!request.RunHttpRequest()) - { - errorCode = request.ErrorCode(); - LOG(LINFO, ("Couldn't run traffic request", url, ". Error:", errorCode)); - return false; - } - - errorCode = request.ErrorCode(); - - string const & result = request.ServerResponse(); - contents.resize(result.size()); - memcpy(contents.data(), result.data(), result.size()); - - if (errorCode != 200) - { - LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", errorCode)); - return false; - } - - return true; -} - -char constexpr kETag[] = "etag"; -} // namespace - // TrafficInfo::RoadSegmentId ----------------------------------------------------------------- TrafficInfo::RoadSegmentId::RoadSegmentId() : m_fid(0), m_idx(0), m_dir(0) {} From 645ca792f755470250ea69d8e36c53c862457afa Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 22 Nov 2025 15:38:47 +0200 Subject: [PATCH 225/252] [traffic] Remove obsolete code Signed-off-by: mvglasow --- libs/traffic/pytraffic/bindings.cpp | 21 ++++ libs/traffic/traffic_info.cpp | 111 ------------------ libs/traffic/traffic_info.hpp | 20 ++-- .../traffic_tests/traffic_info_test.cpp | 21 +++- 4 files changed, 51 insertions(+), 122 deletions(-) diff --git a/libs/traffic/pytraffic/bindings.cpp b/libs/traffic/pytraffic/bindings.cpp index b2278e500..e6c197f8b 100644 --- a/libs/traffic/pytraffic/bindings.cpp +++ b/libs/traffic/pytraffic/bindings.cpp @@ -103,6 +103,11 @@ boost::python::list GenerateTrafficKeys(std::string const & mwmPath) return pyhelpers::StdVectorToPythonList(result); } +/* + * TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed). + * We no longer separate keys (segments IDs) from values (their speed groups). + * See if we can refactor this into something meaningful and useful, else remove. + */ std::vector GenerateTrafficValues(std::vector const & keys, boost::python::dict const & segmentMappingDict, uint8_t useTempBlock) { @@ -139,6 +144,13 @@ std::vector GenerateTrafficValues(std::vector GenerateTrafficValuesFromList(boost::python::list const & keys, boost::python::dict const & segmentMappingDict) { @@ -148,6 +160,13 @@ std::vector GenerateTrafficValuesFromList(boost::python::list const & k return GenerateTrafficValues(keysVec, segmentMappingDict, 1 /* useTempBlock */); } +/* + * TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed). + * We no longer separate keys (segments IDs) from values (their speed groups), nor do we store + * either in binary files: Segment/speed group pairs are generated from TraFF data and cached in + * XML format (using a custom extension to TraFF). + * See if we can refactor this into something meaningful and useful, else remove. + */ std::vector GenerateTrafficValuesFromBinary(std::vector const & keysBlob, boost::python::dict const & segmentMappingDict, uint8_t useTempBlock = 1) @@ -201,7 +220,9 @@ BOOST_PYTHON_MODULE(pytraffic) def("load_classificator", LoadClassificator); def("generate_traffic_keys", GenerateTrafficKeys); + // TODO obsolete, see function definition def("generate_traffic_values_from_list", GenerateTrafficValuesFromList); + // TODO obsolete, see function definition def("generate_traffic_values_from_binary", GenerateTrafficValuesFromBinary, (arg("keysBlob"), arg("segmentMappingDict"), arg("useTempBlock") = 1)); } diff --git a/libs/traffic/traffic_info.cpp b/libs/traffic/traffic_info.cpp index a036851bd..e6bb11b7a 100644 --- a/libs/traffic/traffic_info.cpp +++ b/libs/traffic/traffic_info.cpp @@ -125,70 +125,6 @@ void TrafficInfo::CombineColorings(vector const & ke ASSERT_EQUAL(numUnexpectedKeys, 0, ()); } -// static -void TrafficInfo::SerializeTrafficKeys(vector const & keys, vector & result) -{ - vector fids; - vector numSegs; - vector oneWay; - for (size_t i = 0; i < keys.size();) - { - size_t j = i; - while (j < keys.size() && keys[i].m_fid == keys[j].m_fid) - ++j; - - bool ow = true; - for (size_t k = i; k < j; ++k) - { - if (keys[k].m_dir == RoadSegmentId::kReverseDirection) - { - ow = false; - break; - } - } - - auto const numDirs = ow ? 1 : 2; - size_t numSegsForThisFid = j - i; - CHECK_GREATER(numDirs, 0, ()); - CHECK_EQUAL(numSegsForThisFid % numDirs, 0, ()); - numSegsForThisFid /= numDirs; - - fids.push_back(keys[i].m_fid); - numSegs.push_back(numSegsForThisFid); - oneWay.push_back(ow); - - i = j; - } - - MemWriter> memWriter(result); - WriteToSink(memWriter, kLatestKeysVersion); - WriteVarUint(memWriter, fids.size()); - - { - BitWriter bitWriter(memWriter); - - uint32_t prevFid = 0; - for (auto const & fid : fids) - { - uint64_t const fidDiff = static_cast(fid - prevFid); - bool ok = coding::GammaCoder::Encode(bitWriter, fidDiff + 1); - ASSERT(ok, ()); - UNUSED_VALUE(ok); - prevFid = fid; - } - - for (auto const & s : numSegs) - { - bool ok = coding::GammaCoder::Encode(bitWriter, s + 1); - ASSERT(ok, ()); - UNUSED_VALUE(ok); - } - - for (auto const val : oneWay) - bitWriter.Write(val ? 1 : 0, 1 /* numBits */); - } -} - // static void TrafficInfo::DeserializeTrafficKeys(vector const & data, vector & result) { @@ -261,53 +197,6 @@ void TrafficInfo::SerializeTrafficValues(vector const & values, vect deflate(buf.data(), buf.size(), back_inserter(result)); } -// static -void TrafficInfo::DeserializeTrafficValues(vector const & data, vector & result) -{ - using Inflate = coding::ZLib::Inflate; - - vector decompressedData; - - Inflate inflate(Inflate::Format::ZLib); - inflate(data.data(), data.size(), back_inserter(decompressedData)); - - MemReaderWithExceptions memReader(decompressedData.data(), decompressedData.size()); - ReaderSource src(memReader); - - auto const version = ReadPrimitiveFromSource(src); - CHECK_EQUAL(version, kLatestValuesVersion, ("Unsupported version of traffic keys.")); - - auto const n = ReadVarUint(src); - result.resize(n); - BitReader bitReader(src); - for (size_t i = 0; i < static_cast(n); ++i) - { - // SpeedGroup's values fit into 3 bits. - result[i] = static_cast(bitReader.Read(3)); - } - - ASSERT_EQUAL(src.Size(), 0, ()); -} - -bool TrafficInfo::UpdateTrafficData(vector const & values) -{ - m_coloring.clear(); - - if (m_keys.size() != values.size()) - { - LOG(LWARNING, ("The number of received traffic values does not correspond to the number of keys:", m_keys.size(), - "keys", values.size(), "values.")); - m_availability = Availability::NoData; - return false; - } - - for (size_t i = 0; i < m_keys.size(); ++i) - if (values[i] != SpeedGroup::Unknown) - m_coloring.emplace(m_keys[i], values[i]); - - return true; -} - string DebugPrint(TrafficInfo::RoadSegmentId const & id) { string const dir = id.m_dir == TrafficInfo::RoadSegmentId::kForwardDirection ? "Forward" : "Backward"; diff --git a/libs/traffic/traffic_info.hpp b/libs/traffic/traffic_info.hpp index 3c606b82e..8e1776135 100644 --- a/libs/traffic/traffic_info.hpp +++ b/libs/traffic/traffic_info.hpp @@ -126,6 +126,12 @@ class TrafficInfo /** * @brief Extracts RoadSegmentIds from an MWM and stores them in a sorted order. * @param mwmPath Path to the MWM file + * + * @todo We don’t need this any longer as the API has been reworked: We no longer separate keys + * (segment IDs) from values (their speed groups) and no longer have a use case for retrieving a + * list of all possible segment IDs – rather, we decode TraFF messages into segments, or have a + * cached list of the segments affected by a particular message. However, pytraffic still has some + * references to this function. We need to clean those up first, then we can delete this function. */ static void ExtractTrafficKeys(std::string const & mwmPath, std::vector & result); @@ -150,15 +156,14 @@ class TrafficInfo // Serializes the keys of the coloring map to |result|. // The keys are road segments ids which do not change during // an mwm's lifetime so there's no point in downloading them every time. - // todo(@m) Document the format. - static void SerializeTrafficKeys(std::vector const & keys, std::vector & result); - + /* + * TODO We don’t need these any longer as the format is obsolete, but pytraffic still has some + * references to these. We need to clean those up first, then we can delete these functions. + */ static void DeserializeTrafficKeys(std::vector const & data, std::vector & result); static void SerializeTrafficValues(std::vector const & values, std::vector & result); - static void DeserializeTrafficValues(std::vector const & data, std::vector & result); - private: /** * @brief Result of the last request to the server. @@ -175,11 +180,6 @@ class TrafficInfo Error, }; - friend void UnitTest_TrafficInfo_UpdateTrafficData(); - - // Updates the coloring and changes the availability status if needed. - bool UpdateTrafficData(std::vector const & values); - /** * @brief The mapping from feature segments to speed groups (see speed_groups.hpp). */ diff --git a/libs/traffic/traffic_tests/traffic_info_test.cpp b/libs/traffic/traffic_tests/traffic_info_test.cpp index 003ef0c97..b638f5dc0 100644 --- a/libs/traffic/traffic_tests/traffic_info_test.cpp +++ b/libs/traffic/traffic_tests/traffic_info_test.cpp @@ -37,7 +37,11 @@ class TestMwmSet : public MwmSet }; } // namespace -/// @todo Need TRAFFIC_DATA_BASE_URL for this test. +/* + * TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed). + * Leaving it here for now, maybe we can derive some TraFF tests from it. + * This tests retrieval of traffic information from the server. + */ /* UNIT_TEST(TrafficInfo_RemoteFile) { @@ -69,6 +73,13 @@ UNIT_TEST(TrafficInfo_RemoteFile) } */ +/* + * TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed). + * Leaving it here for now, maybe we can derive some TraFF tests from it. + * This tests serialization of traffic data to files and reading it back, results should be + * identical and satisfy whatever constraints there are in the app. + */ +/* UNIT_TEST(TrafficInfo_Serialization) { TrafficInfo::Coloring coloring = { @@ -114,7 +125,14 @@ UNIT_TEST(TrafficInfo_Serialization) TEST_EQUAL(values, deserializedValues, ()); } } +*/ +/* + * TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed). + * Leaving it here for now, maybe we can derive some TraFF tests from it. + * This tests processing of updated traffic data. + */ +/* UNIT_TEST(TrafficInfo_UpdateTrafficData) { vector const keys = { @@ -147,4 +165,5 @@ UNIT_TEST(TrafficInfo_UpdateTrafficData) for (size_t i = 0; i < keys.size(); ++i) TEST_EQUAL(info.GetSpeedGroup(keys[i]), values2[i], ()); } +*/ } // namespace traffic From 6dbde46dca98a3869ebc375c4ec13bbec4a1e557 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 23 Nov 2025 17:06:15 +0200 Subject: [PATCH 226/252] [traffic] Remove obsolete code, fixes regression from 645ca792 Signed-off-by: mvglasow --- generator/CMakeLists.txt | 2 - generator/generator_tool/generator_tool.cpp | 8 ---- generator/traffic_generator.cpp | 44 --------------------- generator/traffic_generator.hpp | 8 ---- 4 files changed, 62 deletions(-) delete mode 100644 generator/traffic_generator.cpp delete mode 100644 generator/traffic_generator.hpp diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index b1af498d3..72bf57ef2 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -207,8 +207,6 @@ set(SRC tesselator.hpp towns_dumper.cpp towns_dumper.hpp - traffic_generator.cpp - traffic_generator.hpp transit_generator.cpp transit_generator.hpp transit_generator_experimental.cpp diff --git a/generator/generator_tool/generator_tool.cpp b/generator/generator_tool/generator_tool.cpp index c0dcc8cef..75c887f9e 100644 --- a/generator/generator_tool/generator_tool.cpp +++ b/generator/generator_tool/generator_tool.cpp @@ -26,7 +26,6 @@ #include "generator/routing_world_roads_generator.hpp" #include "generator/search_index_builder.hpp" #include "generator/statistics.hpp" -#include "generator/traffic_generator.hpp" #include "generator/transit_generator.hpp" #include "generator/transit_generator_experimental.hpp" #include "generator/unpack_mwm.hpp" @@ -171,7 +170,6 @@ DEFINE_string(unpack_borders, "", "Convert packed_polygons to a directory of pol DEFINE_bool(unpack_mwm, false, "Unpack each section of mwm into a separate file with name filePath.sectionName."); DEFINE_bool(check_mwm, false, "Check map file to be correct."); DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container."); -DEFINE_bool(generate_traffic_keys, false, "Generate keys for the traffic map (road segment -> speed group)."); DEFINE_bool(dump_mwm_tmp, false, "Prints feature builder objects from .mwm.tmp"); @@ -546,12 +544,6 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv) BuildPopularPlacesFromDescriptions(dataFile); } } - - if (FLAGS_generate_traffic_keys) - { - if (!traffic::GenerateTrafficKeysFromDataFile(dataFile)) - LOG(LCRITICAL, ("Error generating traffic keys.")); - } } string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION); diff --git a/generator/traffic_generator.cpp b/generator/traffic_generator.cpp deleted file mode 100644 index c2dd80217..000000000 --- a/generator/traffic_generator.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "generator/traffic_generator.hpp" - -#include "routing/routing_helpers.hpp" - -#include "traffic/traffic_info.hpp" - -#include "routing_common/car_model.hpp" - -#include "platform/mwm_traits.hpp" - -#include "indexer/feature_algo.hpp" -#include "indexer/feature_processor.hpp" -#include "indexer/features_offsets_table.hpp" - -#include "coding/file_writer.hpp" -#include "coding/files_container.hpp" - -#include - -namespace traffic -{ -bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath) -{ - try - { - std::vector keys; - TrafficInfo::ExtractTrafficKeys(mwmPath, keys); - - std::vector buf; - TrafficInfo::SerializeTrafficKeys(keys, buf); - - FilesContainerW writeContainer(mwmPath, FileWriter::OP_WRITE_EXISTING); - auto writer = writeContainer.GetWriter(TRAFFIC_KEYS_FILE_TAG); - writer->Write(buf.data(), buf.size()); - } - catch (RootException const & e) - { - LOG(LERROR, ("Failed to build traffic keys:", e.Msg())); - return false; - } - - return true; -} -} // namespace traffic diff --git a/generator/traffic_generator.hpp b/generator/traffic_generator.hpp deleted file mode 100644 index f09cc24cc..000000000 --- a/generator/traffic_generator.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -namespace traffic -{ -bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath); -} // namespace traffic From 43e05476ba1842580a748685ce3d9f696c5388fa Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 28 Jan 2026 19:49:28 +0200 Subject: [PATCH 227/252] [android/traffic] Fix typo Signed-off-by: mvglasow --- android/app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 7d040905b..14a07c955 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -813,7 +813,7 @@ No apps installed - No apps salected + No apps selected Use data from legacy TraFF applications From 2cbb30eff1d564bdbd9144a7f5a918145143dee8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 28 Jan 2026 20:30:01 +0200 Subject: [PATCH 228/252] [android][traffic] Style traffic preference switches like the others Signed-off-by: mvglasow --- android/app/src/main/res/xml/prefs_main.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/app/src/main/res/xml/prefs_main.xml b/android/app/src/main/res/xml/prefs_main.xml index 11bc0583c..10b9b0f82 100644 --- a/android/app/src/main/res/xml/prefs_main.xml +++ b/android/app/src/main/res/xml/prefs_main.xml @@ -211,6 +211,7 @@ app:singleLineTitle="false" android:summary="@string/traffic_http_enabled_description" android:defaultValue="true" + android:widgetLayout="@layout/preference_switch" android:order="1"/> From ce4a96c6cb213b43464a309e22b9b015f21636a6 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 21 Feb 2026 17:24:31 +0200 Subject: [PATCH 229/252] [traff_assessment_tool] Draw selected location as overlay Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 32 +++++++++++++++++++ tools/traff_assessment_tool/traffic_model.hpp | 1 + 2 files changed, 33 insertions(+) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index 1dfbf0a8f..d0ebc4d30 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -24,6 +24,8 @@ constexpr static dp::Color kColorAt(0x1a5ec1ff); constexpr static dp::Color kColorVia(0xf19721ff); constexpr static dp::Color kColorNotVia(0x8c5678ff); constexpr static dp::Color kColorTo(0xe42300ff); +constexpr static dp::Color kColorDecoded(0x4070ffff); +constexpr static std::string const kDecodedLineId = "decodedPath"; namespace { @@ -330,6 +332,7 @@ TrafficModel::TrafficModel(Framework & framework, , m_dataSource(framework.GetDataSource()) , m_drawerDelegate(std::make_unique(framework)) , m_pointsDelegate(std::make_unique(framework)) + , m_drapeApi(m_framework.GetDrapeApi()) { framework.GetTrafficManager().SetTrafficUpdateCallbackFn([this, &framework](bool final) { /* @@ -353,6 +356,7 @@ TrafficModel::TrafficModel(Framework & framework, auto editSession = m_framework.GetBookmarkManager().GetEditSession(); editSession.ClearGroup(UserMark::Type::COLORED); editSession.SetIsVisible(UserMark::Type::COLORED, false); + m_drapeApi.Clear(); // restore status bar if (final) @@ -479,6 +483,7 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio auto editSession = m_framework.GetBookmarkManager().GetEditSession(); editSession.ClearGroup(UserMark::Type::COLORED); + m_drapeApi.Clear(); if (static_cast(row) >= m_messages.size()) { @@ -512,6 +517,33 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio mark->SetColor(color); } + if (message->m_location.value().m_from && message->m_location.value().m_to) { + for (auto & [mwmId, coloring] : message->m_decoded) { + FeaturesLoaderGuard g(m_dataSource, mwmId); + for (auto & [rsid, sg]: coloring) { + /* + * Not the most efficient way: even if we get multiple segments of the same feature (which + * is typically the case), we fetch the feature from scratch for each segment, parse its + * geometry, retrieve two points from it and add one line per segment. + * It would be more efficient to group by feature, determine its segment range, then fetch + * the feature, parse its geometries, retrieve all points in range and add one line for the + * entire set of points. + * However, since we do this for just one TraFF message at a time, performance is less of a + * concern here, and implementing the above improvements would make the code more complex. + */ + auto f = g.GetOriginalFeatureByIndex(rsid.m_fid); + f->ParseGeometry(FeatureType::BEST_GEOMETRY); + std::vector points = { + f->GetPoint(rsid.m_idx), + f->GetPoint(rsid.m_idx + 1) + }; + m_drapeApi.AddLine( + kDecodedLineId + ", " + DebugPrint(mwmId) + ", " + DebugPrint(rsid), + df::DrapeApiLineData(points, kColorDecoded).Width(3.0f)); + } + } + } + if (rect.IsValid()) { rect.Scale(1.5); diff --git a/tools/traff_assessment_tool/traffic_model.hpp b/tools/traff_assessment_tool/traffic_model.hpp index 9450ecb76..d8baa50bc 100644 --- a/tools/traff_assessment_tool/traffic_model.hpp +++ b/tools/traff_assessment_tool/traffic_model.hpp @@ -124,6 +124,7 @@ public slots: Framework & m_framework; DataSource const & m_dataSource; + df::DrapeApi & m_drapeApi; TrafficPanel * m_trafficPanel = nullptr; #ifdef openlr_obsolete std::vector m_segments; From 9749d206448ca05840fcf857821f36e1d026b21d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 21 Feb 2026 19:57:49 +0200 Subject: [PATCH 230/252] [traff_assessment_tool] Draw overlay on selection for all decoded locations Signed-off-by: mvglasow --- tools/traff_assessment_tool/traffic_model.cpp | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/tools/traff_assessment_tool/traffic_model.cpp b/tools/traff_assessment_tool/traffic_model.cpp index d0ebc4d30..db89dadd7 100644 --- a/tools/traff_assessment_tool/traffic_model.cpp +++ b/tools/traff_assessment_tool/traffic_model.cpp @@ -517,30 +517,28 @@ void TrafficModel::OnItemSelected(QItemSelection const & selected, QItemSelectio mark->SetColor(color); } - if (message->m_location.value().m_from && message->m_location.value().m_to) { - for (auto & [mwmId, coloring] : message->m_decoded) { - FeaturesLoaderGuard g(m_dataSource, mwmId); - for (auto & [rsid, sg]: coloring) { - /* - * Not the most efficient way: even if we get multiple segments of the same feature (which - * is typically the case), we fetch the feature from scratch for each segment, parse its - * geometry, retrieve two points from it and add one line per segment. - * It would be more efficient to group by feature, determine its segment range, then fetch - * the feature, parse its geometries, retrieve all points in range and add one line for the - * entire set of points. - * However, since we do this for just one TraFF message at a time, performance is less of a - * concern here, and implementing the above improvements would make the code more complex. - */ - auto f = g.GetOriginalFeatureByIndex(rsid.m_fid); - f->ParseGeometry(FeatureType::BEST_GEOMETRY); - std::vector points = { - f->GetPoint(rsid.m_idx), - f->GetPoint(rsid.m_idx + 1) - }; - m_drapeApi.AddLine( - kDecodedLineId + ", " + DebugPrint(mwmId) + ", " + DebugPrint(rsid), - df::DrapeApiLineData(points, kColorDecoded).Width(3.0f)); - } + for (auto & [mwmId, coloring] : message->m_decoded) { + FeaturesLoaderGuard g(m_dataSource, mwmId); + for (auto & [rsid, sg]: coloring) { + /* + * Not the most efficient way: even if we get multiple segments of the same feature (which + * is typically the case), we fetch the feature from scratch for each segment, parse its + * geometry, retrieve two points from it and add one line per segment. + * It would be more efficient to group by feature, determine its segment range, then fetch + * the feature, parse its geometries, retrieve all points in range and add one line for the + * entire set of points. + * However, since we do this for just one TraFF message at a time, performance is less of a + * concern here, and implementing the above improvements would make the code more complex. + */ + auto f = g.GetOriginalFeatureByIndex(rsid.m_fid); + f->ParseGeometry(FeatureType::BEST_GEOMETRY); + std::vector points = { + f->GetPoint(rsid.m_idx), + f->GetPoint(rsid.m_idx + 1) + }; + m_drapeApi.AddLine( + kDecodedLineId + ", " + DebugPrint(mwmId) + ", " + DebugPrint(rsid), + df::DrapeApiLineData(points, kColorDecoded).Width(3.0f)); } } From f850e6c47ce777a2dd4e9e19839862ffc4002153 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 21 Feb 2026 21:42:20 +0200 Subject: [PATCH 231/252] [drape] Document DrapeApiLineData members Signed-off-by: mvglasow --- libs/drape_frontend/drape_api.hpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/libs/drape_frontend/drape_api.hpp b/libs/drape_frontend/drape_api.hpp index 47b2d6e7b..89ee0b676 100644 --- a/libs/drape_frontend/drape_api.hpp +++ b/libs/drape_frontend/drape_api.hpp @@ -14,6 +14,9 @@ namespace df { +/** + * @brief Geometry and style for a line rendered with Drape. + */ struct DrapeApiLineData { DrapeApiLineData() = default; @@ -21,6 +24,14 @@ struct DrapeApiLineData DrapeApiLineData(std::vector const & points, dp::Color const & color) : m_points(points), m_color(color) {} + /** + * @brief Enables showing the points of the line. + * + * Calling this method will set the internal showPoints attribute to true (causing the points of + * the line to be shown as dots) and the markPoints attribite to the value supplied. + * + * @param markPoints Whether to mark the points. + */ DrapeApiLineData & ShowPoints(bool markPoints) { m_showPoints = true; @@ -28,6 +39,9 @@ struct DrapeApiLineData return *this; } + /** + * @brief Sets the width for the line in pixels. + */ DrapeApiLineData & Width(float width) { m_width = width; @@ -58,8 +72,25 @@ class DrapeApi void SetDrapeEngine(ref_ptr engine); + /** + * @brief Adds a new line. + * + * @param id A unique identifier for the line. If a line with the same identifier exists, it will + * be replaced by the new line. + * @param data The data (geometry and style) for the new line. + */ void AddLine(std::string const & id, DrapeApiLineData const & data); + + /** + * @brief Removes a line. + * + * @param id The id of the line to remove. + */ void RemoveLine(std::string const & id); + + /** + * @brief Removes all lines added with AddLine. + */ void Clear(); void Invalidate(); From 2c2559f640b94148feba9bc81233b7e7499ea3fd Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 22 Feb 2026 14:17:19 +0200 Subject: [PATCH 232/252] [traffic] Remove unused import Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 70070a69f..a30580350 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -15,7 +15,6 @@ #include "openlr/openlr_model.hpp" #endif -#include "routing/async_router.hpp" #include "routing/checkpoints.hpp" #include "routing/edge_estimator.hpp" #include "routing/maxspeeds.hpp" From 10ed4682c3107b3049a4b2da7f428a3f7264c9a8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 22 Feb 2026 14:32:40 +0200 Subject: [PATCH 233/252] [routing] Synchronize access to cached regions Signed-off-by: mvglasow --- libs/routing/absent_regions_finder.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/routing/absent_regions_finder.cpp b/libs/routing/absent_regions_finder.cpp index 558ad0c90..2ff35fe1f 100644 --- a/libs/routing/absent_regions_finder.cpp +++ b/libs/routing/absent_regions_finder.cpp @@ -17,7 +17,10 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate) { - m_regions.clear(); + { + std::lock_guard lock(m_mutex); + m_regions.clear(); + } if (m_routerThread) { @@ -55,15 +58,19 @@ void AbsentRegionsFinder::GetAllRegions(std::set & countries) { m_routerThread->Join(); - for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) { - if (!mwmName.empty()) - m_regions.emplace(mwmName); + std::lock_guard lock(m_mutex); + for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) + { + if (!mwmName.empty()) + m_regions.emplace(mwmName); + } } m_routerThread.reset(); } + std::lock_guard lock(m_mutex); countries = m_regions; } From 6eb30a4311a1423525c5057d16a1ded20dc003e2 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 22 Feb 2026 16:24:11 +0200 Subject: [PATCH 234/252] [routing] Synchronize all access to non-const AbsentRegionsFinder members Signed-off-by: mvglasow --- libs/routing/absent_regions_finder.cpp | 17 ++++++----------- libs/routing/absent_regions_finder.hpp | 5 +++-- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/libs/routing/absent_regions_finder.cpp b/libs/routing/absent_regions_finder.cpp index 2ff35fe1f..25d335cd8 100644 --- a/libs/routing/absent_regions_finder.cpp +++ b/libs/routing/absent_regions_finder.cpp @@ -17,10 +17,8 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate) { - { - std::lock_guard lock(m_mutex); - m_regions.clear(); - } + std::lock_guard lock(m_mutex); + m_regions.clear(); if (m_routerThread) { @@ -53,24 +51,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set & regions) void AbsentRegionsFinder::GetAllRegions(std::set & countries) { + std::lock_guard lock(m_mutex); // Note: if called from `RoutingSession` callback, m_state will still have its pre-update value. if (m_routerThread) { m_routerThread->Join(); + for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) { - std::lock_guard lock(m_mutex); - for (auto const & mwmName : m_routerThread->GetRoutineAs()->GetMwmNames()) - { - if (!mwmName.empty()) - m_regions.emplace(mwmName); - } + if (!mwmName.empty()) + m_regions.emplace(mwmName); } m_routerThread.reset(); } - std::lock_guard lock(m_mutex); countries = m_regions; } diff --git a/libs/routing/absent_regions_finder.hpp b/libs/routing/absent_regions_finder.hpp index 41004e011..6f5d42611 100644 --- a/libs/routing/absent_regions_finder.hpp +++ b/libs/routing/absent_regions_finder.hpp @@ -67,9 +67,10 @@ class AbsentRegionsFinder std::unique_ptr m_routerThread; /** - * @brief Mutex for access to `m_regions`. + * @brief Mutex for access to member variables. * - * Threads which access `m_regions` must lock this mutex while doing so. + * Methods which access any of the non-`const` class member variables must lock this mutex while + * doing so. */ std::mutex m_mutex; From 5b8de1f1aa7f1b78f927c5c3aaa809a932978b6d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Fri, 17 Apr 2026 21:43:40 +0300 Subject: [PATCH 235/252] [tools] Remove generate_traffic_keys from maps_generator Fixes regression from 6dbde46d Signed-off-by: mvglasow --- tools/python/maps_generator/generator/gen_tool.py | 1 - tools/python/maps_generator/generator/steps.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tools/python/maps_generator/generator/gen_tool.py b/tools/python/maps_generator/generator/gen_tool.py index 9b6ed1310..7abd22a1c 100644 --- a/tools/python/maps_generator/generator/gen_tool.py +++ b/tools/python/maps_generator/generator/gen_tool.py @@ -31,7 +31,6 @@ class GenTool: "generate_regions": bool, "generate_regions_kv": bool, "generate_search_index": bool, - "generate_traffic_keys": bool, "generate_world": bool, "have_borders_for_whole_world": bool, "make_city_roads": bool, diff --git a/tools/python/maps_generator/generator/steps.py b/tools/python/maps_generator/generator/steps.py index 73de465c9..478feb4be 100644 --- a/tools/python/maps_generator/generator/steps.py +++ b/tools/python/maps_generator/generator/steps.py @@ -403,7 +403,6 @@ def step_routing(env: Env, country: AnyStr, **kwargs): make_cross_mwm=True, generate_cameras=True, make_routing_index=True, - generate_traffic_keys=False, output=country, **kwargs, ) From 903be18eeab704ca08b69a0ffe0b904ee88b81b4 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 4 May 2026 19:01:42 +0300 Subject: [PATCH 236/252] Documentation Signed-off-by: mvglasow --- libs/indexer/classificator.hpp | 25 +++++++++++++++++++++++++ libs/routing_common/car_model.hpp | 17 +++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libs/indexer/classificator.hpp b/libs/indexer/classificator.hpp index 152b87e2d..4a0420d6a 100644 --- a/libs/indexer/classificator.hpp +++ b/libs/indexer/classificator.hpp @@ -26,12 +26,37 @@ void PushValue(uint32_t & type, uint8_t value); /// @pre level < GetLevel(type). uint8_t GetValue(uint32_t type, uint8_t level); void PopValue(uint32_t & type); + +/** + * @brief Truncates a type to the number of levels specified. + * + * For example, truncating `natural-beach-sand` to 2 levels would result in `natural-beach`. + * + * Truncation is done in place, i.e. `type` is altered. + * + * @param type The type to truncate + * @param level The number of levels to truncate to + */ void TruncValue(uint32_t & type, uint8_t level); + +/** + * @brief Truncates a type to the number of levels specified, and returns the result. + * + * For example, truncating `natural-beach-sand` to 2 levels would result in `natural-beach`. + * + * This is a convenience wrapper around `TruncValue()`, which will not alter `type`. + * + * @param type The type to truncate + * @param level The number of levels to truncate to + * + * @return The truncated type + */ inline uint32_t Trunc(uint32_t type, uint8_t level) { TruncValue(type, level); return type; } + uint8_t GetLevel(uint32_t type); } // namespace ftype diff --git a/libs/routing_common/car_model.hpp b/libs/routing_common/car_model.hpp index 9201d9f25..4147655fc 100644 --- a/libs/routing_common/car_model.hpp +++ b/libs/routing_common/car_model.hpp @@ -5,6 +5,15 @@ namespace routing { +/** + * @brief A `VehicleModel` suitable for car routing. + * + * Each instance can have its own set of feature type-specific access limitations. These specify + * which roads can be used by this vehicle, and which roads can only be used for destination + * traffic. + * + * Instances are typically retrieved from `CarModelFactory` or the `AllLimitsInstance()` method. + */ class CarModel : public VehicleModel { public: @@ -15,8 +24,16 @@ class CarModel : public VehicleModel SpeedKMpH GetSpeed(FeatureTypes const & types, SpeedParams const & speedParams) const override; SpeedKMpH const & GetOffroadSpeed() const override; + /** + * @brief Returns an instance with the default access limitations. + */ static CarModel const & AllLimitsInstance(); + + /** + * @brief Returns the default access limitations. + */ static LimitsInitList const & GetOptions(); + static SurfaceInitList const & GetSurfaces(); }; From 930ad13b2d41f9c2b22b9c71307035a547b02fa2 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 28 Apr 2026 22:41:18 +0300 Subject: [PATCH 237/252] [generator] Introduce routable highway-construction types Add highway-construction types reflecting the construction tag Include highway-construction in MWM routing section Signed-off-by: mvglasow --- data/mapcss-mapping.csv | 16 +++++ .../default/include/priorities_3_FG.prio.txt | 16 +++++ .../include/priorities_4_overlays.prio.txt | 16 +++++ .../outdoors/include/priorities_3_FG.prio.txt | 16 +++++ .../include/priorities_4_overlays.prio.txt | 16 +++++ .../vehicle/include/priorities_3_FG.prio.txt | 16 +++++ generator/routing_index_generator.cpp | 6 -- libs/routing_common/car_model.cpp | 17 ++++++ libs/routing_common/car_model_coefs.hpp | 58 +++++++++++++++++++ libs/routing_common/vehicle_model.cpp | 28 ++++++++- libs/routing_common/vehicle_model.hpp | 18 ++++++ 11 files changed, 216 insertions(+), 7 deletions(-) diff --git a/data/mapcss-mapping.csv b/data/mapcss-mapping.csv index 2b5a43e86..03ac40174 100644 --- a/data/mapcss-mapping.csv +++ b/data/mapcss-mapping.csv @@ -1760,3 +1760,19 @@ amenity|luggage_locker;1629; building|guardhouse;[building=guardhouse],[amenity=security_booth],[amenity=checkpoint];;;;1630; office|security;1631; shop|lighting;1632; +highway|construction|motorway;[highway=construction][construction=motorway];;name;int_name;1633; +highway|construction|motorway_link;[highway=construction][construction=motorway_link];;name;int_name;1634; +highway|construction|trunk;[highway=construction][construction=trunk];;name;int_name;1635; +highway|construction|trunk_link;[highway=construction][construction=trunk_link];;name;int_name;1636; +highway|construction|primary;[highway=construction][construction=primary];;name;int_name;1637; +highway|construction|primary_link;[highway=construction][construction=primary_link];;name;int_name;1638; +highway|construction|secondary;[highway=construction][construction=secondary];;name;int_name;1639; +highway|construction|secondary_link;[highway=construction][construction=secondary_link];;name;int_name;1640; +highway|construction|tertiary;[highway=construction][construction=tertiary];;name;int_name;1641; +highway|construction|tertiary_link;[highway=construction][construction=tertiary_link];;name;int_name;1642; +highway|construction|residential;[highway=construction][construction=residential];;name;int_name;1643; +highway|construction|unclassified;[highway=construction][construction=unclassified];;name;int_name;1644; +highway|construction|service;[highway=construction][construction=service];;name;int_name;1645; +highway|construction|living_street;[highway=construction][construction=living_street];;name;int_name;1646; +highway|construction|road;[highway=construction][construction=road];;name;int_name;1647; +highway|construction|track;[highway=construction][construction=track];;name;int_name;1648; diff --git a/data/styles/default/include/priorities_3_FG.prio.txt b/data/styles/default/include/priorities_3_FG.prio.txt index 7fe56ebeb..9befbf7a1 100644 --- a/data/styles/default/include/priorities_3_FG.prio.txt +++ b/data/styles/default/include/priorities_3_FG.prio.txt @@ -293,6 +293,22 @@ highway-track-tunnel # line z15- (also has line:: === 190 highway-construction # line z13- (also has pathtext z15-) +highway-construction-living_street # line z13- (also has pathtext z15-) +highway-construction-motorway # line z13- (also has pathtext z15-) +highway-construction-motorway_link # line z13- (also has pathtext z15-) +highway-construction-primary # line z13- (also has pathtext z15-) +highway-construction-primary_link # line z13- (also has pathtext z15-) +highway-construction-residential # line z13- (also has pathtext z15-) +highway-construction-road # line z13- (also has pathtext z15-) +highway-construction-secondary # line z13- (also has pathtext z15-) +highway-construction-secondary_link # line z13- (also has pathtext z15-) +highway-construction-service # line z13- (also has pathtext z15-) +highway-construction-tertiary # line z13- (also has pathtext z15-) +highway-construction-tertiary_link # line z13- (also has pathtext z15-) +highway-construction-track # line z13- (also has pathtext z15-) +highway-construction-trunk # line z13- (also has pathtext z15-) +highway-construction-trunk_link # line z13- (also has pathtext z15-) +highway-construction-unclassified # line z13- (also has pathtext z15-) leisure-track # line z15- (also has caption z16-) railway-abandoned # line z16- railway-construction # line z15- diff --git a/data/styles/default/include/priorities_4_overlays.prio.txt b/data/styles/default/include/priorities_4_overlays.prio.txt index 1d898111d..337f1166d 100644 --- a/data/styles/default/include/priorities_4_overlays.prio.txt +++ b/data/styles/default/include/priorities_4_overlays.prio.txt @@ -838,6 +838,22 @@ attraction-maze # icon z16- (also has captio attraction-roller_coaster # icon z16- (also has caption(optional) z16-) attraction-water_slide # icon z17- (also has caption(optional) z17-) highway-construction # pathtext z15- (also has line z13-) +highway-construction-living_street # pathtext z15- (also has line z13-) +highway-construction-motorway # pathtext z15- (also has line z13-) +highway-construction-motorway_link # pathtext z15- (also has line z13-) +highway-construction-primary # pathtext z15- (also has line z13-) +highway-construction-primary_link # pathtext z15- (also has line z13-) +highway-construction-residential # pathtext z15- (also has line z13-) +highway-construction-road # pathtext z15- (also has line z13-) +highway-construction-secondary # pathtext z15- (also has line z13-) +highway-construction-secondary_link # pathtext z15- (also has line z13-) +highway-construction-service # pathtext z15- (also has line z13-) +highway-construction-tertiary # pathtext z15- (also has line z13-) +highway-construction-tertiary_link # pathtext z15- (also has line z13-) +highway-construction-track # pathtext z15- (also has line z13-) +highway-construction-trunk # pathtext z15- (also has line z13-) +highway-construction-trunk_link # pathtext z15- (also has line z13-) +highway-construction-unclassified # pathtext z15- (also has line z13-) highway-living_street # pathtext z14- (also has line z12-, line::border z14-) highway-living_street-bridge # pathtext z14- (also has line z12-, line::border z14-) highway-living_street-tunnel # pathtext z14- (also has line z12-, line::border z14-, line(casing) z16-) diff --git a/data/styles/outdoors/include/priorities_3_FG.prio.txt b/data/styles/outdoors/include/priorities_3_FG.prio.txt index c9dd78f72..0c0e76124 100644 --- a/data/styles/outdoors/include/priorities_3_FG.prio.txt +++ b/data/styles/outdoors/include/priorities_3_FG.prio.txt @@ -295,6 +295,22 @@ highway-track-tunnel # line z11- (also has line:: === 190 highway-construction # line z11- (also has pathtext z15-) +highway-construction-living_street # line z11- (also has pathtext z15-) +highway-construction-motorway # line z11- (also has pathtext z15-) +highway-construction-motorway_link # line z11- (also has pathtext z15-) +highway-construction-primary # line z11- (also has pathtext z15-) +highway-construction-primary_link # line z11- (also has pathtext z15-) +highway-construction-residential # line z11- (also has pathtext z15-) +highway-construction-road # line z11- (also has pathtext z15-) +highway-construction-secondary # line z11- (also has pathtext z15-) +highway-construction-secondary_link # line z11- (also has pathtext z15-) +highway-construction-service # line z11- (also has pathtext z15-) +highway-construction-tertiary # line z11- (also has pathtext z15-) +highway-construction-tertiary_link # line z11- (also has pathtext z15-) +highway-construction-track # line z11- (also has pathtext z15-) +highway-construction-trunk # line z11- (also has pathtext z15-) +highway-construction-trunk_link # line z11- (also has pathtext z15-) +highway-construction-unclassified # line z11- (also has pathtext z15-) leisure-track # line z15- (also has caption z16-) railway-abandoned # line z13- railway-construction # line z13- diff --git a/data/styles/outdoors/include/priorities_4_overlays.prio.txt b/data/styles/outdoors/include/priorities_4_overlays.prio.txt index 1785479cc..2320321bc 100644 --- a/data/styles/outdoors/include/priorities_4_overlays.prio.txt +++ b/data/styles/outdoors/include/priorities_4_overlays.prio.txt @@ -838,6 +838,22 @@ attraction-maze # icon z16- (also has captio attraction-roller_coaster # icon z16- (also has caption(optional) z16-) attraction-water_slide # icon z17- (also has caption(optional) z17-) highway-construction # pathtext z15- (also has line z11-) +highway-construction-living_street # pathtext z15- (also has line z11-) +highway-construction-motorway # pathtext z15- (also has line z11-) +highway-construction-motorway_link # pathtext z15- (also has line z11-) +highway-construction-primary # pathtext z15- (also has line z11-) +highway-construction-primary_link # pathtext z15- (also has line z11-) +highway-construction-residential # pathtext z15- (also has line z11-) +highway-construction-road # pathtext z15- (also has line z11-) +highway-construction-secondary # pathtext z15- (also has line z11-) +highway-construction-secondary_link # pathtext z15- (also has line z11-) +highway-construction-service # pathtext z15- (also has line z11-) +highway-construction-tertiary # pathtext z15- (also has line z11-) +highway-construction-tertiary_link # pathtext z15- (also has line z11-) +highway-construction-track # pathtext z15- (also has line z11-) +highway-construction-trunk # pathtext z15- (also has line z11-) +highway-construction-trunk_link # pathtext z15- (also has line z11-) +highway-construction-unclassified # pathtext z15- (also has line z11-) highway-living_street # pathtext z14- (also has line z12-, line::border z12-) highway-living_street-bridge # pathtext z14- (also has line z12-, line::border z12-) highway-living_street-tunnel # pathtext z14- (also has line z12-, line::border z12-, line(casing) z16-) diff --git a/data/styles/vehicle/include/priorities_3_FG.prio.txt b/data/styles/vehicle/include/priorities_3_FG.prio.txt index 2a2904281..9068150fe 100644 --- a/data/styles/vehicle/include/priorities_3_FG.prio.txt +++ b/data/styles/vehicle/include/priorities_3_FG.prio.txt @@ -271,6 +271,22 @@ highway-track-no-access # line z16- === 130 highway-construction # line z13- +highway-construction-living_street # line z13- +highway-construction-motorway # line z13- +highway-construction-motorway_link # line z13- +highway-construction-primary # line z13- +highway-construction-primary_link # line z13- +highway-construction-residential # line z13- +highway-construction-road # line z13- +highway-construction-secondary # line z13- +highway-construction-secondary_link # line z13- +highway-construction-service # line z13- +highway-construction-tertiary # line z13- +highway-construction-tertiary_link # line z13- +highway-construction-track # line z13- +highway-construction-trunk # line z13- +highway-construction-trunk_link # line z13- +highway-construction-unclassified # line z13- railway-abandoned # line z16- railway-construction # line z16- railway-disused # line z16- diff --git a/generator/routing_index_generator.cpp b/generator/routing_index_generator.cpp index 094ab83b6..645eca320 100644 --- a/generator/routing_index_generator.cpp +++ b/generator/routing_index_generator.cpp @@ -61,7 +61,6 @@ class VehicleMaskBuilder final : m_pedestrianModel(PedestrianModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_bicycleModel(BicycleModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_carModel(CarModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) - , m_constructionType(classif().GetTypeByPath({"highway", "construction"})) { CHECK(m_pedestrianModel, ()); CHECK(m_bicycleModel, ()); @@ -71,9 +70,6 @@ class VehicleMaskBuilder final VehicleMask CalcRoadMask(FeatureType & f) const { feature::TypesHolder const types(f); - if (types.HasWithSubclass(m_constructionType)) - return 0; - return CalcMask([&](VehicleModelInterface const & model) { return model.IsRoad(types); }); } @@ -101,8 +97,6 @@ class VehicleMaskBuilder final std::shared_ptr const m_pedestrianModel; std::shared_ptr const m_bicycleModel; std::shared_ptr const m_carModel; - - uint32_t const m_constructionType; }; class Processor final diff --git a/libs/routing_common/car_model.cpp b/libs/routing_common/car_model.cpp index aaa405cec..f08a8dc88 100644 --- a/libs/routing_common/car_model.cpp +++ b/libs/routing_common/car_model.cpp @@ -21,6 +21,10 @@ using namespace routing; // of the route except for some edge cases. SpeedKMpH constexpr kSpeedOffroadKMpH = {0.01 /* weight */, kNotUsed /* eta */}; +/* + * The number of entries must match `kHighwayBasedFactors` and `kHighwayBasedSpeeds` + * in `car_model_coefs.hpp`. + */ VehicleModel::LimitsInitList const kDefaultOptions = { // {HighwayType, passThroughAllowed} {HighwayType::HighwayMotorway, true}, {HighwayType::HighwayMotorwayLink, true}, @@ -31,6 +35,19 @@ VehicleModel::LimitsInitList const kDefaultOptions = { {HighwayType::HighwayResidential, true}, {HighwayType::HighwayUnclassified, true}, {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, + // The router truncates types to two levels, so we need this in addition to the long construction types + {HighwayType::HighwayConstruction, true}, + // Construction conterparts to each of the types above + // (needed for the map generator to include them in the routing section) + {HighwayType::HighwayConstructionMotorway, true}, {HighwayType::HighwayConstructionMotorwayLink, true}, + {HighwayType::HighwayConstructionTrunk, true}, {HighwayType::HighwayConstructionTrunkLink, true}, + {HighwayType::HighwayConstructionPrimary, true}, {HighwayType::HighwayConstructionPrimaryLink, true}, + {HighwayType::HighwayConstructionSecondary, true}, {HighwayType::HighwayConstructionSecondaryLink, true}, + {HighwayType::HighwayConstructionTertiary, true}, {HighwayType::HighwayConstructionTertiaryLink, true}, + {HighwayType::HighwayConstructionResidential, true}, {HighwayType::HighwayConstructionUnclassified, true}, + {HighwayType::HighwayConstructionService, true}, {HighwayType::HighwayConstructionLivingStreet, true}, + {HighwayType::HighwayConstructionRoad, true}, {HighwayType::HighwayConstructionTrack, true}, + // Non-highway types {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, {HighwayType::ManMadePier, true}}; diff --git a/libs/routing_common/car_model_coefs.hpp b/libs/routing_common/car_model_coefs.hpp index 6a466d946..6fcc8c62b 100644 --- a/libs/routing_common/car_model_coefs.hpp +++ b/libs/routing_common/car_model_coefs.hpp @@ -3,9 +3,25 @@ #include "routing_common/vehicle_model.hpp" // These are default car model coefficients for open source developers. +// Both maps, as well as `car_model::kDefaultOptions` (in `car_model.cpp`), must have the same number of entries. namespace routing { +/** + * @brief Speed to indicate that a segment is impassable. + */ +/* + * 1.0E-4 is the inverse of 1.0E4, which is used in `edge_estimator.cpp` as the penalty factor + * for the weight of a segment with `SpeedGroup::TempBlock`, i.e. currently impassable. + * Using its inverse as the speed is equivalent to 1 km/h with a penalty factor of 1.0E4. + * The lowest speed is currently 5 km/h (for `HighwayTrack`), thus 1 m of an impassable way has the + * same weight as at least 5 km of any routable way. + * This has worked well in tests. In theory, we could increase it further to 1.0E-8, resulting in + * 0.8 m of an impassable way costing as much as at least 40,000 km (earth circumference) of any + * routable way. + */ +auto const kImpassableSpeedKMpH = 1.0E-4; + HighwayBasedFactors const kHighwayBasedFactors = { // {highway class : InOutCityFactor(in city, out city)} @@ -44,6 +60,27 @@ HighwayBasedFactors const kHighwayBasedFactors = { {HighwayType::RouteFerry, InOutCityFactor(0.90)}, {HighwayType::RouteShuttleTrain, InOutCityFactor(0.90)}, + + // Generic construction type + {HighwayType::HighwayConstruction, InOutCityFactor(1.0)}, + + // Construction types for each highway type: + {HighwayType::HighwayConstructionMotorway, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunk, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionResidential, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionUnclassified, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionService, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionRoad, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrack, InOutCityFactor(1.0)}, }; HighwayBasedSpeeds const kHighwayBasedSpeeds = { @@ -78,5 +115,26 @@ HighwayBasedSpeeds const kHighwayBasedSpeeds = { {HighwayType::RouteFerry, InOutCitySpeedKMpH(10.00 /* in city */, 10.00 /* out city */)}, {HighwayType::RouteShuttleTrain, InOutCitySpeedKMpH(25.00 /* in city */, 25.00 /* out city */)}, + + // Generic and long construction types: + // They are needed for traffic location decoding but we don’t want to route drivers through + // them, which is accomplished by setting their speed to kImpassableSpeedKMpH. + {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, }; } // namespace routing diff --git a/libs/routing_common/vehicle_model.cpp b/libs/routing_common/vehicle_model.cpp index 28d3308d0..bfe9cb5fd 100644 --- a/libs/routing_common/vehicle_model.cpp +++ b/libs/routing_common/vehicle_model.cpp @@ -361,6 +361,23 @@ string DebugPrint(HighwayType type) case HighwayType::HighwayPedestrian: return "highway-pedestrian"; case HighwayType::HighwayTrunkLink: return "highway-trunk_link"; case HighwayType::HighwayPrimaryLink: return "highway-primary_link"; + case HighwayType::HighwayConstruction: return "highway-construction"; + case HighwayType::HighwayConstructionMotorway: return "highway-construction-motorway"; + case HighwayType::HighwayConstructionMotorwayLink: return "highway-construction-motorway_link"; + case HighwayType::HighwayConstructionTrunk: return "highway-construction-trunk"; + case HighwayType::HighwayConstructionTrunkLink: return "highway-construction-trunk-link"; + case HighwayType::HighwayConstructionPrimary: return "highway-construction-primary"; + case HighwayType::HighwayConstructionPrimaryLink: return "highway-construction-primary_link"; + case HighwayType::HighwayConstructionSecondary: return "highway-construction-secondary"; + case HighwayType::HighwayConstructionSecondaryLink: return "highway-construction-secondary_link"; + case HighwayType::HighwayConstructionTertiary: return "highway-construction-tertiary"; + case HighwayType::HighwayConstructionTertiaryLink: return "highway-construction-tertiary_link"; + case HighwayType::HighwayConstructionResidential: return "highway-construction-residential"; + case HighwayType::HighwayConstructionUnclassified: return "highway-construction-unclassified"; + case HighwayType::HighwayConstructionService: return "highway-construction-service"; + case HighwayType::HighwayConstructionLivingStreet: return "highway-construction-living_street"; + case HighwayType::HighwayConstructionRoad: return "highway-construction-road"; + case HighwayType::HighwayConstructionTrack: return "highway-construction-track"; case HighwayType::ManMadePier: return "man_made-pier"; case HighwayType::HighwayBridleway: return "highway-bridleway"; case HighwayType::HighwaySecondaryLink: return "highway-secondary_link"; @@ -390,7 +407,16 @@ void FromString(std::string_view s, HighwayType & highwayType) HighwayType::HighwayTrunk, HighwayType::HighwayPedestrian, HighwayType::HighwayTrunkLink, HighwayType::HighwayPrimaryLink, HighwayType::ManMadePier, HighwayType::HighwayBridleway, HighwayType::HighwaySecondaryLink, HighwayType::RouteFerry, HighwayType::HighwayTertiaryLink, - HighwayType::HighwayBusway, HighwayType::RouteShuttleTrain}; + HighwayType::HighwayBusway, HighwayType::RouteShuttleTrain, HighwayType::HighwayConstruction, + HighwayType::HighwayConstructionMotorway, HighwayType::HighwayConstructionMotorwayLink, + HighwayType::HighwayConstructionTrunk, HighwayType::HighwayConstructionTrunkLink, + HighwayType::HighwayConstructionPrimary, HighwayType::HighwayConstructionPrimaryLink, + HighwayType::HighwayConstructionSecondary, HighwayType::HighwayConstructionSecondaryLink, + HighwayType::HighwayConstructionTertiary, HighwayType::HighwayConstructionTertiaryLink, + HighwayType::HighwayConstructionResidential, HighwayType::HighwayConstructionUnclassified, + HighwayType::HighwayConstructionService, HighwayType::HighwayConstructionLivingStreet, + HighwayType::HighwayConstructionRoad, HighwayType::HighwayConstructionTrack, + }; for (auto type : allTypes) map[DebugPrint(type)] = type; diff --git a/libs/routing_common/vehicle_model.hpp b/libs/routing_common/vehicle_model.hpp index 2eeb6b41e..c9330b618 100644 --- a/libs/routing_common/vehicle_model.hpp +++ b/libs/routing_common/vehicle_model.hpp @@ -52,6 +52,24 @@ enum class HighwayType : uint16_t HighwayTrunkLink = 90, HighwayPrimaryLink = 95, ManMadePier = 119, + // HighwayConstruction and its subtypes are needed for traffic message decoding + HighwayConstruction = 162, + HighwayConstructionMotorway = 1632, + HighwayConstructionMotorwayLink = 1633, + HighwayConstructionTrunk = 1634, + HighwayConstructionTrunkLink = 1635, + HighwayConstructionPrimary = 1636, + HighwayConstructionPrimaryLink = 1637, + HighwayConstructionSecondary = 1638, + HighwayConstructionSecondaryLink = 1639, + HighwayConstructionTertiary = 1640, + HighwayConstructionTertiaryLink = 1641, + HighwayConstructionResidential = 1642, + HighwayConstructionUnclassified = 1643, + HighwayConstructionService = 1644, + HighwayConstructionLivingStreet = 1645, + HighwayConstructionRoad = 1646, + HighwayConstructionTrack = 1647, HighwayBridleway = 167, HighwaySecondaryLink = 176, RouteFerry = 259, From 269cbe66584d95c4982fc864724e08b680d478dd Mon Sep 17 00:00:00 2001 From: mvglasow Date: Mon, 4 May 2026 19:13:15 +0300 Subject: [PATCH 238/252] [traffic] Support construction types in decoder Signed-off-by: mvglasow --- libs/indexer/ftypes_matcher.cpp | 25 ++++++++++++++ libs/routing/index_router.cpp | 1 + libs/routing_common/vehicle_model.cpp | 18 +++++++++- libs/traffxml/traff_decoder.cpp | 47 +++++++++++++++++++++++++++ libs/traffxml/traff_decoder.hpp | 1 + 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/libs/indexer/ftypes_matcher.cpp b/libs/indexer/ftypes_matcher.cpp index 1082d294f..af640ea64 100644 --- a/libs/indexer/ftypes_matcher.cpp +++ b/libs/indexer/ftypes_matcher.cpp @@ -66,7 +66,32 @@ class HighwayClasses m_map[c.GetTypeByPath({"highway", "steps"})] = HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "cycleway"})] = HighwayClass::Pedestrian; m_map[c.GetTypeByPath({"highway", "path"})] = HighwayClass::Pedestrian; + + // mvglasow: Appears to never have been used m_map[c.GetTypeByPath({"highway", "construction"})] = HighwayClass::Pedestrian; + + // Construction types + m_map[c.GetTypeByPath({"highway", "construction", "motorway"})] = HighwayClass::Motorway; + m_map[c.GetTypeByPath({"highway", "construction", "motorway_link"})] = HighwayClass::Motorway; + m_map[c.GetTypeByPath({"highway", "construction", "trunk"})] = HighwayClass::Trunk; + m_map[c.GetTypeByPath({"highway", "construction", "trunk_link"})] = HighwayClass::Trunk; + + m_map[c.GetTypeByPath({"highway", "construction", "primary"})] = HighwayClass::Primary; + m_map[c.GetTypeByPath({"highway", "primary_link"})] = HighwayClass::Primary; + + m_map[c.GetTypeByPath({"highway", "construction", "secondary"})] = HighwayClass::Secondary; + m_map[c.GetTypeByPath({"highway", "construction", "secondary_link"})] = HighwayClass::Secondary; + + m_map[c.GetTypeByPath({"highway", "construction", "tertiary"})] = HighwayClass::Tertiary; + m_map[c.GetTypeByPath({"highway", "construction", "tertiary_link"})] = HighwayClass::Tertiary; + + m_map[c.GetTypeByPath({"highway", "construction", "unclassified"})] = HighwayClass::LivingStreet; + m_map[c.GetTypeByPath({"highway", "construction", "residential"})] = HighwayClass::LivingStreet; + m_map[c.GetTypeByPath({"highway", "construction", "living_street"})] = HighwayClass::LivingStreet; + m_map[c.GetTypeByPath({"highway", "construction", "road"})] = HighwayClass::LivingStreet; + + m_map[c.GetTypeByPath({"highway", "construction", "service"})] = HighwayClass::Service; + m_map[c.GetTypeByPath({"highway", "construction", "track"})] = HighwayClass::Service; } HighwayClass Get(uint32_t t) const diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp index 5330b989f..304a2a706 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -313,6 +313,7 @@ bool IndexRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin auto const rect = mercator::RectByCenterXYAndSizeInMeters(point, radius); std::vector candidates; + // TODO should we increase the count in decoding mode? uint32_t const count = direction.IsAlmostZero() ? 1 : 4; m_roadGraph.FindClosestEdges(rect, count, candidates, (GetMode() == Mode::Decoding)); diff --git a/libs/routing_common/vehicle_model.cpp b/libs/routing_common/vehicle_model.cpp index bfe9cb5fd..d64085793 100644 --- a/libs/routing_common/vehicle_model.cpp +++ b/libs/routing_common/vehicle_model.cpp @@ -71,13 +71,29 @@ void VehicleModel::AddAdditionalRoadTypes(Classificator const & classif, Additio std::optional VehicleModel::GetHighwayType(FeatureTypes const & types) const { + /* + * Iterate through types. + * For each type, truncate to three levels and see if it has a match. If not, truncate to two + * levels and retry. + * First type in `types` which has a match wins over all subsequent ones. + * Within a type, three-level matches win over two-level ones. + */ for (uint32_t t : types) { - ftype::TruncValue(t, 2); + ftype::TruncValue(t, 3); auto const ret = GetHighwayType(t); if (ret) return *ret; + + if (ftype::GetLevel(t) > 2) + { + ftype::TruncValue(t, 2); + + auto const ret = GetHighwayType(t); + if (ret) + return *ret; + } } // For example Denmark has "No track" profile (see kCarOptionsDenmark), but tracks exist in MWM. diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index a30580350..8a6198324 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -122,6 +122,11 @@ auto constexpr kTurnPenaltyMinAngle = 65.0; */ auto constexpr kTurnPenaltyFullAngle = 90.0; +/* + * Penalty applied to impassable ways + */ +auto constexpr kImpassablePenalty = 1.0E4; + /* * Invalid feature ID. * Borrowed from indexer/feature_decl.hpp. @@ -690,6 +695,10 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c result *= m_decoder.GetRoadRefPenalty(refs); } + if ((m_decoder.m_trafficImpact.value().m_speedGroup != traffic::SpeedGroup::TempBlock) + && road.GetHighwayType() && IsConstruction(road.GetHighwayType().value())) + result *= kImpassablePenalty; + return result; } @@ -742,6 +751,7 @@ double RoutingTraffDecoder::GetHighwayTypePenalty(std::optional Date: Tue, 5 May 2026 19:36:21 +0300 Subject: [PATCH 239/252] [routing] Make EdgeEstimator::IsAccessIgnored() const Signed-off-by: mvglasow --- libs/routing/edge_estimator.hpp | 2 +- libs/traffxml/traff_decoder.cpp | 5 ++--- libs/traffxml/traff_decoder.hpp | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libs/routing/edge_estimator.hpp b/libs/routing/edge_estimator.hpp index 20fbe4681..9db432b5c 100644 --- a/libs/routing/edge_estimator.hpp +++ b/libs/routing/edge_estimator.hpp @@ -155,7 +155,7 @@ class EdgeEstimator * * This implementation always returns false. */ - virtual bool IsAccessIgnored() { return false; } + virtual bool IsAccessIgnored() const { return false; } /** * @brief Creates an `EdgeEstimator` based on maximum speeds. diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 8a6198324..91eaa0035 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -695,14 +695,13 @@ double RoutingTraffDecoder::TraffEstimator::CalcSegmentWeight(routing::Segment c result *= m_decoder.GetRoadRefPenalty(refs); } - if ((m_decoder.m_trafficImpact.value().m_speedGroup != traffic::SpeedGroup::TempBlock) - && road.GetHighwayType() && IsConstruction(road.GetHighwayType().value())) + if (!IsAccessIgnored() && road.GetHighwayType() && IsConstruction(road.GetHighwayType().value())) result *= kImpassablePenalty; return result; } -bool RoutingTraffDecoder::TraffEstimator::IsAccessIgnored() +bool RoutingTraffDecoder::TraffEstimator::IsAccessIgnored() const { ASSERT(m_decoder.m_trafficImpact, ("Traffic impact for current message is not set")); return (m_decoder.m_trafficImpact.value().m_speedGroup == traffic::SpeedGroup::TempBlock); diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 30c4a357a..5a66c2bb7 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -336,7 +336,7 @@ class RoutingTraffDecoder : public TraffDecoder, * * This implementation may return true or false, depending on the location being decoded. */ - bool IsAccessIgnored() override; + bool IsAccessIgnored() const override; private: RoutingTraffDecoder & m_decoder; From c267025a42327320b25e36fbc39ce11208f4e4fd Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 6 May 2026 20:27:57 +0300 Subject: [PATCH 240/252] [android] UI strings for new construction types Signed-off-by: mvglasow # Conflicts: # android/sdk/src/main/res/values-nl/types_strings.xml --- .../src/main/res/values-af/types_strings.xml | 17 + .../src/main/res/values-ar/types_strings.xml | 17 + .../src/main/res/values-az/types_strings.xml | 17 + .../src/main/res/values-bg/types_strings.xml | 17 + .../src/main/res/values-ca/types_strings.xml | 17 + .../src/main/res/values-cs/types_strings.xml | 17 + .../src/main/res/values-da/types_strings.xml | 17 + .../src/main/res/values-de/types_strings.xml | 17 + .../src/main/res/values-el/types_strings.xml | 17 + .../main/res/values-es-rMX/types_strings.xml | 17 + .../src/main/res/values-es/types_strings.xml | 17 + .../src/main/res/values-et/types_strings.xml | 17 + .../src/main/res/values-eu/types_strings.xml | 17 + .../src/main/res/values-fa/types_strings.xml | 17 + .../src/main/res/values-fi/types_strings.xml | 17 + .../src/main/res/values-fr/types_strings.xml | 17 + .../src/main/res/values-gl/types_strings.xml | 17 + .../src/main/res/values-gsw/types_strings.xml | 17 + .../src/main/res/values-hi/types_strings.xml | 17 + .../src/main/res/values-hu/types_strings.xml | 17 + .../src/main/res/values-in/types_strings.xml | 17 + .../src/main/res/values-is/types_strings.xml | 17 + .../src/main/res/values-it/types_strings.xml | 17 + .../src/main/res/values-iw/types_strings.xml | 17 + .../src/main/res/values-ja/types_strings.xml | 17 + .../src/main/res/values-ko/types_strings.xml | 17 + .../src/main/res/values-kw/types_strings.xml | 17 + .../src/main/res/values-lt/types_strings.xml | 17 + .../src/main/res/values-mr/types_strings.xml | 17 + .../src/main/res/values-nb/types_strings.xml | 17 + .../src/main/res/values-nl/types_strings.xml | 585 +++++++++--------- .../src/main/res/values-pl/types_strings.xml | 17 + .../main/res/values-pt-rBR/types_strings.xml | 17 + .../src/main/res/values-pt/types_strings.xml | 17 + .../src/main/res/values-ro/types_strings.xml | 17 + .../src/main/res/values-ru/types_strings.xml | 17 + .../src/main/res/values-sk/types_strings.xml | 17 + .../src/main/res/values-sl/types_strings.xml | 17 + .../src/main/res/values-sr/types_strings.xml | 17 + .../src/main/res/values-sv/types_strings.xml | 17 + .../src/main/res/values-sw/types_strings.xml | 17 + .../src/main/res/values-ta/types_strings.xml | 17 + .../src/main/res/values-th/types_strings.xml | 17 + .../src/main/res/values-tr/types_strings.xml | 17 + .../src/main/res/values-uk/types_strings.xml | 17 + .../src/main/res/values-vi/types_strings.xml | 17 + .../main/res/values-zh-rHK/types_strings.xml | 17 + .../main/res/values-zh-rTW/types_strings.xml | 17 + .../src/main/res/values-zh/types_strings.xml | 17 + .../sdk/src/main/res/values/types_strings.xml | 17 + 50 files changed, 1134 insertions(+), 284 deletions(-) diff --git a/android/sdk/src/main/res/values-af/types_strings.xml b/android/sdk/src/main/res/values-af/types_strings.xml index 7a5cf0ee5..4cc8bfa49 100644 --- a/android/sdk/src/main/res/values-af/types_strings.xml +++ b/android/sdk/src/main/res/values-af/types_strings.xml @@ -359,7 +359,24 @@ Tonnel Bushalte + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou + Pad in aanbou Fietspad Brug diff --git a/android/sdk/src/main/res/values-ar/types_strings.xml b/android/sdk/src/main/res/values-ar/types_strings.xml index 099e41382..9696b951d 100644 --- a/android/sdk/src/main/res/values-ar/types_strings.xml +++ b/android/sdk/src/main/res/values-ar/types_strings.xml @@ -380,7 +380,24 @@ نفق موقف حافلات + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء + طريق تحت الإنشاء مسار للدراجات جسر diff --git a/android/sdk/src/main/res/values-az/types_strings.xml b/android/sdk/src/main/res/values-az/types_strings.xml index fd4f9fed8..775844b63 100644 --- a/android/sdk/src/main/res/values-az/types_strings.xml +++ b/android/sdk/src/main/res/values-az/types_strings.xml @@ -370,7 +370,24 @@ Tunel Avtobus dayanacağı + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol + Tikili Yol Velosiped yolu Körpü diff --git a/android/sdk/src/main/res/values-bg/types_strings.xml b/android/sdk/src/main/res/values-bg/types_strings.xml index 06fc1e3ed..da11056df 100644 --- a/android/sdk/src/main/res/values-bg/types_strings.xml +++ b/android/sdk/src/main/res/values-bg/types_strings.xml @@ -956,7 +956,24 @@ Виетнамска Пътека за юздечки Пътека за юздечки + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане + Път в процес на изграждане Жилищна улица Пътека Пътека diff --git a/android/sdk/src/main/res/values-ca/types_strings.xml b/android/sdk/src/main/res/values-ca/types_strings.xml index 8d4f131a0..9c5b751df 100644 --- a/android/sdk/src/main/res/values-ca/types_strings.xml +++ b/android/sdk/src/main/res/values-ca/types_strings.xml @@ -935,7 +935,24 @@ Centre comercial Desfibril·lador Carretera + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció + Via en construcció Continent Quiosc diff --git a/android/sdk/src/main/res/values-cs/types_strings.xml b/android/sdk/src/main/res/values-cs/types_strings.xml index 11a843f26..1127305f3 100644 --- a/android/sdk/src/main/res/values-cs/types_strings.xml +++ b/android/sdk/src/main/res/values-cs/types_strings.xml @@ -327,7 +327,24 @@ Tunel Autobusová zastávka + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci + Silnice v rekonstrukci Most diff --git a/android/sdk/src/main/res/values-da/types_strings.xml b/android/sdk/src/main/res/values-da/types_strings.xml index 5176aca32..3bd536cd2 100644 --- a/android/sdk/src/main/res/values-da/types_strings.xml +++ b/android/sdk/src/main/res/values-da/types_strings.xml @@ -317,7 +317,24 @@ Tunnel Busstoppested + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse + Vej under anlæggelse Bro diff --git a/android/sdk/src/main/res/values-de/types_strings.xml b/android/sdk/src/main/res/values-de/types_strings.xml index f8238a939..aef37da73 100644 --- a/android/sdk/src/main/res/values-de/types_strings.xml +++ b/android/sdk/src/main/res/values-de/types_strings.xml @@ -417,7 +417,24 @@ Tunnel Bushaltestelle + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau + Straße im Bau Radweg Brücke diff --git a/android/sdk/src/main/res/values-el/types_strings.xml b/android/sdk/src/main/res/values-el/types_strings.xml index 9ec8dcba5..3657c3d9e 100644 --- a/android/sdk/src/main/res/values-el/types_strings.xml +++ b/android/sdk/src/main/res/values-el/types_strings.xml @@ -335,7 +335,24 @@ Σήραγγα Στάση λεωφορείου + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή + Οδός υπό κατασκευή Γέφυρα diff --git a/android/sdk/src/main/res/values-es-rMX/types_strings.xml b/android/sdk/src/main/res/values-es-rMX/types_strings.xml index fb4916e55..ef0f8710d 100644 --- a/android/sdk/src/main/res/values-es-rMX/types_strings.xml +++ b/android/sdk/src/main/res/values-es-rMX/types_strings.xml @@ -57,7 +57,24 @@ Puente Túnel + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción Puente diff --git a/android/sdk/src/main/res/values-es/types_strings.xml b/android/sdk/src/main/res/values-es/types_strings.xml index 5bf8a01c6..eed7b763d 100644 --- a/android/sdk/src/main/res/values-es/types_strings.xml +++ b/android/sdk/src/main/res/values-es/types_strings.xml @@ -385,7 +385,24 @@ Túnel Parada de bus + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción + Vía en construcción Ciclovía Puente diff --git a/android/sdk/src/main/res/values-et/types_strings.xml b/android/sdk/src/main/res/values-et/types_strings.xml index 07deaa4b5..c5803de42 100644 --- a/android/sdk/src/main/res/values-et/types_strings.xml +++ b/android/sdk/src/main/res/values-et/types_strings.xml @@ -382,7 +382,24 @@ Tunnel Bussipeatus + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee + Ehitusjärgus tee Jalgrattatee Jalgrattasild diff --git a/android/sdk/src/main/res/values-eu/types_strings.xml b/android/sdk/src/main/res/values-eu/types_strings.xml index ceb4e4263..e112c8a39 100644 --- a/android/sdk/src/main/res/values-eu/types_strings.xml +++ b/android/sdk/src/main/res/values-eu/types_strings.xml @@ -373,7 +373,24 @@ Tunela Autobus geltokia + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea + Eraikitzen ari diren errepidea Bidegorri edo bizikleta-bidea Zubia diff --git a/android/sdk/src/main/res/values-fa/types_strings.xml b/android/sdk/src/main/res/values-fa/types_strings.xml index 5ff2035ee..656cf590e 100644 --- a/android/sdk/src/main/res/values-fa/types_strings.xml +++ b/android/sdk/src/main/res/values-fa/types_strings.xml @@ -244,7 +244,24 @@ تونل حمل و نقل + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است + جاده در دست ساخت است پل diff --git a/android/sdk/src/main/res/values-fi/types_strings.xml b/android/sdk/src/main/res/values-fi/types_strings.xml index e0fa7245b..eb775e21d 100644 --- a/android/sdk/src/main/res/values-fi/types_strings.xml +++ b/android/sdk/src/main/res/values-fi/types_strings.xml @@ -375,7 +375,24 @@ Tunneli Bussipysäkki + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie + Rakenteilla oleva tie Pyörätie Silta diff --git a/android/sdk/src/main/res/values-fr/types_strings.xml b/android/sdk/src/main/res/values-fr/types_strings.xml index 94f7d1c03..d1d93e878 100644 --- a/android/sdk/src/main/res/values-fr/types_strings.xml +++ b/android/sdk/src/main/res/values-fr/types_strings.xml @@ -370,7 +370,24 @@ Tunnel Arrêt de bus + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction + Route en construction Piste cyclable Pont diff --git a/android/sdk/src/main/res/values-gl/types_strings.xml b/android/sdk/src/main/res/values-gl/types_strings.xml index a07fca348..7bed81337 100644 --- a/android/sdk/src/main/res/values-gl/types_strings.xml +++ b/android/sdk/src/main/res/values-gl/types_strings.xml @@ -367,7 +367,24 @@ Puente Túnel Parada de autobús + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción + Vía en construción Ciclovía Puente Ciclovía diff --git a/android/sdk/src/main/res/values-gsw/types_strings.xml b/android/sdk/src/main/res/values-gsw/types_strings.xml index 3ea62239b..04c5a508d 100644 --- a/android/sdk/src/main/res/values-gsw/types_strings.xml +++ b/android/sdk/src/main/res/values-gsw/types_strings.xml @@ -333,7 +333,24 @@ Brugg Tunnel Bushaltistell + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau + Strass im Bau Veloweg Brugg Veloweg diff --git a/android/sdk/src/main/res/values-hi/types_strings.xml b/android/sdk/src/main/res/values-hi/types_strings.xml index 9cc4513fd..055d40fe6 100644 --- a/android/sdk/src/main/res/values-hi/types_strings.xml +++ b/android/sdk/src/main/res/values-hi/types_strings.xml @@ -263,7 +263,24 @@ राजमार्ग बस स्टॉप + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क + निर्माणाधीन सड़क साइकिल मार्ग साइकिल मार्ग उत्थापक diff --git a/android/sdk/src/main/res/values-hu/types_strings.xml b/android/sdk/src/main/res/values-hu/types_strings.xml index ecdcc5f3f..b194a1251 100644 --- a/android/sdk/src/main/res/values-hu/types_strings.xml +++ b/android/sdk/src/main/res/values-hu/types_strings.xml @@ -329,7 +329,24 @@ Alagút Buszmegálló + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés + Útépítés Híd diff --git a/android/sdk/src/main/res/values-in/types_strings.xml b/android/sdk/src/main/res/values-in/types_strings.xml index ca7c5bc74..7acde82a3 100644 --- a/android/sdk/src/main/res/values-in/types_strings.xml +++ b/android/sdk/src/main/res/values-in/types_strings.xml @@ -323,7 +323,24 @@ Terowongan Halte bus + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun + Jalan sedang dibangun Menjembatani diff --git a/android/sdk/src/main/res/values-is/types_strings.xml b/android/sdk/src/main/res/values-is/types_strings.xml index 0ce70b142..40860f438 100644 --- a/android/sdk/src/main/res/values-is/types_strings.xml +++ b/android/sdk/src/main/res/values-is/types_strings.xml @@ -1046,7 +1046,24 @@ Snjóleikjagarður Þráðlaust net Setskýli + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu + Vegur í byggingu Svæði fyrir fótgangandi Svæði fyrir fótgangandi Sögulegur skriðdreki diff --git a/android/sdk/src/main/res/values-it/types_strings.xml b/android/sdk/src/main/res/values-it/types_strings.xml index 25a08eeca..1cb54d3cf 100644 --- a/android/sdk/src/main/res/values-it/types_strings.xml +++ b/android/sdk/src/main/res/values-it/types_strings.xml @@ -382,7 +382,24 @@ Galleria Fermata dell\'autobus + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione + Strada in costruzione Pista ciclabile Ponte diff --git a/android/sdk/src/main/res/values-iw/types_strings.xml b/android/sdk/src/main/res/values-iw/types_strings.xml index d46c06469..d9f68278f 100644 --- a/android/sdk/src/main/res/values-iw/types_strings.xml +++ b/android/sdk/src/main/res/values-iw/types_strings.xml @@ -382,7 +382,24 @@ מִנהָרָה תחנת אוטובוס + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה + כביש בבניה שביל אופניים גשר לאופניים diff --git a/android/sdk/src/main/res/values-ja/types_strings.xml b/android/sdk/src/main/res/values-ja/types_strings.xml index 50557c2c2..7f5990df1 100644 --- a/android/sdk/src/main/res/values-ja/types_strings.xml +++ b/android/sdk/src/main/res/values-ja/types_strings.xml @@ -356,7 +356,24 @@ トンネル バス停 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 + 工事中の道路 自転車道 自転車道(橋) diff --git a/android/sdk/src/main/res/values-ko/types_strings.xml b/android/sdk/src/main/res/values-ko/types_strings.xml index e62739e1a..9d1c1912d 100644 --- a/android/sdk/src/main/res/values-ko/types_strings.xml +++ b/android/sdk/src/main/res/values-ko/types_strings.xml @@ -330,7 +330,24 @@ 터널 버스 정류장 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 + 공사 중 도로 다리 diff --git a/android/sdk/src/main/res/values-kw/types_strings.xml b/android/sdk/src/main/res/values-kw/types_strings.xml index fe985615b..206dc602d 100644 --- a/android/sdk/src/main/res/values-kw/types_strings.xml +++ b/android/sdk/src/main/res/values-kw/types_strings.xml @@ -286,7 +286,24 @@ Pons Kowfordh Kyttrinva + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans + Fordh y\'n Drehevyans Hyns rag Diwrosow Pons Hyns rag Diwrosow diff --git a/android/sdk/src/main/res/values-lt/types_strings.xml b/android/sdk/src/main/res/values-lt/types_strings.xml index 1b44e94f8..5f73fdd1b 100644 --- a/android/sdk/src/main/res/values-lt/types_strings.xml +++ b/android/sdk/src/main/res/values-lt/types_strings.xml @@ -466,7 +466,24 @@ Tiltas Tunelis Stotelė + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias + Tiesiamas kelias Dviračių takas Tiltas Dviračių takas diff --git a/android/sdk/src/main/res/values-mr/types_strings.xml b/android/sdk/src/main/res/values-mr/types_strings.xml index b8628957d..967171b06 100644 --- a/android/sdk/src/main/res/values-mr/types_strings.xml +++ b/android/sdk/src/main/res/values-mr/types_strings.xml @@ -290,7 +290,24 @@ बोगदा बस थांबा + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता + बांधकामाधीन रस्ता सायकल वाट पूल diff --git a/android/sdk/src/main/res/values-nb/types_strings.xml b/android/sdk/src/main/res/values-nb/types_strings.xml index c3b4a5905..36fdb7d5b 100644 --- a/android/sdk/src/main/res/values-nb/types_strings.xml +++ b/android/sdk/src/main/res/values-nb/types_strings.xml @@ -337,7 +337,24 @@ Tunnel Busstopp + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon + Veikonstruksjon Sykkelvei Bru diff --git a/android/sdk/src/main/res/values-nl/types_strings.xml b/android/sdk/src/main/res/values-nl/types_strings.xml index 1659fc12c..9b69c0f26 100644 --- a/android/sdk/src/main/res/values-nl/types_strings.xml +++ b/android/sdk/src/main/res/values-nl/types_strings.xml @@ -145,7 +145,7 @@ GFTE afval Drankverpakkingen Restaurant - Caravantoilet cassette-leegpunt + Camperverzorgingsplaats School Schuilplaats @@ -167,7 +167,7 @@ Sigarettenautomaat Koffieautomaat Condoomautomaat - Drankautomaat + Drankenautomaat Etenswarenautomaat Krantenautomaat Parkeerautomaat @@ -180,7 +180,7 @@ Dierenarts Vuilnisbak Afvalcontainer - Overslagstation voor afval + Afvaloverslagstation Waterpunt Waterpunt Barrière @@ -238,7 +238,7 @@ Brouwerij Cateraar Timmerman - Banketbakkerij + Snoepmakerij Elektricien Elektronica-reparatie Tuinarchitect @@ -279,7 +279,7 @@ Koffie Pannenkoek Kroatisch - Curry\'s + Curry Delicatessen Diner Donuts @@ -292,7 +292,7 @@ Georgisch Duits Grieks - Gegrilde gerechten + Grill Heuriger Hotdogs Hongaars @@ -353,7 +353,7 @@ Ingang Hoofdingang - Uitgang (enig) + Uitgang (alleen uitgang) Gratis Medisch laboratorium Fysiotherapeut @@ -373,13 +373,30 @@ Ruiterpad Tunnel - Speciale busweg + Busbaan Brug Tunnel Bushalte - Baan in aanbouw + + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw + Weg in aanbouw Fietspad Fietsbrug @@ -396,7 +413,7 @@ Voetgangerstunnel Voorde - Straat + Woonerf Brug @@ -407,7 +424,7 @@ Snelwegtunnel Afrit - Snelwegoprit + Snelwegoprit/afrit Brug @@ -416,7 +433,7 @@ Moeilijk of slecht zichtbaar pad - Zeer moeilijk of niet te onderscheiden spoor + Zeer moeilijk of niet te onderscheiden pad Fiets- & voetpad Fiets- & voetpad @@ -491,27 +508,27 @@ Brug Tunnel - Straat - Straat + Bos-/veldweg + Bos-/veldweg Brug - Straat - Straat + Bos-/veldweg + Bos-/veldweg Tunnel Verkeerslichten - Straat + Autoweg Brug Tunnel - Straat + Autowegoprit/afrit Brug Tunnel - Straat - Straat + Weg + Weg Brug @@ -525,12 +542,12 @@ Hoofdweg Straat Secundaire weg - Straat + Serviceweg Tertiaire weg - Trappen - Straat - Straat - Straat + Trap + Bos-/veldweg + Autoweg + Weg Historisch object Historische vliegtuigen @@ -546,7 +563,7 @@ Vesting Heuvelfort Kremlin - Manoir + Landhuis Paleis Japans kasteel Landhuis @@ -566,7 +583,7 @@ Historische mijn Monument Schandpaal - Ruïne + Historische ruïne Historisch schip Historische tank Graftombe @@ -575,7 +592,7 @@ Wegkruis Kruisbeeld - Schipbreuk + Schipwrak Internet Draadloos internet Kruispunt @@ -590,35 +607,35 @@ Christelijke begraafplaats Commercieel gebied Bouwplaats - Educatieve voorzieningen + Onderwijsgebied Landbouwgrond Boerenerf - Bloemenbed + Bloembed Bos Naaldbos Loofbos Gemengd bos Garages Gazon - Kassen + Kas Industrieterrein Stortplaats Weiland Militair terrein Boomgaard Steengroeve - Spoorwegfaciliteiten + Spoorwegterrein Recreatiezone Woonwijk - Detailhandel - Zoutpoel + Detailhandelsgebied + Zoutpan Dorpspark Wijngaard - Ontspanning + Recreatie Openbare grond Hondenuitlaatplek Fitnesscentrum - Fitness-Station + Fitness-station Danszaal Tuin Tuin @@ -629,9 +646,9 @@ Natuurreservaat Zitplaatsen buiten Park - Park + Privépark Park - Park + Privépark Picknicktafel Sportveld Speeltuin @@ -643,18 +660,18 @@ Yoga Stadion Zwembad - Privé zwembad + Privézwembad Parcours Parcours Waterpark Strandresort - Kunstmatige constructies + Kunstmatige constructie Golfbreker - Steengroeve + Steenmannetje Fabrieksschoorsteen - Bospad + Cutline Meetpunt - Vlaggenpaal + Vlaggenmast Vuurtoren Mast Pier @@ -680,7 +697,7 @@ Waterput Windmolen Fabriek - Militair + Militair object Bunker Bergpas Natuur @@ -695,9 +712,9 @@ Zandstrand Kiezelstrand Kaap - Grot + Grotingang Klif - Klif + Aardhelling Baanlichaam Kustlijn Woestijn @@ -707,8 +724,8 @@ Heide Hete bron Meer - Slotkamer - Plas + Schutkolk + Vijver Reservoir Waterbassin Rivier @@ -739,7 +756,7 @@ NGO-kantoor Mobiele provider Alleen biologisch - o.a. biologisch + Biologische opties Stad Hoofdstad Stad @@ -770,72 +787,72 @@ Zee Plein Deelstaat - Deelstaat + Staat Buitenwijk Stad Dorp - Electriciteit + Elektriciteit Zonne-generator - Wind generator - Gasturbine elektriciteitscentrale + Windgenerator + Gascentrale Waterkrachtcentrale Hoogspanningsleiding Ondergrondse hoogspanningsleiding Laag-/middelspanningsleiding Energiecentrale - Kolen elektriciteitscentrale - Gasturbine elektriciteitscentrale + Kolencentrale + Gascentrale Waterkrachtcentrale Zonne-energiecentrale Windkrachtcentrale Transformatorstation - Elektriciteitsmast + Hoogspanningsmast Openbaar vervoer - Platform + Perron Spoorweg Voormalig treinspoor - Treinspoor in aanbouw + Spoor in aanbouw Spoorwegovergang Ongebruikt spoor - Tunnel - Kabelspoorbuur + Kabelspoor + Kabelspoorbrug Kabelspoortunnel Station Spoorwegovergang Lightrail - Lightrail-brug - Lightrail-tunnel + Lightrailbrug + Lightrailtunnel Monorail - Monorail-brug - Monorail-tunnel - Smalspoorbaan + Monorailbrug + Monorailtunnel + Smalspoor Smalspoorbrug Smalspoortunnel Perron - Museumspoorbaan - Museumspoorbaanbrug - Museumspoorbaantunnel - Spoorlijn - Hogesnelheidstrein + Museumspoorweg + Museumspoorbrug + Museumspoortunnel + Spoor + Hogesnelheidsspoor Toeristische spoorlijn Spoor - Secundaire spoorwegen + Secundair spoor - Spoorwegen + Dienstspoor Zijspoor Hulpspoor - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug - Treinspoorbrug + Spoorbrug + Spoorbrug + Spoorbrug + Spoorbrug + Spoorbrug + Spoorbrug + Spoorbrug + Spoorbrug Spoortunnel Spoortunnel Spoortunnel @@ -845,12 +862,12 @@ Spoortunnel Spoortunnel Station - Tunnel - Station + Kabelspoorstation + Lightrailstation S-bahn station Station Station - Station + Monorailstation Metrostation Metrostation Metrostation @@ -965,124 +982,124 @@ Metrostation Metrostation Metrostation - Metro + Metrolijn Metrobrug Metrotunnel - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Metro ingang - Tram + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Metro-ingang + Tramspoor Trambrug Tramtunnel Tramhalte @@ -1093,27 +1110,27 @@ Bakkerij Badkamerinrichting Schoonheidssalon - Drank + Drankenwinkel Fietsenwinkel - Boekbinder + Wedkantoor Boekwinkel Slager Coffeeshop - Autohandelaar - Auto-onderdelen - Auto reparatie + Autodealer + Auto-onderdelenwinkel + Autogarage Bandenreparatie - Caravan en camper verkoper + Caravan- en camperdealer Tapijtenwinkel Drogisterij Chocolaterie Kledingwinkel - Koffieverkoop + Koffiewinkel Computerwinkel Snoepwinkel - Buurtwinkel + Gemakswinkel Kopieerwinkel - Schoonheidsmiddelen + Cosmeticawinkel Gordijnenwinkel Delicatessenwinkel Warenhuis @@ -1123,21 +1140,21 @@ Erotiekwinkel Stoffenwinkel Boerderijwinkel - Modeaccessoires + Modeaccessoirewinkel Bloemist - Begrafenisondernemer + Uitvaartondernemer Meubelwinkel Tuincentrum Gaswinkel Cadeauwinkel Groentenwinkel - Boodschappen + Kruidenierswinkel Kapper - IJzerwaren + IJzerwarenwinkel Natuurvoedingswinkel - Winkel met hoortoestellen + Hoortoestellenwinkel Kruidenwinkel - HiFi-audio + Hifiwinkel Huishoudwinkel Juwelier Kiosk @@ -1145,18 +1162,18 @@ Wasserette Winkelcentrum Massagesalon - Mobiele telefoonwinkel + Mobiele-telefoonwinkel Geldschieter Motorzaak Motorfietsreparatie Muziekwinkel Muziekinstrumentenwinkel - Kiosk + Krantenkiosk Opticien - Outdooruitrusting + Buitensportwinkel Afhaalpunt Pastawinkel - Banketbakker + Banketbakkerij Pandjeshuis Dierenwinkel Huisdierverzorging @@ -1166,10 +1183,10 @@ Visboer Tweedehandswinkel Schoenenwinkel - Sportartikelen + Sportwinkel Kantoorboekhandel Supermarkt - Tattoosalon + Tatoeagestudio Theewinkel Kaartjesverkoop Speelgoedwinkel @@ -1184,27 +1201,27 @@ Witgoedwinkel Kunstwinkel - Babyspullenwinkel + Babywinkel Tassenwinkel Beddenwinkel Boetiek Kringloopwinkel Kaaswinkel - Kunst en ambacht - Zuivelproducten + Kunst- en hobbywinkel + Zuivelwinkel Elektronische benodigdhedenwinkel Viswinkel Interieurdecoratiewinkel - Loten - Medische benodigdheden + Loterijwinkel + Medische benodigdhedenwinkel Voedingssupplementenwinkel Verfwinkel Parfumerie - Naaibenodigdheden + Naai- en fourniturenwinkel Opslagverhuur - Tabakszaak - Handelsbenodigdheden - Horlogezaak + Tabakswinkel + Technische groothandel + Horlogewinkel Groothandel Sport American football @@ -1214,18 +1231,18 @@ Honkbal Basketbal Beachvolleybal - Bowling + Bowls Schaken Cricket Curling - Paardesport + Paardensport Golf Gymnastiek Handbal Diverse sporten Duiken - schieten + Schietsport Skateboarden Skiën Voetbal @@ -1236,57 +1253,57 @@ Bowlen Bowlen Padel - Futsal + Zaalvoetbal IJshockey Veldhockey Badminton - Baskische peloton + Pelota Tourisme Aquarium Berghut - Appartement + Vakantieappartement Kunstwerk - Kunstwerk + Architectonisch kunstwerk Schilderij - Kunstwerk + Sculptuur Standbeeld - Toeristische attractie - Prettocht + Bezienswaardigheid + Attractie Dierenverblijf Botsauto\'s - Reuzenrad + Draaimolen Historische attractie Doolhof Achtbaan Waterglijbaan Attractie - Kampeerterrein - Caravan site + Camping + Caravan- en camperplaats Vakantiehuis - Galerij - Gasthuis + Galerie + Pension Jeugdherberg Hotel Toeristische informatie Informatiebord Wegwijzer Toeristische kaart - Toeristische informatie + Toeristeninformatiepunt Bezoekerscentrum Museum - Picnicplaats + Picknickplaats Complex Attractiepark Uitkijkpunt - Wildernishut + Zelfverzorgingshut Dierentuin Kinderboerderij - Snelheidsmatigende maatregel + Snelheidsremmende maatregel Verkeersdrempel - Verkeersheuvel + Verkeersdrempel Waterweg Kanaal Kanaal @@ -1309,37 +1326,37 @@ Waterval Stuw Rolstoel - Gedeeltelijk uitgerust voor gehandicapten - Niet uitgerust voor gehandicapten - Uitgerust voor gehandicapten + Gedeeltelijk rolstoeltoegankelijk + Niet rolstoeltoegankelijk + Volledig rolstoeltoegankelijk Sleeplift (J) - Loopband - Sleeplift + Lopende band (skilift) + Pannenkoeklift Touwlift Sleeplift (T-beugel) - Ski-afdaling - Ski-afdaling - Moeilijke afdaling - Moeilijke afdaling - Makkelijke afdaling - Makkelijke afdaling - Afdaling voor experts - Afdaling voor experts - Ski-afdaling - Gemiddelde afdaling - Gemiddelde afdaling - Beginnersafdaling - Beginnersafdaling + Skipiste + Skipiste + Moeilijke skipiste + Moeilijke skipiste + Makkelijke skipiste + Makkelijke skipiste + Zeer moeilijke skipiste + Zeer moeilijke skipiste + Freeride ski-afdaling + Gemiddelde skipiste + Gemiddelde skipiste + Beginnersskipiste + Beginnersskipiste Langlaufroute Sleebaan Sleebaan Sneeuwpark Sneeuwwandelpad Pisteverbinding - Skitourspoor + Toerskiroute Evenementenlocatie Veiling - Verzamelobjecten + Verzamelaarswinkel Zelfbediening beschikbaar Alleen zelfbediening Gedeeltelijke zelfbediening @@ -1354,7 +1371,7 @@ Sporthal Bubbelthee - Stroommast + Elektriciteitsmast Paal Fish and Chips Reservoir @@ -1362,7 +1379,7 @@ Reuzenrad Afschotbeschutting Braakliggend terrein - Greenfield + Bouwgrond Generator Hackerspace Foodcourt @@ -1387,7 +1404,7 @@ Overdekte fietsenstalling Post- en pakketpunt Escaperoom - Boswachterkantoor + Boswachterskantoor Dierenasiel Klinket Beveiligingskantoor @@ -1403,7 +1420,7 @@ Observatorium Carpool Reddingsboei - Muziekpodium + Muziekkoepel Sportcentrum Sportcentrum Sportcentrum @@ -1437,14 +1454,14 @@ Sportcentrum Zwemcentrum Telecommunicatiewinkel - Leegstand bedrijfspand + Leegstaand bedrijfspand Gaarkeuken Voedselbank Verzamelpunt Yogastudio Ligbed Betaalkantoor - Speelhal + Indoor speeltuin Zeilschool Vliegschool Bijscholing @@ -1483,7 +1500,7 @@ Huis entree Garage entree Dienstingang - Ingang (enig) + Ingang (alleen ingang) Nooduitgang Kerstboom Schoonheidssalon @@ -1493,13 +1510,13 @@ Geïsoleerde gevaarsboei Sferische geïsoleerde gevaarsboei Nagelsalon - Voedsel delen - Giftenkist + Voedseldeelpunt + Weggeefkast Watertappunt|waterkraan - Miniatuur spoorweg - Miniatuur spoorbrug - Miniatuur spoortunnel - Spoorweg-keerpunt + Miniatuurspoorweg + Miniatuurspoorbrug + Miniatuurspoortunnel + Spoordraaischijf Tactiele kaart Ongebruikte spoorbrug Ongebruikte spoortunnel @@ -1514,8 +1531,8 @@ Four Square Sportcentrum Sportcentrum - jeu de boules + Jeu de boules Sportcentrum Pickleball - Niet biologisch + Geen biologische opties diff --git a/android/sdk/src/main/res/values-pl/types_strings.xml b/android/sdk/src/main/res/values-pl/types_strings.xml index 18294752c..9a0806436 100644 --- a/android/sdk/src/main/res/values-pl/types_strings.xml +++ b/android/sdk/src/main/res/values-pl/types_strings.xml @@ -380,7 +380,24 @@ Tunel Przystanek autobusowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy + Droga w trakcie budowy Droga rowerowa Most drogowy dla rowerów diff --git a/android/sdk/src/main/res/values-pt-rBR/types_strings.xml b/android/sdk/src/main/res/values-pt-rBR/types_strings.xml index 8bcfffa2a..b28cadd25 100644 --- a/android/sdk/src/main/res/values-pt-rBR/types_strings.xml +++ b/android/sdk/src/main/res/values-pt-rBR/types_strings.xml @@ -333,7 +333,24 @@ Túnel Ponto de ônibus + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção + Via em construção Ciclovia Ponte para ciclistas diff --git a/android/sdk/src/main/res/values-pt/types_strings.xml b/android/sdk/src/main/res/values-pt/types_strings.xml index 95d457e82..dd7b42fd1 100644 --- a/android/sdk/src/main/res/values-pt/types_strings.xml +++ b/android/sdk/src/main/res/values-pt/types_strings.xml @@ -368,7 +368,24 @@ Túnel Paragem de autocarros + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção + Estrada em construção Ciclovia Ponte diff --git a/android/sdk/src/main/res/values-ro/types_strings.xml b/android/sdk/src/main/res/values-ro/types_strings.xml index 8cce5160e..a566ed311 100644 --- a/android/sdk/src/main/res/values-ro/types_strings.xml +++ b/android/sdk/src/main/res/values-ro/types_strings.xml @@ -322,7 +322,24 @@ Tunel Stație de autobuz + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție + Drum în construcție Pod diff --git a/android/sdk/src/main/res/values-ru/types_strings.xml b/android/sdk/src/main/res/values-ru/types_strings.xml index a498d2f31..b9f104442 100644 --- a/android/sdk/src/main/res/values-ru/types_strings.xml +++ b/android/sdk/src/main/res/values-ru/types_strings.xml @@ -386,7 +386,24 @@ Тоннель Остановка + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога + Строящаяся дорога Велодорожка Мост diff --git a/android/sdk/src/main/res/values-sk/types_strings.xml b/android/sdk/src/main/res/values-sk/types_strings.xml index c20f3af01..3fe46e90f 100644 --- a/android/sdk/src/main/res/values-sk/types_strings.xml +++ b/android/sdk/src/main/res/values-sk/types_strings.xml @@ -374,7 +374,24 @@ Tunel Autobusová zastávka + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe + Cesta vo výstavbe Cyklocesta Most diff --git a/android/sdk/src/main/res/values-sl/types_strings.xml b/android/sdk/src/main/res/values-sl/types_strings.xml index 1ccc079f5..6fda6fedb 100644 --- a/android/sdk/src/main/res/values-sl/types_strings.xml +++ b/android/sdk/src/main/res/values-sl/types_strings.xml @@ -498,7 +498,24 @@ Most Predor Avtobusno postajališče + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji + Cesta v gradnji Kolesarska steza Most Kolesarska steza diff --git a/android/sdk/src/main/res/values-sr/types_strings.xml b/android/sdk/src/main/res/values-sr/types_strings.xml index 85f564cfb..48577eb07 100644 --- a/android/sdk/src/main/res/values-sr/types_strings.xml +++ b/android/sdk/src/main/res/values-sr/types_strings.xml @@ -383,7 +383,24 @@ Тунел Аутобуско стајалиште + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи + Пут у изградњи Бициклистичка стаза Мост diff --git a/android/sdk/src/main/res/values-sv/types_strings.xml b/android/sdk/src/main/res/values-sv/types_strings.xml index 3e1aa59d5..7ba2636d8 100644 --- a/android/sdk/src/main/res/values-sv/types_strings.xml +++ b/android/sdk/src/main/res/values-sv/types_strings.xml @@ -325,7 +325,24 @@ Tunnel Busshållplats + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande + Väg under uppförande Cykelbro diff --git a/android/sdk/src/main/res/values-sw/types_strings.xml b/android/sdk/src/main/res/values-sw/types_strings.xml index d8e1e523d..95b44a2ce 100644 --- a/android/sdk/src/main/res/values-sw/types_strings.xml +++ b/android/sdk/src/main/res/values-sw/types_strings.xml @@ -131,7 +131,24 @@ Daraja Mtaro + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa + Barabara inatengenezwa Daraja diff --git a/android/sdk/src/main/res/values-ta/types_strings.xml b/android/sdk/src/main/res/values-ta/types_strings.xml index 05dcd1825..9bb5e3f0a 100644 --- a/android/sdk/src/main/res/values-ta/types_strings.xml +++ b/android/sdk/src/main/res/values-ta/types_strings.xml @@ -386,7 +386,24 @@ பாலம் சுரங்கப்பாதை பேருந்து நிறுத்தம் + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது + சாலை கட்டுமானத்தில் உள்ளது சைக்கிள் பாதை பாலம் சைக்கிள் பாதை diff --git a/android/sdk/src/main/res/values-th/types_strings.xml b/android/sdk/src/main/res/values-th/types_strings.xml index 202ca4b7a..5291511e8 100644 --- a/android/sdk/src/main/res/values-th/types_strings.xml +++ b/android/sdk/src/main/res/values-th/types_strings.xml @@ -331,7 +331,24 @@ อุโมงค์ ป้ายรถเมล์ + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง + ทางกำลังอยู่ในการก่อสร้าง สะพาน diff --git a/android/sdk/src/main/res/values-tr/types_strings.xml b/android/sdk/src/main/res/values-tr/types_strings.xml index 3fb63a9d9..d0f9d0c1b 100644 --- a/android/sdk/src/main/res/values-tr/types_strings.xml +++ b/android/sdk/src/main/res/values-tr/types_strings.xml @@ -376,7 +376,24 @@ Tünel Otobüs Durağı + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol + Yapım Aşamasında Yol Bisiklet Yolu Köprü diff --git a/android/sdk/src/main/res/values-uk/types_strings.xml b/android/sdk/src/main/res/values-uk/types_strings.xml index b2297ed76..ad54caf50 100644 --- a/android/sdk/src/main/res/values-uk/types_strings.xml +++ b/android/sdk/src/main/res/values-uk/types_strings.xml @@ -381,7 +381,24 @@ Тунель Зупинка + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується + Дорога, що будується Велодоріжка Міст diff --git a/android/sdk/src/main/res/values-vi/types_strings.xml b/android/sdk/src/main/res/values-vi/types_strings.xml index 128f8f60f..d35888d66 100644 --- a/android/sdk/src/main/res/values-vi/types_strings.xml +++ b/android/sdk/src/main/res/values-vi/types_strings.xml @@ -329,7 +329,24 @@ Đường hầm Bến xe buýt + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công + Đường đang thi công Cầu diff --git a/android/sdk/src/main/res/values-zh-rHK/types_strings.xml b/android/sdk/src/main/res/values-zh-rHK/types_strings.xml index ece4baf03..2f0dc49e2 100644 --- a/android/sdk/src/main/res/values-zh-rHK/types_strings.xml +++ b/android/sdk/src/main/res/values-zh-rHK/types_strings.xml @@ -379,7 +379,24 @@ 隧道 巴士站 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 自行車道 diff --git a/android/sdk/src/main/res/values-zh-rTW/types_strings.xml b/android/sdk/src/main/res/values-zh-rTW/types_strings.xml index 73032d099..1d996d812 100644 --- a/android/sdk/src/main/res/values-zh-rTW/types_strings.xml +++ b/android/sdk/src/main/res/values-zh-rTW/types_strings.xml @@ -626,7 +626,24 @@ 橋樑 隧道 公車站 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 + 施工中道路 單車道 橋樑 單車道 diff --git a/android/sdk/src/main/res/values-zh/types_strings.xml b/android/sdk/src/main/res/values-zh/types_strings.xml index da409ba11..b5afc1da0 100644 --- a/android/sdk/src/main/res/values-zh/types_strings.xml +++ b/android/sdk/src/main/res/values-zh/types_strings.xml @@ -380,7 +380,24 @@ 隧道 公交站 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 + 在建道路 自行车道 diff --git a/android/sdk/src/main/res/values/types_strings.xml b/android/sdk/src/main/res/values/types_strings.xml index feb7aedc5..5dfd855c3 100644 --- a/android/sdk/src/main/res/values/types_strings.xml +++ b/android/sdk/src/main/res/values/types_strings.xml @@ -456,7 +456,24 @@ Tunnel Bus Stop + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction + Road Under Construction Cycle Path Bridge From 5400bec4bbbfaad0cc819afaf82d1ccb94880ad9 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 6 May 2026 20:16:54 +0300 Subject: [PATCH 241/252] [iphone] UI strings for new construction types Signed-off-by: mvglasow --- .../af.lproj/LocalizableTypes.strings | 17 + .../ar.lproj/LocalizableTypes.strings | 17 + .../ast.lproj/LocalizableTypes.strings | 17 + .../az.lproj/LocalizableTypes.strings | 17 + .../be.lproj/LocalizableTypes.strings | 17 + .../ber.lproj/LocalizableTypes.strings | 17 + .../bg.lproj/LocalizableTypes.strings | 17 + .../bn.lproj/LocalizableTypes.strings | 17 + .../brh.lproj/LocalizableTypes.strings | 17 + .../ca.lproj/LocalizableTypes.strings | 17 + .../cs.lproj/LocalizableTypes.strings | 17 + .../cy.lproj/LocalizableTypes.strings | 17 + .../da.lproj/LocalizableTypes.strings | 17 + .../de.lproj/LocalizableTypes.strings | 17 + .../el.lproj/LocalizableTypes.strings | 17 + .../en-CA.lproj/LocalizableTypes.strings | 17 + .../en-GB.lproj/LocalizableTypes.strings | 17 + .../en.lproj/LocalizableTypes.strings | 17 + .../en_CA.lproj/LocalizableTypes.strings | 17 + .../eo.lproj/LocalizableTypes.strings | 17 + .../es-419.lproj/LocalizableTypes.strings | 17 + .../es-MX.lproj/LocalizableTypes.strings | 17 + .../es.lproj/LocalizableTypes.strings | 17 + .../es_419.lproj/LocalizableTypes.strings | 17 + .../et.lproj/LocalizableTypes.strings | 17 + .../eu.lproj/LocalizableTypes.strings | 17 + .../fa.lproj/LocalizableTypes.strings | 17 + .../fi.lproj/LocalizableTypes.strings | 17 + .../fr.lproj/LocalizableTypes.strings | 17 + .../ga.lproj/LocalizableTypes.strings | 17 + .../gl.lproj/LocalizableTypes.strings | 17 + .../gsw.lproj/LocalizableTypes.strings | 17 + .../he.lproj/LocalizableTypes.strings | 17 + .../hi.lproj/LocalizableTypes.strings | 17 + .../hr.lproj/LocalizableTypes.strings | 17 + .../hu.lproj/LocalizableTypes.strings | 17 + .../ia.lproj/LocalizableTypes.strings | 17 + .../id.lproj/LocalizableTypes.strings | 17 + .../is.lproj/LocalizableTypes.strings | 17 + .../it.lproj/LocalizableTypes.strings | 17 + .../ja.lproj/LocalizableTypes.strings | 17 + .../kab.lproj/LocalizableTypes.strings | 17 + .../kk.lproj/LocalizableTypes.strings | 17 + .../kmr.lproj/LocalizableTypes.strings | 17 + .../kn.lproj/LocalizableTypes.strings | 17 + .../ko.lproj/LocalizableTypes.strings | 17 + .../lt.lproj/LocalizableTypes.strings | 17 + .../lv.lproj/LocalizableTypes.strings | 17 + .../ml.lproj/LocalizableTypes.strings | 17 + .../mr.lproj/LocalizableTypes.strings | 17 + .../mt.lproj/LocalizableTypes.strings | 17 + .../nb.lproj/LocalizableTypes.strings | 17 + .../nl.lproj/LocalizableTypes.strings | 583 +++++++++--------- .../pl.lproj/LocalizableTypes.strings | 17 + .../pt-BR.lproj/LocalizableTypes.strings | 17 + .../pt-PT.lproj/LocalizableTypes.strings | 17 + .../pt.lproj/LocalizableTypes.strings | 17 + .../pt_PT.lproj/LocalizableTypes.strings | 17 + .../ro.lproj/LocalizableTypes.strings | 17 + .../ru.lproj/LocalizableTypes.strings | 17 + .../si.lproj/LocalizableTypes.strings | 17 + .../sk.lproj/LocalizableTypes.strings | 17 + .../sl.lproj/LocalizableTypes.strings | 17 + .../sq.lproj/LocalizableTypes.strings | 17 + .../sr.lproj/LocalizableTypes.strings | 17 + .../sv.lproj/LocalizableTypes.strings | 17 + .../sw.lproj/LocalizableTypes.strings | 17 + .../ta.lproj/LocalizableTypes.strings | 17 + .../th.lproj/LocalizableTypes.strings | 17 + .../tr.lproj/LocalizableTypes.strings | 17 + .../uk.lproj/LocalizableTypes.strings | 17 + .../ur.lproj/LocalizableTypes.strings | 17 + .../vi.lproj/LocalizableTypes.strings | 17 + .../zh-Hans.lproj/LocalizableTypes.strings | 17 + .../zh-Hant.lproj/LocalizableTypes.strings | 17 + 75 files changed, 1558 insertions(+), 283 deletions(-) diff --git a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings index cd7a56a43..59d81a53b 100644 --- a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tonnel"; "type.highway.bus_stop" = "Bushalte"; "type.highway.construction" = "Pad in aanbou"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Pad in aanbou"; +"type.highway.construction.motorway_link" = "Pad in aanbou"; +"type.highway.construction.trunk" = "Pad in aanbou"; +"type.highway.construction.trunk_link" = "Pad in aanbou"; +"type.highway.construction.primary" = "Pad in aanbou"; +"type.highway.construction.primary_link" = "Pad in aanbou"; +"type.highway.construction.secondary" = "Pad in aanbou"; +"type.highway.construction.secondary_link" = "Pad in aanbou"; +"type.highway.construction.tertiary" = "Pad in aanbou"; +"type.highway.construction.tertiary_link" = "Pad in aanbou"; +"type.highway.construction.residential" = "Pad in aanbou"; +"type.highway.construction.unclassified" = "Pad in aanbou"; +"type.highway.construction.service" = "Pad in aanbou"; +"type.highway.construction.living_street" = "Pad in aanbou"; +"type.highway.construction.road" = "Pad in aanbou"; +"type.highway.construction.track" = "Pad in aanbou"; "type.highway.cycleway" = "Fietspad"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brug"; diff --git a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings index 33224871a..23f7180bf 100644 --- a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "نفق"; "type.highway.bus_stop" = "موقف حافلات"; "type.highway.construction" = "طريق تحت الإنشاء"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "طريق تحت الإنشاء"; +"type.highway.construction.motorway_link" = "طريق تحت الإنشاء"; +"type.highway.construction.trunk" = "طريق تحت الإنشاء"; +"type.highway.construction.trunk_link" = "طريق تحت الإنشاء"; +"type.highway.construction.primary" = "طريق تحت الإنشاء"; +"type.highway.construction.primary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.secondary" = "طريق تحت الإنشاء"; +"type.highway.construction.secondary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.tertiary" = "طريق تحت الإنشاء"; +"type.highway.construction.tertiary_link" = "طريق تحت الإنشاء"; +"type.highway.construction.residential" = "طريق تحت الإنشاء"; +"type.highway.construction.unclassified" = "طريق تحت الإنشاء"; +"type.highway.construction.service" = "طريق تحت الإنشاء"; +"type.highway.construction.living_street" = "طريق تحت الإنشاء"; +"type.highway.construction.road" = "طريق تحت الإنشاء"; +"type.highway.construction.track" = "طريق تحت الإنشاء"; "type.highway.cycleway" = "مسار للدراجات"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "جسر"; diff --git a/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings index 749265c8b..88de29402 100644 --- a/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada d’autobús"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings index 7045faf53..9c91775bf 100644 --- a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Avtobus dayanacağı"; "type.highway.construction" = "Tikili Yol"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Tikili Yol"; +"type.highway.construction.motorway_link" = "Tikili Yol"; +"type.highway.construction.trunk" = "Tikili Yol"; +"type.highway.construction.trunk_link" = "Tikili Yol"; +"type.highway.construction.primary" = "Tikili Yol"; +"type.highway.construction.primary_link" = "Tikili Yol"; +"type.highway.construction.secondary" = "Tikili Yol"; +"type.highway.construction.secondary_link" = "Tikili Yol"; +"type.highway.construction.tertiary" = "Tikili Yol"; +"type.highway.construction.tertiary_link" = "Tikili Yol"; +"type.highway.construction.residential" = "Tikili Yol"; +"type.highway.construction.unclassified" = "Tikili Yol"; +"type.highway.construction.service" = "Tikili Yol"; +"type.highway.construction.living_street" = "Tikili Yol"; +"type.highway.construction.road" = "Tikili Yol"; +"type.highway.construction.track" = "Tikili Yol"; "type.highway.cycleway" = "Velosiped yolu"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Körpü"; diff --git a/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings index 3f4b132df..241096622 100644 --- a/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Тунэль"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings index 43a70662e..d2cdd393f 100644 --- a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Спирка"; "type.highway.construction" = "Път в процес на изграждане"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Път в процес на изграждане"; +"type.highway.construction.motorway_link" = "Път в процес на изграждане"; +"type.highway.construction.trunk" = "Път в процес на изграждане"; +"type.highway.construction.trunk_link" = "Път в процес на изграждане"; +"type.highway.construction.primary" = "Път в процес на изграждане"; +"type.highway.construction.primary_link" = "Път в процес на изграждане"; +"type.highway.construction.secondary" = "Път в процес на изграждане"; +"type.highway.construction.secondary_link" = "Път в процес на изграждане"; +"type.highway.construction.tertiary" = "Път в процес на изграждане"; +"type.highway.construction.tertiary_link" = "Път в процес на изграждане"; +"type.highway.construction.residential" = "Път в процес на изграждане"; +"type.highway.construction.unclassified" = "Път в процес на изграждане"; +"type.highway.construction.service" = "Път в процес на изграждане"; +"type.highway.construction.living_street" = "Път в процес на изграждане"; +"type.highway.construction.road" = "Път в процес на изграждане"; +"type.highway.construction.track" = "Път в процес на изграждане"; "type.highway.cycleway" = "Пътека за колела"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings index 9663f70a0..0893dfa09 100644 --- a/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "বাস স্টপ"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings index 8a187724e..7b58576dd 100644 --- a/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings index a552c8947..4dd48670a 100644 --- a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada d’autobús"; "type.highway.construction" = "Via en construcció"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Via en construcció"; +"type.highway.construction.motorway_link" = "Via en construcció"; +"type.highway.construction.trunk" = "Via en construcció"; +"type.highway.construction.trunk_link" = "Via en construcció"; +"type.highway.construction.primary" = "Via en construcció"; +"type.highway.construction.primary_link" = "Via en construcció"; +"type.highway.construction.secondary" = "Via en construcció"; +"type.highway.construction.secondary_link" = "Via en construcció"; +"type.highway.construction.tertiary" = "Via en construcció"; +"type.highway.construction.tertiary_link" = "Via en construcció"; +"type.highway.construction.residential" = "Via en construcció"; +"type.highway.construction.unclassified" = "Via en construcció"; +"type.highway.construction.service" = "Via en construcció"; +"type.highway.construction.living_street" = "Via en construcció"; +"type.highway.construction.road" = "Via en construcció"; +"type.highway.construction.track" = "Via en construcció"; "type.highway.cycleway" = "Carril bici"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; diff --git a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings index c6fb365e1..bec29d3fa 100644 --- a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Silnice v rekonstrukci"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Silnice v rekonstrukci"; +"type.highway.construction.motorway_link" = "Silnice v rekonstrukci"; +"type.highway.construction.trunk" = "Silnice v rekonstrukci"; +"type.highway.construction.trunk_link" = "Silnice v rekonstrukci"; +"type.highway.construction.primary" = "Silnice v rekonstrukci"; +"type.highway.construction.primary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.secondary" = "Silnice v rekonstrukci"; +"type.highway.construction.secondary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.tertiary" = "Silnice v rekonstrukci"; +"type.highway.construction.tertiary_link" = "Silnice v rekonstrukci"; +"type.highway.construction.residential" = "Silnice v rekonstrukci"; +"type.highway.construction.unclassified" = "Silnice v rekonstrukci"; +"type.highway.construction.service" = "Silnice v rekonstrukci"; +"type.highway.construction.living_street" = "Silnice v rekonstrukci"; +"type.highway.construction.road" = "Silnice v rekonstrukci"; +"type.highway.construction.track" = "Silnice v rekonstrukci"; "type.highway.cycleway" = "Cyklostezka"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings index fa1e66f9c..9f8aef6fe 100644 --- a/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Twnnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; diff --git a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings index 5ef17b5da..2b915c8d6 100644 --- a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstoppested"; "type.highway.construction" = "Vej under anlæggelse"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Vej under anlæggelse"; +"type.highway.construction.motorway_link" = "Vej under anlæggelse"; +"type.highway.construction.trunk" = "Vej under anlæggelse"; +"type.highway.construction.trunk_link" = "Vej under anlæggelse"; +"type.highway.construction.primary" = "Vej under anlæggelse"; +"type.highway.construction.primary_link" = "Vej under anlæggelse"; +"type.highway.construction.secondary" = "Vej under anlæggelse"; +"type.highway.construction.secondary_link" = "Vej under anlæggelse"; +"type.highway.construction.tertiary" = "Vej under anlæggelse"; +"type.highway.construction.tertiary_link" = "Vej under anlæggelse"; +"type.highway.construction.residential" = "Vej under anlæggelse"; +"type.highway.construction.unclassified" = "Vej under anlæggelse"; +"type.highway.construction.service" = "Vej under anlæggelse"; +"type.highway.construction.living_street" = "Vej under anlæggelse"; +"type.highway.construction.road" = "Vej under anlæggelse"; +"type.highway.construction.track" = "Vej under anlæggelse"; "type.highway.cycleway" = "Cykelsti"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bro"; diff --git a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings index 0be87478c..a21d20580 100644 --- a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltestelle"; "type.highway.construction" = "Straße im Bau"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Straße im Bau"; +"type.highway.construction.motorway_link" = "Straße im Bau"; +"type.highway.construction.trunk" = "Straße im Bau"; +"type.highway.construction.trunk_link" = "Straße im Bau"; +"type.highway.construction.primary" = "Straße im Bau"; +"type.highway.construction.primary_link" = "Straße im Bau"; +"type.highway.construction.secondary" = "Straße im Bau"; +"type.highway.construction.secondary_link" = "Straße im Bau"; +"type.highway.construction.tertiary" = "Straße im Bau"; +"type.highway.construction.tertiary_link" = "Straße im Bau"; +"type.highway.construction.residential" = "Straße im Bau"; +"type.highway.construction.unclassified" = "Straße im Bau"; +"type.highway.construction.service" = "Straße im Bau"; +"type.highway.construction.living_street" = "Straße im Bau"; +"type.highway.construction.road" = "Straße im Bau"; +"type.highway.construction.track" = "Straße im Bau"; "type.highway.cycleway" = "Radweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brücke"; diff --git a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings index cbd6fb769..9be9446ff 100644 --- a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Σήραγγα"; "type.highway.bus_stop" = "Στάση λεωφορείου"; "type.highway.construction" = "Οδός υπό κατασκευή"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Οδός υπό κατασκευή"; +"type.highway.construction.motorway_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.trunk" = "Οδός υπό κατασκευή"; +"type.highway.construction.trunk_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.primary" = "Οδός υπό κατασκευή"; +"type.highway.construction.primary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.secondary" = "Οδός υπό κατασκευή"; +"type.highway.construction.secondary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.tertiary" = "Οδός υπό κατασκευή"; +"type.highway.construction.tertiary_link" = "Οδός υπό κατασκευή"; +"type.highway.construction.residential" = "Οδός υπό κατασκευή"; +"type.highway.construction.unclassified" = "Οδός υπό κατασκευή"; +"type.highway.construction.service" = "Οδός υπό κατασκευή"; +"type.highway.construction.living_street" = "Οδός υπό κατασκευή"; +"type.highway.construction.road" = "Οδός υπό κατασκευή"; +"type.highway.construction.track" = "Οδός υπό κατασκευή"; "type.highway.cycleway" = "Ποδηλατόδρομος"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Γέφυρα"; diff --git a/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings index d2922bacf..6cd5196eb 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings index 7560d8c4a..29848ce51 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings index 3a898d591..7f20555bb 100644 --- a/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings index 39a884153..c9028e4b0 100644 --- a/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings index 952a54026..baa447bee 100644 --- a/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings index cd4a82b12..f0c7d6ed9 100644 --- a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construcción"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Vía en construcción"; +"type.highway.construction.motorway_link" = "Vía en construcción"; +"type.highway.construction.trunk" = "Vía en construcción"; +"type.highway.construction.trunk_link" = "Vía en construcción"; +"type.highway.construction.primary" = "Vía en construcción"; +"type.highway.construction.primary_link" = "Vía en construcción"; +"type.highway.construction.secondary" = "Vía en construcción"; +"type.highway.construction.secondary_link" = "Vía en construcción"; +"type.highway.construction.tertiary" = "Vía en construcción"; +"type.highway.construction.tertiary_link" = "Vía en construcción"; +"type.highway.construction.residential" = "Vía en construcción"; +"type.highway.construction.unclassified" = "Vía en construcción"; +"type.highway.construction.service" = "Vía en construcción"; +"type.highway.construction.living_street" = "Vía en construcción"; +"type.highway.construction.road" = "Vía en construcción"; +"type.highway.construction.track" = "Vía en construcción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings index d1b2f4e87..cea33b598 100644 --- a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de bus"; "type.highway.construction" = "Vía en construcción"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Vía en construcción"; +"type.highway.construction.motorway_link" = "Vía en construcción"; +"type.highway.construction.trunk" = "Vía en construcción"; +"type.highway.construction.trunk_link" = "Vía en construcción"; +"type.highway.construction.primary" = "Vía en construcción"; +"type.highway.construction.primary_link" = "Vía en construcción"; +"type.highway.construction.secondary" = "Vía en construcción"; +"type.highway.construction.secondary_link" = "Vía en construcción"; +"type.highway.construction.tertiary" = "Vía en construcción"; +"type.highway.construction.tertiary_link" = "Vía en construcción"; +"type.highway.construction.residential" = "Vía en construcción"; +"type.highway.construction.unclassified" = "Vía en construcción"; +"type.highway.construction.service" = "Vía en construcción"; +"type.highway.construction.living_street" = "Vía en construcción"; +"type.highway.construction.road" = "Vía en construcción"; +"type.highway.construction.track" = "Vía en construcción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings index 3a898d591..7f20555bb 100644 --- a/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings index 5351c7076..2b96a0ffa 100644 --- a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bussipeatus"; "type.highway.construction" = "Ehitusjärgus tee"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Ehitusjärgus tee"; +"type.highway.construction.motorway_link" = "Ehitusjärgus tee"; +"type.highway.construction.trunk" = "Ehitusjärgus tee"; +"type.highway.construction.trunk_link" = "Ehitusjärgus tee"; +"type.highway.construction.primary" = "Ehitusjärgus tee"; +"type.highway.construction.primary_link" = "Ehitusjärgus tee"; +"type.highway.construction.secondary" = "Ehitusjärgus tee"; +"type.highway.construction.secondary_link" = "Ehitusjärgus tee"; +"type.highway.construction.tertiary" = "Ehitusjärgus tee"; +"type.highway.construction.tertiary_link" = "Ehitusjärgus tee"; +"type.highway.construction.residential" = "Ehitusjärgus tee"; +"type.highway.construction.unclassified" = "Ehitusjärgus tee"; +"type.highway.construction.service" = "Ehitusjärgus tee"; +"type.highway.construction.living_street" = "Ehitusjärgus tee"; +"type.highway.construction.road" = "Ehitusjärgus tee"; +"type.highway.construction.track" = "Ehitusjärgus tee"; "type.highway.cycleway" = "Jalgrattatee"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Jalgrattasild"; diff --git a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings index 48cceea08..2f80be8a7 100644 --- a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunela"; "type.highway.bus_stop" = "Autobus geltokia"; "type.highway.construction" = "Eraikitzen ari diren errepidea"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.motorway_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.trunk" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.trunk_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.primary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.primary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.secondary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.secondary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.tertiary" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.tertiary_link" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.residential" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.unclassified" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.service" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.living_street" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.road" = "Eraikitzen ari diren errepidea"; +"type.highway.construction.track" = "Eraikitzen ari diren errepidea"; "type.highway.cycleway" = "Bidegorri edo bizikleta-bidea"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Zubia"; diff --git a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings index b4874226f..17ead7f2d 100644 --- a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "تونل"; "type.highway.bus_stop" = "حمل و نقل"; "type.highway.construction" = "جاده در دست ساخت است"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "جاده در دست ساخت است"; +"type.highway.construction.motorway_link" = "جاده در دست ساخت است"; +"type.highway.construction.trunk" = "جاده در دست ساخت است"; +"type.highway.construction.trunk_link" = "جاده در دست ساخت است"; +"type.highway.construction.primary" = "جاده در دست ساخت است"; +"type.highway.construction.primary_link" = "جاده در دست ساخت است"; +"type.highway.construction.secondary" = "جاده در دست ساخت است"; +"type.highway.construction.secondary_link" = "جاده در دست ساخت است"; +"type.highway.construction.tertiary" = "جاده در دست ساخت است"; +"type.highway.construction.tertiary_link" = "جاده در دست ساخت است"; +"type.highway.construction.residential" = "جاده در دست ساخت است"; +"type.highway.construction.unclassified" = "جاده در دست ساخت است"; +"type.highway.construction.service" = "جاده در دست ساخت است"; +"type.highway.construction.living_street" = "جاده در دست ساخت است"; +"type.highway.construction.road" = "جاده در دست ساخت است"; +"type.highway.construction.track" = "جاده در دست ساخت است"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "پل"; diff --git a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings index b2a48f0c8..80b77d573 100644 --- a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunneli"; "type.highway.bus_stop" = "Bussipysäkki"; "type.highway.construction" = "Rakenteilla oleva tie"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Rakenteilla oleva tie"; +"type.highway.construction.motorway_link" = "Rakenteilla oleva tie"; +"type.highway.construction.trunk" = "Rakenteilla oleva tie"; +"type.highway.construction.trunk_link" = "Rakenteilla oleva tie"; +"type.highway.construction.primary" = "Rakenteilla oleva tie"; +"type.highway.construction.primary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.secondary" = "Rakenteilla oleva tie"; +"type.highway.construction.secondary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.tertiary" = "Rakenteilla oleva tie"; +"type.highway.construction.tertiary_link" = "Rakenteilla oleva tie"; +"type.highway.construction.residential" = "Rakenteilla oleva tie"; +"type.highway.construction.unclassified" = "Rakenteilla oleva tie"; +"type.highway.construction.service" = "Rakenteilla oleva tie"; +"type.highway.construction.living_street" = "Rakenteilla oleva tie"; +"type.highway.construction.road" = "Rakenteilla oleva tie"; +"type.highway.construction.track" = "Rakenteilla oleva tie"; "type.highway.cycleway" = "Pyörätie"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Silta"; diff --git a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings index a59cef009..200ffd5d0 100644 --- a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Arrêt de bus"; "type.highway.construction" = "Route en construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Route en construction"; +"type.highway.construction.motorway_link" = "Route en construction"; +"type.highway.construction.trunk" = "Route en construction"; +"type.highway.construction.trunk_link" = "Route en construction"; +"type.highway.construction.primary" = "Route en construction"; +"type.highway.construction.primary_link" = "Route en construction"; +"type.highway.construction.secondary" = "Route en construction"; +"type.highway.construction.secondary_link" = "Route en construction"; +"type.highway.construction.tertiary" = "Route en construction"; +"type.highway.construction.tertiary_link" = "Route en construction"; +"type.highway.construction.residential" = "Route en construction"; +"type.highway.construction.unclassified" = "Route en construction"; +"type.highway.construction.service" = "Route en construction"; +"type.highway.construction.living_street" = "Route en construction"; +"type.highway.construction.road" = "Route en construction"; +"type.highway.construction.track" = "Route en construction"; "type.highway.cycleway" = "Piste cyclable"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; diff --git a/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings index 01a65cd56..0fb97491e 100644 --- a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construción"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Vía en construción"; +"type.highway.construction.motorway_link" = "Vía en construción"; +"type.highway.construction.trunk" = "Vía en construción"; +"type.highway.construction.trunk_link" = "Vía en construción"; +"type.highway.construction.primary" = "Vía en construción"; +"type.highway.construction.primary_link" = "Vía en construción"; +"type.highway.construction.secondary" = "Vía en construción"; +"type.highway.construction.secondary_link" = "Vía en construción"; +"type.highway.construction.tertiary" = "Vía en construción"; +"type.highway.construction.tertiary_link" = "Vía en construción"; +"type.highway.construction.residential" = "Vía en construción"; +"type.highway.construction.unclassified" = "Vía en construción"; +"type.highway.construction.service" = "Vía en construción"; +"type.highway.construction.living_street" = "Vía en construción"; +"type.highway.construction.road" = "Vía en construción"; +"type.highway.construction.track" = "Vía en construción"; "type.highway.cycleway" = "Ciclovía"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Puente"; diff --git a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings index 0d7a0159f..63bdb328e 100644 --- a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltistell"; "type.highway.construction" = "Strass im Bau"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Strass im Bau"; +"type.highway.construction.motorway_link" = "Strass im Bau"; +"type.highway.construction.trunk" = "Strass im Bau"; +"type.highway.construction.trunk_link" = "Strass im Bau"; +"type.highway.construction.primary" = "Strass im Bau"; +"type.highway.construction.primary_link" = "Strass im Bau"; +"type.highway.construction.secondary" = "Strass im Bau"; +"type.highway.construction.secondary_link" = "Strass im Bau"; +"type.highway.construction.tertiary" = "Strass im Bau"; +"type.highway.construction.tertiary_link" = "Strass im Bau"; +"type.highway.construction.residential" = "Strass im Bau"; +"type.highway.construction.unclassified" = "Strass im Bau"; +"type.highway.construction.service" = "Strass im Bau"; +"type.highway.construction.living_street" = "Strass im Bau"; +"type.highway.construction.road" = "Strass im Bau"; +"type.highway.construction.track" = "Strass im Bau"; "type.highway.cycleway" = "Veloweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brugg"; diff --git a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings index eaae92700..fcc624317 100644 --- a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "מִנהָרָה"; "type.highway.bus_stop" = "תחנת אוטובוס"; "type.highway.construction" = "כביש בבניה"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "כביש בבניה"; +"type.highway.construction.motorway_link" = "כביש בבניה"; +"type.highway.construction.trunk" = "כביש בבניה"; +"type.highway.construction.trunk_link" = "כביש בבניה"; +"type.highway.construction.primary" = "כביש בבניה"; +"type.highway.construction.primary_link" = "כביש בבניה"; +"type.highway.construction.secondary" = "כביש בבניה"; +"type.highway.construction.secondary_link" = "כביש בבניה"; +"type.highway.construction.tertiary" = "כביש בבניה"; +"type.highway.construction.tertiary_link" = "כביש בבניה"; +"type.highway.construction.residential" = "כביש בבניה"; +"type.highway.construction.unclassified" = "כביש בבניה"; +"type.highway.construction.service" = "כביש בבניה"; +"type.highway.construction.living_street" = "כביש בבניה"; +"type.highway.construction.road" = "כביש בבניה"; +"type.highway.construction.track" = "כביש בבניה"; "type.highway.cycleway" = "שביל אופניים"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "גשר לאופניים"; diff --git a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings index d00b5f6bf..77a2b6ec3 100644 --- a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "बस स्टॉप"; "type.highway.construction" = "निर्माणाधीन सड़क"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "निर्माणाधीन सड़क"; +"type.highway.construction.motorway_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.trunk" = "निर्माणाधीन सड़क"; +"type.highway.construction.trunk_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.primary" = "निर्माणाधीन सड़क"; +"type.highway.construction.primary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.secondary" = "निर्माणाधीन सड़क"; +"type.highway.construction.secondary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.tertiary" = "निर्माणाधीन सड़क"; +"type.highway.construction.tertiary_link" = "निर्माणाधीन सड़क"; +"type.highway.construction.residential" = "निर्माणाधीन सड़क"; +"type.highway.construction.unclassified" = "निर्माणाधीन सड़क"; +"type.highway.construction.service" = "निर्माणाधीन सड़क"; +"type.highway.construction.living_street" = "निर्माणाधीन सड़क"; +"type.highway.construction.road" = "निर्माणाधीन सड़क"; +"type.highway.construction.track" = "निर्माणाधीन सड़क"; "type.highway.cycleway" = "साइकिल मार्ग"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings index 4a73352a4..17c86c348 100644 --- a/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings index 7312d8abe..7d515066b 100644 --- a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Alagút"; "type.highway.bus_stop" = "Buszmegálló"; "type.highway.construction" = "Útépítés"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Útépítés"; +"type.highway.construction.motorway_link" = "Útépítés"; +"type.highway.construction.trunk" = "Útépítés"; +"type.highway.construction.trunk_link" = "Útépítés"; +"type.highway.construction.primary" = "Útépítés"; +"type.highway.construction.primary_link" = "Útépítés"; +"type.highway.construction.secondary" = "Útépítés"; +"type.highway.construction.secondary_link" = "Útépítés"; +"type.highway.construction.tertiary" = "Útépítés"; +"type.highway.construction.tertiary_link" = "Útépítés"; +"type.highway.construction.residential" = "Útépítés"; +"type.highway.construction.unclassified" = "Útépítés"; +"type.highway.construction.service" = "Útépítés"; +"type.highway.construction.living_street" = "Útépítés"; +"type.highway.construction.road" = "Útépítés"; +"type.highway.construction.track" = "Útépítés"; "type.highway.cycleway" = "Kerékpárút"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Híd"; diff --git a/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings index 8eed20dc8..659d0b74c 100644 --- a/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings index 65e0abc17..62dcf3b17 100644 --- a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings @@ -430,6 +430,23 @@ "type.highway.busway.tunnel" = "Terowongan"; "type.highway.bus_stop" = "Halte bus"; "type.highway.construction" = "Jalan sedang dibangun"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Jalan sedang dibangun"; +"type.highway.construction.motorway_link" = "Jalan sedang dibangun"; +"type.highway.construction.trunk" = "Jalan sedang dibangun"; +"type.highway.construction.trunk_link" = "Jalan sedang dibangun"; +"type.highway.construction.primary" = "Jalan sedang dibangun"; +"type.highway.construction.primary_link" = "Jalan sedang dibangun"; +"type.highway.construction.secondary" = "Jalan sedang dibangun"; +"type.highway.construction.secondary_link" = "Jalan sedang dibangun"; +"type.highway.construction.tertiary" = "Jalan sedang dibangun"; +"type.highway.construction.tertiary_link" = "Jalan sedang dibangun"; +"type.highway.construction.residential" = "Jalan sedang dibangun"; +"type.highway.construction.unclassified" = "Jalan sedang dibangun"; +"type.highway.construction.service" = "Jalan sedang dibangun"; +"type.highway.construction.living_street" = "Jalan sedang dibangun"; +"type.highway.construction.road" = "Jalan sedang dibangun"; +"type.highway.construction.track" = "Jalan sedang dibangun"; "type.highway.cycleway" = "Jalur Sepeda"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Menjembatani"; diff --git a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings index 6df51d2f9..74642db8d 100644 --- a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Göng"; "type.highway.bus_stop" = "Strætisvagnabiðstöð"; "type.highway.construction" = "Vegur í byggingu"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Vegur í byggingu"; +"type.highway.construction.motorway_link" = "Vegur í byggingu"; +"type.highway.construction.trunk" = "Vegur í byggingu"; +"type.highway.construction.trunk_link" = "Vegur í byggingu"; +"type.highway.construction.primary" = "Vegur í byggingu"; +"type.highway.construction.primary_link" = "Vegur í byggingu"; +"type.highway.construction.secondary" = "Vegur í byggingu"; +"type.highway.construction.secondary_link" = "Vegur í byggingu"; +"type.highway.construction.tertiary" = "Vegur í byggingu"; +"type.highway.construction.tertiary_link" = "Vegur í byggingu"; +"type.highway.construction.residential" = "Vegur í byggingu"; +"type.highway.construction.unclassified" = "Vegur í byggingu"; +"type.highway.construction.service" = "Vegur í byggingu"; +"type.highway.construction.living_street" = "Vegur í byggingu"; +"type.highway.construction.road" = "Vegur í byggingu"; +"type.highway.construction.track" = "Vegur í byggingu"; "type.highway.cycleway" = "Hjólastígur"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Brú"; diff --git a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings index a53066363..bf70de693 100644 --- a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Galleria"; "type.highway.bus_stop" = "Fermata dell'autobus"; "type.highway.construction" = "Strada in costruzione"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Strada in costruzione"; +"type.highway.construction.motorway_link" = "Strada in costruzione"; +"type.highway.construction.trunk" = "Strada in costruzione"; +"type.highway.construction.trunk_link" = "Strada in costruzione"; +"type.highway.construction.primary" = "Strada in costruzione"; +"type.highway.construction.primary_link" = "Strada in costruzione"; +"type.highway.construction.secondary" = "Strada in costruzione"; +"type.highway.construction.secondary_link" = "Strada in costruzione"; +"type.highway.construction.tertiary" = "Strada in costruzione"; +"type.highway.construction.tertiary_link" = "Strada in costruzione"; +"type.highway.construction.residential" = "Strada in costruzione"; +"type.highway.construction.unclassified" = "Strada in costruzione"; +"type.highway.construction.service" = "Strada in costruzione"; +"type.highway.construction.living_street" = "Strada in costruzione"; +"type.highway.construction.road" = "Strada in costruzione"; +"type.highway.construction.track" = "Strada in costruzione"; "type.highway.cycleway" = "Pista ciclabile"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings index d738e2909..4f63aec5d 100644 --- a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "トンネル"; "type.highway.bus_stop" = "バス停"; "type.highway.construction" = "工事中の道路"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "工事中の道路"; +"type.highway.construction.motorway_link" = "工事中の道路"; +"type.highway.construction.trunk" = "工事中の道路"; +"type.highway.construction.trunk_link" = "工事中の道路"; +"type.highway.construction.primary" = "工事中の道路"; +"type.highway.construction.primary_link" = "工事中の道路"; +"type.highway.construction.secondary" = "工事中の道路"; +"type.highway.construction.secondary_link" = "工事中の道路"; +"type.highway.construction.tertiary" = "工事中の道路"; +"type.highway.construction.tertiary_link" = "工事中の道路"; +"type.highway.construction.residential" = "工事中の道路"; +"type.highway.construction.unclassified" = "工事中の道路"; +"type.highway.construction.service" = "工事中の道路"; +"type.highway.construction.living_street" = "工事中の道路"; +"type.highway.construction.road" = "工事中の道路"; +"type.highway.construction.track" = "工事中の道路"; "type.highway.cycleway" = "自転車道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "自転車道(橋)"; diff --git a/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings index 5b4135a06..f31ed19bd 100644 --- a/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings index 8046ba124..3ee4a016a 100644 --- a/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings index 0e670e6fa..908e8ea3d 100644 --- a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "터널"; "type.highway.bus_stop" = "버스 정류장"; "type.highway.construction" = "공사 중 도로"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "공사 중 도로"; +"type.highway.construction.motorway_link" = "공사 중 도로"; +"type.highway.construction.trunk" = "공사 중 도로"; +"type.highway.construction.trunk_link" = "공사 중 도로"; +"type.highway.construction.primary" = "공사 중 도로"; +"type.highway.construction.primary_link" = "공사 중 도로"; +"type.highway.construction.secondary" = "공사 중 도로"; +"type.highway.construction.secondary_link" = "공사 중 도로"; +"type.highway.construction.tertiary" = "공사 중 도로"; +"type.highway.construction.tertiary_link" = "공사 중 도로"; +"type.highway.construction.residential" = "공사 중 도로"; +"type.highway.construction.unclassified" = "공사 중 도로"; +"type.highway.construction.service" = "공사 중 도로"; +"type.highway.construction.living_street" = "공사 중 도로"; +"type.highway.construction.road" = "공사 중 도로"; +"type.highway.construction.track" = "공사 중 도로"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "다리"; diff --git a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings index 5f826c63c..2c00df34a 100644 --- a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunelis"; "type.highway.bus_stop" = "Stotelė"; "type.highway.construction" = "Tiesiamas kelias"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Tiesiamas kelias"; +"type.highway.construction.motorway_link" = "Tiesiamas kelias"; +"type.highway.construction.trunk" = "Tiesiamas kelias"; +"type.highway.construction.trunk_link" = "Tiesiamas kelias"; +"type.highway.construction.primary" = "Tiesiamas kelias"; +"type.highway.construction.primary_link" = "Tiesiamas kelias"; +"type.highway.construction.secondary" = "Tiesiamas kelias"; +"type.highway.construction.secondary_link" = "Tiesiamas kelias"; +"type.highway.construction.tertiary" = "Tiesiamas kelias"; +"type.highway.construction.tertiary_link" = "Tiesiamas kelias"; +"type.highway.construction.residential" = "Tiesiamas kelias"; +"type.highway.construction.unclassified" = "Tiesiamas kelias"; +"type.highway.construction.service" = "Tiesiamas kelias"; +"type.highway.construction.living_street" = "Tiesiamas kelias"; +"type.highway.construction.road" = "Tiesiamas kelias"; +"type.highway.construction.track" = "Tiesiamas kelias"; "type.highway.cycleway" = "Dviračių takas"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Tiltas"; diff --git a/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings index a157e086e..daa5972d8 100644 --- a/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunelis"; "type.highway.bus_stop" = "Pietura"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings index 23d444651..ccd86ac21 100644 --- a/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings index 14918935b..e0d28c77a 100644 --- a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "बोगदा"; "type.highway.bus_stop" = "बस थांबा"; "type.highway.construction" = "बांधकामाधीन रस्ता"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "बांधकामाधीन रस्ता"; +"type.highway.construction.motorway_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.trunk" = "बांधकामाधीन रस्ता"; +"type.highway.construction.trunk_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.primary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.primary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.secondary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.secondary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.tertiary" = "बांधकामाधीन रस्ता"; +"type.highway.construction.tertiary_link" = "बांधकामाधीन रस्ता"; +"type.highway.construction.residential" = "बांधकामाधीन रस्ता"; +"type.highway.construction.unclassified" = "बांधकामाधीन रस्ता"; +"type.highway.construction.service" = "बांधकामाधीन रस्ता"; +"type.highway.construction.living_street" = "बांधकामाधीन रस्ता"; +"type.highway.construction.road" = "बांधकामाधीन रस्ता"; +"type.highway.construction.track" = "बांधकामाधीन रस्ता"; "type.highway.cycleway" = "सायकल वाट"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "पूल"; diff --git a/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings index 7d2efad89..492c56659 100644 --- a/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings index e97471363..eb3f2aca6 100644 --- a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstopp"; "type.highway.construction" = "Veikonstruksjon"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Veikonstruksjon"; +"type.highway.construction.motorway_link" = "Veikonstruksjon"; +"type.highway.construction.trunk" = "Veikonstruksjon"; +"type.highway.construction.trunk_link" = "Veikonstruksjon"; +"type.highway.construction.primary" = "Veikonstruksjon"; +"type.highway.construction.primary_link" = "Veikonstruksjon"; +"type.highway.construction.secondary" = "Veikonstruksjon"; +"type.highway.construction.secondary_link" = "Veikonstruksjon"; +"type.highway.construction.tertiary" = "Veikonstruksjon"; +"type.highway.construction.tertiary_link" = "Veikonstruksjon"; +"type.highway.construction.residential" = "Veikonstruksjon"; +"type.highway.construction.unclassified" = "Veikonstruksjon"; +"type.highway.construction.service" = "Veikonstruksjon"; +"type.highway.construction.living_street" = "Veikonstruksjon"; +"type.highway.construction.road" = "Veikonstruksjon"; +"type.highway.construction.track" = "Veikonstruksjon"; "type.highway.cycleway" = "Sykkelvei"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bru"; diff --git a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings index 1119cabb8..ac36d8209 100644 --- a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings @@ -52,7 +52,7 @@ "type.leisure.amusement_arcade" = "Speelhal"; "type.amenity.charging_station" = "Laadstation"; "type.amenity.charging_station.motorcar" = "Auto's"; -"type.amenity.charging_station.motorcycle" = "Motors"; +"type.amenity.charging_station.motorcycle" = "Motorfietsen"; "type.amenity.charging_station.bicycle" = "Fietsen"; "type.amenity.charging_station.small" = "Beperkte capaciteit"; "type.amenity.childcare" = "Crèche"; @@ -167,7 +167,7 @@ "type.recycling.green_waste" = "GFTE afval"; "type.recycling.cartons" = "Drankverpakkingen"; "type.amenity.restaurant" = "Restaurant"; -"type.amenity.sanitary_dump_station" = "Caravantoilet cassette-leegpunt"; +"type.amenity.sanitary_dump_station" = "Camperverzorgingsplaats"; "type.amenity.school" = "School"; /* Weather shelter (including sun shelters and natural rock shelters). */ "type.amenity.shelter" = "Schuilplaats"; @@ -191,7 +191,7 @@ "type.amenity.vending_machine.cigarettes" = "Sigarettenautomaat"; "type.amenity.vending_machine.coffee" = "Koffieautomaat"; "type.amenity.vending_machine.condoms" = "Condoomautomaat"; -"type.amenity.vending_machine.drinks" = "Drankautomaat"; +"type.amenity.vending_machine.drinks" = "Drankenautomaat"; "type.amenity.vending_machine.food" = "Etenswarenautomaat"; "type.amenity.vending_machine.newspapers" = "Krantenautomaat"; "type.amenity.vending_machine.parking_tickets" = "Parkeerautomaat"; @@ -205,7 +205,7 @@ "type.amenity.animal_shelter" = "Dierenasiel"; "type.amenity.waste_basket" = "Vuilnisbak"; "type.amenity.waste_disposal" = "Afvalcontainer"; -"type.amenity.waste_transfer_station" = "Overslagstation voor afval"; +"type.amenity.waste_transfer_station" = "Afvaloverslagstation"; "type.amenity.water_point" = "Waterpunt"; "type.amenity.water_point.drinking_water_no" = "Waterpunt"; "type.barrier" = "Barrière"; @@ -265,7 +265,7 @@ "type.craft.brewery" = "Brouwerij"; "type.craft.caterer" = "Cateraar"; "type.craft.carpenter" = "Timmerman"; -"type.craft.confectionery" = "Banketbakkerij"; +"type.craft.confectionery" = "Snoepmakerij"; "type.craft.electrician" = "Elektricien"; "type.craft.electronics_repair" = "Elektronica-reparatie"; "type.craft.gardener" = "Tuinarchitect"; @@ -307,7 +307,7 @@ "type.cuisine.coffee_shop" = "Koffie"; "type.cuisine.crepe" = "Pannenkoek"; "type.cuisine.croatian" = "Kroatisch"; -"type.cuisine.curry" = "Curry's"; +"type.cuisine.curry" = "Curry"; "type.cuisine.deli" = "Delicatessen"; "type.cuisine.diner" = "Diner"; "type.cuisine.donut" = "Donuts"; @@ -321,7 +321,7 @@ "type.cuisine.georgian" = "Georgisch"; "type.cuisine.german" = "Duits"; "type.cuisine.greek" = "Grieks"; -"type.cuisine.grill" = "Gegrilde gerechten"; +"type.cuisine.grill" = "Grill"; "type.cuisine.heuriger" = "Heuriger"; "type.cuisine.hotdog" = "Hotdogs"; "type.cuisine.hungarian" = "Hongaars"; @@ -423,8 +423,8 @@ "type.entrance.house" = "Huis entree"; "type.entrance.garage" = "Garage entree"; "type.entrance.service" = "Dienstingang"; -"type.entrance.entry" = "Ingang (enig)"; -"type.entrance.exit" = "Uitgang (enig)"; +"type.entrance.entry" = "Ingang (alleen ingang)"; +"type.entrance.exit" = "Uitgang (alleen uitgang)"; "type.entrance.emergency" = "Nooduitgang"; "type.fee.yes" = "€"; "type.fee.no" = "Gratis"; @@ -445,13 +445,30 @@ "type.highway.bridleway.permissive" = "Ruiterpad"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.bridleway.tunnel" = "Tunnel"; -"type.highway.busway" = "Speciale busweg"; +"type.highway.busway" = "Busbaan"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.busway.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushalte"; -"type.highway.construction" = "Baan in aanbouw"; +"type.highway.construction" = "Weg in aanbouw"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Weg in aanbouw"; +"type.highway.construction.motorway_link" = "Weg in aanbouw"; +"type.highway.construction.trunk" = "Weg in aanbouw"; +"type.highway.construction.trunk_link" = "Weg in aanbouw"; +"type.highway.construction.primary" = "Weg in aanbouw"; +"type.highway.construction.primary_link" = "Weg in aanbouw"; +"type.highway.construction.secondary" = "Weg in aanbouw"; +"type.highway.construction.secondary_link" = "Weg in aanbouw"; +"type.highway.construction.tertiary" = "Weg in aanbouw"; +"type.highway.construction.tertiary_link" = "Weg in aanbouw"; +"type.highway.construction.residential" = "Weg in aanbouw"; +"type.highway.construction.unclassified" = "Weg in aanbouw"; +"type.highway.construction.service" = "Weg in aanbouw"; +"type.highway.construction.living_street" = "Weg in aanbouw"; +"type.highway.construction.road" = "Weg in aanbouw"; +"type.highway.construction.track" = "Weg in aanbouw"; "type.highway.cycleway" = "Fietspad"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Fietsbrug"; @@ -468,7 +485,7 @@ /* These translations are used for all type.highway.*.tunnel. */ "type.highway.footway.tunnel" = "Voetgangerstunnel"; "type.highway.ford" = "Voorde"; -"type.highway.living_street" = "Straat"; +"type.highway.living_street" = "Woonerf"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.living_street.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ @@ -479,7 +496,7 @@ /* These translations are used for all type.highway.*.tunnel. */ "type.highway.motorway.tunnel" = "Snelwegtunnel"; "type.highway.motorway_junction" = "Afrit"; -"type.highway.motorway_link" = "Snelwegoprit"; +"type.highway.motorway_link" = "Snelwegoprit/afrit"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.motorway_link.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ @@ -488,7 +505,7 @@ /* Hiking trail tagged as sac_scale=demanding_mountain_hiking (3 of 6) or trail_visibility=bad. */ "type.highway.path.difficult" = "Moeilijk of slecht zichtbaar pad"; /* Hiking trail tagged as sac_scale=alpine_hiking (4+ of 6) or trail_visibility=horrible or more extreme. */ -"type.highway.path.expert" = "Zeer moeilijk of niet te onderscheiden spoor"; +"type.highway.path.expert" = "Zeer moeilijk of niet te onderscheiden pad"; "type.highway.path.bicycle" = "Fiets- & voetpad"; "type.highway.footway.bicycle" = "Fiets- & voetpad"; /* These translations are used for all type.highway.*.bridge. */ @@ -564,27 +581,27 @@ "type.highway.tertiary_link.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.tertiary_link.tunnel" = "Tunnel"; -"type.highway.track" = "Straat"; -"type.highway.track.area" = "Straat"; +"type.highway.track" = "Bos-/veldweg"; +"type.highway.track.area" = "Bos-/veldweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.track.bridge" = "Brug"; -"type.highway.track.grade1" = "Straat"; -"type.highway.track.no.access" = "Straat"; +"type.highway.track.grade1" = "Bos-/veldweg"; +"type.highway.track.no.access" = "Bos-/veldweg"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.track.tunnel" = "Tunnel"; "type.highway.traffic_signals" = "Verkeerslichten"; -"type.highway.trunk" = "Straat"; +"type.highway.trunk" = "Autoweg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.trunk.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.trunk.tunnel" = "Tunnel"; -"type.highway.trunk_link" = "Straat"; +"type.highway.trunk_link" = "Autowegoprit/afrit"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.trunk_link.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ "type.highway.trunk_link.tunnel" = "Tunnel"; -"type.highway.unclassified" = "Straat"; -"type.highway.unclassified.area" = "Straat"; +"type.highway.unclassified" = "Weg"; +"type.highway.unclassified.area" = "Weg"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.unclassified.bridge" = "Brug"; /* These translations are used for all type.highway.*.tunnel. */ @@ -598,12 +615,12 @@ "type.area_highway.primary" = "Hoofdweg"; "type.area_highway.residential" = "Straat"; "type.area_highway.secondary" = "Secundaire weg"; -"type.area_highway.service" = "Straat"; +"type.area_highway.service" = "Serviceweg"; "type.area_highway.tertiary" = "Tertiaire weg"; -"type.area_highway.steps" = "Trappen"; -"type.area_highway.track" = "Straat"; -"type.area_highway.trunk" = "Straat"; -"type.area_highway.unclassified" = "Straat"; +"type.area_highway.steps" = "Trap"; +"type.area_highway.track" = "Bos-/veldweg"; +"type.area_highway.trunk" = "Autoweg"; +"type.area_highway.unclassified" = "Weg"; "type.historic" = "Historisch object"; "type.historic.aircraft" = "Historische vliegtuigen"; "type.historic.anchor" = "Historisch anker"; @@ -618,7 +635,7 @@ "type.historic.castle.fortress" = "Vesting"; "type.historic.castle.hillfort" = "Heuvelfort"; "type.historic.castle.kremlin" = "Kremlin"; -"type.historic.castle.manor" = "Manoir"; +"type.historic.castle.manor" = "Landhuis"; "type.historic.castle.palace" = "Paleis"; "type.historic.castle.shiro" = "Japans kasteel"; "type.historic.castle.stately" = "Landhuis"; @@ -638,7 +655,7 @@ "type.historic.mine" = "Historische mijn"; "type.historic.monument" = "Monument"; "type.historic.pillory" = "Schandpaal"; -"type.historic.ruins" = "Ruïne"; +"type.historic.ruins" = "Historische ruïne"; "type.historic.ship" = "Historisch schip"; "type.historic.tank" = "Historische tank"; "type.historic.tomb" = "Graftombe"; @@ -647,7 +664,7 @@ /* Usually a Christian historical cross placed along a road. */ "type.historic.wayside_cross" = "Wegkruis"; "type.historic.wayside_shrine" = "Kruisbeeld"; -"type.historic.wreck" = "Schipbreuk"; +"type.historic.wreck" = "Schipwrak"; "type.internet_access" = "Internet"; "type.internet_access.wlan" = "Draadloos internet"; "type.junction" = "Kruispunt"; @@ -664,41 +681,41 @@ "type.landuse.religious" = "Religieus Land"; "type.landuse.commercial" = "Commercieel gebied"; "type.landuse.construction" = "Bouwplaats"; -"type.landuse.education" = "Educatieve voorzieningen"; +"type.landuse.education" = "Onderwijsgebied"; "type.landuse.farmland" = "Landbouwgrond"; "type.landuse.farmyard" = "Boerenerf"; "type.landuse.field" = "Veld"; -"type.landuse.flowerbed" = "Bloemenbed"; +"type.landuse.flowerbed" = "Bloembed"; "type.landuse.forest" = "Bos"; "type.landuse.forest.coniferous" = "Naaldbos"; "type.landuse.forest.deciduous" = "Loofbos"; "type.landuse.forest.mixed" = "Gemengd bos"; "type.landuse.garages" = "Garages"; "type.landuse.grass" = "Gazon"; -"type.landuse.greenfield" = "Greenfield"; -"type.landuse.greenhouse_horticulture" = "Kassen"; +"type.landuse.greenfield" = "Bouwgrond"; +"type.landuse.greenhouse_horticulture" = "Kas"; "type.landuse.industrial" = "Industrieterrein"; "type.landuse.landfill" = "Stortplaats"; "type.landuse.meadow" = "Weiland"; "type.landuse.military" = "Militair terrein"; "type.landuse.orchard" = "Boomgaard"; "type.landuse.quarry" = "Steengroeve"; -"type.landuse.railway" = "Spoorwegfaciliteiten"; +"type.landuse.railway" = "Spoorwegterrein"; "type.landuse.recreation_ground" = "Recreatiezone"; "type.landuse.reservoir" = "Reservoir"; "type.landuse.residential" = "Woonwijk"; -"type.landuse.retail" = "Detailhandel"; -"type.landuse.salt_pond" = "Zoutpoel"; +"type.landuse.retail" = "Detailhandelsgebied"; +"type.landuse.salt_pond" = "Zoutpan"; "type.landuse.village_green" = "Dorpspark"; "type.landuse.vineyard" = "Wijngaard"; "type.landuse.plant_nursery" = "Plantenkwekerij"; -"type.leisure" = "Ontspanning"; -"type.leisure.bandstand" = "Muziekpodium"; +"type.leisure" = "Recreatie"; +"type.leisure.bandstand" = "Muziekkoepel"; "type.leisure.common" = "Openbare grond"; "type.leisure.dog_park" = "Hondenuitlaatplek"; "type.leisure.escape_game" = "Escaperoom"; "type.leisure.fitness_centre" = "Fitnesscentrum"; -"type.leisure.fitness_station" = "Fitness-Station"; +"type.leisure.fitness_station" = "Fitness-station"; "type.leisure.dance" = "Danszaal"; "type.leisure.garden" = "Tuin"; "type.leisure.garden.residential" = "Tuin"; @@ -706,15 +723,15 @@ "type.leisure.miniature_golf" = "Minigolf"; "type.leisure.hackerspace" = "Hackerspace"; "type.leisure.ice_rink" = "Schaatsbaan"; -"type.leisure.indoor_play" = "Speelhal"; +"type.leisure.indoor_play" = "Indoor speeltuin"; "type.leisure.marina" = "Jachthaven"; "type.leisure.nature_reserve" = "Natuurreservaat"; "type.leisure.outdoor_seating" = "Zitplaatsen buiten"; "type.leisure.firepit" = "Vuurplaats"; "type.leisure.park" = "Park"; -"type.leisure.park.no.access" = "Park"; +"type.leisure.park.no.access" = "Privépark"; "type.leisure.park.permissive" = "Park"; -"type.leisure.park.private" = "Park"; +"type.leisure.park.private" = "Privépark"; "type.leisure.picnic_table" = "Picknicktafel"; "type.leisure.pitch" = "Sportveld"; "type.leisure.playground" = "Speeltuin"; @@ -757,18 +774,18 @@ "type.leisure.fitness_centre.sport.yoga" = "Yogastudio"; "type.leisure.stadium" = "Stadion"; "type.leisure.swimming_pool" = "Zwembad"; -"type.leisure.swimming_pool.private" = "Privé zwembad"; +"type.leisure.swimming_pool.private" = "Privézwembad"; "type.leisure.track" = "Parcours"; "type.leisure.track.area" = "Parcours"; "type.leisure.water_park" = "Waterpark"; "type.leisure.beach_resort" = "Strandresort"; -"type.man_made" = "Kunstmatige constructies"; +"type.man_made" = "Kunstmatige constructie"; "type.man_made.breakwater" = "Golfbreker"; -"type.man_made.cairn" = "Steengroeve"; +"type.man_made.cairn" = "Steenmannetje"; "type.man_made.chimney" = "Fabrieksschoorsteen"; -"type.man_made.cutline" = "Bospad"; +"type.man_made.cutline" = "Cutline"; "type.man_made.survey_point" = "Meetpunt"; -"type.man_made.flagpole" = "Vlaggenpaal"; +"type.man_made.flagpole" = "Vlaggenmast"; "type.man_made.lighthouse" = "Vuurtoren"; "type.man_made.mast" = "Mast"; "type.man_made.pier" = "Pier"; @@ -797,7 +814,7 @@ "type.man_made.water_well.drinking_water_no" = "Waterput"; "type.man_made.windmill" = "Windmolen"; "type.man_made.works" = "Fabriek"; -"type.military" = "Militair"; +"type.military" = "Militair object"; "type.military.bunker" = "Bunker"; "type.mountain_pass" = "Bergpas"; "type.natural" = "Natuur"; @@ -812,9 +829,9 @@ "type.natural.beach.sand" = "Zandstrand"; "type.natural.beach.gravel" = "Kiezelstrand"; "type.natural.cape" = "Kaap"; -"type.natural.cave_entrance" = "Grot"; +"type.natural.cave_entrance" = "Grotingang"; "type.natural.cliff" = "Klif"; -"type.natural.earth_bank" = "Klif"; +"type.natural.earth_bank" = "Aardhelling"; "type.man_made.embankment" = "Baanlichaam"; "type.natural.coastline" = "Kustlijn"; "type.natural.desert" = "Woestijn"; @@ -825,8 +842,8 @@ "type.natural.heath" = "Heide"; "type.natural.hot_spring" = "Hete bron"; "type.natural.water.lake" = "Meer"; -"type.natural.water.lock" = "Slotkamer"; -"type.natural.water.pond" = "Plas"; +"type.natural.water.lock" = "Schutkolk"; +"type.natural.water.pond" = "Vijver"; "type.natural.water.reservoir" = "Reservoir"; "type.natural.water.basin" = "Waterbassin"; "type.natural.water.river" = "Rivier"; @@ -896,83 +913,83 @@ "type.place.sea" = "Zee"; "type.place.square" = "Plein"; "type.place.state" = "Deelstaat"; -"type.place.state.USA" = "Deelstaat"; +"type.place.state.USA" = "Staat"; /* Named part of a city or town, bigger than place=quarter (Wiki: https://wiki.openstreetmap.org/wiki/Tag:place%3Dsuburb) */ "type.place.suburb" = "Buitenwijk"; "type.place.town" = "Stad"; "type.place.village" = "Dorp"; -"type.power" = "Electriciteit"; +"type.power" = "Elektriciteit"; "type.power.generator" = "Generator"; "type.power.generator.solar" = "Zonne-generator"; -"type.power.generator.wind" = "Wind generator"; -"type.power.generator.gas" = "Gasturbine elektriciteitscentrale"; +"type.power.generator.wind" = "Windgenerator"; +"type.power.generator.gas" = "Gascentrale"; "type.power.generator.hydro" = "Waterkrachtcentrale"; "type.power.line" = "Hoogspanningsleiding"; "type.power.line.underground" = "Ondergrondse hoogspanningsleiding"; "type.power.minor_line" = "Laag-/middelspanningsleiding"; "type.power.plant" = "Energiecentrale"; -"type.power.plant.coal" = "Kolen elektriciteitscentrale"; -"type.power.plant.gas" = "Gasturbine elektriciteitscentrale"; +"type.power.plant.coal" = "Kolencentrale"; +"type.power.plant.gas" = "Gascentrale"; "type.power.plant.hydro" = "Waterkrachtcentrale"; "type.power.plant.solar" = "Zonne-energiecentrale"; "type.power.plant.wind" = "Windkrachtcentrale"; "type.power.substation" = "Transformatorstation"; /* A tower or pylon carrying high voltage electricity cables. */ -"type.power.tower" = "Elektriciteitsmast"; +"type.power.tower" = "Hoogspanningsmast"; /* A single pole supporting minor power lines. */ -"type.power.pole" = "Stroommast"; +"type.power.pole" = "Elektriciteitsmast"; /* A single pole supporting various public utilities, such as lighting or telephony. */ "type.man_made.utility_pole" = "Paal"; "type.public_transport" = "Openbaar vervoer"; -"type.public_transport.platform" = "Platform"; +"type.public_transport.platform" = "Perron"; "type.railway" = "Spoorweg"; "type.railway.abandoned" = "Voormalig treinspoor"; -"type.railway.construction" = "Treinspoor in aanbouw"; +"type.railway.construction" = "Spoor in aanbouw"; "type.railway.crossing" = "Spoorwegovergang"; "type.railway.disused" = "Ongebruikt spoor"; "type.railway.disused.bridge" = "Ongebruikte spoorbrug"; "type.railway.disused.tunnel" = "Ongebruikte spoortunnel"; -"type.railway.funicular" = "Tunnel"; -"type.railway.funicular.bridge" = "Kabelspoorbuur"; +"type.railway.funicular" = "Kabelspoor"; +"type.railway.funicular.bridge" = "Kabelspoorbrug"; "type.railway.funicular.tunnel" = "Kabelspoortunnel"; "type.railway.halt" = "Station"; "type.railway.level_crossing" = "Spoorwegovergang"; "type.railway.light_rail" = "Lightrail"; -"type.railway.light_rail.bridge" = "Lightrail-brug"; -"type.railway.light_rail.tunnel" = "Lightrail-tunnel"; +"type.railway.light_rail.bridge" = "Lightrailbrug"; +"type.railway.light_rail.tunnel" = "Lightrailtunnel"; "type.railway.monorail" = "Monorail"; -"type.railway.monorail.bridge" = "Monorail-brug"; -"type.railway.monorail.tunnel" = "Monorail-tunnel"; -"type.railway.miniature" = "Miniatuur spoorweg"; -"type.railway.miniature.bridge" = "Miniatuur spoorbrug"; -"type.railway.miniature.tunnel" = "Miniatuur spoortunnel"; -"type.railway.narrow_gauge" = "Smalspoorbaan"; +"type.railway.monorail.bridge" = "Monorailbrug"; +"type.railway.monorail.tunnel" = "Monorailtunnel"; +"type.railway.miniature" = "Miniatuurspoorweg"; +"type.railway.miniature.bridge" = "Miniatuurspoorbrug"; +"type.railway.miniature.tunnel" = "Miniatuurspoortunnel"; +"type.railway.narrow_gauge" = "Smalspoor"; "type.railway.narrow_gauge.bridge" = "Smalspoorbrug"; "type.railway.narrow_gauge.tunnel" = "Smalspoortunnel"; "type.railway.platform" = "Perron"; -"type.railway.preserved" = "Museumspoorbaan"; -"type.railway.preserved.bridge" = "Museumspoorbaanbrug"; -"type.railway.preserved.tunnel" = "Museumspoorbaantunnel"; -"type.railway.rail" = "Spoorlijn"; -"type.railway.rail.highspeed" = "Hogesnelheidstrein"; +"type.railway.preserved" = "Museumspoorweg"; +"type.railway.preserved.bridge" = "Museumspoorbrug"; +"type.railway.preserved.tunnel" = "Museumspoortunnel"; +"type.railway.rail" = "Spoor"; +"type.railway.rail.highspeed" = "Hogesnelheidsspoor"; "type.railway.rail.tourism" = "Toeristische spoorlijn"; "type.railway.rail.main" = "Spoor"; -"type.railway.turntable" = "Spoorweg-keerpunt"; +"type.railway.turntable" = "Spoordraaischijf"; /* Includes ordinary railway=rail w/o more specific usage= and service= tags. */ -"type.railway.rail.branch" = "Secundaire spoorwegen"; +"type.railway.rail.branch" = "Secundair spoor"; /* Non-passenger utility tracks: industrial, military, test. */ -"type.railway.rail.utility" = "Spoorwegen"; +"type.railway.rail.utility" = "Dienstspoor"; "type.railway.rail.spur" = "Zijspoor"; /* Short service tracks: siding, yard, crossover. */ "type.railway.rail.service" = "Hulpspoor"; -"type.railway.rail.bridge" = "Treinspoorbrug"; -"type.railway.rail.highspeed.bridge" = "Treinspoorbrug"; -"type.railway.rail.tourism.bridge" = "Treinspoorbrug"; -"type.railway.rail.main.bridge" = "Treinspoorbrug"; -"type.railway.rail.branch.bridge" = "Treinspoorbrug"; -"type.railway.rail.utility.bridge" = "Treinspoorbrug"; -"type.railway.rail.spur.bridge" = "Treinspoorbrug"; -"type.railway.rail.service.bridge" = "Treinspoorbrug"; +"type.railway.rail.bridge" = "Spoorbrug"; +"type.railway.rail.highspeed.bridge" = "Spoorbrug"; +"type.railway.rail.tourism.bridge" = "Spoorbrug"; +"type.railway.rail.main.bridge" = "Spoorbrug"; +"type.railway.rail.branch.bridge" = "Spoorbrug"; +"type.railway.rail.utility.bridge" = "Spoorbrug"; +"type.railway.rail.spur.bridge" = "Spoorbrug"; +"type.railway.rail.service.bridge" = "Spoorbrug"; "type.railway.rail.tunnel" = "Spoortunnel"; "type.railway.rail.highspeed.tunnel" = "Spoortunnel"; "type.railway.rail.tourism.tunnel" = "Spoortunnel"; @@ -982,12 +999,12 @@ "type.railway.rail.spur.tunnel" = "Spoortunnel"; "type.railway.rail.service.tunnel" = "Spoortunnel"; "type.railway.station" = "Station"; -"type.railway.station.funicular" = "Tunnel"; -"type.railway.station.light_rail" = "Station"; +"type.railway.station.funicular" = "Kabelspoorstation"; +"type.railway.station.light_rail" = "Lightrailstation"; "type.railway.station.light_rail.berlin" = "S-bahn station"; "type.railway.station.light_rail.london" = "Station"; "type.railway.station.light_rail.porto" = "Station"; -"type.railway.station.monorail" = "Station"; +"type.railway.station.monorail" = "Monorailstation"; "type.railway.station.subway" = "Metrostation"; "type.railway.station.subway.adana" = "Metrostation"; "type.railway.station.subway.algiers" = "Metrostation"; @@ -1103,124 +1120,124 @@ "type.railway.station.subway.wuhan" = "Metrostation"; "type.railway.station.subway.yerevan" = "Metrostation"; "type.railway.station.subway.yokohama" = "Metrostation"; -"type.railway.subway" = "Metro"; +"type.railway.subway" = "Metrolijn"; "type.railway.subway.bridge" = "Metrobrug"; "type.railway.subway.tunnel" = "Metrotunnel"; -"type.railway.subway_entrance" = "Metro ingang"; -"type.railway.subway_entrance.adana" = "Metro ingang"; -"type.railway.subway_entrance.algiers" = "Metro ingang"; -"type.railway.subway_entrance.almaty" = "Metro ingang"; -"type.railway.subway_entrance.amsterdam" = "Metro ingang"; -"type.railway.subway_entrance.ankara" = "Metro ingang"; -"type.railway.subway_entrance.athens" = "Metro ingang"; -"type.railway.subway_entrance.baku" = "Metro ingang"; -"type.railway.subway_entrance.bangkok" = "Metro ingang"; -"type.railway.subway_entrance.barcelona" = "Metro ingang"; -"type.railway.subway_entrance.beijing" = "Metro ingang"; -"type.railway.subway_entrance.bengalore" = "Metro ingang"; -"type.railway.subway_entrance.berlin" = "Metro ingang"; -"type.railway.subway_entrance.bilbao" = "Metro ingang"; -"type.railway.subway_entrance.brasilia" = "Metro ingang"; -"type.railway.subway_entrance.brescia" = "Metro ingang"; -"type.railway.subway_entrance.brussels" = "Metro ingang"; -"type.railway.subway_entrance.bucharest" = "Metro ingang"; -"type.railway.subway_entrance.budapest" = "Metro ingang"; -"type.railway.subway_entrance.buenos_aires" = "Metro ingang"; -"type.railway.subway_entrance.bursa" = "Metro ingang"; -"type.railway.subway_entrance.cairo" = "Metro ingang"; -"type.railway.subway_entrance.caracas" = "Metro ingang"; -"type.railway.subway_entrance.catania" = "Metro ingang"; -"type.railway.subway_entrance.changchun" = "Metro ingang"; -"type.railway.subway_entrance.chengdu" = "Metro ingang"; -"type.railway.subway_entrance.chicago" = "Metro ingang"; -"type.railway.subway_entrance.chongqing" = "Metro ingang"; -"type.railway.subway_entrance.dalian" = "Metro ingang"; -"type.railway.subway_entrance.delhi" = "Metro ingang"; -"type.railway.subway_entrance.dnepro" = "Metro ingang"; -"type.railway.subway_entrance.dubai" = "Metro ingang"; -"type.railway.subway_entrance.ekb" = "Metro ingang"; -"type.railway.subway_entrance.fukuoka" = "Metro ingang"; -"type.railway.subway_entrance.glasgow" = "Metro ingang"; -"type.railway.subway_entrance.guangzhou" = "Metro ingang"; -"type.railway.subway_entrance.hamburg" = "Metro ingang"; -"type.railway.subway_entrance.helsinki" = "Metro ingang"; -"type.railway.subway_entrance.hiroshima" = "Metro ingang"; -"type.railway.subway_entrance.hongkong" = "Metro ingang"; -"type.railway.subway_entrance.isfahan" = "Metro ingang"; -"type.railway.subway_entrance.istanbul" = "Metro ingang"; -"type.railway.subway_entrance.izmir" = "Metro ingang"; -"type.railway.subway_entrance.kazan" = "Metro ingang"; -"type.railway.subway_entrance.kharkiv" = "Metro ingang"; -"type.railway.subway_entrance.kiev" = "Metro ingang"; -"type.railway.subway_entrance.kobe" = "Metro ingang"; -"type.railway.subway_entrance.kolkata" = "Metro ingang"; -"type.railway.subway_entrance.kunming" = "Metro ingang"; -"type.railway.subway_entrance.kyoto" = "Metro ingang"; -"type.railway.subway_entrance.la" = "Metro ingang"; -"type.railway.subway_entrance.lausanne" = "Metro ingang"; -"type.railway.subway_entrance.lille" = "Metro ingang"; -"type.railway.subway_entrance.lima" = "Metro ingang"; -"type.railway.subway_entrance.lisboa" = "Metro ingang"; -"type.railway.subway_entrance.london" = "Metro ingang"; -"type.railway.subway_entrance.lyon" = "Metro ingang"; -"type.railway.subway_entrance.madrid" = "Metro ingang"; -"type.railway.subway_entrance.malaga" = "Metro ingang"; -"type.railway.subway_entrance.manila" = "Metro ingang"; -"type.railway.subway_entrance.maracaibo" = "Metro ingang"; -"type.railway.subway_entrance.mashhad" = "Metro ingang"; -"type.railway.subway_entrance.mecca" = "Metro ingang"; -"type.railway.subway_entrance.medellin" = "Metro ingang"; -"type.railway.subway_entrance.mexico" = "Metro ingang"; -"type.railway.subway_entrance.milan" = "Metro ingang"; -"type.railway.subway_entrance.minsk" = "Metro ingang"; -"type.railway.subway_entrance.montreal" = "Metro ingang"; -"type.railway.subway_entrance.moscow" = "Metro ingang"; -"type.railway.subway_entrance.munchen" = "Metro ingang"; -"type.railway.subway_entrance.nagoya" = "Metro ingang"; -"type.railway.subway_entrance.newyork" = "Metro ingang"; -"type.railway.subway_entrance.nnov" = "Metro ingang"; -"type.railway.subway_entrance.novosibirsk" = "Metro ingang"; -"type.railway.subway_entrance.osaka" = "Metro ingang"; -"type.railway.subway_entrance.oslo" = "Metro ingang"; -"type.railway.subway_entrance.palma" = "Metro ingang"; -"type.railway.subway_entrance.panama" = "Metro ingang"; -"type.railway.subway_entrance.paris" = "Metro ingang"; -"type.railway.subway_entrance.philadelphia" = "Metro ingang"; -"type.railway.subway_entrance.pyongyang" = "Metro ingang"; -"type.railway.subway_entrance.rennes" = "Metro ingang"; -"type.railway.subway_entrance.rio" = "Metro ingang"; -"type.railway.subway_entrance.roma" = "Metro ingang"; -"type.railway.subway_entrance.rotterdam" = "Metro ingang"; -"type.railway.subway_entrance.samara" = "Metro ingang"; -"type.railway.subway_entrance.santiago" = "Metro ingang"; -"type.railway.subway_entrance.santo_domingo" = "Metro ingang"; -"type.railway.subway_entrance.saopaulo" = "Metro ingang"; -"type.railway.subway_entrance.sapporo" = "Metro ingang"; -"type.railway.subway_entrance.sendai" = "Metro ingang"; -"type.railway.subway_entrance.sf" = "Metro ingang"; -"type.railway.subway_entrance.shanghai" = "Metro ingang"; -"type.railway.subway_entrance.shenzhen" = "Metro ingang"; -"type.railway.subway_entrance.shiraz" = "Metro ingang"; -"type.railway.subway_entrance.singapore" = "Metro ingang"; -"type.railway.subway_entrance.sofia" = "Metro ingang"; -"type.railway.subway_entrance.spb" = "Metro ingang"; -"type.railway.subway_entrance.stockholm" = "Metro ingang"; -"type.railway.subway_entrance.tabriz" = "Metro ingang"; -"type.railway.subway_entrance.taipei" = "Metro ingang"; -"type.railway.subway_entrance.taoyuan" = "Metro ingang"; -"type.railway.subway_entrance.tashkent" = "Metro ingang"; -"type.railway.subway_entrance.tbilisi" = "Metro ingang"; -"type.railway.subway_entrance.tehran" = "Metro ingang"; -"type.railway.subway_entrance.tianjin" = "Metro ingang"; -"type.railway.subway_entrance.tokyo" = "Metro ingang"; -"type.railway.subway_entrance.valencia" = "Metro ingang"; -"type.railway.subway_entrance.vienna" = "Metro ingang"; -"type.railway.subway_entrance.warszawa" = "Metro ingang"; -"type.railway.subway_entrance.washington" = "Metro ingang"; -"type.railway.subway_entrance.wuhan" = "Metro ingang"; -"type.railway.subway_entrance.yerevan" = "Metro ingang"; -"type.railway.subway_entrance.yokohama" = "Metro ingang"; -"type.railway.tram" = "Tram"; +"type.railway.subway_entrance" = "Metro-ingang"; +"type.railway.subway_entrance.adana" = "Metro-ingang"; +"type.railway.subway_entrance.algiers" = "Metro-ingang"; +"type.railway.subway_entrance.almaty" = "Metro-ingang"; +"type.railway.subway_entrance.amsterdam" = "Metro-ingang"; +"type.railway.subway_entrance.ankara" = "Metro-ingang"; +"type.railway.subway_entrance.athens" = "Metro-ingang"; +"type.railway.subway_entrance.baku" = "Metro-ingang"; +"type.railway.subway_entrance.bangkok" = "Metro-ingang"; +"type.railway.subway_entrance.barcelona" = "Metro-ingang"; +"type.railway.subway_entrance.beijing" = "Metro-ingang"; +"type.railway.subway_entrance.bengalore" = "Metro-ingang"; +"type.railway.subway_entrance.berlin" = "Metro-ingang"; +"type.railway.subway_entrance.bilbao" = "Metro-ingang"; +"type.railway.subway_entrance.brasilia" = "Metro-ingang"; +"type.railway.subway_entrance.brescia" = "Metro-ingang"; +"type.railway.subway_entrance.brussels" = "Metro-ingang"; +"type.railway.subway_entrance.bucharest" = "Metro-ingang"; +"type.railway.subway_entrance.budapest" = "Metro-ingang"; +"type.railway.subway_entrance.buenos_aires" = "Metro-ingang"; +"type.railway.subway_entrance.bursa" = "Metro-ingang"; +"type.railway.subway_entrance.cairo" = "Metro-ingang"; +"type.railway.subway_entrance.caracas" = "Metro-ingang"; +"type.railway.subway_entrance.catania" = "Metro-ingang"; +"type.railway.subway_entrance.changchun" = "Metro-ingang"; +"type.railway.subway_entrance.chengdu" = "Metro-ingang"; +"type.railway.subway_entrance.chicago" = "Metro-ingang"; +"type.railway.subway_entrance.chongqing" = "Metro-ingang"; +"type.railway.subway_entrance.dalian" = "Metro-ingang"; +"type.railway.subway_entrance.delhi" = "Metro-ingang"; +"type.railway.subway_entrance.dnepro" = "Metro-ingang"; +"type.railway.subway_entrance.dubai" = "Metro-ingang"; +"type.railway.subway_entrance.ekb" = "Metro-ingang"; +"type.railway.subway_entrance.fukuoka" = "Metro-ingang"; +"type.railway.subway_entrance.glasgow" = "Metro-ingang"; +"type.railway.subway_entrance.guangzhou" = "Metro-ingang"; +"type.railway.subway_entrance.hamburg" = "Metro-ingang"; +"type.railway.subway_entrance.helsinki" = "Metro-ingang"; +"type.railway.subway_entrance.hiroshima" = "Metro-ingang"; +"type.railway.subway_entrance.hongkong" = "Metro-ingang"; +"type.railway.subway_entrance.isfahan" = "Metro-ingang"; +"type.railway.subway_entrance.istanbul" = "Metro-ingang"; +"type.railway.subway_entrance.izmir" = "Metro-ingang"; +"type.railway.subway_entrance.kazan" = "Metro-ingang"; +"type.railway.subway_entrance.kharkiv" = "Metro-ingang"; +"type.railway.subway_entrance.kiev" = "Metro-ingang"; +"type.railway.subway_entrance.kobe" = "Metro-ingang"; +"type.railway.subway_entrance.kolkata" = "Metro-ingang"; +"type.railway.subway_entrance.kunming" = "Metro-ingang"; +"type.railway.subway_entrance.kyoto" = "Metro-ingang"; +"type.railway.subway_entrance.la" = "Metro-ingang"; +"type.railway.subway_entrance.lausanne" = "Metro-ingang"; +"type.railway.subway_entrance.lille" = "Metro-ingang"; +"type.railway.subway_entrance.lima" = "Metro-ingang"; +"type.railway.subway_entrance.lisboa" = "Metro-ingang"; +"type.railway.subway_entrance.london" = "Metro-ingang"; +"type.railway.subway_entrance.lyon" = "Metro-ingang"; +"type.railway.subway_entrance.madrid" = "Metro-ingang"; +"type.railway.subway_entrance.malaga" = "Metro-ingang"; +"type.railway.subway_entrance.manila" = "Metro-ingang"; +"type.railway.subway_entrance.maracaibo" = "Metro-ingang"; +"type.railway.subway_entrance.mashhad" = "Metro-ingang"; +"type.railway.subway_entrance.mecca" = "Metro-ingang"; +"type.railway.subway_entrance.medellin" = "Metro-ingang"; +"type.railway.subway_entrance.mexico" = "Metro-ingang"; +"type.railway.subway_entrance.milan" = "Metro-ingang"; +"type.railway.subway_entrance.minsk" = "Metro-ingang"; +"type.railway.subway_entrance.montreal" = "Metro-ingang"; +"type.railway.subway_entrance.moscow" = "Metro-ingang"; +"type.railway.subway_entrance.munchen" = "Metro-ingang"; +"type.railway.subway_entrance.nagoya" = "Metro-ingang"; +"type.railway.subway_entrance.newyork" = "Metro-ingang"; +"type.railway.subway_entrance.nnov" = "Metro-ingang"; +"type.railway.subway_entrance.novosibirsk" = "Metro-ingang"; +"type.railway.subway_entrance.osaka" = "Metro-ingang"; +"type.railway.subway_entrance.oslo" = "Metro-ingang"; +"type.railway.subway_entrance.palma" = "Metro-ingang"; +"type.railway.subway_entrance.panama" = "Metro-ingang"; +"type.railway.subway_entrance.paris" = "Metro-ingang"; +"type.railway.subway_entrance.philadelphia" = "Metro-ingang"; +"type.railway.subway_entrance.pyongyang" = "Metro-ingang"; +"type.railway.subway_entrance.rennes" = "Metro-ingang"; +"type.railway.subway_entrance.rio" = "Metro-ingang"; +"type.railway.subway_entrance.roma" = "Metro-ingang"; +"type.railway.subway_entrance.rotterdam" = "Metro-ingang"; +"type.railway.subway_entrance.samara" = "Metro-ingang"; +"type.railway.subway_entrance.santiago" = "Metro-ingang"; +"type.railway.subway_entrance.santo_domingo" = "Metro-ingang"; +"type.railway.subway_entrance.saopaulo" = "Metro-ingang"; +"type.railway.subway_entrance.sapporo" = "Metro-ingang"; +"type.railway.subway_entrance.sendai" = "Metro-ingang"; +"type.railway.subway_entrance.sf" = "Metro-ingang"; +"type.railway.subway_entrance.shanghai" = "Metro-ingang"; +"type.railway.subway_entrance.shenzhen" = "Metro-ingang"; +"type.railway.subway_entrance.shiraz" = "Metro-ingang"; +"type.railway.subway_entrance.singapore" = "Metro-ingang"; +"type.railway.subway_entrance.sofia" = "Metro-ingang"; +"type.railway.subway_entrance.spb" = "Metro-ingang"; +"type.railway.subway_entrance.stockholm" = "Metro-ingang"; +"type.railway.subway_entrance.tabriz" = "Metro-ingang"; +"type.railway.subway_entrance.taipei" = "Metro-ingang"; +"type.railway.subway_entrance.taoyuan" = "Metro-ingang"; +"type.railway.subway_entrance.tashkent" = "Metro-ingang"; +"type.railway.subway_entrance.tbilisi" = "Metro-ingang"; +"type.railway.subway_entrance.tehran" = "Metro-ingang"; +"type.railway.subway_entrance.tianjin" = "Metro-ingang"; +"type.railway.subway_entrance.tokyo" = "Metro-ingang"; +"type.railway.subway_entrance.valencia" = "Metro-ingang"; +"type.railway.subway_entrance.vienna" = "Metro-ingang"; +"type.railway.subway_entrance.warszawa" = "Metro-ingang"; +"type.railway.subway_entrance.washington" = "Metro-ingang"; +"type.railway.subway_entrance.wuhan" = "Metro-ingang"; +"type.railway.subway_entrance.yerevan" = "Metro-ingang"; +"type.railway.subway_entrance.yokohama" = "Metro-ingang"; +"type.railway.tram" = "Tramspoor"; "type.railway.tram.bridge" = "Trambrug"; "type.railway.tram.tunnel" = "Tramtunnel"; "type.railway.tram_stop" = "Tramhalte"; @@ -1232,27 +1249,27 @@ "type.shop.bathroom_furnishing" = "Badkamerinrichting"; "type.shop.beauty" = "Schoonheidssalon"; "type.shop.beauty.nails" = "Nagelsalon"; -"type.shop.beverages" = "Drank"; +"type.shop.beverages" = "Drankenwinkel"; "type.shop.bicycle" = "Fietsenwinkel"; -"type.shop.bookmaker" = "Boekbinder"; +"type.shop.bookmaker" = "Wedkantoor"; "type.shop.books" = "Boekwinkel"; "type.shop.butcher" = "Slager"; "type.shop.cannabis" = "Coffeeshop"; -"type.shop.car" = "Autohandelaar"; -"type.shop.car_parts" = "Auto-onderdelen"; -"type.shop.car_repair" = "Auto reparatie"; +"type.shop.car" = "Autodealer"; +"type.shop.car_parts" = "Auto-onderdelenwinkel"; +"type.shop.car_repair" = "Autogarage"; "type.shop.car_repair.tyres" = "Bandenreparatie"; -"type.shop.caravan" = "Caravan en camper verkoper"; +"type.shop.caravan" = "Caravan- en camperdealer"; "type.shop.carpet" = "Tapijtenwinkel"; "type.shop.chemist" = "Drogisterij"; "type.shop.chocolate" = "Chocolaterie"; "type.shop.clothes" = "Kledingwinkel"; -"type.shop.coffee" = "Koffieverkoop"; +"type.shop.coffee" = "Koffiewinkel"; "type.shop.computer" = "Computerwinkel"; "type.shop.confectionery" = "Snoepwinkel"; -"type.shop.convenience" = "Buurtwinkel"; +"type.shop.convenience" = "Gemakswinkel"; "type.shop.copyshop" = "Kopieerwinkel"; -"type.shop.cosmetics" = "Schoonheidsmiddelen"; +"type.shop.cosmetics" = "Cosmeticawinkel"; "type.shop.curtain" = "Gordijnenwinkel"; "type.shop.deli" = "Delicatessenwinkel"; "type.shop.department_store" = "Warenhuis"; @@ -1262,21 +1279,21 @@ "type.shop.erotic" = "Erotiekwinkel"; "type.shop.fabric" = "Stoffenwinkel"; "type.shop.farm" = "Boerderijwinkel"; -"type.shop.fashion_accessories" = "Modeaccessoires"; +"type.shop.fashion_accessories" = "Modeaccessoirewinkel"; "type.shop.florist" = "Bloemist"; -"type.shop.funeral_directors" = "Begrafenisondernemer"; +"type.shop.funeral_directors" = "Uitvaartondernemer"; "type.shop.furniture" = "Meubelwinkel"; "type.shop.garden_centre" = "Tuincentrum"; "type.shop.gas" = "Gaswinkel"; "type.shop.gift" = "Cadeauwinkel"; "type.shop.greengrocer" = "Groentenwinkel"; -"type.shop.grocery" = "Boodschappen"; +"type.shop.grocery" = "Kruidenierswinkel"; "type.shop.hairdresser" = "Kapper"; -"type.shop.hardware" = "IJzerwaren"; +"type.shop.hardware" = "IJzerwarenwinkel"; "type.shop.health_food" = "Natuurvoedingswinkel"; -"type.shop.hearing_aids" = "Winkel met hoortoestellen"; +"type.shop.hearing_aids" = "Hoortoestellenwinkel"; "type.shop.herbalist" = "Kruidenwinkel"; -"type.shop.hifi" = "HiFi-audio"; +"type.shop.hifi" = "Hifiwinkel"; "type.shop.houseware" = "Huishoudwinkel"; "type.shop.jewelry" = "Juwelier"; "type.shop.kiosk" = "Kiosk"; @@ -1285,18 +1302,18 @@ "type.shop.mall" = "Winkelcentrum"; "type.shop.beauty.day_spa" = "Schoonheidssalon"; "type.shop.massage" = "Massagesalon"; -"type.shop.mobile_phone" = "Mobiele telefoonwinkel"; +"type.shop.mobile_phone" = "Mobiele-telefoonwinkel"; "type.shop.money_lender" = "Geldschieter"; "type.shop.motorcycle" = "Motorzaak"; "type.shop.motorcycle_repair" = "Motorfietsreparatie"; "type.shop.music" = "Muziekwinkel"; "type.shop.musical_instrument" = "Muziekinstrumentenwinkel"; -"type.shop.newsagent" = "Kiosk"; +"type.shop.newsagent" = "Krantenkiosk"; "type.shop.optician" = "Opticien"; -"type.shop.outdoor" = "Outdooruitrusting"; +"type.shop.outdoor" = "Buitensportwinkel"; "type.shop.outpost" = "Afhaalpunt"; "type.shop.pasta" = "Pastawinkel"; -"type.shop.pastry" = "Banketbakker"; +"type.shop.pastry" = "Banketbakkerij"; "type.shop.pawnbroker" = "Pandjeshuis"; "type.shop.pet" = "Dierenwinkel"; "type.shop.pet_grooming" = "Huisdierverzorging"; @@ -1306,10 +1323,10 @@ "type.shop.seafood" = "Visboer"; "type.shop.second_hand" = "Tweedehandswinkel"; "type.shop.shoes" = "Schoenenwinkel"; -"type.shop.sports" = "Sportartikelen"; +"type.shop.sports" = "Sportwinkel"; "type.shop.stationery" = "Kantoorboekhandel"; "type.shop.supermarket" = "Supermarkt"; -"type.shop.tattoo" = "Tattoosalon"; +"type.shop.tattoo" = "Tatoeagestudio"; "type.shop.tea" = "Theewinkel"; "type.shop.telecommunication" = "Telecommunicatiewinkel"; "type.shop.ticket" = "Kaartjesverkoop"; @@ -1326,30 +1343,30 @@ "type.shop.water" = "Waterwinkel"; /* maybe change to Art Gallery for en-US when supported */ "type.shop.art" = "Kunstwinkel"; -"type.shop.baby_goods" = "Babyspullenwinkel"; +"type.shop.baby_goods" = "Babywinkel"; "type.shop.bag" = "Tassenwinkel"; "type.shop.bed" = "Beddenwinkel"; "type.shop.boutique" = "Boetiek"; "type.shop.charity" = "Kringloopwinkel"; "type.shop.cheese" = "Kaaswinkel"; -"type.shop.craft" = "Kunst en ambacht"; -"type.shop.dairy" = "Zuivelproducten"; +"type.shop.craft" = "Kunst- en hobbywinkel"; +"type.shop.dairy" = "Zuivelwinkel"; "type.shop.electrical" = "Elektronische benodigdhedenwinkel"; "type.shop.fishing" = "Viswinkel"; "type.shop.interior_decoration" = "Interieurdecoratiewinkel"; -"type.shop.lottery" = "Loten"; -"type.shop.medical_supply" = "Medische benodigdheden"; +"type.shop.lottery" = "Loterijwinkel"; +"type.shop.medical_supply" = "Medische benodigdhedenwinkel"; "type.shop.nutrition_supplements" = "Voedingssupplementenwinkel"; "type.shop.paint" = "Verfwinkel"; "type.shop.perfumery" = "Parfumerie"; -"type.shop.sewing" = "Naaibenodigdheden"; +"type.shop.sewing" = "Naai- en fourniturenwinkel"; "type.shop.storage_rental" = "Opslagverhuur"; -"type.shop.tobacco" = "Tabakszaak"; -"type.shop.trade" = "Handelsbenodigdheden"; -"type.shop.watches" = "Horlogezaak"; +"type.shop.tobacco" = "Tabakswinkel"; +"type.shop.trade" = "Technische groothandel"; +"type.shop.watches" = "Horlogewinkel"; "type.shop.wholesale" = "Groothandel"; "type.shop.lighting" = "Verlichtingswinkel"; -"type.disusedbusiness" = "Leegstand bedrijfspand"; +"type.disusedbusiness" = "Leegstaand bedrijfspand"; "type.sport" = "Sport"; "type.sport.multi" = "Diverse sporten"; "type.sport.9pin" = "Bowlen"; @@ -1362,23 +1379,23 @@ "type.sport.baseball" = "Honkbal"; "type.sport.basketball" = "Basketbal"; "type.sport.beachvolleyball" = "Beachvolleybal"; -"type.sport.bowls" = "Bowling"; +"type.sport.bowls" = "Bowls"; "type.sport.chess" = "Schaken"; "type.sport.climbing" = "Klimmen"; "type.sport.cricket" = "Cricket"; "type.sport.curling" = "Curling"; "type.sport.diving" = "Klifduiken"; -"type.sport.equestrian" = "Paardesport"; +"type.sport.equestrian" = "Paardensport"; "type.sport.field_hockey" = "Veldhockey"; -"type.sport.futsal" = "Futsal"; +"type.sport.futsal" = "Zaalvoetbal"; "type.sport.golf" = "Golf"; "type.sport.gymnastics" = "Gymnastiek"; "type.sport.handball" = "Handbal"; "type.sport.ice_hockey" = "IJshockey"; "type.sport.padel" = "Padel"; -"type.sport.pelota" = "Baskische peloton"; +"type.sport.pelota" = "Pelota"; "type.sport.scuba_diving" = "Duiken"; -"type.sport.shooting" = "schieten"; +"type.sport.shooting" = "Schietsport"; "type.sport.skateboard" = "Skateboarden"; "type.sport.skiing" = "Skiën"; "type.sport.soccer" = "Voetbal"; @@ -1391,30 +1408,30 @@ "type.tourism.aquarium" = "Aquarium"; /* Typically serviced, staff is present and food is available (compared to wilderness_hut). */ "type.tourism.alpine_hut" = "Berghut"; -"type.tourism.apartment" = "Appartement"; +"type.tourism.apartment" = "Vakantieappartement"; "type.tourism.artwork" = "Kunstwerk"; -"type.tourism.artwork.architecture" = "Kunstwerk"; +"type.tourism.artwork.architecture" = "Architectonisch kunstwerk"; "type.tourism.artwork.painting" = "Schilderij"; -"type.tourism.artwork.sculpture" = "Kunstwerk"; +"type.tourism.artwork.sculpture" = "Sculptuur"; "type.tourism.artwork.statue" = "Standbeeld"; -"type.tourism.attraction" = "Toeristische attractie"; +"type.tourism.attraction" = "Bezienswaardigheid"; "type.attraction" = "Attractie"; -"type.attraction.amusement_ride" = "Prettocht"; +"type.attraction.amusement_ride" = "Attractie"; "type.attraction.animal" = "Dierenverblijf"; "type.attraction.bumper_car" = "Botsauto's"; "type.attraction.big_wheel" = "Reuzenrad"; -"type.attraction.carousel" = "Reuzenrad"; +"type.attraction.carousel" = "Draaimolen"; "type.attraction.historic" = "Historische attractie"; "type.attraction.maze" = "Doolhof"; "type.attraction.roller_coaster" = "Achtbaan"; "type.attraction.water_slide" = "Waterglijbaan"; "type.tourism.attraction.specified" = "Attractie"; -"type.tourism.camp_site" = "Kampeerterrein"; -"type.tourism.caravan_site" = "Caravan site"; +"type.tourism.camp_site" = "Camping"; +"type.tourism.caravan_site" = "Caravan- en camperplaats"; /* A rentable countryside vacation house. */ "type.tourism.chalet" = "Vakantiehuis"; -"type.tourism.gallery" = "Galerij"; -"type.tourism.guest_house" = "Gasthuis"; +"type.tourism.gallery" = "Galerie"; +"type.tourism.guest_house" = "Pension"; "type.tourism.hostel" = "Jeugdherberg"; "type.tourism.hotel" = "Hotel"; "type.tourism.information" = "Toeristische informatie"; @@ -1422,22 +1439,22 @@ "type.tourism.information.guidepost" = "Wegwijzer"; "type.tourism.information.map" = "Toeristische kaart"; "type.tourism.information.tactile_map" = "Tactiele kaart"; -"type.tourism.information.office" = "Toeristische informatie"; +"type.tourism.information.office" = "Toeristeninformatiepunt"; "type.tourism.information.visitor_centre" = "Bezoekerscentrum"; -"type.amenity.ranger_station" = "Boswachterkantoor"; +"type.amenity.ranger_station" = "Boswachterskantoor"; "type.tourism.motel" = "Motel"; "type.tourism.museum" = "Museum"; -"type.tourism.picnic_site" = "Picnicplaats"; +"type.tourism.picnic_site" = "Picknickplaats"; "type.leisure.resort" = "Complex"; "type.tourism.theme_park" = "Attractiepark"; "type.tourism.viewpoint" = "Uitkijkpunt"; /* Typically more basic, not staffed and free (compared to alpine_hut). */ -"type.tourism.wilderness_hut" = "Wildernishut"; +"type.tourism.wilderness_hut" = "Zelfverzorgingshut"; "type.tourism.zoo" = "Dierentuin"; "type.tourism.zoo.petting" = "Kinderboerderij"; -"type.traffic_calming" = "Snelheidsmatigende maatregel"; +"type.traffic_calming" = "Snelheidsremmende maatregel"; "type.traffic_calming.bump" = "Verkeersdrempel"; -"type.traffic_calming.hump" = "Verkeersheuvel"; +"type.traffic_calming.hump" = "Verkeersdrempel"; "type.waterway" = "Waterweg"; "type.waterway.canal" = "Kanaal"; "type.waterway.canal.tunnel" = "Kanaal"; @@ -1461,37 +1478,37 @@ "type.waterway.waterfall" = "Waterval"; "type.waterway.weir" = "Stuw"; "type.wheelchair" = "Rolstoel"; -"type.wheelchair.limited" = "Gedeeltelijk uitgerust voor gehandicapten"; -"type.wheelchair.no" = "Niet uitgerust voor gehandicapten"; -"type.wheelchair.yes" = "Uitgerust voor gehandicapten"; +"type.wheelchair.limited" = "Gedeeltelijk rolstoeltoegankelijk"; +"type.wheelchair.no" = "Niet rolstoeltoegankelijk"; +"type.wheelchair.yes" = "Volledig rolstoeltoegankelijk"; "type.aerialway.j.bar" = "Sleeplift (J)"; -"type.aerialway.magic_carpet" = "Loopband"; -"type.aerialway.platter" = "Sleeplift"; +"type.aerialway.magic_carpet" = "Lopende band (skilift)"; +"type.aerialway.platter" = "Pannenkoeklift"; "type.aerialway.rope_tow" = "Touwlift"; "type.aerialway.t.bar" = "Sleeplift (T-beugel)"; -"type.piste_type.downhill" = "Ski-afdaling"; -"type.piste_type.downhill.area" = "Ski-afdaling"; -"type.piste_type.downhill.advanced" = "Moeilijke afdaling"; -"type.piste_type.downhill.advanced.area" = "Moeilijke afdaling"; -"type.piste_type.downhill.easy" = "Makkelijke afdaling"; -"type.piste_type.downhill.easy.area" = "Makkelijke afdaling"; -"type.piste_type.downhill.expert" = "Afdaling voor experts"; -"type.piste_type.downhill.expert.area" = "Afdaling voor experts"; -"type.piste_type.downhill.freeride" = "Ski-afdaling"; -"type.piste_type.downhill.intermediate" = "Gemiddelde afdaling"; -"type.piste_type.downhill.intermediate.area" = "Gemiddelde afdaling"; -"type.piste_type.downhill.novice" = "Beginnersafdaling"; -"type.piste_type.downhill.novice.area" = "Beginnersafdaling"; +"type.piste_type.downhill" = "Skipiste"; +"type.piste_type.downhill.area" = "Skipiste"; +"type.piste_type.downhill.advanced" = "Moeilijke skipiste"; +"type.piste_type.downhill.advanced.area" = "Moeilijke skipiste"; +"type.piste_type.downhill.easy" = "Makkelijke skipiste"; +"type.piste_type.downhill.easy.area" = "Makkelijke skipiste"; +"type.piste_type.downhill.expert" = "Zeer moeilijke skipiste"; +"type.piste_type.downhill.expert.area" = "Zeer moeilijke skipiste"; +"type.piste_type.downhill.freeride" = "Freeride ski-afdaling"; +"type.piste_type.downhill.intermediate" = "Gemiddelde skipiste"; +"type.piste_type.downhill.intermediate.area" = "Gemiddelde skipiste"; +"type.piste_type.downhill.novice" = "Beginnersskipiste"; +"type.piste_type.downhill.novice.area" = "Beginnersskipiste"; "type.piste_type.nordic" = "Langlaufroute"; "type.piste_type.sled" = "Sleebaan"; "type.piste_type.sled.area" = "Sleebaan"; "type.piste_type.snow_park" = "Sneeuwpark"; "type.piste_type.hike" = "Sneeuwwandelpad"; "type.piste_type.connection" = "Pisteverbinding"; -"type.piste_type.skitour" = "Skitourspoor"; +"type.piste_type.skitour" = "Toerskiroute"; "type.amenity.events_venue" = "Evenementenlocatie"; "type.shop.auction" = "Veiling"; -"type.shop.collector" = "Verzamelobjecten"; +"type.shop.collector" = "Verzamelaarswinkel"; "type.self_service.yes" = "Zelfbediening beschikbaar"; "type.self_service.only" = "Alleen zelfbediening"; "type.self_service.partially" = "Gedeeltelijke zelfbediening"; @@ -1500,8 +1517,8 @@ "type.amenity.social_facility" = "Sociale voorziening"; "type.social_facility.soup_kitchen" = "Gaarkeuken"; "type.social_facility.food_bank" = "Voedselbank"; -"type.amenity.food_sharing" = "Voedsel delen"; -"type.amenity.give_box" = "Giftenkist"; +"type.amenity.food_sharing" = "Voedseldeelpunt"; +"type.amenity.give_box" = "Weggeefkast"; /* https://wiki.openstreetmap.org/wiki/Tag:emergency=emergency_ward_entrance */ "type.emergency.emergency_ward_entrance" = "Ingang noodafdeling"; /* https://wiki.openstreetmap.org/wiki/Tag:amenity=dojo */ @@ -1511,7 +1528,7 @@ "type.xmas.tree" = "Kerstboom"; "type.leisure.sports_centre.sport.four_square" = "Sportcentrum"; "type.sport.four_square" = "Four Square"; -"type.sport.boules" = "jeu de boules"; +"type.sport.boules" = "Jeu de boules"; "type.leisure.sports_centre.sport.boules" = "Sportcentrum"; "type.sport.pickleball" = "Pickleball"; "type.leisure.sports_centre.sport.pickleball" = "Sportcentrum"; diff --git a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings index d1ebcf6e2..f33295bc8 100644 --- a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Przystanek autobusowy"; "type.highway.construction" = "Droga w trakcie budowy"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Droga w trakcie budowy"; +"type.highway.construction.motorway_link" = "Droga w trakcie budowy"; +"type.highway.construction.trunk" = "Droga w trakcie budowy"; +"type.highway.construction.trunk_link" = "Droga w trakcie budowy"; +"type.highway.construction.primary" = "Droga w trakcie budowy"; +"type.highway.construction.primary_link" = "Droga w trakcie budowy"; +"type.highway.construction.secondary" = "Droga w trakcie budowy"; +"type.highway.construction.secondary_link" = "Droga w trakcie budowy"; +"type.highway.construction.tertiary" = "Droga w trakcie budowy"; +"type.highway.construction.tertiary_link" = "Droga w trakcie budowy"; +"type.highway.construction.residential" = "Droga w trakcie budowy"; +"type.highway.construction.unclassified" = "Droga w trakcie budowy"; +"type.highway.construction.service" = "Droga w trakcie budowy"; +"type.highway.construction.living_street" = "Droga w trakcie budowy"; +"type.highway.construction.road" = "Droga w trakcie budowy"; +"type.highway.construction.track" = "Droga w trakcie budowy"; "type.highway.cycleway" = "Droga rowerowa"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most drogowy dla rowerów"; diff --git a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings index c72164138..a838dc594 100644 --- a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Ponto de ônibus"; "type.highway.construction" = "Via em construção"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Via em construção"; +"type.highway.construction.motorway_link" = "Via em construção"; +"type.highway.construction.trunk" = "Via em construção"; +"type.highway.construction.trunk_link" = "Via em construção"; +"type.highway.construction.primary" = "Via em construção"; +"type.highway.construction.primary_link" = "Via em construção"; +"type.highway.construction.secondary" = "Via em construção"; +"type.highway.construction.secondary_link" = "Via em construção"; +"type.highway.construction.tertiary" = "Via em construção"; +"type.highway.construction.tertiary_link" = "Via em construção"; +"type.highway.construction.residential" = "Via em construção"; +"type.highway.construction.unclassified" = "Via em construção"; +"type.highway.construction.service" = "Via em construção"; +"type.highway.construction.living_street" = "Via em construção"; +"type.highway.construction.road" = "Via em construção"; +"type.highway.construction.track" = "Via em construção"; "type.highway.cycleway" = "Ciclovia"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte para ciclistas"; diff --git a/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings index 3a898d591..7f20555bb 100644 --- a/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings index 66f873814..7a36841f0 100644 --- a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Paragem de autocarros"; "type.highway.construction" = "Estrada em construção"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Estrada em construção"; +"type.highway.construction.motorway_link" = "Estrada em construção"; +"type.highway.construction.trunk" = "Estrada em construção"; +"type.highway.construction.trunk_link" = "Estrada em construção"; +"type.highway.construction.primary" = "Estrada em construção"; +"type.highway.construction.primary_link" = "Estrada em construção"; +"type.highway.construction.secondary" = "Estrada em construção"; +"type.highway.construction.secondary_link" = "Estrada em construção"; +"type.highway.construction.tertiary" = "Estrada em construção"; +"type.highway.construction.tertiary_link" = "Estrada em construção"; +"type.highway.construction.residential" = "Estrada em construção"; +"type.highway.construction.unclassified" = "Estrada em construção"; +"type.highway.construction.service" = "Estrada em construção"; +"type.highway.construction.living_street" = "Estrada em construção"; +"type.highway.construction.road" = "Estrada em construção"; +"type.highway.construction.track" = "Estrada em construção"; "type.highway.cycleway" = "Ciclovia"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings index c6c7fd80c..8942353a6 100644 --- a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Stație de autobuz"; "type.highway.construction" = "Drum în construcție"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Drum în construcție"; +"type.highway.construction.motorway_link" = "Drum în construcție"; +"type.highway.construction.trunk" = "Drum în construcție"; +"type.highway.construction.trunk_link" = "Drum în construcție"; +"type.highway.construction.primary" = "Drum în construcție"; +"type.highway.construction.primary_link" = "Drum în construcție"; +"type.highway.construction.secondary" = "Drum în construcție"; +"type.highway.construction.secondary_link" = "Drum în construcție"; +"type.highway.construction.tertiary" = "Drum în construcție"; +"type.highway.construction.tertiary_link" = "Drum în construcție"; +"type.highway.construction.residential" = "Drum în construcție"; +"type.highway.construction.unclassified" = "Drum în construcție"; +"type.highway.construction.service" = "Drum în construcție"; +"type.highway.construction.living_street" = "Drum în construcție"; +"type.highway.construction.road" = "Drum în construcție"; +"type.highway.construction.track" = "Drum în construcție"; "type.highway.cycleway" = "Pistă de Biciclete"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pod"; diff --git a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings index 3ad63b87d..659f41691 100644 --- a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Тоннель"; "type.highway.bus_stop" = "Остановка"; "type.highway.construction" = "Строящаяся дорога"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Строящаяся дорога"; +"type.highway.construction.motorway_link" = "Строящаяся дорога"; +"type.highway.construction.trunk" = "Строящаяся дорога"; +"type.highway.construction.trunk_link" = "Строящаяся дорога"; +"type.highway.construction.primary" = "Строящаяся дорога"; +"type.highway.construction.primary_link" = "Строящаяся дорога"; +"type.highway.construction.secondary" = "Строящаяся дорога"; +"type.highway.construction.secondary_link" = "Строящаяся дорога"; +"type.highway.construction.tertiary" = "Строящаяся дорога"; +"type.highway.construction.tertiary_link" = "Строящаяся дорога"; +"type.highway.construction.residential" = "Строящаяся дорога"; +"type.highway.construction.unclassified" = "Строящаяся дорога"; +"type.highway.construction.service" = "Строящаяся дорога"; +"type.highway.construction.living_street" = "Строящаяся дорога"; +"type.highway.construction.road" = "Строящаяся дорога"; +"type.highway.construction.track" = "Строящаяся дорога"; "type.highway.cycleway" = "Велодорожка"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings index 818ee970b..33e2867eb 100644 --- a/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings @@ -433,6 +433,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; "type.highway.cycleway.bridge" = "Bridge"; "type.highway.cycleway.permissive" = "Cycle Path"; diff --git a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings index 3a58f841c..bbc14a551 100644 --- a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Cesta vo výstavbe"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Cesta vo výstavbe"; +"type.highway.construction.motorway_link" = "Cesta vo výstavbe"; +"type.highway.construction.trunk" = "Cesta vo výstavbe"; +"type.highway.construction.trunk_link" = "Cesta vo výstavbe"; +"type.highway.construction.primary" = "Cesta vo výstavbe"; +"type.highway.construction.primary_link" = "Cesta vo výstavbe"; +"type.highway.construction.secondary" = "Cesta vo výstavbe"; +"type.highway.construction.secondary_link" = "Cesta vo výstavbe"; +"type.highway.construction.tertiary" = "Cesta vo výstavbe"; +"type.highway.construction.tertiary_link" = "Cesta vo výstavbe"; +"type.highway.construction.residential" = "Cesta vo výstavbe"; +"type.highway.construction.unclassified" = "Cesta vo výstavbe"; +"type.highway.construction.service" = "Cesta vo výstavbe"; +"type.highway.construction.living_street" = "Cesta vo výstavbe"; +"type.highway.construction.road" = "Cesta vo výstavbe"; +"type.highway.construction.track" = "Cesta vo výstavbe"; "type.highway.cycleway" = "Cyklocesta"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings index b8b71c21c..df6c606b6 100644 --- a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Predor"; "type.highway.bus_stop" = "Avtobusno postajališče"; "type.highway.construction" = "Cesta v gradnji"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Cesta v gradnji"; +"type.highway.construction.motorway_link" = "Cesta v gradnji"; +"type.highway.construction.trunk" = "Cesta v gradnji"; +"type.highway.construction.trunk_link" = "Cesta v gradnji"; +"type.highway.construction.primary" = "Cesta v gradnji"; +"type.highway.construction.primary_link" = "Cesta v gradnji"; +"type.highway.construction.secondary" = "Cesta v gradnji"; +"type.highway.construction.secondary_link" = "Cesta v gradnji"; +"type.highway.construction.tertiary" = "Cesta v gradnji"; +"type.highway.construction.tertiary_link" = "Cesta v gradnji"; +"type.highway.construction.residential" = "Cesta v gradnji"; +"type.highway.construction.unclassified" = "Cesta v gradnji"; +"type.highway.construction.service" = "Cesta v gradnji"; +"type.highway.construction.living_street" = "Cesta v gradnji"; +"type.highway.construction.road" = "Cesta v gradnji"; +"type.highway.construction.track" = "Cesta v gradnji"; "type.highway.cycleway" = "Kolesarska steza"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Most"; diff --git a/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings index 562c34578..3e24089a2 100644 --- a/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings index 04faa5d22..8ee3cc69f 100644 --- a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Аутобуско стајалиште"; "type.highway.construction" = "Пут у изградњи"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Пут у изградњи"; +"type.highway.construction.motorway_link" = "Пут у изградњи"; +"type.highway.construction.trunk" = "Пут у изградњи"; +"type.highway.construction.trunk_link" = "Пут у изградњи"; +"type.highway.construction.primary" = "Пут у изградњи"; +"type.highway.construction.primary_link" = "Пут у изградњи"; +"type.highway.construction.secondary" = "Пут у изградњи"; +"type.highway.construction.secondary_link" = "Пут у изградњи"; +"type.highway.construction.tertiary" = "Пут у изградњи"; +"type.highway.construction.tertiary_link" = "Пут у изградњи"; +"type.highway.construction.residential" = "Пут у изградњи"; +"type.highway.construction.unclassified" = "Пут у изградњи"; +"type.highway.construction.service" = "Пут у изградњи"; +"type.highway.construction.living_street" = "Пут у изградњи"; +"type.highway.construction.road" = "Пут у изградњи"; +"type.highway.construction.track" = "Пут у изградњи"; "type.highway.cycleway" = "Бициклистичка стаза"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings index fb7710792..fbfff9ca6 100644 --- a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busshållplats"; "type.highway.construction" = "Väg under uppförande"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Väg under uppförande"; +"type.highway.construction.motorway_link" = "Väg under uppförande"; +"type.highway.construction.trunk" = "Väg under uppförande"; +"type.highway.construction.trunk_link" = "Väg under uppförande"; +"type.highway.construction.primary" = "Väg under uppförande"; +"type.highway.construction.primary_link" = "Väg under uppförande"; +"type.highway.construction.secondary" = "Väg under uppförande"; +"type.highway.construction.secondary_link" = "Väg under uppförande"; +"type.highway.construction.tertiary" = "Väg under uppförande"; +"type.highway.construction.tertiary_link" = "Väg under uppförande"; +"type.highway.construction.residential" = "Väg under uppförande"; +"type.highway.construction.unclassified" = "Väg under uppförande"; +"type.highway.construction.service" = "Väg under uppförande"; +"type.highway.construction.living_street" = "Väg under uppförande"; +"type.highway.construction.road" = "Väg under uppförande"; +"type.highway.construction.track" = "Väg under uppförande"; "type.highway.cycleway" = "Cykelbana"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Cykelbro"; diff --git a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings index 2e55cf16e..6ad6c4177 100644 --- a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Mtaro"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Barabara inatengenezwa"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Barabara inatengenezwa"; +"type.highway.construction.motorway_link" = "Barabara inatengenezwa"; +"type.highway.construction.trunk" = "Barabara inatengenezwa"; +"type.highway.construction.trunk_link" = "Barabara inatengenezwa"; +"type.highway.construction.primary" = "Barabara inatengenezwa"; +"type.highway.construction.primary_link" = "Barabara inatengenezwa"; +"type.highway.construction.secondary" = "Barabara inatengenezwa"; +"type.highway.construction.secondary_link" = "Barabara inatengenezwa"; +"type.highway.construction.tertiary" = "Barabara inatengenezwa"; +"type.highway.construction.tertiary_link" = "Barabara inatengenezwa"; +"type.highway.construction.residential" = "Barabara inatengenezwa"; +"type.highway.construction.unclassified" = "Barabara inatengenezwa"; +"type.highway.construction.service" = "Barabara inatengenezwa"; +"type.highway.construction.living_street" = "Barabara inatengenezwa"; +"type.highway.construction.road" = "Barabara inatengenezwa"; +"type.highway.construction.track" = "Barabara inatengenezwa"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Daraja"; diff --git a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings index afabc5b62..49b9ff78f 100644 --- a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "சுரங்கப்பாதை"; "type.highway.bus_stop" = "பேருந்து நிறுத்தம்"; "type.highway.construction" = "சாலை கட்டுமானத்தில் உள்ளது"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.motorway_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.trunk" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.trunk_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.primary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.primary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.secondary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.secondary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.tertiary" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.tertiary_link" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.residential" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.unclassified" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.service" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.living_street" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.road" = "சாலை கட்டுமானத்தில் உள்ளது"; +"type.highway.construction.track" = "சாலை கட்டுமானத்தில் உள்ளது"; "type.highway.cycleway" = "சைக்கிள் பாதை"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "பாலம்"; diff --git a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings index 539dee496..ce4406171 100644 --- a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "อุโมงค์"; "type.highway.bus_stop" = "ป้ายรถเมล์"; "type.highway.construction" = "ทางกำลังอยู่ในการก่อสร้าง"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.motorway_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.trunk" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.trunk_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.primary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.primary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.secondary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.secondary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.tertiary" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.tertiary_link" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.residential" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.unclassified" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.service" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.living_street" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.road" = "ทางกำลังอยู่ในการก่อสร้าง"; +"type.highway.construction.track" = "ทางกำลังอยู่ในการก่อสร้าง"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "สะพาน"; diff --git a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings index 64c184d8c..69bb7354f 100644 --- a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tünel"; "type.highway.bus_stop" = "Otobüs Durağı"; "type.highway.construction" = "Yapım Aşamasında Yol"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Yapım Aşamasında Yol"; +"type.highway.construction.motorway_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.trunk" = "Yapım Aşamasında Yol"; +"type.highway.construction.trunk_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.primary" = "Yapım Aşamasında Yol"; +"type.highway.construction.primary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.secondary" = "Yapım Aşamasında Yol"; +"type.highway.construction.secondary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.tertiary" = "Yapım Aşamasında Yol"; +"type.highway.construction.tertiary_link" = "Yapım Aşamasında Yol"; +"type.highway.construction.residential" = "Yapım Aşamasında Yol"; +"type.highway.construction.unclassified" = "Yapım Aşamasında Yol"; +"type.highway.construction.service" = "Yapım Aşamasında Yol"; +"type.highway.construction.living_street" = "Yapım Aşamasında Yol"; +"type.highway.construction.road" = "Yapım Aşamasında Yol"; +"type.highway.construction.track" = "Yapım Aşamasında Yol"; "type.highway.cycleway" = "Bisiklet Yolu"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Köprü"; diff --git a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings index f77c8e226..fbd7b8bed 100644 --- a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Тунель"; "type.highway.bus_stop" = "Зупинка"; "type.highway.construction" = "Дорога, що будується"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Дорога, що будується"; +"type.highway.construction.motorway_link" = "Дорога, що будується"; +"type.highway.construction.trunk" = "Дорога, що будується"; +"type.highway.construction.trunk_link" = "Дорога, що будується"; +"type.highway.construction.primary" = "Дорога, що будується"; +"type.highway.construction.primary_link" = "Дорога, що будується"; +"type.highway.construction.secondary" = "Дорога, що будується"; +"type.highway.construction.secondary_link" = "Дорога, що будується"; +"type.highway.construction.tertiary" = "Дорога, що будується"; +"type.highway.construction.tertiary_link" = "Дорога, що будується"; +"type.highway.construction.residential" = "Дорога, що будується"; +"type.highway.construction.unclassified" = "Дорога, що будується"; +"type.highway.construction.service" = "Дорога, що будується"; +"type.highway.construction.living_street" = "Дорога, що будується"; +"type.highway.construction.road" = "Дорога, що будується"; +"type.highway.construction.track" = "Дорога, що будується"; "type.highway.cycleway" = "Велодоріжка"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Міст"; diff --git a/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings index 436fc328c..43e3bdac2 100644 --- a/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Road Under Construction"; +"type.highway.construction.motorway_link" = "Road Under Construction"; +"type.highway.construction.trunk" = "Road Under Construction"; +"type.highway.construction.trunk_link" = "Road Under Construction"; +"type.highway.construction.primary" = "Road Under Construction"; +"type.highway.construction.primary_link" = "Road Under Construction"; +"type.highway.construction.secondary" = "Road Under Construction"; +"type.highway.construction.secondary_link" = "Road Under Construction"; +"type.highway.construction.tertiary" = "Road Under Construction"; +"type.highway.construction.tertiary_link" = "Road Under Construction"; +"type.highway.construction.residential" = "Road Under Construction"; +"type.highway.construction.unclassified" = "Road Under Construction"; +"type.highway.construction.service" = "Road Under Construction"; +"type.highway.construction.living_street" = "Road Under Construction"; +"type.highway.construction.road" = "Road Under Construction"; +"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings index d0812b31a..21f088a08 100644 --- a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "Đường hầm"; "type.highway.bus_stop" = "Bến xe buýt"; "type.highway.construction" = "Đường đang thi công"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "Đường đang thi công"; +"type.highway.construction.motorway_link" = "Đường đang thi công"; +"type.highway.construction.trunk" = "Đường đang thi công"; +"type.highway.construction.trunk_link" = "Đường đang thi công"; +"type.highway.construction.primary" = "Đường đang thi công"; +"type.highway.construction.primary_link" = "Đường đang thi công"; +"type.highway.construction.secondary" = "Đường đang thi công"; +"type.highway.construction.secondary_link" = "Đường đang thi công"; +"type.highway.construction.tertiary" = "Đường đang thi công"; +"type.highway.construction.tertiary_link" = "Đường đang thi công"; +"type.highway.construction.residential" = "Đường đang thi công"; +"type.highway.construction.unclassified" = "Đường đang thi công"; +"type.highway.construction.service" = "Đường đang thi công"; +"type.highway.construction.living_street" = "Đường đang thi công"; +"type.highway.construction.road" = "Đường đang thi công"; +"type.highway.construction.track" = "Đường đang thi công"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Cầu"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings index 04034ad3b..94aa5ef97 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公交站"; "type.highway.construction" = "在建道路"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "在建道路"; +"type.highway.construction.motorway_link" = "在建道路"; +"type.highway.construction.trunk" = "在建道路"; +"type.highway.construction.trunk_link" = "在建道路"; +"type.highway.construction.primary" = "在建道路"; +"type.highway.construction.primary_link" = "在建道路"; +"type.highway.construction.secondary" = "在建道路"; +"type.highway.construction.secondary_link" = "在建道路"; +"type.highway.construction.tertiary" = "在建道路"; +"type.highway.construction.tertiary_link" = "在建道路"; +"type.highway.construction.residential" = "在建道路"; +"type.highway.construction.unclassified" = "在建道路"; +"type.highway.construction.service" = "在建道路"; +"type.highway.construction.living_street" = "在建道路"; +"type.highway.construction.road" = "在建道路"; +"type.highway.construction.track" = "在建道路"; "type.highway.cycleway" = "自行车道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "桥"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings index 17f755638..bf2bdc376 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings @@ -452,6 +452,23 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公車站"; "type.highway.construction" = "施工中道路"; +/* These translations are used for all type.highway.construction.*. */ +"type.highway.construction.motorway" = "施工中道路"; +"type.highway.construction.motorway_link" = "施工中道路"; +"type.highway.construction.trunk" = "施工中道路"; +"type.highway.construction.trunk_link" = "施工中道路"; +"type.highway.construction.primary" = "施工中道路"; +"type.highway.construction.primary_link" = "施工中道路"; +"type.highway.construction.secondary" = "施工中道路"; +"type.highway.construction.secondary_link" = "施工中道路"; +"type.highway.construction.tertiary" = "施工中道路"; +"type.highway.construction.tertiary_link" = "施工中道路"; +"type.highway.construction.residential" = "施工中道路"; +"type.highway.construction.unclassified" = "施工中道路"; +"type.highway.construction.service" = "施工中道路"; +"type.highway.construction.living_street" = "施工中道路"; +"type.highway.construction.road" = "施工中道路"; +"type.highway.construction.track" = "施工中道路"; "type.highway.cycleway" = "單車道"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "橋樑"; From 1fd20a3bda9ef7206e3f08cdd727bdb8eda8ce45 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 9 May 2026 14:49:07 +0300 Subject: [PATCH 242/252] [routing] Documentation Signed-off-by: mvglasow --- libs/routing/edge_estimator.hpp | 3 +++ libs/routing/index_graph.hpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/libs/routing/edge_estimator.hpp b/libs/routing/edge_estimator.hpp index 9db432b5c..f224c44c8 100644 --- a/libs/routing/edge_estimator.hpp +++ b/libs/routing/edge_estimator.hpp @@ -126,6 +126,9 @@ class EdgeEstimator * * The penalty is a fixed amount of time, determined by the implementation. * + * U turns are determined and the penalty is applied in `IndexGraph::GetPenalties()`, + * in (`index_graph.cpp`). Actual U turn detection is deferred to `IsUTurn()` in the same file. + * * @param purpose The purpose for which the result is to be used. * @return The penalty in seconds. */ diff --git a/libs/routing/index_graph.hpp b/libs/routing/index_graph.hpp index 66c553089..56c0cec12 100644 --- a/libs/routing/index_graph.hpp +++ b/libs/routing/index_graph.hpp @@ -28,6 +28,20 @@ namespace routing { +/** + * @brief Whether a maneuver between two segments is a U turn. + * + * A maneuver between two segments `u` and `v` is a U turn if, and only if, both segments differ in + * direction but are otherwise identical. + * + * For U turns on multiple-carriageway roads, i.e. from one carriageway onto a crossing road (or + * connecting cariageway) and further to the opposite cariageway, this function returns false. + * + * @param u The first segment + * @param v The second segment + * + * @return True if the maneuver is a U turn on the same segment, false otherwise. + */ bool IsUTurn(Segment const & u, Segment const & v); enum class WorldGraphMode; From 1b9cb87b9aea73d6c5667009b1be8d4d06382ab2 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 9 May 2026 14:52:26 +0300 Subject: [PATCH 243/252] [traffic] Comments Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 91eaa0035..72ebd6eba 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -533,10 +533,20 @@ void OpenLrV3TraffDecoder::DecodeLocation(traffxml::TraffMessage & message, traf double RoutingTraffDecoder::TraffEstimator::GetUTurnPenalty(Purpose /* purpose */) const { - // Adds 2 minutes penalty for U-turn. The value is quite arbitrary - // and needs to be properly selected after a number of real-world - // experiments. - return 2 * 60; // seconds + /* + * Copied from `CarEstimator::GetUTurnPenalty()`, see comments there. + * + * For the decoder use case, there should never be a need to call this function: Only going back + * on the exact segment is considered a U turn, going back on the opposite carriageway (i.e. on + * a different segment) is not. + * + * Such maneuvers would only be needed when the vehicle is heading in the direction opposite to + * the one in which it needs to travel, which should only occur at the start of a route (or after + * recalculation) and should not happen at all when decoding locations. + * + * U turns on multiple-carriageway roads are addressed by `GetTurnPenalty()`. + */ + return 2 * 60; // seconds, or meters (before penalty) for the decoder use case } double RoutingTraffDecoder::TraffEstimator::GetTurnPenalty(Purpose /* purpose */, double angle, From 84e40907e3b1875eb4564909edefa3619888ffa3 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 9 May 2026 17:24:22 +0300 Subject: [PATCH 244/252] [traffic] Consider construction types in IsRamp() Signed-off-by: mvglasow --- libs/traffxml/traff_decoder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index 72ebd6eba..f7590d759 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -1494,10 +1494,15 @@ bool IsRamp(routing::HighwayType highwayType) switch(highwayType) { case routing::HighwayType::HighwayMotorwayLink: + case routing::HighwayType::HighwayConstructionMotorwayLink: case routing::HighwayType::HighwayTrunkLink: + case routing::HighwayType::HighwayConstructionTrunkLink: case routing::HighwayType::HighwayPrimaryLink: + case routing::HighwayType::HighwayConstructionPrimaryLink: case routing::HighwayType::HighwaySecondaryLink: + case routing::HighwayType::HighwayConstructionSecondaryLink: case routing::HighwayType::HighwayTertiaryLink: + case routing::HighwayType::HighwayConstructionTertiaryLink: return true; default: return false; From 2b4b739296029a5f6bc918ea94a691c3f96b3e25 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sat, 9 May 2026 18:52:27 +0300 Subject: [PATCH 245/252] [generator] Resolve comments from #4200 Re-use deprecated type IDs instead of new ones Simplify InOutCitySpeedKMpH for construction types Disallow through traffic for construction types Signed-off-by: mvglasow --- data/mapcss-mapping.csv | 48 +++++++++---------------- libs/routing_common/car_model.cpp | 18 +++++----- libs/routing_common/car_model_coefs.hpp | 34 +++++++++--------- libs/routing_common/vehicle_model.hpp | 32 ++++++++--------- 4 files changed, 58 insertions(+), 74 deletions(-) diff --git a/data/mapcss-mapping.csv b/data/mapcss-mapping.csv index 03ac40174..7f0470359 100644 --- a/data/mapcss-mapping.csv +++ b/data/mapcss-mapping.csv @@ -916,22 +916,22 @@ leisure|sports_centre|sport|soccer;[leisure=sports_centre][sport=soccer];;name;i leisure|sports_centre|sport|four_square;[leisure=sports_centre][sport=four_square];;name;int_name;787; leisure|sports_centre|sport|boules;[leisure=sports_centre][sport=boules];;name;int_name;788; leisure|sports_centre|sport|pickleball;[leisure=sports_centre][sport=pickleball];;name;int_name;789; -deprecated|deprecated;790;x -deprecated|deprecated;791;x -deprecated|deprecated;792;x -deprecated|deprecated;793;x -deprecated|deprecated;794;x -deprecated|deprecated;795;x -deprecated|deprecated;796;x -deprecated|deprecated;797;x -deprecated|deprecated;798;x -deprecated|deprecated;799;x -deprecated|deprecated;800;x -deprecated|deprecated;801;x -deprecated|deprecated;802;x -deprecated|deprecated;803;x -deprecated|deprecated;804;x -deprecated|deprecated;805;x +highway|construction|motorway;[highway=construction][construction=motorway];;name;int_name;790; +highway|construction|motorway_link;[highway=construction][construction=motorway_link];;name;int_name;791; +highway|construction|trunk;[highway=construction][construction=trunk];;name;int_name;792; +highway|construction|trunk_link;[highway=construction][construction=trunk_link];;name;int_name;793; +highway|construction|primary;[highway=construction][construction=primary];;name;int_name;794; +highway|construction|primary_link;[highway=construction][construction=primary_link];;name;int_name;795; +highway|construction|secondary;[highway=construction][construction=secondary];;name;int_name;796; +highway|construction|secondary_link;[highway=construction][construction=secondary_link];;name;int_name;797; +highway|construction|tertiary;[highway=construction][construction=tertiary];;name;int_name;798; +highway|construction|tertiary_link;[highway=construction][construction=tertiary_link];;name;int_name;799; +highway|construction|residential;[highway=construction][construction=residential];;name;int_name;800; +highway|construction|unclassified;[highway=construction][construction=unclassified];;name;int_name;801; +highway|construction|service;[highway=construction][construction=service];;name;int_name;802; +highway|construction|living_street;[highway=construction][construction=living_street];;name;int_name;803; +highway|construction|road;[highway=construction][construction=road];;name;int_name;804; +highway|construction|track;[highway=construction][construction=track];;name;int_name;805; deprecated|deprecated;806;x deprecated|deprecated;807;x deprecated|deprecated;808;x @@ -1760,19 +1760,3 @@ amenity|luggage_locker;1629; building|guardhouse;[building=guardhouse],[amenity=security_booth],[amenity=checkpoint];;;;1630; office|security;1631; shop|lighting;1632; -highway|construction|motorway;[highway=construction][construction=motorway];;name;int_name;1633; -highway|construction|motorway_link;[highway=construction][construction=motorway_link];;name;int_name;1634; -highway|construction|trunk;[highway=construction][construction=trunk];;name;int_name;1635; -highway|construction|trunk_link;[highway=construction][construction=trunk_link];;name;int_name;1636; -highway|construction|primary;[highway=construction][construction=primary];;name;int_name;1637; -highway|construction|primary_link;[highway=construction][construction=primary_link];;name;int_name;1638; -highway|construction|secondary;[highway=construction][construction=secondary];;name;int_name;1639; -highway|construction|secondary_link;[highway=construction][construction=secondary_link];;name;int_name;1640; -highway|construction|tertiary;[highway=construction][construction=tertiary];;name;int_name;1641; -highway|construction|tertiary_link;[highway=construction][construction=tertiary_link];;name;int_name;1642; -highway|construction|residential;[highway=construction][construction=residential];;name;int_name;1643; -highway|construction|unclassified;[highway=construction][construction=unclassified];;name;int_name;1644; -highway|construction|service;[highway=construction][construction=service];;name;int_name;1645; -highway|construction|living_street;[highway=construction][construction=living_street];;name;int_name;1646; -highway|construction|road;[highway=construction][construction=road];;name;int_name;1647; -highway|construction|track;[highway=construction][construction=track];;name;int_name;1648; diff --git a/libs/routing_common/car_model.cpp b/libs/routing_common/car_model.cpp index f08a8dc88..028c8fd01 100644 --- a/libs/routing_common/car_model.cpp +++ b/libs/routing_common/car_model.cpp @@ -36,17 +36,17 @@ VehicleModel::LimitsInitList const kDefaultOptions = { {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, // The router truncates types to two levels, so we need this in addition to the long construction types - {HighwayType::HighwayConstruction, true}, + {HighwayType::HighwayConstruction, false}, // Construction conterparts to each of the types above // (needed for the map generator to include them in the routing section) - {HighwayType::HighwayConstructionMotorway, true}, {HighwayType::HighwayConstructionMotorwayLink, true}, - {HighwayType::HighwayConstructionTrunk, true}, {HighwayType::HighwayConstructionTrunkLink, true}, - {HighwayType::HighwayConstructionPrimary, true}, {HighwayType::HighwayConstructionPrimaryLink, true}, - {HighwayType::HighwayConstructionSecondary, true}, {HighwayType::HighwayConstructionSecondaryLink, true}, - {HighwayType::HighwayConstructionTertiary, true}, {HighwayType::HighwayConstructionTertiaryLink, true}, - {HighwayType::HighwayConstructionResidential, true}, {HighwayType::HighwayConstructionUnclassified, true}, - {HighwayType::HighwayConstructionService, true}, {HighwayType::HighwayConstructionLivingStreet, true}, - {HighwayType::HighwayConstructionRoad, true}, {HighwayType::HighwayConstructionTrack, true}, + {HighwayType::HighwayConstructionMotorway, false}, {HighwayType::HighwayConstructionMotorwayLink, false}, + {HighwayType::HighwayConstructionTrunk, false}, {HighwayType::HighwayConstructionTrunkLink, false}, + {HighwayType::HighwayConstructionPrimary, false}, {HighwayType::HighwayConstructionPrimaryLink, false}, + {HighwayType::HighwayConstructionSecondary, false}, {HighwayType::HighwayConstructionSecondaryLink, false}, + {HighwayType::HighwayConstructionTertiary, false}, {HighwayType::HighwayConstructionTertiaryLink, false}, + {HighwayType::HighwayConstructionResidential, false}, {HighwayType::HighwayConstructionUnclassified, false}, + {HighwayType::HighwayConstructionService, false}, {HighwayType::HighwayConstructionLivingStreet, false}, + {HighwayType::HighwayConstructionRoad, false}, {HighwayType::HighwayConstructionTrack, false}, // Non-highway types {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, {HighwayType::ManMadePier, true}}; diff --git a/libs/routing_common/car_model_coefs.hpp b/libs/routing_common/car_model_coefs.hpp index 6fcc8c62b..9e01e18a0 100644 --- a/libs/routing_common/car_model_coefs.hpp +++ b/libs/routing_common/car_model_coefs.hpp @@ -119,22 +119,22 @@ HighwayBasedSpeeds const kHighwayBasedSpeeds = { // Generic and long construction types: // They are needed for traffic location decoding but we don’t want to route drivers through // them, which is accomplished by setting their speed to kImpassableSpeedKMpH. - {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, - {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kImpassableSpeedKMpH /* in city */, kImpassableSpeedKMpH /* out city */)}, + {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, + {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, }; } // namespace routing diff --git a/libs/routing_common/vehicle_model.hpp b/libs/routing_common/vehicle_model.hpp index c9330b618..490b951f9 100644 --- a/libs/routing_common/vehicle_model.hpp +++ b/libs/routing_common/vehicle_model.hpp @@ -54,22 +54,22 @@ enum class HighwayType : uint16_t ManMadePier = 119, // HighwayConstruction and its subtypes are needed for traffic message decoding HighwayConstruction = 162, - HighwayConstructionMotorway = 1632, - HighwayConstructionMotorwayLink = 1633, - HighwayConstructionTrunk = 1634, - HighwayConstructionTrunkLink = 1635, - HighwayConstructionPrimary = 1636, - HighwayConstructionPrimaryLink = 1637, - HighwayConstructionSecondary = 1638, - HighwayConstructionSecondaryLink = 1639, - HighwayConstructionTertiary = 1640, - HighwayConstructionTertiaryLink = 1641, - HighwayConstructionResidential = 1642, - HighwayConstructionUnclassified = 1643, - HighwayConstructionService = 1644, - HighwayConstructionLivingStreet = 1645, - HighwayConstructionRoad = 1646, - HighwayConstructionTrack = 1647, + HighwayConstructionMotorway = 789, + HighwayConstructionMotorwayLink = 790, + HighwayConstructionTrunk = 791, + HighwayConstructionTrunkLink = 792, + HighwayConstructionPrimary = 793, + HighwayConstructionPrimaryLink = 794, + HighwayConstructionSecondary = 795, + HighwayConstructionSecondaryLink = 796, + HighwayConstructionTertiary = 797, + HighwayConstructionTertiaryLink = 798, + HighwayConstructionResidential = 799, + HighwayConstructionUnclassified = 800, + HighwayConstructionService = 801, + HighwayConstructionLivingStreet = 802, + HighwayConstructionRoad = 803, + HighwayConstructionTrack = 804, HighwayBridleway = 167, HighwaySecondaryLink = 176, RouteFerry = 259, From 194366e39e63717f1ebf205b89ed8144cf779d5a Mon Sep 17 00:00:00 2001 From: mvglasow Date: Wed, 20 May 2026 20:26:23 +0300 Subject: [PATCH 246/252] [iphone] Remove UI strings which are not actually translations Signed-off-by: mvglasow --- .../af.lproj/LocalizableTypes.strings | 1 - .../ar.lproj/LocalizableTypes.strings | 1 - .../ast.lproj/LocalizableTypes.strings | 17 ----------------- .../az.lproj/LocalizableTypes.strings | 1 - .../be.lproj/LocalizableTypes.strings | 17 ----------------- .../ber.lproj/LocalizableTypes.strings | 17 ----------------- .../bg.lproj/LocalizableTypes.strings | 1 - .../bn.lproj/LocalizableTypes.strings | 17 ----------------- .../brh.lproj/LocalizableTypes.strings | 17 ----------------- .../ca.lproj/LocalizableTypes.strings | 1 - .../cs.lproj/LocalizableTypes.strings | 1 - .../cy.lproj/LocalizableTypes.strings | 17 ----------------- .../da.lproj/LocalizableTypes.strings | 1 - .../de.lproj/LocalizableTypes.strings | 1 - .../el.lproj/LocalizableTypes.strings | 1 - .../en-CA.lproj/LocalizableTypes.strings | 17 ----------------- .../en-GB.lproj/LocalizableTypes.strings | 17 ----------------- .../en.lproj/LocalizableTypes.strings | 17 ----------------- .../en_CA.lproj/LocalizableTypes.strings | 17 ----------------- .../eo.lproj/LocalizableTypes.strings | 17 ----------------- .../es-419.lproj/LocalizableTypes.strings | 17 ----------------- .../es-MX.lproj/LocalizableTypes.strings | 1 - .../es.lproj/LocalizableTypes.strings | 1 - .../es_419.lproj/LocalizableTypes.strings | 17 ----------------- .../et.lproj/LocalizableTypes.strings | 1 - .../eu.lproj/LocalizableTypes.strings | 1 - .../fa.lproj/LocalizableTypes.strings | 1 - .../fi.lproj/LocalizableTypes.strings | 1 - .../fr.lproj/LocalizableTypes.strings | 1 - .../ga.lproj/LocalizableTypes.strings | 17 ----------------- .../gl.lproj/LocalizableTypes.strings | 1 - .../gsw.lproj/LocalizableTypes.strings | 1 - .../he.lproj/LocalizableTypes.strings | 1 - .../hi.lproj/LocalizableTypes.strings | 1 - .../hr.lproj/LocalizableTypes.strings | 17 ----------------- .../hu.lproj/LocalizableTypes.strings | 1 - .../ia.lproj/LocalizableTypes.strings | 17 ----------------- .../id.lproj/LocalizableTypes.strings | 1 - .../is.lproj/LocalizableTypes.strings | 1 - .../it.lproj/LocalizableTypes.strings | 1 - .../ja.lproj/LocalizableTypes.strings | 1 - .../kab.lproj/LocalizableTypes.strings | 17 ----------------- .../kk.lproj/LocalizableTypes.strings | 17 ----------------- .../kmr.lproj/LocalizableTypes.strings | 17 ----------------- .../kn.lproj/LocalizableTypes.strings | 17 ----------------- .../ko.lproj/LocalizableTypes.strings | 1 - .../lt.lproj/LocalizableTypes.strings | 1 - .../lv.lproj/LocalizableTypes.strings | 17 ----------------- .../ml.lproj/LocalizableTypes.strings | 17 ----------------- .../mr.lproj/LocalizableTypes.strings | 1 - .../mt.lproj/LocalizableTypes.strings | 17 ----------------- .../nb.lproj/LocalizableTypes.strings | 1 - .../nl.lproj/LocalizableTypes.strings | 1 - .../pl.lproj/LocalizableTypes.strings | 1 - .../pt-BR.lproj/LocalizableTypes.strings | 1 - .../pt-PT.lproj/LocalizableTypes.strings | 17 ----------------- .../pt.lproj/LocalizableTypes.strings | 1 - .../pt_PT.lproj/LocalizableTypes.strings | 17 ----------------- .../ro.lproj/LocalizableTypes.strings | 1 - .../ru.lproj/LocalizableTypes.strings | 1 - .../si.lproj/LocalizableTypes.strings | 17 ----------------- .../sk.lproj/LocalizableTypes.strings | 1 - .../sl.lproj/LocalizableTypes.strings | 1 - .../sq.lproj/LocalizableTypes.strings | 17 ----------------- .../sr.lproj/LocalizableTypes.strings | 1 - .../sv.lproj/LocalizableTypes.strings | 1 - .../sw.lproj/LocalizableTypes.strings | 1 - .../ta.lproj/LocalizableTypes.strings | 1 - .../th.lproj/LocalizableTypes.strings | 1 - .../tr.lproj/LocalizableTypes.strings | 1 - .../uk.lproj/LocalizableTypes.strings | 1 - .../ur.lproj/LocalizableTypes.strings | 17 ----------------- .../vi.lproj/LocalizableTypes.strings | 1 - .../zh-Hans.lproj/LocalizableTypes.strings | 1 - .../zh-Hant.lproj/LocalizableTypes.strings | 1 - 75 files changed, 523 deletions(-) diff --git a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings index 59d81a53b..f47faaab2 100644 --- a/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/af.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tonnel"; "type.highway.bus_stop" = "Bushalte"; "type.highway.construction" = "Pad in aanbou"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Pad in aanbou"; "type.highway.construction.motorway_link" = "Pad in aanbou"; "type.highway.construction.trunk" = "Pad in aanbou"; diff --git a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings index 23f7180bf..331e949ca 100644 --- a/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ar.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "نفق"; "type.highway.bus_stop" = "موقف حافلات"; "type.highway.construction" = "طريق تحت الإنشاء"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "طريق تحت الإنشاء"; "type.highway.construction.motorway_link" = "طريق تحت الإنشاء"; "type.highway.construction.trunk" = "طريق تحت الإنشاء"; diff --git a/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings index 88de29402..749265c8b 100644 --- a/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ast.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada d’autobús"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Ponte"; diff --git a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings index 9c91775bf..1cc3d133c 100644 --- a/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/az.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Avtobus dayanacağı"; "type.highway.construction" = "Tikili Yol"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Tikili Yol"; "type.highway.construction.motorway_link" = "Tikili Yol"; "type.highway.construction.trunk" = "Tikili Yol"; diff --git a/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings index 241096622..3f4b132df 100644 --- a/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/be.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Тунэль"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Мост"; diff --git a/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ber.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings index d2cdd393f..3b887aa1b 100644 --- a/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/bg.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Спирка"; "type.highway.construction" = "Път в процес на изграждане"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Път в процес на изграждане"; "type.highway.construction.motorway_link" = "Път в процес на изграждане"; "type.highway.construction.trunk" = "Път в процес на изграждане"; diff --git a/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings index 0893dfa09..9663f70a0 100644 --- a/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/bn.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "বাস স্টপ"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings index 7b58576dd..8a187724e 100644 --- a/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/brh.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings index 4dd48670a..389e1eb25 100644 --- a/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ca.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada d’autobús"; "type.highway.construction" = "Via en construcció"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Via en construcció"; "type.highway.construction.motorway_link" = "Via en construcció"; "type.highway.construction.trunk" = "Via en construcció"; diff --git a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings index bec29d3fa..487e5cbe2 100644 --- a/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/cs.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Silnice v rekonstrukci"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Silnice v rekonstrukci"; "type.highway.construction.motorway_link" = "Silnice v rekonstrukci"; "type.highway.construction.trunk" = "Silnice v rekonstrukci"; diff --git a/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings index 9f8aef6fe..fa1e66f9c 100644 --- a/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/cy.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Twnnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Pont"; diff --git a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings index 2b915c8d6..0397a1007 100644 --- a/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/da.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstoppested"; "type.highway.construction" = "Vej under anlæggelse"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Vej under anlæggelse"; "type.highway.construction.motorway_link" = "Vej under anlæggelse"; "type.highway.construction.trunk" = "Vej under anlæggelse"; diff --git a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings index a21d20580..d50f857bb 100644 --- a/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/de.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltestelle"; "type.highway.construction" = "Straße im Bau"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Straße im Bau"; "type.highway.construction.motorway_link" = "Straße im Bau"; "type.highway.construction.trunk" = "Straße im Bau"; diff --git a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings index 9be9446ff..30c13237c 100644 --- a/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/el.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Σήραγγα"; "type.highway.bus_stop" = "Στάση λεωφορείου"; "type.highway.construction" = "Οδός υπό κατασκευή"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Οδός υπό κατασκευή"; "type.highway.construction.motorway_link" = "Οδός υπό κατασκευή"; "type.highway.construction.trunk" = "Οδός υπό κατασκευή"; diff --git a/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en-CA.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings index 6cd5196eb..d2922bacf 100644 --- a/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en-GB.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings index 29848ce51..7560d8c4a 100644 --- a/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings index 7f20555bb..3a898d591 100644 --- a/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/en_CA.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings index c9028e4b0..39a884153 100644 --- a/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/eo.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings index baa447bee..952a54026 100644 --- a/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es-419.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings index f0c7d6ed9..34cb13dac 100644 --- a/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es-MX.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construcción"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Vía en construcción"; "type.highway.construction.motorway_link" = "Vía en construcción"; "type.highway.construction.trunk" = "Vía en construcción"; diff --git a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings index cea33b598..ef978a6a7 100644 --- a/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de bus"; "type.highway.construction" = "Vía en construcción"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Vía en construcción"; "type.highway.construction.motorway_link" = "Vía en construcción"; "type.highway.construction.trunk" = "Vía en construcción"; diff --git a/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings index 7f20555bb..3a898d591 100644 --- a/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/es_419.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings index 2b96a0ffa..3ebbfc57a 100644 --- a/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/et.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bussipeatus"; "type.highway.construction" = "Ehitusjärgus tee"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Ehitusjärgus tee"; "type.highway.construction.motorway_link" = "Ehitusjärgus tee"; "type.highway.construction.trunk" = "Ehitusjärgus tee"; diff --git a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings index 2f80be8a7..ae9d56c0d 100644 --- a/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/eu.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunela"; "type.highway.bus_stop" = "Autobus geltokia"; "type.highway.construction" = "Eraikitzen ari diren errepidea"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Eraikitzen ari diren errepidea"; "type.highway.construction.motorway_link" = "Eraikitzen ari diren errepidea"; "type.highway.construction.trunk" = "Eraikitzen ari diren errepidea"; diff --git a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings index 17ead7f2d..b22b14412 100644 --- a/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fa.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "تونل"; "type.highway.bus_stop" = "حمل و نقل"; "type.highway.construction" = "جاده در دست ساخت است"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "جاده در دست ساخت است"; "type.highway.construction.motorway_link" = "جاده در دست ساخت است"; "type.highway.construction.trunk" = "جاده در دست ساخت است"; diff --git a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings index 80b77d573..ec141413b 100644 --- a/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fi.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunneli"; "type.highway.bus_stop" = "Bussipysäkki"; "type.highway.construction" = "Rakenteilla oleva tie"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Rakenteilla oleva tie"; "type.highway.construction.motorway_link" = "Rakenteilla oleva tie"; "type.highway.construction.trunk" = "Rakenteilla oleva tie"; diff --git a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings index 200ffd5d0..5e4f0431b 100644 --- a/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/fr.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Arrêt de bus"; "type.highway.construction" = "Route en construction"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Route en construction"; "type.highway.construction.motorway_link" = "Route en construction"; "type.highway.construction.trunk" = "Route en construction"; diff --git a/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ga.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings index 0fb97491e..1570bad09 100644 --- a/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gl.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Parada de autobús"; "type.highway.construction" = "Vía en construción"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Vía en construción"; "type.highway.construction.motorway_link" = "Vía en construción"; "type.highway.construction.trunk" = "Vía en construción"; diff --git a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings index 63bdb328e..0748e8c45 100644 --- a/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/gsw.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushaltistell"; "type.highway.construction" = "Strass im Bau"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Strass im Bau"; "type.highway.construction.motorway_link" = "Strass im Bau"; "type.highway.construction.trunk" = "Strass im Bau"; diff --git a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings index fcc624317..144fc0041 100644 --- a/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/he.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "מִנהָרָה"; "type.highway.bus_stop" = "תחנת אוטובוס"; "type.highway.construction" = "כביש בבניה"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "כביש בבניה"; "type.highway.construction.motorway_link" = "כביש בבניה"; "type.highway.construction.trunk" = "כביש בבניה"; diff --git a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings index 77a2b6ec3..979a024f0 100644 --- a/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hi.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "बस स्टॉप"; "type.highway.construction" = "निर्माणाधीन सड़क"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "निर्माणाधीन सड़क"; "type.highway.construction.motorway_link" = "निर्माणाधीन सड़क"; "type.highway.construction.trunk" = "निर्माणाधीन सड़क"; diff --git a/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings index 17c86c348..4a73352a4 100644 --- a/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hr.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings index 7d515066b..292bb067c 100644 --- a/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/hu.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Alagút"; "type.highway.bus_stop" = "Buszmegálló"; "type.highway.construction" = "Útépítés"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Útépítés"; "type.highway.construction.motorway_link" = "Útépítés"; "type.highway.construction.trunk" = "Útépítés"; diff --git a/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings index 659d0b74c..8eed20dc8 100644 --- a/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ia.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings index 62dcf3b17..f31e4c265 100644 --- a/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/id.lproj/LocalizableTypes.strings @@ -430,7 +430,6 @@ "type.highway.busway.tunnel" = "Terowongan"; "type.highway.bus_stop" = "Halte bus"; "type.highway.construction" = "Jalan sedang dibangun"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Jalan sedang dibangun"; "type.highway.construction.motorway_link" = "Jalan sedang dibangun"; "type.highway.construction.trunk" = "Jalan sedang dibangun"; diff --git a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings index 74642db8d..e3d4aded1 100644 --- a/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/is.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Göng"; "type.highway.bus_stop" = "Strætisvagnabiðstöð"; "type.highway.construction" = "Vegur í byggingu"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Vegur í byggingu"; "type.highway.construction.motorway_link" = "Vegur í byggingu"; "type.highway.construction.trunk" = "Vegur í byggingu"; diff --git a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings index bf70de693..7ff467b0e 100644 --- a/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/it.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Galleria"; "type.highway.bus_stop" = "Fermata dell'autobus"; "type.highway.construction" = "Strada in costruzione"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Strada in costruzione"; "type.highway.construction.motorway_link" = "Strada in costruzione"; "type.highway.construction.trunk" = "Strada in costruzione"; diff --git a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings index 4f63aec5d..b5245d9ef 100644 --- a/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ja.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "トンネル"; "type.highway.bus_stop" = "バス停"; "type.highway.construction" = "工事中の道路"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "工事中の道路"; "type.highway.construction.motorway_link" = "工事中の道路"; "type.highway.construction.trunk" = "工事中の道路"; diff --git a/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings index f31ed19bd..5b4135a06 100644 --- a/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kab.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kk.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kmr.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings index 3ee4a016a..8046ba124 100644 --- a/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/kn.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings index 908e8ea3d..b8d6b720b 100644 --- a/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ko.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "터널"; "type.highway.bus_stop" = "버스 정류장"; "type.highway.construction" = "공사 중 도로"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "공사 중 도로"; "type.highway.construction.motorway_link" = "공사 중 도로"; "type.highway.construction.trunk" = "공사 중 도로"; diff --git a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings index 2c00df34a..afc6749b1 100644 --- a/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/lt.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunelis"; "type.highway.bus_stop" = "Stotelė"; "type.highway.construction" = "Tiesiamas kelias"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Tiesiamas kelias"; "type.highway.construction.motorway_link" = "Tiesiamas kelias"; "type.highway.construction.trunk" = "Tiesiamas kelias"; diff --git a/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings index daa5972d8..a157e086e 100644 --- a/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/lv.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunelis"; "type.highway.bus_stop" = "Pietura"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings index ccd86ac21..23d444651 100644 --- a/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ml.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings index e0d28c77a..d73ca0f31 100644 --- a/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/mr.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "बोगदा"; "type.highway.bus_stop" = "बस थांबा"; "type.highway.construction" = "बांधकामाधीन रस्ता"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "बांधकामाधीन रस्ता"; "type.highway.construction.motorway_link" = "बांधकामाधीन रस्ता"; "type.highway.construction.trunk" = "बांधकामाधीन रस्ता"; diff --git a/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings index 492c56659..7d2efad89 100644 --- a/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/mt.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings index eb3f2aca6..811985abb 100644 --- a/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nb.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busstopp"; "type.highway.construction" = "Veikonstruksjon"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Veikonstruksjon"; "type.highway.construction.motorway_link" = "Veikonstruksjon"; "type.highway.construction.trunk" = "Veikonstruksjon"; diff --git a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings index ac36d8209..7a0990d1c 100644 --- a/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/nl.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bushalte"; "type.highway.construction" = "Weg in aanbouw"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Weg in aanbouw"; "type.highway.construction.motorway_link" = "Weg in aanbouw"; "type.highway.construction.trunk" = "Weg in aanbouw"; diff --git a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings index f33295bc8..47f40d336 100644 --- a/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pl.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Przystanek autobusowy"; "type.highway.construction" = "Droga w trakcie budowy"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Droga w trakcie budowy"; "type.highway.construction.motorway_link" = "Droga w trakcie budowy"; "type.highway.construction.trunk" = "Droga w trakcie budowy"; diff --git a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings index a838dc594..f014c7469 100644 --- a/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt-BR.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Ponto de ônibus"; "type.highway.construction" = "Via em construção"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Via em construção"; "type.highway.construction.motorway_link" = "Via em construção"; "type.highway.construction.trunk" = "Via em construção"; diff --git a/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings index 7f20555bb..3a898d591 100644 --- a/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt-PT.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings index 7a36841f0..779b15f49 100644 --- a/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Túnel"; "type.highway.bus_stop" = "Paragem de autocarros"; "type.highway.construction" = "Estrada em construção"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Estrada em construção"; "type.highway.construction.motorway_link" = "Estrada em construção"; "type.highway.construction.trunk" = "Estrada em construção"; diff --git a/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/pt_PT.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings index 8942353a6..761979379 100644 --- a/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ro.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Stație de autobuz"; "type.highway.construction" = "Drum în construcție"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Drum în construcție"; "type.highway.construction.motorway_link" = "Drum în construcție"; "type.highway.construction.trunk" = "Drum în construcție"; diff --git a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings index 659f41691..e1eeb2d67 100644 --- a/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ru.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Тоннель"; "type.highway.bus_stop" = "Остановка"; "type.highway.construction" = "Строящаяся дорога"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Строящаяся дорога"; "type.highway.construction.motorway_link" = "Строящаяся дорога"; "type.highway.construction.trunk" = "Строящаяся дорога"; diff --git a/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings index 33e2867eb..818ee970b 100644 --- a/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/si.lproj/LocalizableTypes.strings @@ -433,23 +433,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; "type.highway.cycleway.bridge" = "Bridge"; "type.highway.cycleway.permissive" = "Cycle Path"; diff --git a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings index bbc14a551..c9527912b 100644 --- a/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sk.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunel"; "type.highway.bus_stop" = "Autobusová zastávka"; "type.highway.construction" = "Cesta vo výstavbe"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Cesta vo výstavbe"; "type.highway.construction.motorway_link" = "Cesta vo výstavbe"; "type.highway.construction.trunk" = "Cesta vo výstavbe"; diff --git a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings index df6c606b6..d103a4b4d 100644 --- a/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sl.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Predor"; "type.highway.bus_stop" = "Avtobusno postajališče"; "type.highway.construction" = "Cesta v gradnji"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Cesta v gradnji"; "type.highway.construction.motorway_link" = "Cesta v gradnji"; "type.highway.construction.trunk" = "Cesta v gradnji"; diff --git a/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings index 3e24089a2..562c34578 100644 --- a/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sq.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings index 8ee3cc69f..0e4b62f0b 100644 --- a/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sr.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Тунел"; "type.highway.bus_stop" = "Аутобуско стајалиште"; "type.highway.construction" = "Пут у изградњи"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Пут у изградњи"; "type.highway.construction.motorway_link" = "Пут у изградњи"; "type.highway.construction.trunk" = "Пут у изградњи"; diff --git a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings index fbfff9ca6..9940f4cad 100644 --- a/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sv.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Busshållplats"; "type.highway.construction" = "Väg under uppförande"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Väg under uppförande"; "type.highway.construction.motorway_link" = "Väg under uppförande"; "type.highway.construction.trunk" = "Väg under uppförande"; diff --git a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings index 6ad6c4177..370cab5b4 100644 --- a/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/sw.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Mtaro"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Barabara inatengenezwa"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Barabara inatengenezwa"; "type.highway.construction.motorway_link" = "Barabara inatengenezwa"; "type.highway.construction.trunk" = "Barabara inatengenezwa"; diff --git a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings index 49b9ff78f..b79562586 100644 --- a/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ta.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "சுரங்கப்பாதை"; "type.highway.bus_stop" = "பேருந்து நிறுத்தம்"; "type.highway.construction" = "சாலை கட்டுமானத்தில் உள்ளது"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "சாலை கட்டுமானத்தில் உள்ளது"; "type.highway.construction.motorway_link" = "சாலை கட்டுமானத்தில் உள்ளது"; "type.highway.construction.trunk" = "சாலை கட்டுமானத்தில் உள்ளது"; diff --git a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings index ce4406171..44a861866 100644 --- a/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/th.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "อุโมงค์"; "type.highway.bus_stop" = "ป้ายรถเมล์"; "type.highway.construction" = "ทางกำลังอยู่ในการก่อสร้าง"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "ทางกำลังอยู่ในการก่อสร้าง"; "type.highway.construction.motorway_link" = "ทางกำลังอยู่ในการก่อสร้าง"; "type.highway.construction.trunk" = "ทางกำลังอยู่ในการก่อสร้าง"; diff --git a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings index 69bb7354f..aa4118850 100644 --- a/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/tr.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Tünel"; "type.highway.bus_stop" = "Otobüs Durağı"; "type.highway.construction" = "Yapım Aşamasında Yol"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Yapım Aşamasında Yol"; "type.highway.construction.motorway_link" = "Yapım Aşamasında Yol"; "type.highway.construction.trunk" = "Yapım Aşamasında Yol"; diff --git a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings index fbd7b8bed..cf619f2d2 100644 --- a/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/uk.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Тунель"; "type.highway.bus_stop" = "Зупинка"; "type.highway.construction" = "Дорога, що будується"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Дорога, що будується"; "type.highway.construction.motorway_link" = "Дорога, що будується"; "type.highway.construction.trunk" = "Дорога, що будується"; diff --git a/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings index 43e3bdac2..436fc328c 100644 --- a/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/ur.lproj/LocalizableTypes.strings @@ -452,23 +452,6 @@ "type.highway.busway.tunnel" = "Tunnel"; "type.highway.bus_stop" = "Bus Stop"; "type.highway.construction" = "Road Under Construction"; -/* These translations are used for all type.highway.construction.*. */ -"type.highway.construction.motorway" = "Road Under Construction"; -"type.highway.construction.motorway_link" = "Road Under Construction"; -"type.highway.construction.trunk" = "Road Under Construction"; -"type.highway.construction.trunk_link" = "Road Under Construction"; -"type.highway.construction.primary" = "Road Under Construction"; -"type.highway.construction.primary_link" = "Road Under Construction"; -"type.highway.construction.secondary" = "Road Under Construction"; -"type.highway.construction.secondary_link" = "Road Under Construction"; -"type.highway.construction.tertiary" = "Road Under Construction"; -"type.highway.construction.tertiary_link" = "Road Under Construction"; -"type.highway.construction.residential" = "Road Under Construction"; -"type.highway.construction.unclassified" = "Road Under Construction"; -"type.highway.construction.service" = "Road Under Construction"; -"type.highway.construction.living_street" = "Road Under Construction"; -"type.highway.construction.road" = "Road Under Construction"; -"type.highway.construction.track" = "Road Under Construction"; "type.highway.cycleway" = "Cycle Path"; /* These translations are used for all type.highway.*.bridge. */ "type.highway.cycleway.bridge" = "Bridge"; diff --git a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings index 21f088a08..e893f6a66 100644 --- a/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/vi.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "Đường hầm"; "type.highway.bus_stop" = "Bến xe buýt"; "type.highway.construction" = "Đường đang thi công"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "Đường đang thi công"; "type.highway.construction.motorway_link" = "Đường đang thi công"; "type.highway.construction.trunk" = "Đường đang thi công"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings index 94aa5ef97..6c048a662 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hans.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公交站"; "type.highway.construction" = "在建道路"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "在建道路"; "type.highway.construction.motorway_link" = "在建道路"; "type.highway.construction.trunk" = "在建道路"; diff --git a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings index bf2bdc376..a43aec4ba 100644 --- a/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings +++ b/iphone/Maps/LocalizedStrings/zh-Hant.lproj/LocalizableTypes.strings @@ -452,7 +452,6 @@ "type.highway.busway.tunnel" = "隧道"; "type.highway.bus_stop" = "公車站"; "type.highway.construction" = "施工中道路"; -/* These translations are used for all type.highway.construction.*. */ "type.highway.construction.motorway" = "施工中道路"; "type.highway.construction.motorway_link" = "施工中道路"; "type.highway.construction.trunk" = "施工中道路"; From 8e04f79a89b3a96400a7c8052324fceb3b587172 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 31 May 2026 17:03:16 +0300 Subject: [PATCH 247/252] [routing] Introduce DecoderModel, revert traffic tweaks in CarModel Signed-off-by: mvglasow --- generator/road_access_generator.cpp | 1 + generator/routing_index_generator.cpp | 6 + libs/map/routing_manager.cpp | 2 +- libs/platform/mwm_version.hpp | 3 +- libs/routing/edge_estimator.cpp | 5 + libs/routing/index_router.cpp | 14 +- libs/routing/road_access_serialization.cpp | 1 + libs/routing/road_access_serialization.hpp | 94 ++++-- .../routing/routes_builder/routes_builder.cpp | 2 +- .../routes_builder_tool/utils.cpp | 2 + libs/routing/routing_settings.cpp | 1 + libs/routing/vehicle_mask.cpp | 3 + libs/routing/vehicle_mask.hpp | 4 +- libs/routing_common/CMakeLists.txt | 2 + libs/routing_common/car_model.cpp | 12 - libs/routing_common/car_model_coefs.hpp | 57 ---- libs/routing_common/decoder_model.cpp | 293 ++++++++++++++++++ libs/routing_common/decoder_model.hpp | 51 +++ 18 files changed, 459 insertions(+), 94 deletions(-) create mode 100644 libs/routing_common/decoder_model.cpp create mode 100644 libs/routing_common/decoder_model.hpp diff --git a/generator/road_access_generator.cpp b/generator/road_access_generator.cpp index 9c5707c14..0420e0967 100644 --- a/generator/road_access_generator.cpp +++ b/generator/road_access_generator.cpp @@ -220,6 +220,7 @@ RoadAccessTagProcessor::RoadAccessTagProcessor(VehicleType vehicleType) : m_vehi switch (vehicleType) { case VehicleType::Car: + case VehicleType::Decoder: // Order is important here starting from most specific (motorcar) to generic (access). m_accessMappings.push_back(&kMotorCarTagMapping); m_accessMappings.push_back(&kMotorVehicleTagMapping); diff --git a/generator/routing_index_generator.cpp b/generator/routing_index_generator.cpp index 645eca320..dfd411670 100644 --- a/generator/routing_index_generator.cpp +++ b/generator/routing_index_generator.cpp @@ -22,6 +22,7 @@ #include "routing_common/bicycle_model.hpp" #include "routing_common/car_model.hpp" +#include "routing_common/decoder_model.hpp" #include "routing_common/pedestrian_model.hpp" #include "indexer/feature.hpp" @@ -61,10 +62,12 @@ class VehicleMaskBuilder final : m_pedestrianModel(PedestrianModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_bicycleModel(BicycleModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) , m_carModel(CarModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) + , m_decoderModel(DecoderModelFactory(countryParentNameGetterFn).GetVehicleModelForCountry(country)) { CHECK(m_pedestrianModel, ()); CHECK(m_bicycleModel, ()); CHECK(m_carModel, ()); + CHECK(m_decoderModel, ()); } VehicleMask CalcRoadMask(FeatureType & f) const @@ -90,6 +93,8 @@ class VehicleMaskBuilder final mask |= kBicycleMask; if (fn(*m_carModel)) mask |= kCarMask; + if (fn(*m_decoderModel)) + mask |= kDecoderMask; return mask; } @@ -97,6 +102,7 @@ class VehicleMaskBuilder final std::shared_ptr const m_pedestrianModel; std::shared_ptr const m_bicycleModel; std::shared_ptr const m_carModel; + std::shared_ptr const m_decoderModel; }; class Processor final diff --git a/libs/map/routing_manager.cpp b/libs/map/routing_manager.cpp index 0f881f5d6..3876a5f19 100644 --- a/libs/map/routing_manager.cpp +++ b/libs/map/routing_manager.cpp @@ -498,7 +498,7 @@ void RoutingManager::SetRouterImpl(RouterType type) VehicleType const vehicleType = GetVehicleType(type); - m_loadAltitudes = vehicleType != VehicleType::Car; + m_loadAltitudes = (vehicleType != VehicleType::Car) && (vehicleType != VehicleType::Decoder); auto const countryFileGetter = [this](m2::PointD const & p) -> string { diff --git a/libs/platform/mwm_version.hpp b/libs/platform/mwm_version.hpp index f58944f24..02186b4df 100644 --- a/libs/platform/mwm_version.hpp +++ b/libs/platform/mwm_version.hpp @@ -30,7 +30,8 @@ enum class Format // header, sdx section with header, dat section renamed to features, features section with // header). v11, // September 2020 (compressed string storage for metadata). - lastFormat = v11 + v12, // June 2026 (additional vehicle model DecoderModel, construction types allowed in routing section). + lastFormat = v12 }; std::string DebugPrint(Format f); diff --git a/libs/routing/edge_estimator.cpp b/libs/routing/edge_estimator.cpp index ed9df5336..881a94a53 100644 --- a/libs/routing/edge_estimator.cpp +++ b/libs/routing/edge_estimator.cpp @@ -826,6 +826,11 @@ shared_ptr EdgeEstimator::Create(VehicleType vehicleType, double case VehicleType::Bicycle: return make_shared(maxWeighSpeedKMpH, offroadSpeedKMpH); case VehicleType::Car: return make_shared(dataSourcePtr, numMwmIds, trafficStash, maxWeighSpeedKMpH, offroadSpeedKMpH); + /* + * VehicleType::Decoder is for use with the TraFF decoder, which creates its EdgeEstimator by + * explicitly calling the constructor for the appropriate subclass. + */ + case VehicleType::Decoder: CHECK(false, ("Creating EdgeEstimator for Decoder is not supported")); return nullptr; case VehicleType::Count: CHECK(false, ("Can't create EdgeEstimator for", vehicleType)); return nullptr; } UNREACHABLE(); diff --git a/libs/routing/index_router.cpp b/libs/routing/index_router.cpp index 304a2a706..37879ba2a 100644 --- a/libs/routing/index_router.cpp +++ b/libs/routing/index_router.cpp @@ -27,6 +27,7 @@ #include "routing_common/bicycle_model.hpp" #include "routing_common/car_model.hpp" +#include "routing_common/decoder_model.hpp" #include "routing_common/pedestrian_model.hpp" #include "indexer/data_source.hpp" @@ -104,6 +105,7 @@ shared_ptr CreateVehicleModelFactory( case VehicleType::Transit: return make_shared(countryParentNameGetterFn); case VehicleType::Bicycle: return make_shared(countryParentNameGetterFn); case VehicleType::Car: return make_shared(countryParentNameGetterFn); + case VehicleType::Decoder: return make_shared(countryParentNameGetterFn); case VehicleType::Count: CHECK(false, ("Can't create VehicleModelFactoryInterface for", vehicleType)); return nullptr; } UNREACHABLE(); @@ -117,7 +119,14 @@ unique_ptr CreateDirectionsEngine(VehicleType vehicleType, sha case VehicleType::Pedestrian: case VehicleType::Transit: return make_unique(dataSource, numMwmIds); case VehicleType::Bicycle: - case VehicleType::Car: return make_unique(dataSource, numMwmIds); + case VehicleType::Car: + case VehicleType::Decoder: + /* + * For `VehicleType::Decoder` the directions engine serves no useful purpose. + * We could return `nullptr` here, but we would have to go through every usage of + * `m_directionsEngine` and ensure it can handle a null pointer. + */ + return make_unique(dataSource, numMwmIds); case VehicleType::Count: CHECK(false, ("Can't create DirectionsEngine for", vehicleType)); return nullptr; } UNREACHABLE(); @@ -581,7 +590,7 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints, FakeEnding startFakeEnding = m_guides.GetFakeEnding(i); FakeEnding finishFakeEnding = m_guides.GetFakeEnding(i + 1); - bool isStartSegmentStrictForward = (m_vehicleType == VehicleType::Car); + bool isStartSegmentStrictForward = ((m_vehicleType == VehicleType::Car) || (m_vehicleType == VehicleType::Decoder)); if (startFakeEnding.m_projections.empty() || finishFakeEnding.m_projections.empty()) { bool const isFirstSubroute = (i == checkpoints.GetPassedIdx()); @@ -1836,6 +1845,7 @@ void IndexRouter::SetupAlgorithmMode(IndexGraphStarter & starter, bool guidesAct case VehicleType::Bicycle: starter.GetGraph().SetMode(WorldGraphMode::Joints); break; case VehicleType::Transit: starter.GetGraph().SetMode(WorldGraphMode::NoLeaps); break; case VehicleType::Car: + case VehicleType::Decoder: starter.GetGraph().SetMode(AreMwmsNear(starter) ? WorldGraphMode::Joints : WorldGraphMode::LeapsOnly); break; case VehicleType::Count: CHECK(false, ("Unknown vehicle type:", m_vehicleType)); break; diff --git a/libs/routing/road_access_serialization.cpp b/libs/routing/road_access_serialization.cpp index 7e7191587..1dde0a865 100644 --- a/libs/routing/road_access_serialization.cpp +++ b/libs/routing/road_access_serialization.cpp @@ -11,6 +11,7 @@ std::string DebugPrint(RoadAccessSerializer::Header const & header) case RoadAccessSerializer::Header::TheFirstVersionRoadAccess: return "TheFirstVersionRoadAccess"; case RoadAccessSerializer::Header::WithoutAccessConditional: return "WithoutAccessConditional"; case RoadAccessSerializer::Header::WithAccessConditional: return "WithAccessConditional"; + case RoadAccessSerializer::Header::DecoderModelAccess: return "DecoderModelAccess"; } UNREACHABLE(); } diff --git a/libs/routing/road_access_serialization.hpp b/libs/routing/road_access_serialization.hpp index d0913f66e..a2b007c9b 100644 --- a/libs/routing/road_access_serialization.hpp +++ b/libs/routing/road_access_serialization.hpp @@ -33,11 +33,24 @@ class RoadAccessSerializer final using PointToAccessConditional = RoadAccess::PointToAccessConditional; using RoadAccessByVehicleType = std::array(VehicleType::Count)>; + /** + * @brief Values for the access version header. + * + * Each version introduced a breaking change to the way road access is stored in the MWM, + * requiring a different approach to deserialization. + * + * For any future format changes, append a new value with a descriptive name to this enum, + * and update `kLatestVersion` as well as `kVehicleTypeCount`. + * + * For any new values added here, the corresponding `DebugPrint()` function in the cpp file also + * needs to be updated. + */ enum class Header : uint32_t { - TheFirstVersionRoadAccess = 0, // Version of section roadaccess in 2017. - WithoutAccessConditional = 1, // Section roadaccess before conditional was implemented. - WithAccessConditional = 2 // Section roadaccess with conditional. + TheFirstVersionRoadAccess = 0, //< Version of section roadaccess in 2017. + WithoutAccessConditional = 1, //< Section roadaccess before conditional was implemented. + WithAccessConditional = 2, //< Section roadaccess with conditional. + DecoderModelAccess = 3 //< New vehicle model `DecoderModel` added. }; RoadAccessSerializer() = delete; @@ -59,16 +72,47 @@ class RoadAccessSerializer final switch (header) { case Header::TheFirstVersionRoadAccess: break; // Version of 2017. Unsupported. - case Header::WithoutAccessConditional: DeserializeAccess(src, vehicleType, roadAccess); break; + case Header::WithoutAccessConditional: DeserializeAccess(src, vehicleType, roadAccess, header); break; case Header::WithAccessConditional: - DeserializeAccess(src, vehicleType, roadAccess); - DeserializeAccessConditional(src, vehicleType, roadAccess); + case Header::DecoderModelAccess: + DeserializeAccess(src, vehicleType, roadAccess, header); + DeserializeAccessConditional(src, vehicleType, roadAccess, header); break; } } private: - inline static Header const kLatestVersion = Header::WithAccessConditional; + /** + * @brief Current version of the road access format. + * + * This should always be equal to the last value of `Header` and must be updated whenever format + * changes are introduced that require different processing. + * + * When changing this value, you must ensure backward compatibility with older versions. + */ + inline static Header const kLatestVersion = Header::DecoderModelAccess; + + /** + * @brief Number of vehicle types by version. + * + * Use `Header` as a numeric index into this array to determine the number of vehicle types for + * which access should be read upon encountering that version. + * + * When adding or removing vehicle types, append a new value to `Header` (see there), set + * `kLatestVersion` to that new value, and append an entry here, indicating the new number of + * vehicle types. You also need to review all functions in which `kVehicleTypeCount` is used and + * adapt them as needed to be compatible with both the old and new format. + */ + inline static std::array const kVehicleTypeCount = { + // TheFirstVersionRoadAccess (version of section roadaccess in 2017, no longer supported) + 0, + // WithoutAccessConditional (section roadaccess before conditional was implemented, 4 vehicle types) + 4, + // WithAccessConditional (section roadaccess with conditional, 4 vehicle types) + 4, + // DecoderModelAccess (new vehicle model `DecoderModel` and vehicle type added) + 5 + }; class AccessPosition { @@ -130,13 +174,18 @@ class RoadAccessSerializer final } template - static void DeserializeAccess(Source & src, VehicleType vehicleType, RoadAccess & roadAccess) + static void DeserializeAccess(Source & src, VehicleType vehicleType, RoadAccess & roadAccess, Header header) { - std::array(VehicleType::Count)> sectionSizes{}; - static_assert(static_cast(VehicleType::Count) == 4, - "It is assumed below that there are only 4 vehicle types and we store 4 numbers " - "of sections size. If you add or remove vehicle type you should up " - "|kLatestVersion| and save back compatibility here."); + std::vector sectionSizes(kVehicleTypeCount[static_cast(header)]); + static_assert(kVehicleTypeCount.size() == (static_cast(kLatestVersion) + 1), + "The number of entries in `kVehicleTypeCount` does not match `kLatestVersion`. " + "Ensure `kVehicleTypeCount` has an entry for each possible value of `Header`, " + "and `kLatestVersion` matches the highest possible value of `Header`."); + static_assert(static_cast(VehicleType::Count) == 5, + "The current version of the access header assumes there are exactly 5 vehicle " + "types. Adding or removing vehicle types is a breaking change and requires " + "increasing `kLatestVersion` and updating `kVehicleTypeCount`. See documentation " + "of `kVehicleTypeCount` for details."); for (auto & sectionSize : sectionSizes) sectionSize = ReadPrimitiveFromSource(src); @@ -186,13 +235,18 @@ class RoadAccessSerializer final } template - static void DeserializeAccessConditional(Source & src, VehicleType vehicleType, RoadAccess & roadAccess) + static void DeserializeAccessConditional(Source & src, VehicleType vehicleType, RoadAccess & roadAccess, Header header) { - std::array(VehicleType::Count)> sectionSizes{}; - static_assert(static_cast(VehicleType::Count) == 4, - "It is assumed below that there are only 4 vehicle types and we store 4 numbers " - "of sections size. If you add or remove vehicle type you should up " - "|kLatestVersion| and save back compatibility here."); + std::vector sectionSizes(kVehicleTypeCount[static_cast(header)]); + static_assert(kVehicleTypeCount.size() == (static_cast(kLatestVersion) + 1), + "The number of entries in `kVehicleTypeCount` does not match `kLatestVersion`. " + "Ensure `kVehicleTypeCount` has an entry for each possible value of `Header`, " + "and `kLatestVersion` matches the highest possible value of `Header`."); + static_assert(static_cast(VehicleType::Count) == 5, + "The current version of the access header assumes there are exactly 5 vehicle " + "types. Adding or removing vehicle types is a breaking change and requires " + "increasing `kLatestVersion` and updating `kVehicleTypeCount`. See documentation " + "of `kVehicleTypeCount` for details."); for (auto & sectionSize : sectionSizes) sectionSize = ReadPrimitiveFromSource(src); @@ -341,6 +395,8 @@ class RoadAccessSerializer final } // todo(@m) This code borrows heavily from traffic/traffic_info.hpp:SerializeTrafficKeys. + // SerializeTrafficKeys is from the defunct MapsWithMe traffic module and will be obsoleted by + // the new traffic feature currently under development. template static void SerializeSegments(Sink & sink, std::vector const & segments) { diff --git a/libs/routing/routes_builder/routes_builder.cpp b/libs/routing/routes_builder/routes_builder.cpp index 9e1eaa0d8..a37108d5e 100644 --- a/libs/routing/routes_builder/routes_builder.cpp +++ b/libs/routing/routes_builder/routes_builder.cpp @@ -269,7 +269,7 @@ void RoutesBuilder::Processor::InitRouter(VehicleType type) return cigSharedPtr->GetLimitRectForLeaf(countryId); }; - bool const loadAltitudes = type != VehicleType::Car; + bool const loadAltitudes = (type != VehicleType::Car) && (type != VehicleType::Decoder); if (!m_dataSource) m_dataSource = m_dataSourceStorage.GetDataSource(); diff --git a/libs/routing/routes_builder/routes_builder_tool/utils.cpp b/libs/routing/routes_builder/routes_builder_tool/utils.cpp index cdb24d5e3..8662f95bc 100644 --- a/libs/routing/routes_builder/routes_builder_tool/utils.cpp +++ b/libs/routing/routes_builder/routes_builder_tool/utils.cpp @@ -56,6 +56,8 @@ routing::VehicleType ConvertVehicleTypeFromString(std::string const & str) return routing::VehicleType::Bicycle; if (str == "transit") return routing::VehicleType::Transit; + if (str == "decoder") + return routing::VehicleType::Decoder; CHECK(false, ("Unknown vehicle type:", str)); UNREACHABLE(); diff --git a/libs/routing/routing_settings.cpp b/libs/routing/routing_settings.cpp index c2ffae340..ca95b0eba 100644 --- a/libs/routing/routing_settings.cpp +++ b/libs/routing/routing_settings.cpp @@ -75,6 +75,7 @@ RoutingSettings GetRoutingSettings(VehicleType vehicleType) 3 /* m_notSoCloseMaxPointsCount */, 25.0 /* m_notSoCloseMaxDistMeters */}; case VehicleType::Car: + case VehicleType::Decoder: return {true /* useDirectionForRouteBuilding */, true /* m_matchRoute */, true /* m_soundDirection */, diff --git a/libs/routing/vehicle_mask.cpp b/libs/routing/vehicle_mask.cpp index 11c4395ec..22528d4ec 100644 --- a/libs/routing/vehicle_mask.cpp +++ b/libs/routing/vehicle_mask.cpp @@ -15,6 +15,7 @@ std::string DebugPrint(VehicleType vehicleType) case VehicleType::Bicycle: return "Bicycle"; case VehicleType::Car: return "Car"; case VehicleType::Transit: return "Transit"; + case VehicleType::Decoder: return "Decoder"; case VehicleType::Count: return "Count"; } UNREACHABLE(); @@ -35,6 +36,8 @@ void FromString(std::string_view s, VehicleType & vehicleType) vehicleType = VehicleType::Car; else if (s == "Transit") vehicleType = VehicleType::Transit; + else if (s == "Decoder") + vehicleType = VehicleType::Decoder; else { ASSERT(false, ("Could not read VehicleType from string", s)); diff --git a/libs/routing/vehicle_mask.hpp b/libs/routing/vehicle_mask.hpp index 61b31c3fb..6a7d2d146 100644 --- a/libs/routing/vehicle_mask.hpp +++ b/libs/routing/vehicle_mask.hpp @@ -13,7 +13,8 @@ enum class VehicleType Bicycle = 1, Car = 2, Transit = 3, - Count = 4 + Decoder = 4, + Count = 5 }; using VehicleMask = uint32_t; @@ -30,6 +31,7 @@ VehicleMask constexpr kPedestrianMask = GetVehicleMask(VehicleType::Pedestrian); VehicleMask constexpr kBicycleMask = GetVehicleMask(VehicleType::Bicycle); VehicleMask constexpr kCarMask = GetVehicleMask(VehicleType::Car); VehicleMask constexpr kTransitMask = GetVehicleMask(VehicleType::Transit); +VehicleMask constexpr kDecoderMask = GetVehicleMask(VehicleType::Decoder); std::string DebugPrint(VehicleType vehicleType); std::string ToString(VehicleType vehicleType); diff --git a/libs/routing_common/CMakeLists.txt b/libs/routing_common/CMakeLists.txt index f03636b2d..e031491ec 100644 --- a/libs/routing_common/CMakeLists.txt +++ b/libs/routing_common/CMakeLists.txt @@ -6,6 +6,8 @@ set(SRC car_model.cpp car_model.hpp car_model_coefs.hpp # This file is generated by running ../configure.sh script. + decoder_model.cpp + decoder_model.hpp maxspeed_conversion.cpp maxspeed_conversion.hpp num_mwm_id.hpp diff --git a/libs/routing_common/car_model.cpp b/libs/routing_common/car_model.cpp index 028c8fd01..9fd87c2ce 100644 --- a/libs/routing_common/car_model.cpp +++ b/libs/routing_common/car_model.cpp @@ -35,18 +35,6 @@ VehicleModel::LimitsInitList const kDefaultOptions = { {HighwayType::HighwayResidential, true}, {HighwayType::HighwayUnclassified, true}, {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, - // The router truncates types to two levels, so we need this in addition to the long construction types - {HighwayType::HighwayConstruction, false}, - // Construction conterparts to each of the types above - // (needed for the map generator to include them in the routing section) - {HighwayType::HighwayConstructionMotorway, false}, {HighwayType::HighwayConstructionMotorwayLink, false}, - {HighwayType::HighwayConstructionTrunk, false}, {HighwayType::HighwayConstructionTrunkLink, false}, - {HighwayType::HighwayConstructionPrimary, false}, {HighwayType::HighwayConstructionPrimaryLink, false}, - {HighwayType::HighwayConstructionSecondary, false}, {HighwayType::HighwayConstructionSecondaryLink, false}, - {HighwayType::HighwayConstructionTertiary, false}, {HighwayType::HighwayConstructionTertiaryLink, false}, - {HighwayType::HighwayConstructionResidential, false}, {HighwayType::HighwayConstructionUnclassified, false}, - {HighwayType::HighwayConstructionService, false}, {HighwayType::HighwayConstructionLivingStreet, false}, - {HighwayType::HighwayConstructionRoad, false}, {HighwayType::HighwayConstructionTrack, false}, // Non-highway types {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, {HighwayType::ManMadePier, true}}; diff --git a/libs/routing_common/car_model_coefs.hpp b/libs/routing_common/car_model_coefs.hpp index 9e01e18a0..447f6bbb5 100644 --- a/libs/routing_common/car_model_coefs.hpp +++ b/libs/routing_common/car_model_coefs.hpp @@ -7,21 +7,6 @@ namespace routing { -/** - * @brief Speed to indicate that a segment is impassable. - */ -/* - * 1.0E-4 is the inverse of 1.0E4, which is used in `edge_estimator.cpp` as the penalty factor - * for the weight of a segment with `SpeedGroup::TempBlock`, i.e. currently impassable. - * Using its inverse as the speed is equivalent to 1 km/h with a penalty factor of 1.0E4. - * The lowest speed is currently 5 km/h (for `HighwayTrack`), thus 1 m of an impassable way has the - * same weight as at least 5 km of any routable way. - * This has worked well in tests. In theory, we could increase it further to 1.0E-8, resulting in - * 0.8 m of an impassable way costing as much as at least 40,000 km (earth circumference) of any - * routable way. - */ -auto const kImpassableSpeedKMpH = 1.0E-4; - HighwayBasedFactors const kHighwayBasedFactors = { // {highway class : InOutCityFactor(in city, out city)} @@ -60,27 +45,6 @@ HighwayBasedFactors const kHighwayBasedFactors = { {HighwayType::RouteFerry, InOutCityFactor(0.90)}, {HighwayType::RouteShuttleTrain, InOutCityFactor(0.90)}, - - // Generic construction type - {HighwayType::HighwayConstruction, InOutCityFactor(1.0)}, - - // Construction types for each highway type: - {HighwayType::HighwayConstructionMotorway, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionMotorwayLink, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionTrunk, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionTrunkLink, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionPrimary, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionPrimaryLink, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionSecondary, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionSecondaryLink, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionTertiary, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionTertiaryLink, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionResidential, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionUnclassified, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionService, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionLivingStreet, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionRoad, InOutCityFactor(1.0)}, - {HighwayType::HighwayConstructionTrack, InOutCityFactor(1.0)}, }; HighwayBasedSpeeds const kHighwayBasedSpeeds = { @@ -115,26 +79,5 @@ HighwayBasedSpeeds const kHighwayBasedSpeeds = { {HighwayType::RouteFerry, InOutCitySpeedKMpH(10.00 /* in city */, 10.00 /* out city */)}, {HighwayType::RouteShuttleTrain, InOutCitySpeedKMpH(25.00 /* in city */, 25.00 /* out city */)}, - - // Generic and long construction types: - // They are needed for traffic location decoding but we don’t want to route drivers through - // them, which is accomplished by setting their speed to kImpassableSpeedKMpH. - {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, - {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kImpassableSpeedKMpH)}, }; } // namespace routing diff --git a/libs/routing_common/decoder_model.cpp b/libs/routing_common/decoder_model.cpp new file mode 100644 index 000000000..b11e92f49 --- /dev/null +++ b/libs/routing_common/decoder_model.cpp @@ -0,0 +1,293 @@ +#include "routing_common/decoder_model.hpp" + +#include "indexer/classificator.hpp" + +namespace decoder_model +{ +using namespace routing; + +// See model specifics in different countries here: +// https://wiki.openstreetmap.org/wiki/OSM_tags_for_routing/Access-Restrictions + +// See road types here: +// https://wiki.openstreetmap.org/wiki/Key:highway + +/* + * One meter per second. The TraffEstimator works on distance in meters, not travel time. For code + * which works with speeds and assumes cost to be time-based, a speed of 1 m/s means such + * calculations will effectively return distances in meters. + */ +auto constexpr kOneMpSInKmpH = 3.6; + +/* + * Penalty factor for using a fake segment to get to a nearby road. + * Offroad penalty applies to direct distance whereas road penalty applies to roads, which can be up + * to around 3 times the direct distance (theoretically unlimited). Therefore, a factor of 3–4 times + * the penalty of a well-matched road may be needed to avoid competing with the correct route. + * On the other hand, a very high offroad penalty would give preference to a poorly matched route + * over a well-matched one if it is closer to the reference points. + * Maximum penalty for roads is currently 64 (4 for ramps * 4 for road type * 4 for ref). + * A well-matched road may still have a penalty of around 4 (twice the reduced attribute penalty, or + * once the full attribute penalty). + * A “wrong” road may also just have a penalty of 4 (e.g. road name mismatch, but road class and + * ramp type match). + * A value of 16 has worked well for the DE-B2R-SendlingSued-Passauerstrasse test case. (The + * DE-A10-Werder-GrossKreutz or DE-A115-PotsdamDrewitz-Nuthetal test cases gave incorrect results + * due to lack of fake segments, which was fixed through truncation and now works correctly even + * with an offroad penalty of 128.) + */ +auto constexpr kOffroadPenalty = 16; + +// |kSpeedOffroadKMpH| is a speed which is used for edges that don't lie on road features. +// For example for pure fake edges. In car routing, off road speed for calculation ETA is not used. +// The weight of such edges is considered as 0 seconds. It's especially actual when an airport is +// a start or finish. On the other hand, while route calculation the fake edges are considered +// as quite heavy. The idea behind that is to use the closest edge for the start and the finish +// of the route except for some edge cases. +SpeedKMpH constexpr kSpeedOffroadKMpH = kOneMpSInKmpH / kOffroadPenalty; + +HighwayBasedFactors const kDefaultFactors = { + // {highway class : InOutCityFactor(in city, out city)} + + {HighwayType::HighwayMotorway, InOutCityFactor(1.0)}, + {HighwayType::HighwayMotorwayLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrunk, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrunkLink, InOutCityFactor(1.0)}, + + {HighwayType::HighwayPrimary, InOutCityFactor(1.0)}, + {HighwayType::HighwayPrimaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwaySecondary, InOutCityFactor(1.0)}, + {HighwayType::HighwaySecondaryLink, InOutCityFactor(1.0)}, + + {HighwayType::HighwayTertiary, InOutCityFactor(1.0)}, + {HighwayType::HighwayTertiaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayUnclassified, InOutCityFactor(1.0)}, + + {HighwayType::HighwayResidential, InOutCityFactor(1.0)}, + {HighwayType::HighwayLivingStreet, InOutCityFactor(1.0)}, + + {HighwayType::HighwayService, InOutCityFactor(1.0)}, + {HighwayType::HighwayRoad, InOutCityFactor(1.0)}, + {HighwayType::HighwayTrack, InOutCityFactor(1.0)}, + {HighwayType::ManMadePier, InOutCityFactor(1.0)}, + + {HighwayType::RouteFerry, InOutCityFactor(1.0)}, + {HighwayType::RouteShuttleTrain, InOutCityFactor(1.0)}, + + // Generic construction type + {HighwayType::HighwayConstruction, InOutCityFactor(1.0)}, + + // Construction types for each highway type: + {HighwayType::HighwayConstructionMotorway, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunk, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiary, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionResidential, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionUnclassified, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionService, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionRoad, InOutCityFactor(1.0)}, + {HighwayType::HighwayConstructionTrack, InOutCityFactor(1.0)}, +}; + +HighwayBasedSpeeds const kDefaultSpeeds = { + // {highway class : InOutCitySpeedKMpH(in city(weight, eta), out city(weight eta))} + {HighwayType::HighwayMotorway, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayMotorwayLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrunk, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrunkLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayPrimary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayPrimaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwaySecondary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwaySecondaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTertiary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTertiaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayResidential, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayUnclassified, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayService, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayLivingStreet, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayRoad, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayTrack, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // The router truncates types to two levels, so we need this in addition to the long construction types + {HighwayType::HighwayConstruction, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // Construction conterparts to each of the types above + // (needed for the map generator to include them in the routing section) + {HighwayType::HighwayConstructionMotorway, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionMotorwayLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrunk, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrunkLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionPrimary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionPrimaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionSecondary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionSecondaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTertiary, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTertiaryLink, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionResidential, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionUnclassified, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionService, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionLivingStreet, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionRoad, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::HighwayConstructionTrack, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + // Non-highway types + {HighwayType::RouteShuttleTrain, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::RouteFerry, InOutCitySpeedKMpH(kOneMpSInKmpH)}, + {HighwayType::ManMadePier, InOutCitySpeedKMpH(kOneMpSInKmpH)}}; + +/* + * The number of entries must match `kHighwayBasedFactors` and `kHighwayBasedSpeeds` + * in `decoder_model_coefs.hpp`. + */ +VehicleModel::LimitsInitList const kDefaultOptions = { + // {HighwayType, passThroughAllowed} + {HighwayType::HighwayMotorway, true}, {HighwayType::HighwayMotorwayLink, true}, + {HighwayType::HighwayTrunk, true}, {HighwayType::HighwayTrunkLink, true}, + {HighwayType::HighwayPrimary, true}, {HighwayType::HighwayPrimaryLink, true}, + {HighwayType::HighwaySecondary, true}, {HighwayType::HighwaySecondaryLink, true}, + {HighwayType::HighwayTertiary, true}, {HighwayType::HighwayTertiaryLink, true}, + {HighwayType::HighwayResidential, true}, {HighwayType::HighwayUnclassified, true}, + {HighwayType::HighwayService, true}, {HighwayType::HighwayLivingStreet, true}, + {HighwayType::HighwayRoad, true}, {HighwayType::HighwayTrack, true}, + // The router truncates types to two levels, so we need this in addition to the long construction types + {HighwayType::HighwayConstruction, true}, + // Construction conterparts to each of the types above + // (needed for the map generator to include them in the routing section) + {HighwayType::HighwayConstructionMotorway, true}, {HighwayType::HighwayConstructionMotorwayLink, true}, + {HighwayType::HighwayConstructionTrunk, true}, {HighwayType::HighwayConstructionTrunkLink, true}, + {HighwayType::HighwayConstructionPrimary, true}, {HighwayType::HighwayConstructionPrimaryLink, true}, + {HighwayType::HighwayConstructionSecondary, true}, {HighwayType::HighwayConstructionSecondaryLink, true}, + {HighwayType::HighwayConstructionTertiary, true}, {HighwayType::HighwayConstructionTertiaryLink, true}, + {HighwayType::HighwayConstructionResidential, true}, {HighwayType::HighwayConstructionUnclassified, true}, + {HighwayType::HighwayConstructionService, true}, {HighwayType::HighwayConstructionLivingStreet, true}, + {HighwayType::HighwayConstructionRoad, true}, {HighwayType::HighwayConstructionTrack, true}, + // Non-highway types + {HighwayType::RouteShuttleTrain, true}, {HighwayType::RouteFerry, true}, + {HighwayType::ManMadePier, true}}; + +VehicleModel::LimitsInitList NoPassThroughLivingStreet() +{ + auto res = kDefaultOptions; + for (auto & e : res) + if (e.m_type == HighwayType::HighwayLivingStreet) + e.m_isPassThroughAllowed = false; + return res; +} + +VehicleModel::LimitsInitList NoPassThroughService(VehicleModel::LimitsInitList res = kDefaultOptions) +{ + for (auto & e : res) + if (e.m_type == HighwayType::HighwayService) + e.m_isPassThroughAllowed = false; + return res; +} + +VehicleModel::LimitsInitList NoTrack() +{ + VehicleModel::LimitsInitList res; + res.reserve(kDefaultOptions.size() - 1); + for (auto const & e : kDefaultOptions) + if (e.m_type != HighwayType::HighwayTrack) + res.push_back(e); + return res; +} + +VehicleModel::LimitsInitList NoPassThroughTrack() +{ + auto res = kDefaultOptions; + for (auto & e : res) + if (e.m_type == HighwayType::HighwayTrack) + e.m_isPassThroughAllowed = false; + return res; +} + +/// @todo Should make some compare constrains (like in CarModel_TrackVsGravelTertiary test) +/// to better fit these factors with reality. I have no idea, how they were set. +VehicleModel::SurfaceInitList const kDecoderSurface = { + // {{surfaceType, surfaceType}, {weightFactor, etaFactor}} + {{"psurface", "paved_good"}, {1.0, 1.0}}, + {{"psurface", "paved_bad"}, {0.6, 0.7}}, + {{"psurface", "unpaved_good"}, {0.4, 0.7}}, + {{"psurface", "unpaved_bad"}, {0.2, 0.3}}}; +} // namespace decoder_model + +namespace routing +{ +DecoderModel::DecoderModel() : DecoderModel(decoder_model::kDefaultOptions) {} + +DecoderModel::DecoderModel(VehicleModel::LimitsInitList const & roadLimits) + : VehicleModel(classif(), roadLimits, decoder_model::kDecoderSurface, + {decoder_model::kDefaultSpeeds, decoder_model::kDefaultFactors}) +{ + ASSERT_EQUAL(decoder_model::kDefaultFactors.size(), decoder_model::kDefaultSpeeds.size(), ()); + ASSERT_EQUAL(decoder_model::kDefaultOptions.size(), decoder_model::kDefaultSpeeds.size(), ()); + + std::vector hwtagYesCar = {"hwtag", "yescar"}; + auto const & cl = classif(); + + m_noType = cl.GetTypeByPath({"hwtag", "nocar"}); + m_yesType = cl.GetTypeByPath(hwtagYesCar); + + // Set small track speed if highway is not in kDefaultSpeeds (path, pedestrian), but marked as yescar. + AddAdditionalRoadTypes(cl, {{std::move(hwtagYesCar), decoder_model::kDefaultSpeeds.at(HighwayType::HighwayTrack)}}); + + // Set max possible (reasonable) car speed. See EdgeEstimator::CalcHeuristic. + SpeedKMpH constexpr kMaxCarSpeedKMpH(200.0); + CHECK_LESS(m_maxModelSpeed, kMaxCarSpeedKMpH, ()); + m_maxModelSpeed = kMaxCarSpeedKMpH; +} + +SpeedKMpH DecoderModel::GetSpeed(FeatureTypes const & types, SpeedParams const & speedParams) const +{ + return GetTypeSpeedImpl(types, speedParams, true /* isCar */); +} + +SpeedKMpH const & DecoderModel::GetOffroadSpeed() const +{ + return decoder_model::kSpeedOffroadKMpH; +} + +// static +DecoderModel const & DecoderModel::AllLimitsInstance() +{ + static DecoderModel const instance; + return instance; +} + +// static +VehicleModel::LimitsInitList const & DecoderModel::GetOptions() +{ + return decoder_model::kDefaultOptions; +} + +DecoderModelFactory::DecoderModelFactory(CountryParentNameGetterFn const & countryParentNameGetterFn) + : VehicleModelFactory(countryParentNameGetterFn) +{ + using namespace decoder_model; + using std::make_shared; + + // Names must be the same with country names from countries.txt + m_models[""] = make_shared(); + + /* + * This section should mirror the definitions in `CarModel`. Any deviations should be considered + * a bug, unless there is a comment stating that this is intentional (and ideally also the reason + * it is needed). + */ + m_models["Austria"] = make_shared(NoPassThroughLivingStreet()); + m_models["Belarus"] = make_shared(NoPassThroughLivingStreet()); + m_models["Brazil"] = make_shared(NoPassThroughService(NoPassThroughTrack())); + m_models["Denmark"] = make_shared(NoTrack()); + m_models["Germany"] = make_shared(NoPassThroughTrack()); + m_models["Hungary"] = make_shared(NoPassThroughLivingStreet()); + m_models["Poland"] = make_shared(NoPassThroughService()); + m_models["Romania"] = make_shared(NoPassThroughLivingStreet()); + m_models["Russian Federation"] = make_shared(NoPassThroughService(NoPassThroughLivingStreet())); + m_models["Slovakia"] = make_shared(NoPassThroughLivingStreet()); + m_models["Ukraine"] = make_shared(NoPassThroughService(NoPassThroughLivingStreet())); +} +} // namespace routing diff --git a/libs/routing_common/decoder_model.hpp b/libs/routing_common/decoder_model.hpp new file mode 100644 index 000000000..005756c26 --- /dev/null +++ b/libs/routing_common/decoder_model.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "routing_common/vehicle_model.hpp" + +namespace routing +{ + +/** + * @brief A `VehicleModel` suitable for TraFF location decoding. + * + * Each instance can have its own set of feature type-specific access limitations. These specify + * which roads can be used by this vehicle, and which roads can only be used for destination + * traffic. + * + * `DecoderModel` is similar to `CarModel`, with the following differences: + * + * Construction types are routable and treated like regular roads (the decoder may still + * exclude them on a case-by-case basis). + * + * Speed is assumed to be 1 m/s for any routable road, and 1 m/s divided by offline penalty for + * offroad. + * + * Instances are typically retrieved from `DecoderModelFactory` or the `AllLimitsInstance()` method. + */ +class DecoderModel : public VehicleModel +{ +public: + DecoderModel(); + explicit DecoderModel(LimitsInitList const & roadLimits); + + // VehicleModelInterface overrides: + SpeedKMpH GetSpeed(FeatureTypes const & types, SpeedParams const & speedParams) const override; + SpeedKMpH const & GetOffroadSpeed() const override; + + /** + * @brief Returns an instance with the default access limitations. + */ + static DecoderModel const & AllLimitsInstance(); + + /** + * @brief Returns the default access limitations. + */ + static LimitsInitList const & GetOptions(); +}; + +class DecoderModelFactory : public VehicleModelFactory +{ +public: + DecoderModelFactory(CountryParentNameGetterFn const & countryParentNameGetterF); +}; +} // namespace routing From 71095545074f22ff9f6fb181194582d54765cba8 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Thu, 4 Jun 2026 00:50:19 +0300 Subject: [PATCH 248/252] [routing] Relax rules for number of vehicle types in map data Signed-off-by: mvglasow --- libs/routing/index_graph_loader.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/routing/index_graph_loader.cpp b/libs/routing/index_graph_loader.cpp index 56c7a47b2..959e7b811 100644 --- a/libs/routing/index_graph_loader.cpp +++ b/libs/routing/index_graph_loader.cpp @@ -227,7 +227,12 @@ bool ReadRoadPenaltyFromMwm(MwmValue const & mwmValue, VehicleType vehicleType, // Read number of vehicle types uint32_t numVehicleTypes = ReadPrimitiveFromSource(src); - CHECK_EQUAL(numVehicleTypes, static_cast(VehicleType::Count), ()); + if (numVehicleTypes <= static_cast(vehicleType) + && vehicleType == VehicleType::Decoder + && numVehicleTypes > static_cast(VehicleType::Car)) + // This is expected for older mwm files (up to v11) - not an error + vehicleType = VehicleType::Car; + CHECK(numVehicleTypes > static_cast(vehicleType), ()); // Skip to the correct vehicle type for (uint32_t i = 0; i < static_cast(vehicleType); ++i) From 5806f9fdcd89ff457cf3ce0bf3a8ee2e6e3d3d4d Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 7 Jun 2026 12:59:32 +0300 Subject: [PATCH 249/252] [traffic] Switch to DecoderModel Signed-off-by: mvglasow --- libs/routing/edge_estimator.cpp | 1 + libs/routing_common/decoder_model.cpp | 19 +++++++++++++++++++ libs/traffxml/traff_decoder.cpp | 4 ++-- libs/traffxml/traff_decoder.hpp | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/libs/routing/edge_estimator.cpp b/libs/routing/edge_estimator.cpp index 881a94a53..e5665d5df 100644 --- a/libs/routing/edge_estimator.cpp +++ b/libs/routing/edge_estimator.cpp @@ -196,6 +196,7 @@ EdgeEstimator::EdgeEstimator(VehicleType vehicleType, double maxWeightSpeedKMpH, #define N 288 + // TODO do we need entries for VehicleType::Decoder here? static auto constexpr kTurnPenaltyMatrix = [] { array constexpr kTable = {{ diff --git a/libs/routing_common/decoder_model.cpp b/libs/routing_common/decoder_model.cpp index b11e92f49..442c58a55 100644 --- a/libs/routing_common/decoder_model.cpp +++ b/libs/routing_common/decoder_model.cpp @@ -44,6 +44,11 @@ auto constexpr kOffroadPenalty = 16; // a start or finish. On the other hand, while route calculation the fake edges are considered // as quite heavy. The idea behind that is to use the closest edge for the start and the finish // of the route except for some edge cases. +/* + * TODO move this constant to the header + * `traff_decoder.cpp` uses `kOneMpSInKmpH / kOffroadPenalty` to initialize its `EdgeEstimator`, + * and includes the header for its vehicle model. + */ SpeedKMpH constexpr kSpeedOffroadKMpH = kOneMpSInKmpH / kOffroadPenalty; HighwayBasedFactors const kDefaultFactors = { @@ -205,6 +210,13 @@ VehicleModel::LimitsInitList NoPassThroughTrack() return res; } +/* + * TODO are these entries meaningful in TraFF decoder context? + * They are stored in the vehicle model and retrieved with VehicleModel::GetSurfaceFactor(), which + * is called by VehicleModel::GetTypeSpeedImpl(). The latter is also called by + * `DecoderModel::GetSpeed()`, which seems to have the same callers as its `CarModel` counterpart, + * excluding tests. + */ /// @todo Should make some compare constrains (like in CarModel_TrackVsGravelTertiary test) /// to better fit these factors with reality. I have no idea, how they were set. VehicleModel::SurfaceInitList const kDecoderSurface = { @@ -236,6 +248,13 @@ DecoderModel::DecoderModel(VehicleModel::LimitsInitList const & roadLimits) AddAdditionalRoadTypes(cl, {{std::move(hwtagYesCar), decoder_model::kDefaultSpeeds.at(HighwayType::HighwayTrack)}}); // Set max possible (reasonable) car speed. See EdgeEstimator::CalcHeuristic. + /* + * TODO revisit, we don’t use actual speeds. + * Segment weight is distance-based, optionally with a penalty factor (>= 1). Thus maximum speed + * is 1 m/s (`traff_decoder.cpp` has `kOneMpSInKmpH = 3.6`). + * We might want to move `kOneMpSInKmpH` to `decoder_model.hpp` as `traff_decoder.cpp` includes it. + * PS: setting this value more aggressively might also help with performance, see #1253. + */ SpeedKMpH constexpr kMaxCarSpeedKMpH(200.0); CHECK_LESS(m_maxModelSpeed, kMaxCarSpeedKMpH, ()); m_maxModelSpeed = kMaxCarSpeedKMpH; diff --git a/libs/traffxml/traff_decoder.cpp b/libs/traffxml/traff_decoder.cpp index f7590d759..62622482b 100644 --- a/libs/traffxml/traff_decoder.cpp +++ b/libs/traffxml/traff_decoder.cpp @@ -22,7 +22,7 @@ #include "routing/router_delegate.hpp" #include "routing/routing_helpers.hpp" -#include "routing_common/car_model.hpp" +#include "routing_common/decoder_model.hpp" #include "routing_common/maxspeed_conversion.hpp" #include "storage/routing_helpers.hpp" @@ -723,7 +723,7 @@ RoutingTraffDecoder::DecoderRouter::DecoderRouter(CountryParentNameGetterFn cons std::shared_ptr numMwmIds, std::unique_ptr> numMwmTree, DataSource & dataSource, RoutingTraffDecoder & decoder) - : routing::IndexRouter(routing::VehicleType::Car /* VehicleType vehicleType */, + : routing::IndexRouter(routing::VehicleType::Decoder /* VehicleType vehicleType */, false /* bool loadAltitudes */, countryParentNameGetterFn, countryFileFn, diff --git a/libs/traffxml/traff_decoder.hpp b/libs/traffxml/traff_decoder.hpp index 5a66c2bb7..3aa78c130 100644 --- a/libs/traffxml/traff_decoder.hpp +++ b/libs/traffxml/traff_decoder.hpp @@ -288,7 +288,7 @@ class RoutingTraffDecoder : public TraffDecoder, double maxWeightSpeedKMpH, routing::SpeedKMpH const & offroadSpeedKMpH, RoutingTraffDecoder & decoder) - : EdgeEstimator(routing::VehicleType::Car, maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds) + : EdgeEstimator(routing::VehicleType::Decoder, maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds) , m_decoder(decoder) { } From 6d759b3480a17c60aea91a47d640621c831ca199 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 7 Jun 2026 13:00:14 +0300 Subject: [PATCH 250/252] [core] Document version::Format Signed-off-by: mvglasow --- libs/platform/mwm_version.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/platform/mwm_version.hpp b/libs/platform/mwm_version.hpp index 02186b4df..dc69ce80c 100644 --- a/libs/platform/mwm_version.hpp +++ b/libs/platform/mwm_version.hpp @@ -13,6 +13,20 @@ DECLARE_EXCEPTION(CorruptedMwmFile, RootException); namespace version { +/** + * @brief The MWM format version. + * + * This is the global versioning for the MWM format. Some structures in the MWM file may have their + * own versioning in addition to the MWM format version. + * + * When a new version is introduced, add a new member to this `enum` and bump `lastFormat` to the + * new version. + * + * @todo Document where checks for the format version happen, and criteria for adding or not adding + * a new version when changes to the format are made. See #4414. + * + * Versions up to v11 were introduced by Maps.me; v10 and earlier were deprecated by OrganicMaps. + */ enum class Format { unknownFormat = -1, From cede6a4f3cc0fc23b012494a03fedef73af6e002 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Sun, 7 Jun 2026 18:19:52 +0300 Subject: [PATCH 251/252] [traffic] Remove TODO comments Signed-off-by: mvglasow --- libs/routing/edge_estimator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/routing/edge_estimator.cpp b/libs/routing/edge_estimator.cpp index e5665d5df..881a94a53 100644 --- a/libs/routing/edge_estimator.cpp +++ b/libs/routing/edge_estimator.cpp @@ -196,7 +196,6 @@ EdgeEstimator::EdgeEstimator(VehicleType vehicleType, double maxWeightSpeedKMpH, #define N 288 - // TODO do we need entries for VehicleType::Decoder here? static auto constexpr kTurnPenaltyMatrix = [] { array constexpr kTable = {{ From e7b0718d5ddd08d7b16f9bc1418b8d5af514fa69 Mon Sep 17 00:00:00 2001 From: mvglasow Date: Tue, 9 Jun 2026 22:22:22 +0300 Subject: [PATCH 252/252] [traffic] More aggressive heuristic for DecoderModel Signed-off-by: mvglasow --- libs/routing_common/decoder_model.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/routing_common/decoder_model.cpp b/libs/routing_common/decoder_model.cpp index 442c58a55..7a116287b 100644 --- a/libs/routing_common/decoder_model.cpp +++ b/libs/routing_common/decoder_model.cpp @@ -247,15 +247,14 @@ DecoderModel::DecoderModel(VehicleModel::LimitsInitList const & roadLimits) // Set small track speed if highway is not in kDefaultSpeeds (path, pedestrian), but marked as yescar. AddAdditionalRoadTypes(cl, {{std::move(hwtagYesCar), decoder_model::kDefaultSpeeds.at(HighwayType::HighwayTrack)}}); - // Set max possible (reasonable) car speed. See EdgeEstimator::CalcHeuristic. /* - * TODO revisit, we don’t use actual speeds. + * Set max possible (reasonable) car speed. See EdgeEstimator::CalcHeuristic. * Segment weight is distance-based, optionally with a penalty factor (>= 1). Thus maximum speed - * is 1 m/s (`traff_decoder.cpp` has `kOneMpSInKmpH = 3.6`). - * We might want to move `kOneMpSInKmpH` to `decoder_model.hpp` as `traff_decoder.cpp` includes it. - * PS: setting this value more aggressively might also help with performance, see #1253. + * is 1 m/s, or 3.6 km/h. Rounding up as the assertion requires `kMaxCarSpeedKMpH` to be truly + * greater than the speed assumed for any road (cannot be equal). + * However, tests do not show a significant performance improvement over 200 km/h. */ - SpeedKMpH constexpr kMaxCarSpeedKMpH(200.0); + SpeedKMpH constexpr kMaxCarSpeedKMpH(4.0); CHECK_LESS(m_maxModelSpeed, kMaxCarSpeedKMpH, ()); m_maxModelSpeed = kMaxCarSpeedKMpH; }