Compare commits

...

234 Commits

Author SHA1 Message Date
mvglasow
6dbde46dca [traffic] Remove obsolete code, fixes regression from 645ca792
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-23 17:06:15 +02:00
mvglasow
7ed4a6ddd9 Merge commit '07cd1ec4f5' into traffic
# Conflicts:
#	libs/indexer/ftypes_matcher.hpp

Bug: boost/regex.hpp may not be found
Workaround: remove `if` in CMakeLists.txt:226, leave `include_directories`
2025-11-23 17:04:45 +02:00
mvglasow
645ca792f7 [traffic] Remove obsolete code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-22 15:38:47 +02:00
mvglasow
cd64d14830 [traffic] Remove obsolete code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-22 14:45:40 +02:00
mvglasow
67b1d918ba Merge commit '7312560f48' into traffic
# Conflicts:
#	iphone/Maps/UI/Settings/SettingsNavigationView.swift
#	libs/drape_frontend/rule_drawer.cpp
#	libs/traffic/traffic_info.cpp
2025-11-21 22:33:12 +02:00
mvglasow
48a3e5d4b0 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-18 00:00:30 +02:00
mvglasow
d4c002851b [traffic] Ignore access flags when decoding closure events
Fixes decoding of closures which are mapped in OSM as access restrictions

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-13 21:16:45 +02:00
mvglasow
9d4801886e [traffxml] Tweak junction search radius for lower road classes
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-12 21:34:23 +02:00
mvglasow
5a26b72431 [traff_assessment_tool] Do not show progress bar on loading an empty feed
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-12 20:20:01 +02:00
mvglasow
859b89e127 [traff_assessment_tool] Make OK the default button in open dialog
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-11 21:41:25 +02:00
mvglasow
8fcf00b0ca [routing] In decoder mode, snap to segment endpoints (no partial segments)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-11 21:10:27 +02:00
mvglasow
5c2cedb19a [traffxml] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-10 23:32:53 +02:00
mvglasow
67a3866529 [geometry] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-10 21:55:52 +02:00
mvglasow
90fdf0ed72 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-09 23:50:46 +02:00
mvglasow
2b7ee4ba44 [traff_assessment_tool] Show location fuzziness in list
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-09 00:49:47 +02:00
mvglasow
cd1201cb02 [traffic] Prioritize message which are updates with an unchanged location
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 23:45:45 +02:00
mvglasow
e6937b211a [traffic] Optimize comparison for cancellation messages
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 23:03:32 +02:00
mvglasow
27ad70d729 [traffic] Decode nearby messages first (based on position and viewport)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 22:28:01 +02:00
mvglasow
a9700156db [traffic] Keep polling and processing messages while routing
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 17:49:46 +02:00
mvglasow
1d87e0e987 [traffic] Cache routing session state, introduce IsObserverInhibited()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 16:32:28 +02:00
mvglasow
f7882636cd [traffic] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-08 16:28:34 +02:00
mvglasow
40fbea11e7 [traffic][android] Keep Proguard from deleting TraFF classes
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-07 00:41:30 +02:00
mvglasow
e87727c168 [traffic] Add missing override, silence warning
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-06 22:29:06 +02:00
mvglasow
d82b545e30 [traffic][android] Move Java source files to android/sdk
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-06 21:09:26 +02:00
mvglasow
781d973faa Merge commit '2601ec854a' into traffic
# Conflicts:
#	iphone/Maps/Model/Settings.swift
#	iphone/Maps/UI/Settings/SettingsNavigationView.swift
2025-11-05 23:06:07 +02:00
mvglasow
d6818786f7 [traffic] Vary junction search radius depending on road class
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-04 20:33:08 +02:00
x7z4w
615f57c604 [traffxml] Faster MessageFromXml
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-11-03 20:21:50 +01:00
mvglasow
af20e2b42b [traffic] Truncate decoded location to nearby junctions
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-03 20:26:04 +02:00
mvglasow
05f6dfad7b [traffic] Dyamically determine radius in which to search for junctions
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-03 20:26:04 +02:00
mvglasow
2b867a64a0 [traffic] Reduce weight for fake segments involving junctions
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-03 20:26:04 +02:00
mvglasow
c0fd405798 [traffic] Refactor methods for penalty calculation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-03 20:26:04 +02:00
mvglasow
207d6c833d [traffic] Pass toJunctions parameter to TruncateRoute()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-11-03 20:26:04 +02:00
mvglasow
d47aa09053 [traffxml] Process fuzziness attribute in location
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-27 22:50:05 +02:00
mvglasow
fbc150fae2 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-26 18:23:10 +02:00
mvglasow
d88ed01bc1 [traffic] Update comment on offroad penalty
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-26 14:40:47 +02:00
mvglasow
3ec32e4415 [traffic] Use references for loop, not copies
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-25 15:12:05 +03:00
mvglasow
9065f45b21 [traffic] Exclude roundabouts from decoded locations (with exceptions)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-25 15:09:28 +03:00
mvglasow
46d363ae24 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-25 14:49:49 +03:00
mvglasow
0dc62c47dd [traff_assessment_tool] Show decoding time
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-24 21:44:38 +03:00
mvglasow
3c9eeb9a75 [traff_assessment_tool] Introduce status line for traffic panel
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-21 21:13:26 +03:00
mvglasow
ae23afd72e [traff_assessment_tool] Hide progress bar only on final update
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-21 00:07:57 +03:00
mvglasow
800cc0641b [traff_assessment_tool] Refactor TrafficModel constructor
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-20 23:55:29 +03:00
mvglasow
1b74062447 [traff_assessment_tool] Show animation while feed is loading
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-20 23:44:48 +03:00
mvglasow
6a694c5d3e [traffic] Documentation and comment cleanup
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-20 22:47:26 +03:00
mvglasow
dda13b8d3d [traffic] Penalize turns near endpoints
This improves decoding quality on urban multi-carriageway roads.

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-20 22:38:29 +03:00
mvglasow
c1340a9941 [traffic] Truncate locations which overshoot their endpoints
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 22:39:06 +03:00
mvglasow
a313526aed [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 18:02:04 +03:00
mvglasow
32d1a3a36e [traffic] Eliminate any ETA calculations, always return weight
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 17:59:48 +03:00
mvglasow
3a45740884 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 17:45:35 +03:00
mvglasow
4e624bd04b [traffic] Comment out unused argument
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-19 17:45:25 +03:00
mvglasow
a3d1ed83c3 [routing][traffic] Different routing options in navigation and decoder mode
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-18 16:34:32 +03:00
mvglasow
38dbad0f7e [traffic] Add test cases
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-18 15:24:00 +03:00
mvglasow
7fe5823140 [traffic] Optimize offroad penalty in decoder
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-17 21:09:54 +03:00
mvglasow
091d510ba1 [traffic] Simplify ferry landing penalty in TraFF decoder
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-14 21:52:12 +03:00
mvglasow
f07c8d66d8 [traffic] Calculate segment weight based on road ref
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-14 21:41:22 +03:00
Yannik Bloscheck
c9f50cdc72 [ios] Enabled traffic button
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2025-10-14 10:54:37 +02:00
mvglasow
2ed9bc1880 [traffic] Properly process message replacement
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-12 16:39:44 +03:00
mvglasow
d098ecae15 [traff_assessment_tool] Ensure traffic layer is enabled
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-11 19:02:43 +03:00
mvglasow
9e06ec815e [traff_assessment_tool] Handle invalid selections gracefully, clear marks
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-11 15:08:17 +03:00
mvglasow
bf6cf27f8c [traff_assessment_tool] Clear markers when updating TraFF data
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-11 14:47:41 +03:00
mvglasow
bf36c875d5 Merge commit 'eb264889e3' into traffic 2025-10-10 18:40:35 +03:00
mvglasow
30b2df89cd [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-10-09 20:44:33 +03:00
mvglasow
ba2d653a30 [3party/protobuf] Fix faulty merge
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-28 13:55:34 +03:00
mvglasow
d6eacd7364 [drape] Correctly render traffic at zoom 20
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-26 22:53:08 +03:00
mvglasow
07c75e627e [traff_assessment_tool] Move to tools
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-12 21:40:35 +03:00
mvglasow
e7bde9aa05 [openlr_decoder] Fix faulty merge
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-12 21:27:21 +03:00
mvglasow
5ba708caff [map] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-12 19:06:21 +03:00
mvglasow
48e8f32e01 [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 <michael -at- vonglasow.com>
2025-09-12 18:46:50 +03:00
mvglasow
38e98df6cc Merge commit '05cc660641' into traffic
# Conflicts:
#	CMakeLists.txt
#	android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java
#	android/sdk/src/main/cpp/app/organicmaps/sdk/Framework.hpp
#	android/sdk/src/main/cpp/app/organicmaps/sdk/OrganicMaps.cpp
#	android/sdk/src/main/cpp/app/organicmaps/sdk/util/Config.cpp
#	libs/indexer/data_source.hpp
#	libs/indexer/feature.hpp
#	libs/indexer/ftypes_matcher.hpp
#	libs/map/framework.cpp
#	libs/map/traffic_manager.cpp
#	libs/routing/absent_regions_finder.cpp
#	libs/routing/edge_estimator.hpp
#	libs/routing/index_router.cpp
#	libs/routing/index_router.hpp
#	libs/routing/routing_session.hpp
#	libs/routing_common/num_mwm_id.hpp
#	libs/traffic/traffic_info.cpp
#	qt/mainwindow.hpp
#	qt/preferences_dialog.cpp
#	tools/openlr/helpers.hpp
#	tools/openlr/openlr_decoder.cpp
#	tools/openlr/openlr_decoder.hpp
#	tools/openlr/openlr_stat/openlr_stat.cpp
#	tools/openlr/router.hpp
#	tools/openlr/score_candidate_paths_getter.cpp
#	tools/openlr/score_candidate_paths_getter.hpp
#	xcode/CoMaps.xcworkspace/contents.xcworkspacedata
2025-09-10 21:22:40 +03:00
mvglasow
0713e22328 [traffic] Set default traffic URL to dev server
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-09-01 22:02:45 +03:00
mvglasow
3ed31a575f [traffic] Inhibit perodic traffic updates during route calculation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-29 22:12:33 +03:00
mvglasow
ac87e3c585 [traffic] Handle MWM removal during traffic update
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-29 17:49:15 +03:00
mvglasow
ef806cf18a Synchronize map updates with traffic manager
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-28 21:10:42 +03:00
mvglasow
d46c0fec76 [traffic] Fix endless loop in TrafficManager::Invalidate()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-24 22:15:30 +03:00
mvglasow
1d42d3b431 [traff_assessment_tool] On click, zoom to message and show reference points
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-19 20:03:04 +03:00
mvglasow
2663eda820 [traff_assessment_tool] Basic traffic message panel
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-19 00:16:17 +03:00
mvglasow
16708aae7f [traff_assessment_tool] Rename TrafficMode to TrafficModel
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-17 18:08:05 +03:00
mvglasow
6963637b1b [traffxml] Fix compiler warnings
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-16 19:59:28 +03:00
mvglasow
57f55f1022 [traffxml] Fix attribute names in output
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-16 19:52:03 +03:00
Eivind Samseth
01476d3dc7 Standard format changes 2025-08-16 17:59:47 +02:00
mvglasow
90d7cadc3f Merge commit '211e3fb4' into traffic
# Conflicts:
#	android/app/src/main/cpp/CMakeLists.txt
#	android/app/src/main/java/app/organicmaps/sdk/util/Config.java
#	android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java
#	android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageLinksFragment.java
#	android/sdk/src/main/cpp/app/organicmaps/sdk/OrganicMaps.cpp
#	indexer/ftypes_matcher.hpp
#	iphone/Maps/Maps.xcodeproj/project.pbxproj
2025-08-16 18:04:14 +03:00
mvglasow
3a6f21dbd1 [traffic][android] Implement Android TraFF source
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-16 14:59:22 +03:00
mvglasow
221fe69840 Merge remote-tracking branch 'upstream/traffic' into traffic 2025-08-12 19:04:30 +03:00
mvglasow
167f0b8377 Add traffic.xml to .gitignore
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-12 19:02:46 +03:00
mvglasow
df7d507e1b WIP: [traffic][android] Implement Android TraFF source
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-12 00:29:04 +03:00
mvglasow
fe737602d8 [traffxml] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-10 18:58:34 +03:00
mvglasow
daa147a721 [traffxml] Provide virtual destructor for TraffSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-10 18:58:20 +03:00
mvglasow
bd555afe61 [traffxml] Remove unused HttpTraffSource::ThreadRoutine()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-08-10 18:57:52 +03:00
Yannik Bloscheck
73a70d943e [traffic][ios] Make HttpTraffSource configurable in Preferences
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2025-08-06 17:21:12 +02:00
mvglasow
dde50bd0a1 [traffic] Default URL
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-29 20:39:57 +03:00
mvglasow
0106dc3fe5 [traffic] Remove unused constants
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-29 20:39:05 +03:00
mvglasow
034856f033 Merge commit '20c9fc5f' into traffic-2025072304
# Conflicts:
#	android/app/src/main/res/xml/prefs_main.xml
#	qt/CMakeLists.txt
2025-07-28 18:27:49 +03:00
mvglasow
f53c794fdd [traffic] Add missing qualifier
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 01:00:43 +03:00
mvglasow
dcab6ee5a0 [qt] Keep traffic URL editable even when source is disabled
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:26 +03:00
mvglasow
edc15ac982 [traffic][android] Make HttpTraffSource configurable in Preferences
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:26 +03:00
mvglasow
871cd73592 [traffic] Make traffic initialization work with LoadMapsAsync()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
958be3dee6 [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 <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
06f63dcb9a [traffxml] Fix compiler warning
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
bebac8d8a7 [traffxml] Remove openlr dependency, disable OpenLrV3TraffDecoder
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
a25602dbe0 [traffxml] Return to features supported by Clang 20.1 for IsoTime
This reverts commit 776444edc7c4730f67e8aa2fa30b983c73e01054.

# Conflicts:
#	traffxml/traff_model.cpp
2025-07-28 00:33:25 +03:00
mvglasow
98796cd6f8 [android] Link against traffxml
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
2729d07732 [traffic] Fix assignment
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
61b15d623d [traffxml] Fix compiler warnings
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
03d6847be3 [traffic][qt] Make HttpTraffSource configurable from Qt GUI
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
3b1fca01e3 [traffic] API to reconfigure a running HttpTrafficSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
be3792b93a [traffic] Remove obsolete code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
7283e4ecb4 [traffic] Read HttpTrafficSource parameters from config
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
75c7d146af [traffic] Unsubscribe when traffic manager is disabled
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
d988ab3326 [traffic] Restore decoded segments from cache on startup
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
798affe0ef [traffxml] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
93a1f9d1a6 [traffxml] Fix bug when storing point distance attribute
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
3f58c6ee20 [traffic] Implement HttpTraffSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
a20d1453e0 [traffic] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
e825753487 [traffic] Remove obsolete code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
cc58eaa50a [traffic] Restore and document enable/disable/pause/resume logic
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
75197a11a8 [traffic] Consider routing MWMs when updating subscriptions on resume
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
d03b47bee0 [traffic] Refactoring
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:25 +03:00
mvglasow
daf344b27f [traffic] Remove m_mwmCache and related logic
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
121bdc4af8 [traffic] Do not announce traffic updates if nothing has changed
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
6656c7e441 [traffxml] Make sources pluggable
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
f32493faaa [traffxml] Comment and documentation cleanup
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
4f4d376a4a [traffic] Comment out unused code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
4324e329e5 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
9eeac05fdf [traffic] Update routing MWMs as route changes
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
b418cf659c Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
964368f5d4 [traffic] Replace full invalidation with subscription recalculation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
23922f1c2b [traffic] Invalidate per MWM on download (untested)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
f02b1538e7 Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
dd65e89f8f [traffic] Feature-complete cache persistence, including decoded coloring
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
f132022e60 [traffxml] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
247f88254e [traffxml] Fix erroneous parsing of event length
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
81a31d6b42 [traffxml] Documentation and comments
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:24 +03:00
mvglasow
89d1365fee [traffxml] Make some arguments const &
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
9fb08bdc56 [traffxml] Store message cache in file
So far only API and tests, and without decoded segments

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
371a58f6f9 [traffic] Use traff_storage to read hardcoded poll feeds
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
af8b748c59 [traffxml] Add traff_storage
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
a43e83d280 [traff_assessment_tool] Use path of last file (if any) for file dialog
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
04b2059ca0 [traffic] workaround for drape bug when updating segments
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
ed15925251 [traffxml] Remove some log output
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
173b5e1718 [traffic] Update clear/purge logic to use update mechanism
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
26aa5e5f54 [traffic] Handle removed segments or eased traffic impact
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
f31541efb2 [traffxml] Purge expired messages
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
db3ed87b92 [traff_assessment_tool] Update window title
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
fbaa5470fd [traff_assessment_tool] Shift timestamps read from TraFF files
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
0681171d69 [traffxml] Introduce timestamp shift operation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
e3d86be324 [traffxml] Use std::chrono:utc_clock for IsoTime, improve parsing
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
ef3de2c781 [traffxml] Use std::chrono::system_clock for IsoTime
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:23 +03:00
mvglasow
d574b536ba [traffxml] Fix ISO8601 parser regex
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
2d3ca8014b Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
df13e279b6 [traffic] Override EdgeEstimator::CalcOffroad()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
b48310e6a5 [traffxml] Parse duration quantifier and use it for delays
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
b98fe1999c Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
d47713516d [traffxml] Ensure decoder uses newly-added maps
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
d72bd9e00e [traffic] Update traffic for all MWMs, active or not
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
8cffe8fa64 [traffic] Documentation and comments
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
ae5dea4a53 [traffic] Comment out more obsolete code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
588332a23b [traffic] Remove dead code
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
3eb99e952c [map] Documentation and comments
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
9f4b6d73ce [traff_assessment_tool] Add UI for Clear()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
488159e2f9 [traffic] Implement Clear()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
f30316d868 [traff_assessment_tool] Open TraFF feed
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
daaf52d27d [traffic] Fix Push() and make it public for testing
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
ba9980ba36 [traffic] Introduce test mode for traffic manager
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
5531b1129b [traffxml] Code comment
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:22 +03:00
mvglasow
394a6673e5 [traffxml] Silence compiler warning
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
62ee9d5b46 [traffic] Abort event loop run immediately if TrafficManager is disabled
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
2592bcf042 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 <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
dd7ed98c1a [traffic] Use enabled state instead of Start()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
76fce016bb [traffxml] Process delay in traffic impact
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
7db32a9922 [traffxml] Do not request additional maps during TraFF decoding
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
fa5608d874 [traffxml] Decode point locations (at)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
185febd8d8 [traffic] Documentation and comments
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
7a5ea64ea0 [traffxml] Score candidates based on road attributes
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
a4106505af [traffxml] Code cleanup
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
083845a502 [traffxml] Fix location matching on dual carriageway roads
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
c6de2a25aa [traffic] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
4c5fb21c33 [traffic] Use distances, not travel time, for weight in TraffEstimator
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
f566f6f0ef [traffxml] Use custom EdgeEstimator for decoding
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
9afb28aaa1 [traffxml] Add router-based decoder, still crude, ugly and buggy
To use it, redefine DefaultTraffDecoder in traffxml/traff_decoder.hpp

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
bd178932c1 [traffic] Refactor TraFF decoder into separate class
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
2894218573 [traffic] Use LFRCNP, derived from FRC
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
63f0799161 [traffic] Calculate DNP from nominal distance, if available and plausible
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
5b67d668bd [traffic] Refactor message decoding
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
f7adea08a2 [traffic] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:21 +03:00
mvglasow
c0c8d5da58 [traffxml] Parse and store distance for location points
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
2ed300ca08 [traffic] Comment cleanup
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
52a915211e [traffic] Remove mwms from ThreadRoutine()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
18f1dfac45 [traffxml] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
a7897e2347 [traffic] Calculate filter list for active MWMs
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
e3f5dd3ca8 [traffic] Throttle UI/router refresh while messages are being processed
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
de03995e77 [openlr] Modify openlr_stat to work with single data source
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
74d79e5c8e [traffic] Reuse previously decoded segments and coloring, where possible
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
e2aff53291 [traffic] Comparison operators for TrafficImpact, TraffLocation and Point
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
a39bdee0d1 [traffic] Refactor IsoTime comparison operators
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
136293c308 [traffic] Add IsoTime::IsPast()
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
356b051036 [traffic] Re-enable message deduplication between feed queue and cache
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
c8d5a07262 [traffic] Defer TrafficManager startup until MWMs are first updated
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
e94c23d538 [traffic] Insert mew messages into cache but skip deduplication for now
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
2ba3030366 [traffic] Remove forgotten InitializeDataSources() method
Obsolete since we started using a single data source in 5a031c55

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
3455050876 [traffic] Forgotten hunk of 9f39d3bc (store coloring with message)
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
cf57942a0b [traffic] Allow decoding to be interrupted after each message
Message deduplication currently disabled

Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
3a713c477a [traffic] Refactor IsoTime into a class
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
edb1b7e784 [traffic] Consolidate feed queue before decoding messages
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
53e80b9283 [traffic] Refactor m_feeds to m_feedQueue
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
7107314e2f [traffic] Store colorings with message and build global coloring from that
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
fafec070c9 [traffic] Use MWM ID for Coloring map, now that we have a single DataSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
d7facd5732 [openlr] Initialize OpenLR decoder with a single DataSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
382e46af63 [routing] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:20 +03:00
mvglasow
d0a9c564e4 [traffic] Process TrafficImpact::m_maxspeed
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
a9ceec3995 [traffic] Initialize TrafficManager with a DataSource
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
73d61ff655 [traffic] Store TraFF message ID with decoded path
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
5cdf14386d [traffxml] Set OpenLR FOW for ramps
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
2f6a8564cb [openlr] Evaluate FOW for Sliproad/*_link
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
80a7ed503e [traffxml] Tweak GuessDnp
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
6e65e60c3d [traffxml] Set FRC on all OpenLR location reference points
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
f041f910e7 [openlr] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
dbf253c9d1 [openlr] Always evaluate FRC and FOW, regardless of LinearSegment source
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
24d65bd37f 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 <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
16cb70a952 [traffic] Add TrafficInfo constructor with Coloring
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
8827ec3c09 [indexer] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
7be0b8a256 [traffxml] Adhere to naming convention for member names
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
f0f847b214 [openlr] Debug output for FunctionalRoadClass
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
2017907b1f [traffic] Documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
bb410fc3bc [traffxml] Add module
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
6e8d400611 [traffic] Include OpenLR headers in traffic_manager.cpp
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
7b420def17 [qt] Link against openlr library
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
737d7b5643 [traffic] Initialize TrafficManager with CountryParentNameGetterFn
Signed-off-by: mvglasow <michael -at- vonglasow.com>

# Conflicts:
#	map/framework.cpp
#	map/traffic_manager.cpp
#	map/traffic_manager.hpp
2025-07-28 00:33:19 +03:00
mvglasow
9c93f421ac [traffic] Add documentation
Signed-off-by: mvglasow <michael -at- vonglasow.com>
2025-07-28 00:33:19 +03:00
mvglasow
1574b5b7cb Revert "[routing] Do not create TrafficStash instance."
This reverts commit 3c12ba5134f2aa9d19c0c9a54af89d368f389eb4.
2025-07-28 00:33:18 +03:00
mvglasow
932dda6552 Revert "[desktop] Disable traffic switch and TrafficManager initialization."
This reverts commit 16ad61f4c8ebd22bdc282496122db49a5243f02f.
2025-07-28 00:33:18 +03:00
mvglasow
6f2f61b30a Revert "[qt] Remove defunct Traffic layer button"
This reverts commit df2541e1bf12abca329becdac8de7c92f0893b03.
2025-07-28 00:33:08 +03:00
Konstantin Pastbin
20c9fc5f45 [fdroid] Release version 2025.07.23-4
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2025-07-23 21:00:52 +07:00
Konstantin Pastbin
be3e3d773b [android] Fix Panoramax links not working
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2025-07-23 20:59:37 +07:00
Konstantin Pastbin
0fd7f8d573 [fdroid] Release version 2025.07.23-2
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2025-07-23 10:50:00 +07:00
142 changed files with 13385 additions and 1116 deletions

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@ data/symbols/**/symbols.sdf
data/bookmarks
data/edits.xml
data/traffic.xml
data/World.mwm
data/WorldCoasts.mwm
data/world_mwm/*

View File

@@ -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 { *; }

View File

@@ -1 +1 @@
version: 2025.03.02-7-FDroid+25030207
version: 2025.07.23-4-FDroid+25072304

View File

@@ -62,6 +62,21 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="org.traffxml.traff.GET_CAPABILITIES"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.POLL"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.SUBSCRIBE"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.SUBSCRIPTION_CHANGE"/>
</intent>
<intent>
<action android:name="org.traffxml.traff.UNSUBSCRIBE"/>
</intent>
</queries>
<supports-screens

View File

@@ -12,6 +12,7 @@ public class LayersUtils
availableLayers.add(Mode.OUTDOORS);
availableLayers.add(Mode.ISOLINES);
availableLayers.add(Mode.SUBWAY);
availableLayers.add(Mode.TRAFFIC);
return availableLayers;
}
}

View File

@@ -3,12 +3,19 @@ package app.organicmaps.settings;
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;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;
@@ -28,6 +35,7 @@ import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.search.SearchRecents;
import app.organicmaps.sdk.settings.MapLanguageCode;
import app.organicmaps.sdk.settings.UnitLocale;
import app.organicmaps.sdk.traffxml.AndroidTransport;
import app.organicmaps.sdk.util.Config;
import app.organicmaps.sdk.util.NetworkPolicy;
import app.organicmaps.sdk.util.PowerManagment;
@@ -35,11 +43,13 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
import app.organicmaps.sdk.util.log.LogsManager;
import app.organicmaps.util.ThemeSwitcher;
import app.organicmaps.util.Utils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
{
@@ -61,6 +71,10 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
initAutoDownloadPrefsCallbacks();
initLargeFontSizePrefsCallbacks();
initTransliterationPrefsCallbacks();
initTrafficHttpEnabledPrefsCallbacks();
initTrafficHttpUrlPrefsCallbacks();
initTrafficAppsPrefs();
initTrafficLegacyEnabledPrefsCallbacks();
init3dModePrefsCallbacks();
initPerspectivePrefsCallbacks();
initAutoZoomPrefsCallbacks();
@@ -136,6 +150,46 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
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 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));
@@ -163,6 +217,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
updateVoiceInstructionsPrefsSummary();
updateRoutingSettingsPrefsSummary();
updateMapLanguageCodeSummary();
updateTrafficHttpUrlSummary();
updateTrafficAppsSummary();
}
@Override
@@ -224,6 +280,91 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
});
}
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 initTrafficAppsPrefs()
{
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> 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<String> entryList = new ArrayList<>(receivers.size());
List<String> 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<String>, each item is a package ID
String[] apps = ((Set<String>)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));

View File

@@ -34,6 +34,11 @@
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
<string name="pref_navigation" translatable="false">Navigation</string>
<string name="pref_information" translatable="false">Information</string>
<string name="pref_traffic" translatable="false">Traffic</string>
<string name="pref_traffic_http_enabled" translatable="false">TrafficHttpEnabled</string>
<string name="pref_traffic_http_url" translatable="false">TrafficHttpUrl</string>
<string name="pref_traffic_apps" translatable="false">TrafficApps</string>
<string name="pref_traffic_legacy_enabled" translatable="false">TrafficLegacyEnabled</string>
<string name="pref_transliteration" translatable="false">Transliteration</string>
<string name="pref_power_management" translatable="false">PowerManagment</string>
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>

View File

@@ -215,6 +215,7 @@
<!-- Settings information group in settings screen -->
<string name="prefs_group_information">Information</string>
<string name="prefs_group_route">Navigation</string>
<string name="prefs_group_traffic">Traffic information</string>
<string name="pref_zoom_title">Zoom buttons</string>
<string name="pref_zoom_summary">Display on the map</string>
<!-- Settings «Map» category: «Night style» title -->
@@ -787,6 +788,24 @@
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
<!-- Current language of the map! -->
<string name="change_map_locale">Map language</string>
<!-- Enable live traffic data via HTTP (title) -->
<string name="traffic_http_enabled">Enable live traffic data</string>
<!-- Enable live traffic data via HTTP (description) -->
<string name="traffic_http_enabled_description">When enabled, the app will periodically retrieve traffic information from the configured URL.</string>
<!-- URL for live traffic data -->
<string name="traffic_http_url">Traffic service URL</string>
<!-- Status message indicating that user did not set a traffic URL yet. -->
<string name="traffic_http_url_not_set">Not set</string>
<!-- TraFF 0.8 apps from which to receive data (title) -->
<string name="traffic_apps">Use data from TraFF applications</string>
<!-- Status message indicating that no TraFF 0.8 apps are installed -->
<string name="traffic_apps_not_available">No apps installed</string>
<!-- Status message indicating that no TraFF 0.8 apps are currently selected -->
<string name="traffic_apps_none_selected">No apps salected</string>
<!-- Enable traffic data from TraFF 0.7 apps (title) -->
<string name="traffic_legacy_enabled">Use data from legacy TraFF applications</string>
<!-- Enable traffic data from TraFF 0.7 apps (description) -->
<string name="traffic_legacy_enabled_description">When enabled, the app will receive and process traffic data from legacy TraFF applications.</string>
<!-- OpenStreetMap text on splash screen -->
<string name="splash_subtitle">Map data from OpenStreetMap</string>
<!-- Telegram group url for the "?" About page -->

View File

@@ -175,6 +175,36 @@
</intent>
</PreferenceScreen>
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_traffic"
android:title="@string/prefs_group_traffic"
android:order="4">
<SwitchPreferenceCompat
android:key="@string/pref_traffic_http_enabled"
android:title="@string/traffic_http_enabled"
app:singleLineTitle="false"
android:summary="@string/traffic_http_enabled_description"
android:defaultValue="true"
android:order="1"/>
<EditTextPreference
android:key="@string/pref_traffic_http_url"
android:title="@string/traffic_http_url"
app:singleLineTitle="false"
android:order="2"/>
<MultiSelectListPreference
android:key="@string/pref_traffic_apps"
android:title="@string/traffic_apps"
app:singleLineTitle="false"
android:order="3"/>
<SwitchPreferenceCompat
android:key="@string/pref_traffic_legacy_enabled"
android:title="@string/traffic_legacy_enabled"
app:singleLineTitle="false"
android:summary="@string/traffic_legacy_enabled_description"
android:defaultValue="true"
android:order="4"/>
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory
android:key="@string/pref_privacy"
android:title="@string/privacy"

View File

@@ -17,6 +17,7 @@ set(SRC
app/organicmaps/sdk/opengl/gl3stub.h
app/organicmaps/sdk/platform/GuiThread.hpp
app/organicmaps/sdk/platform/AndroidPlatform.hpp
app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp
app/organicmaps/sdk/util/Distance.hpp
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
@@ -76,6 +77,8 @@ set(SRC
app/organicmaps/sdk/platform/PThreadImpl.cpp
app/organicmaps/sdk/platform/SecureStorage.cpp
app/organicmaps/sdk/platform/SocketImpl.cpp
app/organicmaps/sdk/traffxml/AndroidTraffSource.cpp
app/organicmaps/sdk/traffxml/SourceImpl.cpp
app/organicmaps/sdk/util/Config.cpp
app/organicmaps/sdk/util/GeoUtils.cpp
app/organicmaps/sdk/util/HttpClient.cpp
@@ -127,6 +130,7 @@ target_link_libraries(${PROJECT_NAME}
# icu
# agg
# vulkan_wrapper
traffxml
# Android libs
log

View File

@@ -182,6 +182,8 @@ public:
void Set3dMode(bool allow3d, bool allow3dBuildings);
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
TrafficManager & GetTrafficManager() { return m_work.GetTrafficManager(); }
void SetMapLanguageCode(std::string const & languageCode);
std::string GetMapLanguageCode();

View File

@@ -1,6 +1,7 @@
#include "app/organicmaps/sdk/Framework.hpp"
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
#include "app/organicmaps/sdk/core/jni_helper.hpp"
@@ -34,6 +35,26 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_OrganicMaps_nativeInitFramework(
JNIEnv * env = jni::GetEnv();
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
env->CallVoidMethod(*onComplete, methodId);
ASSERT(g_framework, ("g_framework must be non-null"));
/*
* Add traffic sources for Android.
*/
jclass configClass = env->FindClass("app/organicmaps/sdk/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));
});
}
}

View File

@@ -0,0 +1,115 @@
#include "AndroidTraffSource.hpp"
#include "app/organicmaps/sdk/core/jni_helper.hpp"
namespace traffxml {
void AndroidTraffSourceV0_7::Create(TraffSourceManager & manager)
{
std::unique_ptr<AndroidTraffSourceV0_7> source = std::unique_ptr<AndroidTraffSourceV0_7>(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/sdk/traffxml/SourceImplV0_7");
static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;J)V");
jlong nativeManager = reinterpret_cast<jlong>(&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<MwmSet::MwmId> & 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<AndroidTraffSourceV0_8> source = std::unique_ptr<AndroidTraffSourceV0_8>(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/sdk/traffxml/SourceImplV0_8");
static jmethodID const implConstructor = jni::GetConstructorID(env, implClass, "(Landroid/content/Context;JLjava/lang/String;)V");
jlong nativeManager = reinterpret_cast<jlong>(&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<MwmSet::MwmId> & mwms)
{
JNIEnv * env = jni::GetEnv();
std::string data = "<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>";
env->CallVoidMethod(m_implObject, m_subscribeImpl, jni::ToJavaString(env, data));
}
void AndroidTraffSourceV0_8::ChangeSubscription(std::set<MwmSet::MwmId> & mwms)
{
JNIEnv * env = jni::GetEnv();
std::string data = "<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>";
env->CallVoidMethod(m_implObject, m_changeSubscriptionImpl, jni::ToJavaString(env, data));
}
void AndroidTraffSourceV0_8::Unsubscribe()
{
jni::GetEnv()->CallVoidMethod(m_implObject, m_unsubscribeImpl);
}
} // namespace traffxml

View File

@@ -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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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

View File

@@ -0,0 +1,34 @@
// TODO which of the two do we need? (jni_helper includes jni)
//#include <jni>
#include "app/organicmaps/sdk/core/jni_helper.hpp"
#include "traffxml/traff_source.hpp"
#include "traffxml/traff_model_xml.hpp"
#include <optional>
extern "C"
{
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_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<traffxml::TraffSourceManager*>(nativeManager);
manager.ReceiveFeed(parsedFeed);
}
}

View File

@@ -119,4 +119,74 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_util_Config_nativeSetTranslitera
frm()->SaveTransliteration(value);
frm()->AllowTransliteration(value);
}
JNIEXPORT jboolean JNICALL
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz)
{
return frm()->LoadTrafficHttpEnabled();
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz,
jboolean value)
{
frm()->SaveTrafficHttpEnabled(value);
frm()->SetTrafficHttpEnabled(value);
}
JNIEXPORT jstring JNICALL
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz)
{
std::string value = frm()->LoadTrafficHttpUrl();
return jni::ToJavaString(env, value);
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz,
jstring value)
{
frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value));
frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value));
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_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<traffxml::AndroidTraffSourceV0_7*>(source))
{
traffSource->Close();
return true;
}
else
return false;
});
if (value)
traffxml::AndroidTraffSourceV0_7::Create(tm);
}
JNIEXPORT void JNICALL
Java_app_organicmaps_sdk_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<traffxml::AndroidTraffSourceV0_8*>(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"

View File

@@ -0,0 +1,111 @@
/*
* Copyright © 20172020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
import java.util.List;
import app.organicmaps.sdk.traffxml.Version;
import app.organicmaps.sdk.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.
*
* <p>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.
*
* <p>This encapsulates most of the low-level Android handling.
*
* <p>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.
*
* <p>All intents are sent as explicit ordered broadcasts. This means two things:
*
* <p>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.
*
* <p>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<ResolveInfo> 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);
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
public class AndroidTransport {
/**
* Intent to poll a peer for its capabilities.
*
* <p>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.
*
* <p>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.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>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.
*
* <p>This is a broadcast intent. It can be used in different forms:
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>The filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra.
*
* <p>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,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>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.
*
* <p>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,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>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.
*
* <p>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.
*
* <p>This is a String extra. It contains a {@code feed} XML element.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>This is a String extra.
*/
public static final String EXTRA_PACKAGE = "package";
/**
* Extra which contains a subscription ID.
*
* <p>This is a String extra.
*/
public static final String EXTRA_SUBSCRIPTION_ID = "subscription_id";
/**
* Extra which contains the timeout duration for a subscription.
*
* <p>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];
}
}

View File

@@ -0,0 +1,70 @@
package app.organicmaps.sdk.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);
}

View File

@@ -0,0 +1,127 @@
package app.organicmaps.sdk.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.sdk.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<ResolveInfo> receivers07 = pm.queryBroadcastReceivers(outIntent, 0);
List<ResolveInfo> 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);
}
}
}
}

View File

@@ -0,0 +1,240 @@
package app.organicmaps.sdk.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.sdk.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);
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);
}
/**
* 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())));
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);
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 dont 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 isnt 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("<feed>\n");
while (cursor.moveToNext())
builder.append(cursor.getString(cursor.getColumnIndex(AndroidTransport.COLUMN_DATA))).append("\n");
builder.append("</feed>");
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();
}
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.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;
}

View File

@@ -70,6 +70,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() {}
@@ -388,6 +398,63 @@ public final class Config
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 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()
{
return getBool("NY");
}
@NonNull
public static String getDonateUrl()
{
@@ -531,4 +598,10 @@ public final class Config
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);
private static native void applyTrafficApps(String[] value);
private static native void applyTrafficLegacyEnabled(boolean value);
}

View File

@@ -0,0 +1,17 @@
<!-- From DE-RealWorldLargeFeed. -->
<!--
The route starts at a large junction, goes down the wrong carriageway of the motorway,
changes direction at the next junction, then back.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.7:d.1.10209.n.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_class="MOTORWAY" road_name="Westlicher Berliner Ring" road_ref="A10">
<from junction_name="Werder" junction_ref="21">+52.334801 +12.814650</from>
<to junction_name="Groß Kreutz" junction_ref="22">+52.393700 +12.835000</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,17 @@
<!-- From DE-RealWorldLargeFeed. -->
<!--
The route ends at a large junction, proceeds past the reference point, then
goes back down the wrong carriageway of the motorway.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.7:d.1.10292.p.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_class="MOTORWAY" road_name="Autobahnzubringer Magdeburg" road_ref="A115">
<from junction_name="Potsdam-Drewitz" junction_ref="5a">+52.352650 +13.140700</from>
<to junction_name="Nuthetal" junction_ref="7">+52.300201 +13.083500</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,16 @@
<!--
From DE-realworld5.
Test case for turn penalty (eastern end).
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.29829.n.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Mittlerer Ring" road_ref="B2R">
<from junction_name="Lerchenauer Straße">+48.176102 +11.558100</from>
<to junction_name="Petueltunnel">+48.178001 +11.572800</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_QUEUE">
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,19 @@
<!-- From DE-realworld5. -->
<!--
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.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.22689.p.1" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Mittlerer Ring" road_ref="B2R">
<from junction_name="München-Sendling-Süd">+48.110901 +11.518500</from>
<to junction_name="Passauerstraße">+48.110649 +11.534150</to>
</location>
<events>
<event class="CONGESTION" type="CONGESTION_QUEUE">
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,22 @@
<!--
Subset of realworld5.
Test case for turn penalty (both ends).
Junction names are the names of the crossing roads.
No segments of the crossing roads should be among the matched segments.
-->
<feed>
<message expiration_time="2025-10-09T21:37:08+03:00" id="tmc:d.1.15:d.1.46572.p.1,14" receive_time="2025-10-09T21:17:08+03:00" update_time="2025-10-09T21:17:08+03:00">
<location directionality="ONE_DIRECTION" fuzziness="LOW_RES" road_name="Leopoldstraße" road_ref="GM4">
<from junction_name="Potsdamer Straße">+48.167301 +11.586200</from>
<to junction_name="Ungererstraße">+48.164200 +11.586500</to>
</location>
<events>
<event class="INCIDENT" type="INCIDENT_ACCIDENT">
</event>
<event class="CONGESTION" type="CONGESTION_STATIONARY_TRAFFIC">
</event>
<event class="HAZARD" type="HAZARD_PASSABLE_WITH_CARE_BELOW_ELEVATION">
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,58 @@
<!--
Test cases for reference points inside a roundabout.
If the roundabout itself is included in the decoded location, the message would
affect all roads which connect to it, resulting in incorrect routing, especially
in the case of closure events. For this reason, we must truncate roundabouts if
they occur at the start or end of the decoded location.
-->
<feed>
<message id="tmc:d.1.12:d.1.13962.p.5,11" receive_time="2018-07-27T10:22:22+02:00" update_time="2018-07-27T08:43:15Z" expiration_time="2018-07-27T09:43:15Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="SECONDARY" road_ref="L87">
<from junction_name="Rheinau-Freistett/B36">+48.661098 +7.936800</from>
<to junction_name="Gambsheim (F)">+48.683701 +7.916600</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_CONSTRUCTION_WORK">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.26212.p.5,11" receive_time="2018-07-27T10:21:49+02:00" update_time="2018-07-27T08:42:41Z" expiration_time="2018-07-27T09:42:41Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="PRIMARY" road_ref="B428">
<from junction_name="Hackenheim">+49.822948 +7.906900</from>
<to junction_name="Frei-Laubersheim">+49.803650 +7.901450</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_LONGTERM_ROADWORKS">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.48638.n.9,11" receive_time="2018-07-27T10:11:06+02:00" update_time="2018-07-27T08:31:43Z" expiration_time="2018-07-27T09:31:43Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="BOTH_DIRECTIONS" road_class="PRIMARY" road_ref="B272">
<from junction_name="Hochstadt">+49.239750 +8.222300</from>
<to junction_name="Weingarten">+49.253448 +8.268100</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_ROADWORKS">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED_AHEAD">
</event>
</events>
</message>
<message id="tmc:d.1.12:d.1.56576.n.9,11" receive_time="2018-07-27T10:22:53+02:00" update_time="2018-07-27T08:43:45Z" expiration_time="2018-07-27T09:43:45Z" urgency="URGENT">
<location fuzziness="LOW_RES" directionality="ONE_DIRECTION" road_class="PRIMARY" road_ref="B417">
<from junction_name="Diez">+50.372398 +8.038500</from>
<to junction_name="Diez">+50.370850 +8.004050</to>
</location>
<events>
<event class="CONSTRUCTION" type="CONSTRUCTION_RESURFACING_WORK">
</event>
<event class="RESTRICTION" type="RESTRICTION_CLOSED">
<supplementary_info class="VEHICLE" type="S_VEHICLE_THROUGH_TRAFFIC"/>
</event>
</events>
</message>
</feed>

View File

@@ -0,0 +1,23 @@
<!--
This is a 140 m location. Reference points are almost exactly on the opposite
carriageway; since this is inside a junction, there is a wider gap between
the two carriageways than there would be on a normal stretch of expressway.
Without truncation, the decoded location starts approximately in the right
spot but overshoots the end point, going to the nearest junction, then back
in the opposite direction and to the end point on the opposite carriageway,
ending within 5 m of the end point.
-->
<feed>
<message id="lt.eismoinfo.restrictions:4249b6510b73750684ca94de5fe8cf32,eastbound" receive_time="2025-01-21T12:33:06Z" update_time="2025-01-21T12:33:06Z" expiration_time="2025-12-31T21:00:00Z" cancellation="false" forecast="false" urgency="NORMAL">
<merge/>
<location country="LT" origin="Suvalkai*" directionality="ONE_DIRECTION" destination="Kaunas" road_class="TRUNK" road_ref="A5">
<from distance="0.14">54.939945 23.879789</from>
<to distance="0.0" junction_name="Kaunas">54.940094 23.881950</to>
</location>
<events>
<event class="RESTRICTION" type="RESTRICTION_MAX_WIDTH" q_dimension="3.5"/>
<event class="CONSTRUCTION" type="CONSTRUCTION_ROADWORKS"/>
<event class="RESTRICTION" type="RESTRICTION_SPEED_LIMIT" speed="50"/>
</events>
</message>
</feed>

View File

@@ -108,8 +108,6 @@ auto constexpr TMP_OFFSETS_EXT = OFFSET_EXT EXTENSION_TMP;
#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"

View File

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

View File

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

View File

@@ -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 <vector>
namespace traffic
{
bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath)
{
try
{
std::vector<TrafficInfo::RoadSegmentId> keys;
TrafficInfo::ExtractTrafficKeys(mwmPath, keys);
std::vector<uint8_t> 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

View File

@@ -1,8 +0,0 @@
#pragma once
#include <string>
namespace traffic
{
bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath);
} // namespace traffic

View File

@@ -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;

View File

@@ -27,6 +27,40 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
@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;

View File

@@ -717,6 +717,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";
@@ -816,6 +823,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";

View File

@@ -737,6 +737,13 @@
"editor_place_doesnt_exist_description" = "Describe what the place looks like now to send an error note to the OpenStreetMap community";
"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";
@@ -837,6 +844,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";

View File

@@ -18,6 +18,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 */; };
2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; };
@@ -767,6 +768,7 @@
272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = "<group>"; };
272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
274720592E439FBA00C516DF /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; };
2752B6C92E31197000887CC4 /* MapLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLanguage.swift; sourceTree = "<group>"; };
2752B6CD2E3121D800887CC4 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = "<group>"; };
2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
@@ -1792,6 +1794,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 */,
@@ -2029,6 +2032,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
274720592E439FBA00C516DF /* libtraffxml.a */,
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
FA853BF226BC5DE50026D455 /* libshaders.a */,

View File

@@ -448,6 +448,28 @@ import AVFoundation
}
/// 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

View File

@@ -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 {

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -81,26 +81,35 @@
<rect key="frame" x="16" y="58" width="308" height="64"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
<rect key="frame" x="0.0" y="0.0" width="1024" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
<rect key="frame" x="1024" y="0.0" width="1024" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
<rect key="frame" x="2048" y="0.0" width="1024" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
</connections>
</view>
<view contentMode="scaleToFill" id="95L-lU-yCQ" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="-358" y="-352" width="1024" height="768"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<viewLayoutGuide key="safeArea" id="ZQs-ua-gDZ"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onTrafficButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="tie-i7-JaB"/>
</connections>
</view>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
@@ -144,6 +153,7 @@
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
<outlet property="trafficButton" destination="95L-lU-yCQ" id="O9W-En-8Rc"/>
</connections>
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
</tableViewCell>

View File

@@ -60,6 +60,14 @@ struct SettingsNavigationView: View {
@State var forceRefreshDate: Date = Date.now
/// 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 {
@@ -212,6 +220,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())
@@ -229,6 +255,8 @@ struct SettingsNavigationView: View {
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
hasLiveTraffic = Settings.hasLiveTraffic
liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? ""
}
.onChange(of: scenePhase) { _ in
forceRefreshDate = Date.now
@@ -276,6 +304,15 @@ struct SettingsNavigationView: View {
}
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
.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
}
}
}
}

View File

@@ -19,4 +19,5 @@ add_subdirectory(shaders)
add_subdirectory(storage)
add_subdirectory(tracking)
add_subdirectory(traffic)
add_subdirectory(traffxml)
add_subdirectory(transit)

View File

@@ -38,9 +38,9 @@ namespace
{
// The first zoom level in kAverageSegmentsCount.
int constexpr kFirstZoomInAverageSegments = 10;
std::array<size_t, 10> constexpr kAverageSegmentsCount = {
// 10 11 12 13 14 15 16 17 18 19
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500};
std::array<size_t, 11> constexpr 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;

View File

@@ -1,5 +1,6 @@
#pragma once
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "base/math.hpp"
@@ -9,17 +10,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 <typename Point>
class ParametrizedSegment
{
@@ -36,7 +40,9 @@ public:
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,9 +58,22 @@ public:
return math::Pow2(CrossProduct(diff, m_d));
}
// Returns the point of the segment that is closest to |p|.
m2::PointD ClosestPointTo(Point const & p) const
/**
* @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, 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);

View File

@@ -19,20 +19,31 @@ public:
/// Registers a new map.
std::pair<MwmId, RegResult> 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;
@@ -66,18 +77,24 @@ private:
std::unique_ptr<FeatureSourceFactory> 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<FeatureSourceFactory>()) {}
};
/// 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:

View File

@@ -76,6 +76,18 @@ public:
// (number of points in inner triangle-strips).
using PointsBufferT = buffer_vector<m2::PointD, 32>;
/**
* @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);
@@ -85,6 +97,13 @@ public:
void ParseHeader2();
void ParseRelations();
void ParseAllBeforeGeometry() { ParseRelations(); }
/**
* @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);

View File

@@ -701,24 +701,46 @@ 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,
Trunk,
Primary,
Secondary,
Tertiary,
/**
* Unclassified, residential, living street and `highway=road`.
*/
LivingStreet,
/**
* Service, track, busway and `man_made=pier`.
*/
Service,
// OSM highway=service type is widely used even for _significant_ roads.
// Adding a new type to distinguish mapped driveway or parking_aisle.
ServiceMinor,
/**
* 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);

View File

@@ -157,8 +157,10 @@ public:
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:
@@ -243,19 +245,26 @@ public:
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 */) {}
};
@@ -304,7 +313,14 @@ public:
/// @todo In fact, std::shared_ptr<MwmInfo> is a MwmId. Seems like better to make vector<MwmId> interface.
void GetMwmsInfo(std::vector<std::shared_ptr<MwmInfo>> & 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 wont be marked” mean? Not all will be marked/some might not be marked?
* Or all will be unmarked?
*/
void Clear();
void ClearCache();
@@ -345,10 +361,18 @@ private:
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.

View File

@@ -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"
@@ -17,13 +19,18 @@
#include "search/locality_finder.hpp"
#include "storage/country_info_getter.hpp"
#include "storage/routing_helpers.hpp"
#include "storage/storage.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"
#include "editor/editable_data_source.hpp"
#include "descriptions/loader.hpp"
#include "indexer/categories_holder.hpp"
@@ -39,6 +46,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,11 +69,13 @@
#include "base/logging.hpp"
#include "base/math.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include "std/target_os.hpp"
#include "defines.hpp"
#include "private.h"
#include <algorithm>
@@ -93,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";
@@ -273,8 +285,11 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
[this]() -> StringsBundle const &
{ return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }),
static_cast<RoutingManager::Delegate &>(*this))
, m_trafficManager(bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes,
m_routingManager.RoutingSession())
, 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())
, m_lastReportedCountry(kInvalidCountryId)
, m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG)
, m_descriptionsLoader(std::make_unique<descriptions::Loader>(m_featuresFetcher.GetDataSource()))
@@ -348,10 +363,10 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
editor.SetDelegate(make_unique<search::EditorDelegate>(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());
if (params.m_trafficTestMode)
m_trafficManager.SetTestMode();
m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
m_isolinesManager.SetEnabled(LoadIsolinesEnabled());
@@ -383,6 +398,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();
@@ -398,14 +426,14 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, storage::Loc
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.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_trafficManager.Invalidate();
m_transitManager.Invalidate();
m_isolinesManager.Invalidate();
@@ -487,6 +515,8 @@ void Framework::LoadMapsSync()
LOG(LDEBUG, ("Editor initialized"));
GetStorage().RestoreDownloadQueue();
InitializeTraffic();
}
// Small copy-paste with LoadMapsSync, but I don't have a better solution.
@@ -509,6 +539,8 @@ void Framework::LoadMapsAsync(std::function<void()> && callback)
GetStorage().RestoreDownloadQueue();
InitializeTraffic();
callback();
});
}).detach();
@@ -2475,6 +2507,42 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled)
settings::Set(kTrafficEnabledKey, trafficEnabled);
}
void Framework::SetTrafficHttpEnabled(bool enabled)
{
m_trafficManager.SetHttpTraffSource(enabled, LoadTrafficHttpUrl());
}
bool Framework::LoadTrafficHttpEnabled()
{
bool enabled;
if (!settings::Get(kTrafficHttpEnabledKey, enabled))
enabled = false;
return enabled;
}
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;
if (!settings::Get(kTrafficHttpUrlKey, url))
url = TRAFFIC_HTTP_URL_DEFAULT;
return url;
}
void Framework::SaveTrafficHttpUrl(std::string trafficHttpUrl)
{
settings::Set(kTrafficHttpUrlKey, trafficHttpUrl);
}
bool Framework::LoadTrafficSimplifiedColors()
{
bool simplified;

View File

@@ -103,11 +103,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) {}
};
@@ -233,7 +244,34 @@ public:
/// \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<void()> && callback);
/// Registers all local map files in internal indexes.
@@ -384,6 +422,16 @@ private:
private:
std::vector<m2::TriangleD> 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);
@@ -489,11 +537,18 @@ private:
std::unique_ptr<descriptions::Loader> 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);
void UpdateViewport(search::Results const & results);
@@ -721,6 +776,14 @@ public:
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);
bool LoadTrafficSimplifiedColors();
void SaveTrafficSimplifiedColors(bool simplified);

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,15 @@
#include "indexer/mwm_set.hpp"
#include "routing/routing_session.hpp"
#include "storage/country_info_getter.hpp"
#include "traffxml/traff_decoder.hpp"
#include "traffxml/traff_model.hpp"
#include "traffxml/traff_source.hpp"
#include "traffxml/traff_storage.hpp"
#include "geometry/point2d.hpp"
#include "geometry/polyline2d.hpp"
#include "geometry/screenbase.hpp"
@@ -28,21 +37,68 @@
#include <utility>
#include <vector>
class TrafficManager final
class TrafficManager final : public traffxml::TraffSourceManager
{
public:
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
using TrafficUpdateCallbackFn = std::function<void(bool)>;
/**
* @brief Global state of traffic information.
*/
/*
* 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. */
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
};
/**
* @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);
@@ -55,25 +111,122 @@ public:
using TrafficStateChangedFn = std::function<void(TrafficState)>;
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
traffic::TrafficObserver & observer);
TrafficManager(DataSource & dataSource,
CountryInfoGetterFn countryInfoGetter,
CountryParentNameGetterFn const & countryParentNameGetter,
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
routing::RoutingSession & routingSession);
~TrafficManager();
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<std::string, traffxml::TraffMessage> GetMessageCache();
TrafficState GetState() const;
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
void SetDrapeEngine(ref_ptr<df::DrapeEngine> 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.
*
* Upon creation, the traffic manager is disabled. 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.
*
* @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
*/
void SetEnabled(bool enabled);
/**
* @brief Whether the traffic manager is enabled.
*
* @return True if enabled, false if not
*/
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 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<bool(traffxml::TraffSource*)>& pred);
/**
* @brief Starts the traffic manager.
*
*/
void Start();
void UpdateViewport(ScreenBase const & screen);
void UpdateMyPosition(MyPosition const & myPosition);
void Invalidate();
/**
* @brief Invalidates traffic information for the specified MWM.
*
* 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.
*
* 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);
void OnDestroySurface();
void OnRecoverSurface();
@@ -85,57 +238,295 @@ public:
void SetSimplifiedColorScheme(bool simplified);
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; }
private:
struct CacheEntry
/**
* @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.
* 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.
*/
void SetTestMode();
/**
* @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) override;
/**
* @brief Registers a `TraffSource`.
* @param source The source.
*/
virtual void RegisterSource(std::unique_ptr<traffxml::TraffSource> 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<MwmSet::MwmId> & activeMwms) override;
/**
* @brief Purges expired messages from the cache.
*
* This method is safe to call from any thread, except for the traffic worker thread.
*/
void PurgeExpiredMessages();
/**
* @brief Clears the traffic message cache and feed queue.
*
* This is intended for testing purposes and clears the message cache, as well as the feed queue.
* Subscriptions are not changed.
*/
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);
/**
* @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<void()> f)
{
CacheEntry();
explicit CacheEntry(std::chrono::time_point<std::chrono::steady_clock> const & requestTime);
std::lock_guard<std::mutex> lock(m_mutex);
f();
}
bool m_isLoaded;
size_t m_dataSize;
private:
std::chrono::time_point<std::chrono::steady_clock> m_lastActiveTime;
std::chrono::time_point<std::chrono::steady_clock> m_lastRequestTime;
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
/**
* @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);
int m_retriesCount;
bool m_isWaitingForResponse;
/**
* @brief Ensures every TraFF source has a subscription covering all currently active MWMs.
*
* This method cycles through all TraFF sources in `m_trafficSources` and calls
* `SubscribeOrChangeSubscription()` on each of them.
*/
void SubscribeOrChangeSubscription();
traffic::TrafficInfo::Availability m_lastAvailability;
};
/**
* @brief Unsubscribes from all traffic services we are subscribed to.
*
* This method cycles through all TraFF sources in `m_trafficSources` and calls `Unsubscribe()`
* on each of them.
*/
void Unsubscribe();
/**
* @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 all traffic services for updates.
*
* 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.
*/
void 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.
*
* @return true if messages were purged, false if not
*/
bool PurgeExpiredMessagesImpl();
/**
* @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 Removes the first message from the first feed and decodes it.
*/
void DecodeFirstMessage();
/**
* @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();
bool WaitForRequest(std::vector<MwmSet::MwmId> & mwms);
void OnTrafficDataResponse(traffic::TrafficInfo && info);
void OnTrafficRequestFailed(traffic::TrafficInfo && info);
/**
* @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.
*
* @return `true` during normal operation, `false` during teardown (signaling the event loop to exit).
*/
bool WaitForRequest();
/// \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 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();
/**
* @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 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
* `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<MwmSet::MwmId> & lastMwmsByRect,
std::set<MwmSet::MwmId> & activeMwms);
// This is a group of methods that haven't their own synchronization inside.
void RequestTrafficData();
void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force);
void Clear();
void ClearCache(MwmSet::MwmId const & mwmId);
void ShrinkCacheToAllowableSize();
void UpdateState();
void ChangeState(TrafficState newState);
bool IsInvalidState() const;
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<MwmSet::MwmId> & activeMwms) const;
/**
* @brief Pauses the traffic manager.
*
* Upon creation, the traffic manager is not paused.
*
* While the traffic manager is paused and no route is active, the traffic manager will not update
* its subscription area (upon resuming, 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, and an active route effectively overrides the paused
* 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, and an active route effectively overrides the paused
* state. It is intended for internal use by the framework.
*/
void Resume();
template <class F>
void ForEachMwm(F && f) const
{
std::vector<std::shared_ptr<MwmInfo>> allMwmInfo;
m_dataSource.GetMwmsInfo(allMwmInfo);
std::for_each(allMwmInfo.begin(), allMwmInfo.end(), std::forward<F>(f));
}
template <class F>
void ForEachActiveMwm(F && f) const
{
@@ -144,44 +535,233 @@ private:
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(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); }
/**
* @brief Whether we are currently routing.
*/
bool IsRouting() const { return m_routingSessionState != routing::SessionState::NoValidRoute; }
/**
* @brief Whether the traffic manager is paused and not routing.
*
* This is used to inhibit polling and message decoding.
*/
bool IsPausedAndNotRouting() const { return m_isPaused && !IsRouting(); }
DataSource & m_dataSource;
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;
/**
* @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<int64_t> 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<MyPosition, bool> m_currentPosition = {MyPosition(), false};
std::pair<MyPosition, bool> m_currentPositionLazy = m_currentPosition;
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false};
std::pair<ScreenBase, bool> m_currentModelViewLazy = m_currentModelView;
/**
* The mode in which the traffic manager is running.
*/
Mode m_mode = Mode::Normal;
/**
* Whether the traffic manager accepts mode changes.
*
* Mode cannot be set after the traffic manager has been enabled for the first time.
*/
bool m_canSetMode = true;
std::atomic<TrafficState> m_state;
TrafficStateChangedFn m_onStateChangedFn;
bool m_hasSimplifiedColorScheme = true;
size_t m_maxCacheSizeBytes;
size_t m_currentCacheSizeBytes = 0;
std::map<MwmSet::MwmId, CacheEntry> m_mwmCache;
/**
* @brief The TraFF sources from which we get traffic information.
*
* Threads must lock `m_trafficSourceMutex` prior to accessing this member.
*/
std::vector<std::unique_ptr<traffxml::TraffSource>> m_trafficSources;
bool m_isRunning;
std::condition_variable m_condition;
/*
* 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.
*
* 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. 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.
* * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription()).
*
* Methods which use both, but in a different way:
*
* * 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<MwmSet::MwmId> m_lastDrapeMwmsByRect;
std::set<MwmSet::MwmId> m_activeDrapeMwms;
std::vector<MwmSet::MwmId> m_lastRoutingMwmsByRect;
std::vector<MwmSet::MwmId> m_lastPositionMwmsByRect;
std::set<MwmSet::MwmId> m_activePositionMwms;
std::set<MwmSet::MwmId> m_activeRoutingMwms;
// 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<MwmSet::MwmId, std::string> m_trafficETags;
/**
* @brief Whether active MWMs have changed since the last request.
*/
bool m_activeMwmsChanged = false;
std::atomic<bool> m_isPaused;
std::vector<MwmSet::MwmId> m_requestedMwms;
/**
* @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.
*/
threads::SimpleThread m_thread;
/**
* @brief When the last response was received.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
/**
* @brief When the last update notification to the Drape engine was posted.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastDrapeUpdate;
/**
* @brief When the last update notification to the traffic observer was posted.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastObserverUpdate;
/**
* @brief When the cache file was last updated.
*/
std::chrono::time_point<std::chrono::steady_clock> m_lastStorageUpdate;
/**
* @brief Whether a poll operation is needed.
*
* 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;
/**
* @brief Queue of feeds waiting to be processed.
*
* Threads must lock `m_mutex` before accessing `m_feedQueue`, as some platforms may receive feeds
* on multiple threads.
*/
std::vector<traffxml::TraffFeed> 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<bool> m_isFeedQueueSortInvalid = false;
/**
* @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<std::string, traffxml::TraffMessage> m_messageCache;
/**
* @brief The storage instance.
*
* Used to persist the TraFF message cache between sessions.
*/
std::unique_ptr<traffxml::LocalStorage> m_storage;
/**
* @brief The TraFF decoder instance.
*
* Used to decode TraFF locations into road segments on the map.
*/
std::unique_ptr<traffxml::DefaultTraffDecoder> m_traffDecoder;
/**
* @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<MwmSet::MwmId, traffic::TrafficInfo::Coloring> m_allMwmColoring;
/**
* @brief Callback function which gets called on traffic updates.
*
* Intended for testing.
*/
std::optional<TrafficUpdateCallbackFn> m_trafficUpdateCallbackFn;
};
extern std::string DebugPrint(TrafficManager::TrafficState state);

View File

@@ -36,20 +36,61 @@ public:
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 @@ private:
bool m_hasPosition = false;
};
/**
* @brief A mark in the shape of a dot.
*/
class DebugMarkPoint : public UserMark
{
public:
@@ -141,6 +185,9 @@ public:
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
};
/**
* @brief A mark in the shape of a dot, of caller-defined color and radius.
*/
class ColoredMarkPoint : public UserMark
{
public:

View File

@@ -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 @@ public:
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.

View File

@@ -17,6 +17,8 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile
void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate)
{
m_regions.clear();
if (m_routerThread)
{
m_routerThread->Cancel();
@@ -48,18 +50,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set<std::string> & regions)
void AbsentRegionsFinder::GetAllRegions(std::set<std::string> & countries)
{
countries.clear();
// Note: if called from `RoutingSession` callback, m_state will still have its pre-update value.
if (m_routerThread)
{
m_routerThread->Join();
if (!m_routerThread)
return;
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
{
if (!mwmName.empty())
m_regions.emplace(mwmName);
}
m_routerThread->Join();
m_routerThread.reset();
}
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
if (!mwmName.empty())
countries.emplace(mwmName);
m_routerThread.reset();
countries = m_regions;
}
bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const

View File

@@ -14,19 +14,45 @@ namespace routing
{
using LocalFileCheckerFn = std::function<bool(std::string const &)>;
// 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:
AbsentRegionsFinder(CountryFileGetterFn const & countryFileGetter, LocalFileCheckerFn const & localFileChecker,
std::shared_ptr<NumMwmIds> 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);
// 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<std::string> & 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<std::string> & absentCountries);
private:
@@ -39,5 +65,19 @@ private:
DataSource & m_dataSource;
std::unique_ptr<threads::Thread> 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<std::string> m_regions;
};
} // namespace routing

View File

@@ -80,6 +80,13 @@ bool AsyncRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin
return m_router->FindClosestProjectionToRoad(point, direction, radius, proj);
}
void AsyncRouter::GetAllRegions(std::set<std::string> & countries)
{
if (!m_absentRegionsFinder)
return;
m_absentRegionsFinder->GetAllRegions(countries);
}
void AsyncRouter::RouterDelegateProxy::OnProgress(float progress)
{
ProgressCallback onProgress = nullptr;

View File

@@ -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();
@@ -59,6 +63,15 @@ public:
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<std::string> & countries);
private:
/// Worker thread function
void ThreadFunc();

View File

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

View File

@@ -33,9 +33,18 @@ public:
// @TODO(bykoianko) Method Generate() should fill
// vector<RouteSegment> 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<geometry::PointWithAltitude> const & path,
base::Cancellable const & cancellable, std::vector<RouteSegment> & routeSegments);
void Clear();

View File

@@ -22,41 +22,170 @@ 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 vehicleType The vehicle type.
* @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(VehicleType vehicleType, double maxWeightSpeedKMpH, SpeedKMpH const & offroadSpeedKMpH,
DataSource * dataSourcePtr = nullptr, std::shared_ptr<NumMwmIds> 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.
double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const;
/**
* @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.
*/
virtual 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;
virtual double GetTurnPenalty(Purpose purpose, double angle, RoadGeometry const & from_road,
RoadGeometry const & to_road, bool is_left_hand_traffic = false) 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 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.
*
* @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<EdgeEstimator> Create(VehicleType vehicleType, double maxWeighSpeedKMpH,
SpeedKMpH const & offroadSpeedKMpH,
std::shared_ptr<TrafficStash> trafficStash, DataSource * dataSourcePtr,
std::shared_ptr<NumMwmIds> numMwmIds);
/**
* @brief Creates an `EdgeEstimator` based on a vehicle model.
*
* This is a convenience wrapper around `Create(VehicleType, double, SpeedKMpH const &,
* std::shared_ptr<TrafficStash>, DataSource *, std::shared_ptr<NumMwmIds>)`, 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<EdgeEstimator> Create(VehicleType vehicleType, VehicleModelInterface const & vehicleModel,
std::shared_ptr<TrafficStash> trafficStash, DataSource * dataSourcePtr,
std::shared_ptr<NumMwmIds> numMwmIds);
@@ -74,13 +203,70 @@ private:
// std::shared_ptr<NumMwmIds> m_numMwmIds;
// std::unordered_map<NumMwmId, double> 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);
} // namespace routing

View File

@@ -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<m2::PointD> 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<Segment> const & segments, m2::PointD const & point, WorldGraph & graph)
FakeEnding MakeFakeEnding(vector<Segment> const & segments, m2::PointD const & point,
WorldGraph & graph, bool snapToEnds)
{
FakeEnding ending;
double averageAltitude = 0.0;
@@ -57,7 +58,7 @@ FakeEnding MakeFakeEnding(vector<Segment> 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<Segment> 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());

View File

@@ -40,9 +40,11 @@ struct FakeEnding final
std::vector<Projection> m_projections;
};
FakeEnding MakeFakeEnding(std::vector<Segment> const & segments, m2::PointD const & point, WorldGraph & graph);
FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph);
FakeEnding MakeFakeEnding(std::vector<Segment> 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

View File

@@ -143,9 +143,10 @@ void FeaturesRoadGraphBase::ForEachFeatureClosestToCross(m2::PointD const & cros
}
void FeaturesRoadGraphBase::FindClosestEdges(m2::RectD const & rect, uint32_t count,
vector<pair<Edge, geometry::PointWithAltitude>> & vicinities) const
vector<pair<Edge, geometry::PointWithAltitude>> & vicinities,
bool snapToEnds) const
{
NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */);
NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */, snapToEnds);
m_dataSource.ForEachStreet([&](FeatureType & ft)
{

View File

@@ -84,8 +84,21 @@ public:
/// @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<std::pair<Edge, geometry::PointWithAltitude>> & vicinities) const override;
std::vector<std::pair<Edge, geometry::PointWithAltitude>> & vicinities,
bool snapToEnds = false) const override;
std::vector<IRoadGraph::FullRoadInfo> FindRoads(m2::RectD const & rect,
IsGoodFeatureFn const & isGoodFeature) const override;
void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const override;

View File

@@ -217,6 +217,9 @@ template <typename AccessPositionType>
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);

View File

@@ -123,10 +123,10 @@ unique_ptr<DirectionsEngine> CreateDirectionsEngine(VehicleType vehicleType, sha
UNREACHABLE();
}
shared_ptr<TrafficStash> CreateTrafficStash(VehicleType, shared_ptr<NumMwmIds>, traffic::TrafficCache const &)
shared_ptr<TrafficStash> CreateTrafficStash(VehicleType vehicleType, shared_ptr<NumMwmIds> numMwmIds,
traffic::TrafficCache const & trafficCache)
{
return nullptr;
// return (vehicleType == VehicleType::Car ? make_shared<TrafficStash>(trafficCache, numMwmIds) : nullptr);
return (vehicleType == VehicleType::Car ? make_shared<TrafficStash>(trafficCache, numMwmIds) : nullptr);
}
void PushPassedSubroutes(Checkpoints const & checkpoints, vector<Route::SubrouteAttrs> & subroutes)
@@ -262,6 +262,37 @@ 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> numMwmIds, unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
std::shared_ptr<EdgeEstimator> estimator, 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(estimator)
, 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<WorldGraph> IndexRouter::MakeSingleMwmWorldGraph()
{
auto worldGraph = MakeWorldGraph();
@@ -283,7 +314,7 @@ bool IndexRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin
std::vector<EdgeProjectionT> 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;
@@ -516,7 +547,7 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints,
guidesMwmId = m_numMwmIds->GetId(country);
}
if (!route.GetAbsentCountries().empty())
if ((GetMode() == Mode::Navigation) && !route.GetAbsentCountries().empty())
return RouterResultCode::NeedMoreMaps;
TrafficStash::Guard guard(m_trafficStash);
@@ -1060,10 +1091,15 @@ RouterResultCode IndexRouter::AdjustRoute(Checkpoints const & checkpoints, m2::P
return RouterResultCode::NoError;
}
RoutingOptions IndexRouter::GetRoutingOptions()
{
return RoutingOptions::LoadCarOptionsFromSettings();
}
unique_ptr<WorldGraph> 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);
@@ -1110,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;
}
@@ -1195,12 +1231,11 @@ bool IndexRouter::PointsOnEdgesSnapping::IsFencedOff(m2::PointD const & point, E
return false;
}
// static
void IndexRouter::PointsOnEdgesSnapping::RoadsToNearestEdges(m2::PointD const & point, vector<RoadInfoT> const & roads,
IsEdgeProjGood const & isGood,
vector<EdgeProjectionT> & edgeProj)
{
NearestEdgeFinder finder(point, isGood);
NearestEdgeFinder finder(point, isGood, (m_router.GetMode() == Mode::Decoding));
for (auto const & road : roads)
finder.AddInformationSource(road);
@@ -1324,7 +1359,7 @@ bool IndexRouter::PointsOnEdgesSnapping::FindBestEdges(m2::PointD const & checkp
}
// Removing all candidates which are fenced off by the road graph (|closestRoads|) from |checkpoint|.
return !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.

View File

@@ -44,6 +44,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:
@@ -68,6 +86,24 @@ public:
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 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
*/
IndexRouter(VehicleType vehicleType, bool loadAltitudes, CountryParentNameGetterFn const & countryParentNameGetterFn,
TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn,
std::shared_ptr<NumMwmIds> numMwmIds, std::unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
@@ -90,6 +126,63 @@ public:
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 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
* @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 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> numMwmIds, std::unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
std::shared_ptr<EdgeEstimator> estimator, DataSource & dataSource);
/**
* @brief Returns the mode in which the router is operating.
*
* The `IndexRouter` always returns `Mode::Navigation`; subclasses may override this method and
* return different values.
*
* 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.
*
* 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 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<AStarProgress> const & progress,
@@ -154,8 +247,8 @@ private:
static bool IsFencedOff(m2::PointD const & point, EdgeProjectionT const & edgeProjection,
std::vector<RoadInfoT> const & fences);
static void RoadsToNearestEdges(m2::PointD const & point, std::vector<RoadInfoT> const & roads,
IsEdgeProjGood const & isGood, std::vector<EdgeProjectionT> & edgeProj);
void RoadsToNearestEdges(m2::PointD const & point, std::vector<RoadInfoT> const & roads,
IsEdgeProjGood const & isGood, std::vector<EdgeProjectionT> & edgeProj);
Segment GetSegmentByEdge(Edge const & edge) const;

View File

@@ -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<m2::PointD> 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<m2::PointD> 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;

View File

@@ -27,7 +27,12 @@ using IsEdgeProjGood = std::function<bool(std::pair<Edge, geometry::PointWithAlt
class NearestEdgeFinder
{
public:
NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood);
/**
* @param snapToEnds If false, projections of `point` can be anywhere on a segment;
* if true, `point` is projected onto the nearest segment endpoint.
*/
NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood,
bool snapToEnds = false);
inline bool HasCandidates() const { return !m_candidates.empty(); }
@@ -56,6 +61,7 @@ private:
std::vector<EdgeProjectionT> & res) const;
m2::PointD const m_point;
bool m_snapToEnds;
std::vector<Candidate> m_candidates;
IsEdgeProjGood m_isEdgeProjGood;
};

View File

@@ -263,7 +263,7 @@ public:
/// then returns empty array.
using EdgeProjectionT = std::pair<Edge, JunctionPointT>;
virtual void FindClosestEdges(m2::RectD const & /*rect*/, uint32_t /*count*/,
std::vector<EdgeProjectionT> & /*vicinities*/) const
std::vector<EdgeProjectionT> & /*vicinities*/, bool snapToEnds) const
{}
/// \returns Vector of pairs FeatureID and corresponding RoadInfo for road features

View File

@@ -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 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.
*/
class RoadPoint final
{
public:

View File

@@ -39,8 +39,21 @@ namespace routing
using SubrouteUid = uint64_t;
SubrouteUid constexpr kInvalidSubrouteId = std::numeric_limits<uint64_t>::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:
@@ -69,17 +82,50 @@ public:
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 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
{
// 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;
/**
* @brief Whether the road is part of a roundabout.
*/
bool m_onRoundabout = false;
RoadNameInfo() = default;
RoadNameInfo(std::string name) : m_name(std::move(name)) {}
@@ -158,8 +204,30 @@ public:
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.
*
* 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; }
/**
* @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.
*
* 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; }
bool HasTransitInfo() const { return m_transitInfo.HasTransitInfo(); }

View File

@@ -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 isnt 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,
};
/*

View File

@@ -61,6 +61,26 @@ bool IsRoad(Types const & types)
void FillSegmentInfo(std::vector<double> const & times, std::vector<RouteSegment> & 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<geometry::PointWithAltitude> const & path, std::vector<double> const & times,
Route & route);

View File

@@ -445,6 +445,13 @@ double RoutingSession::GetCompletionPercent() const
return percent;
}
void RoutingSession::GetAllRegions(std::set<std::string> & countries)
{
if (!m_router)
return;
m_router->GetAllRegions(countries);
}
void RoutingSession::PassCheckpoints()
{
CHECK_THREAD_CHECKER(m_threadChecker, ());

View File

@@ -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
@@ -66,24 +69,105 @@ public:
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;
@@ -172,6 +256,15 @@ public:
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<std::string> & countries);
private:
struct DoReadyCallback
{

View File

@@ -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:
@@ -78,7 +80,9 @@ public:
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;
};

View File

@@ -218,19 +218,53 @@ public:
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:

View File

@@ -17,11 +17,21 @@ using NumMwmId = std::uint16_t;
NumMwmId constexpr kFakeNumMwmId = std::numeric_limits<NumMwmId>::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,16 +44,36 @@ public:
// 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);

View File

@@ -147,14 +147,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:

View File

@@ -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<uint8_t> GenerateTrafficValues(std::vector<traffic::TrafficInfo::RoadSegmentId> const & keys,
boost::python::dict const & segmentMappingDict, uint8_t useTempBlock)
{
@@ -139,6 +144,13 @@ std::vector<uint8_t> GenerateTrafficValues(std::vector<traffic::TrafficInfo::Roa
return buf;
}
/*
* 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<uint8_t> GenerateTrafficValuesFromList(boost::python::list const & keys,
boost::python::dict const & segmentMappingDict)
{
@@ -148,6 +160,13 @@ std::vector<uint8_t> 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<uint8_t> GenerateTrafficValuesFromBinary(std::vector<uint8_t> 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));
}

View File

@@ -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<uint8_t>(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<size_t>(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);

View File

@@ -33,49 +33,6 @@ namespace traffic
{
using namespace std;
namespace
{
bool ReadRemoteFile(string const & url, vector<uint8_t> & 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;
}
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();
}
char constexpr kETag[] = "etag";
} // namespace
// TrafficInfo::RoadSegmentId -----------------------------------------------------------------
TrafficInfo::RoadSegmentId::RoadSegmentId() : m_fid(0), m_idx(0), m_dir(0) {}
@@ -88,46 +45,11 @@ TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t di
uint8_t const TrafficInfo::kLatestKeysVersion = 0;
uint8_t const TrafficInfo::kLatestValuesVersion = 0;
TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion)
TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring)
: m_mwmId(mwmId)
, m_currentDataVersion(currentDataVersion)
, m_coloring(std::move(coloring))
{
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<uint8_t> buf(static_cast<size_t>(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"));
}
m_availability = Availability::IsAvailable;
}
// static
@@ -144,19 +66,6 @@ void TrafficInfo::SetTrafficKeysForTesting(vector<RoadSegmentId> const & keys)
m_availability = Availability::IsAvailable;
}
bool TrafficInfo::ReceiveTrafficData(string & etag)
{
vector<SpeedGroup> 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;
}
SpeedGroup TrafficInfo::GetSpeedGroup(RoadSegmentId const & id) const
{
auto const it = m_coloring.find(id);
@@ -216,70 +125,6 @@ void TrafficInfo::CombineColorings(vector<TrafficInfo::RoadSegmentId> const & ke
ASSERT_EQUAL(numUnexpectedKeys, 0, ());
}
// static
void TrafficInfo::SerializeTrafficKeys(vector<RoadSegmentId> const & keys, vector<uint8_t> & result)
{
vector<uint32_t> fids;
vector<size_t> numSegs;
vector<bool> 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<vector<uint8_t>> memWriter(result);
WriteToSink(memWriter, kLatestKeysVersion);
WriteVarUint(memWriter, fids.size());
{
BitWriter<decltype(memWriter)> bitWriter(memWriter);
uint32_t prevFid = 0;
for (auto const & fid : fids)
{
uint64_t const fidDiff = static_cast<uint64_t>(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<uint8_t> const & data, vector<TrafficInfo::RoadSegmentId> & result)
{
@@ -352,166 +197,6 @@ void TrafficInfo::SerializeTrafficValues(vector<SpeedGroup> const & values, vect
deflate(buf.data(), buf.size(), back_inserter(result));
}
// static
void TrafficInfo::DeserializeTrafficValues(vector<uint8_t> const & data, vector<SpeedGroup> & result)
{
using Inflate = coding::ZLib::Inflate;
vector<uint8_t> decompressedData;
Inflate inflate(Inflate::Format::ZLib);
inflate(data.data(), data.size(), back_inserter(decompressedData));
MemReaderWithExceptions memReader(decompressedData.data(), decompressedData.size());
ReaderSource<decltype(memReader)> src(memReader);
auto const version = ReadPrimitiveFromSource<uint8_t>(src);
CHECK_EQUAL(version, kLatestValuesVersion, ("Unsupported version of traffic keys."));
auto const n = ReadVarUint<uint32_t>(src);
result.resize(n);
BitReader<decltype(src)> bitReader(src);
for (size_t i = 0; i < static_cast<size_t>(n); ++i)
{
// SpeedGroup's values fit into 3 bits.
result[i] = static_cast<SpeedGroup>(bitReader.Read(3));
}
ASSERT_EQUAL(src.Size(), 0, ());
}
// 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<uint8_t> contents;
int errorCode;
if (!ReadRemoteFile(url + ".keys", contents, errorCode))
return false;
if (errorCode != 200)
{
LOG(LWARNING, ("Network error when reading keys"));
return false;
}
vector<RoadSegmentId> 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;
}
TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, vector<SpeedGroup> & 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<uint8_t> 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;
}
bool TrafficInfo::UpdateTrafficData(vector<SpeedGroup> 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;
}
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;
}
string DebugPrint(TrafficInfo::RoadSegmentId const & id)
{
string const dir = id.m_dir == TrafficInfo::RoadSegmentId::kForwardDirection ? "Forward" : "Backward";

View File

@@ -15,23 +15,49 @@ 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:
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 23 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
};
/**
* @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.
@@ -68,84 +94,102 @@ public:
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<RoadSegmentId, SpeedGroup>;
TrafficInfo() = default;
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)
* @return The new `TrafficInfo` instance
*/
static TrafficInfo BuildForTesting(Coloring && coloring);
void SetTrafficKeysForTesting(std::vector<RoadSegmentId> 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.
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
*
* @todo We dont 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<RoadSegmentId> & 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<TrafficInfo::RoadSegmentId> const & keys,
TrafficInfo::Coloring const & knownColors, TrafficInfo::Coloring & result);
// 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<RoadSegmentId> const & keys, std::vector<uint8_t> & result);
/*
* TODO We dont 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<uint8_t> const & data, std::vector<RoadSegmentId> & result);
static void SerializeTrafficValues(std::vector<SpeedGroup> const & values, std::vector<uint8_t> & result);
static void DeserializeTrafficValues(std::vector<uint8_t> const & data, std::vector<SpeedGroup> & 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,
};
friend void UnitTest_TrafficInfo_UpdateTrafficData();
// todo(@m) A temporary method. Remove it once the keys are added
// to the generator and the data is regenerated.
bool ReceiveTrafficKeys();
// 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<SpeedGroup> & values);
// Updates the coloring and changes the availability status if needed.
bool UpdateTrafficData(std::vector<SpeedGroup> const & values);
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<RoadSegmentId> m_keys;
MwmSet::MwmId m_mwmId;

View File

@@ -37,7 +37,11 @@ protected:
};
} // 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<TrafficInfo::RoadSegmentId> 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

View File

@@ -0,0 +1,21 @@
project(traffxml)
set(SRC
traff_decoder.cpp
traff_decoder.hpp
traff_model.cpp
traff_model.hpp
traff_model_xml.cpp
traff_model_xml.hpp
traff_source.cpp
traff_source.hpp
traff_storage.cpp
traff_storage.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
pugixml
coding
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,644 @@
#pragma once
#include "traffxml/traff_model.hpp"
#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"
#include "routing/router.hpp"
#include "routing/vehicle_mask.hpp"
#include "routing_common/num_mwm_id.hpp"
#include "storage/country_info_getter.hpp"
#include <optional>
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
{
public:
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
const CountryParentNameGetterFn & countryParentNameGetter,
std::map<std::string, traffxml::TraffMessage> & messageCache);
virtual ~TraffDecoder() {}
/**
* @brief Decodes a single message to its segments and their speed groups.
*
* 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.
*/
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;
CountryInfoGetterFn m_countryInfoGetterFn;
CountryParentNameGetterFn m_countryParentNameGetterFn;
/**
* @brief Cache of all currently active TraFF messages.
*
* Keys are message IDs, values are messages.
*/
std::map<std::string, traffxml::TraffMessage> & m_messageCache;
/**
* @brief Consolidated traffic impact of the message currently being decoded
*/
std::optional<traffxml::TrafficImpact> m_trafficImpact;
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.
*/
class OpenLrV3TraffDecoder : public TraffDecoder
{
public:
OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
const CountryParentNameGetterFn & countryParentNameGetter,
std::map<std::string, traffxml::TraffMessage> & 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) override;
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> & 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<openlr::LinearSegment> 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;
};
#endif
/**
* @brief A `TraffDecoder` implementation which internally uses the routing engine.
*/
class RoutingTraffDecoder : public TraffDecoder,
public MwmSet::Observer
{
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 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
*/
DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn,
routing::TCountryFileFn const & countryFileFn,
routing::CountryRectFn const & countryRectFn,
std::shared_ptr<routing::NumMwmIds> numMwmIds,
std::unique_ptr<m4::Tree<routing::NumMwmId>> numMwmTree,
DataSource & dataSource, RoutingTraffDecoder & decoder);
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.
*/
/**
* @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() override { return IndexRouter::Mode::Decoding; }
/**
* @brief Returns current routing options.
*
* For traffic decoding purposes, all roads are allowed.
*/
routing::RoutingOptions GetRoutingOptions() override;
private:
};
class TraffEstimator final : public routing::EdgeEstimator
{
public:
TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr<routing::NumMwmIds> numMwmIds,
double maxWeightSpeedKMpH,
routing::SpeedKMpH const & offroadSpeedKMpH,
RoutingTraffDecoder & decoder)
: EdgeEstimator(routing::VehicleType::Car, maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds)
, m_decoder(decoder)
{
}
// 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;
/**
* @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;
/**
* @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;
};
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<std::string, traffxml::TraffMessage> & 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 */) 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<routing::HighwayType> highwayType,
std::optional<RoadClass> 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<std::string> & 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.
*
* 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 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.
*
* @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) 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)
* @param backwards True when decoding the backward direction, false when decodign the forward direction.
*/
void TruncateRoute(std::vector<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints, bool backwards);
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 locations 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<m2::PointD, double> & junctions);
/**
* @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<routing::NumMwmIds> m_numMwmIds = std::make_shared<routing::NumMwmIds>();
std::unique_ptr<routing::IRouter> m_router;
std::optional<traffxml::TraffMessage> 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<m2::PointD, double> 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<m2::PointD, double> 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()`
*/
std::vector<std::string> m_roadRef;
};
/**
* @brief The default TraFF decoder implementation, recommended for production use.
*/
//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);
/**
* @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<std::string> ParseRef(std::string const & 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
* @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<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints,
size_t & start, double & startSaving, double const startWeight,
std::map<m2::PointD, double> const & junctions);
/**
* @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
* @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<routing::RouteSegment> & rsegments,
routing::Checkpoints const & checkpoints,
size_t & end, double & endSaving, double const endWeight,
std::map<m2::PointD, double> const & junctions);
} // namespace traffxml

View File

@@ -0,0 +1,635 @@
#include "traffxml/traff_model.hpp"
#include "base/logging.hpp"
#include <iomanip>
#include <unordered_map>
#include <boost/regex.hpp>
using namespace std;
namespace traffxml
{
const std::unordered_map<EventType, traffic::SpeedGroup> 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::unordered_map<EventType, uint8_t> 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::unordered_map<EventType, uint16_t> 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)
};
std::optional<IsoTime> 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<std::chrono::utc_clock>` 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, doesnt support it at all.
*
* The workaround is therefore to use `std::chrono::time_point<std::chrono::system_clock>`, 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
* 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)
*/
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}))?))?");
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;
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;
std::time_t tt = timegm(&tm);
std::chrono::time_point<std::chrono::system_clock> tp = std::chrono::system_clock::from_time_t(tt);
IsoTime result(tp);
return result;
}
else
{
LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString));
return std::nullopt;
}
}
IsoTime IsoTime::Now()
{
return IsoTime(std::chrono::system_clock::now());
}
IsoTime::IsoTime(std::chrono::time_point<std::chrono::system_clock> tp)
: m_tp(tp)
{}
bool IsoTime::IsPast()
{
return m_tp < std::chrono::system_clock::now();
}\
void IsoTime::Shift(IsoTime nowRef)
{
auto const offset = std::chrono::system_clock::now() - nowRef.m_tp;
m_tp += offset;
}
std::string IsoTime::ToString() const
{
auto const tp_seconds = time_point_cast<std::chrono::seconds>(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)
{
return m_tp < rhs.m_tp;
}
bool IsoTime::operator> (IsoTime & rhs)
{
return m_tp > rhs.m_tp;
}
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;
}
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);
}
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<TrafficImpact> TraffMessage::GetTrafficImpact()
{
// no events, no impact
if (m_events.empty())
return std::nullopt;
// examine events
std::vector<TrafficImpact> 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)
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
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;
}
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(const MultiMwmColoring & delta, MultiMwmColoring & target)
{
// for each mwm in delta
for (auto [mwmId, coloring] : delta)
// if target contains mwm
if (auto target_it = target.find(mwmId); target_it != target.end())
// for each segment in delta[mwm] (coloring)
for (auto [rsid, sg] : coloring)
// if target[mwm] contains segment
if (auto c_it = target_it->second.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)
{
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.m_tm, "%Y-%m-%d %H:%M:%S %z");
// %FT%T%z
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();
}
std::string DebugPrint(Directionality directionality)
{
switch (directionality)
{
case Directionality::OneDirection: return "OneDirection";
case Directionality::BothDirections: return "BothDirections";
}
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)
{
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(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;
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;
os << "Point { ";
os << "coordinates: " << DebugPrint(point.m_coordinates) << ", ";
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 << " }";
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") << ", ";
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") << ", ";
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: " << DebugPrint(location.m_ramps);
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") << ", ";
os << "q_duration: "
<< (event.m_qDurationMins
? (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
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<Urgency> 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

View File

@@ -0,0 +1,580 @@
#pragma once
//#include "traffxml/traff_foo.hpp"
#include "geometry/latlon.hpp"
#include "indexer/mwm_set.hpp"
#include "traffic/speed_groups.hpp"
#include "traffic/traffic_info.hpp"
#include <chrono>
#include <map>
#include <string>
#include <vector>
namespace traffxml
{
constexpr uint8_t kMaxspeedNone = 255;
/**
* @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`.
*/
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<IsoTime> ParseIsoTime(std::string timeString);
/**
* @brief Returns an `IsoTime` corresponding to current wall clock time.
*
* @return An `IsoTime` corresponding to current wall clock time.
*/
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();
/**
* @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);
/**
* @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:
friend std::string DebugPrint(IsoTime time);
IsoTime(std::chrono::time_point<std::chrono::system_clock> tp);
std::chrono::time_point<std::chrono::system_clock> m_tp;
};
// TODO enum urgency
enum class Directionality
{
OneDirection,
BothDirections
};
enum class Fuzziness
{
LowRes,
MediumRes,
EndUnknown,
StartUnknown,
ExtentUnknown
};
enum class Ramps
{
None,
All,
Entry,
Exit
};
enum class RoadClass
{
Motorway,
Trunk,
Primary,
Secondary,
Tertiary,
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:
*
* * 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,
Activity,
Authority,
Carpool,
Congestion,
Construction,
Delay,
Environment,
EquipmentStatus,
Hazard,
Incident,
Restriction,
Security,
Transport,
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,
// 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*
};
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.
*
* 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 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.
*/
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 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); }
// TODO role?
ms::LatLon m_coordinates = ms::LatLon::Zero();
std::optional<float> m_distance;
std::optional<std::string> m_junctionName;
std::optional<std::string> m_junctionRef;
};
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); }
std::optional<std::string> m_country;
std::optional<std::string> m_destination;
std::optional<std::string> m_direction;
Directionality m_directionality = Directionality::BothDirections;
std::optional<Fuzziness> m_fuzziness;
std::optional<std::string> m_origin;
Ramps m_ramps = Ramps::None;
std::optional<RoadClass> m_roadClass;
// disabled for now, optional<bool> behaves weird and we don't really need it
//std::optional<bool> m_roadIsUrban;
std::optional<std::string> m_roadRef;
std::optional<std::string> m_roadName;
std::optional<std::string> m_territory;
std::optional<std::string> m_town;
std::optional<Point> m_from;
std::optional<Point> m_to;
std::optional<Point> m_at;
std::optional<Point> m_via;
std::optional<Point> m_notVia;
};
struct TraffEvent
{
EventClass m_class = EventClass::Invalid;
EventType m_type = EventType::Invalid;
std::optional<uint16_t> m_length;
std::optional<uint8_t> m_probability;
std::optional<uint16_t> m_qDurationMins;
/*
* TODO remaining quantifiers
* q_dimension
* q_int
* q_ints
* q_speed
* q_temperature
* q_time
* q_weight
*/
std::optional<uint8_t> m_speed;
// TODO supplementary information
};
/**
* @brief Global mapping from feature segments to speed groups, across all MWMs.
*/
using MultiMwmColoring = std::map<MwmSet::MwmId, std::map<traffic::TrafficInfo::RoadSegmentId, traffic::SpeedGroup>>;
struct TraffMessage
{
/**
* @brief Gets the time after which this message effectively expires.
*
* The effective expiration time is the latest of `m_expirationTime`, `m_startTime` and
* `m_endTime`. `nullopt` values are ignored.
*
* @return The effective expiration time for the message.
*/
IsoTime GetEffectiveExpirationTime();
/**
* @brief Whether the message has expired.
*
* A message is considered to have expired if its effective expiration time (as returned by
* `GetEffectiveExpirationTime()` refers to a point in time before `now`.
*
* @param now The reference time to compare to (usually current time)
* @return True if the message has expired, false if not.
*/
bool IsExpired(IsoTime now);
/**
* @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<TrafficImpact> 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();
IsoTime m_expirationTime = IsoTime::Now();
std::optional<IsoTime> m_startTime = {};
std::optional<IsoTime> m_endTime = {};
bool m_cancellation = false;
bool m_forecast = false;
// TODO std::optional<Urgency> m_urgency;
std::optional<TraffLocation> m_location;
std::vector<TraffEvent> m_events;
std::vector<std::string> m_replaces;
MultiMwmColoring m_decoded;
};
using TraffFeed = std::vector<TraffMessage>;
// 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 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<TraffFeed> m_feed;
};
/**
* @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(const MultiMwmColoring & delta, MultiMwmColoring & target);
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(ResponseStatus status);
std::string DebugPrint(TrafficImpact impact);
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
#pragma once
#include "traffxml/traff_model.hpp"
#include "geometry/rect2d.hpp"
#include "indexer/data_source.hpp"
#include <functional>
#include <string>
#include <vector>
namespace pugi
{
class xml_document;
class xml_node;
} // namespace pugi
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.
*
* 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.
*
* 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.
* 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())`.
*
* @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,
std::optional<std::reference_wrapper<const DataSource>> dataSource,
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<std::string, traffxml::TraffMessage> const & messages,
pugi::xml_document & document);
/**
* @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<m2::RectD> & 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

View File

@@ -0,0 +1,328 @@
#include "traffxml/traff_source.hpp"
#include "traffxml/traff_model_xml.hpp"
#include "traffxml/traff_storage.hpp"
#include "geometry/rect2d.hpp"
#include "platform/platform.hpp"
#include <functional>
#include <thread>
#include <vector>
namespace traffxml {
TraffSource::TraffSource(TraffSourceManager & manager)
: m_manager(manager)
{}
void TraffSource::SubscribeOrChangeSubscription(std::set<MwmSet::MwmId> & mwms)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!IsSubscribed())
Subscribe(mwms);
else
ChangeSubscription(mwms);
}
std::string TraffSource::GetMwmFilters(std::set<MwmSet::MwmId> & mwms)
{
std::vector<m2::RectD> rects;
for (auto mwmId : mwms)
rects.push_back(mwmId.GetInfo()->m_bordersRect);
return traffxml::FiltersToXml(rects);
}
void MockTraffSource::Create(TraffSourceManager & manager)
{
std::unique_ptr<MockTraffSource> source = std::unique_ptr<MockTraffSource>(new MockTraffSource(manager));
manager.RegisterSource(std::move(source));
}
MockTraffSource::MockTraffSource(TraffSourceManager & manager)
: TraffSource(manager)
{}
void MockTraffSource::Subscribe(std::set<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<std::mutex> 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 dont change.
*/
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<HttpTraffSource> source = std::unique_ptr<HttpTraffSource>(new HttpTraffSource(manager, url));
manager.RegisterSource(std::move(source));
}
HttpTraffSource::HttpTraffSource(TraffSourceManager & manager, std::string const & url)
: TraffSource(manager)
, m_url(url)
{}
void HttpTraffSource::Close()
{
std::string data;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_subscriptionId.empty())
return;
data = "<request operation=\"UNSUBSCRIBE\" subscription_id=\"" + m_subscriptionId + "\"/>";
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<MwmSet::MwmId> & mwms)
{
std::string data = "<request operation=\"SUBSCRIBE\">\n<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>\n"
+ "</request>";
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<std::mutex> 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<MwmSet::MwmId> & mwms)
{
std::string data = "<request operation=\"SUBSCRIPTION_CHANGE\" subscription_id=\"" + m_subscriptionId + "\">\n"
+ "<filter_list>\n"
+ GetMwmFilters(mwms)
+ "</filter_list>\n"
+ "</request>";
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<std::mutex> 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<std::mutex> lock(m_mutex);
if (m_subscriptionId.empty())
return;
data = "<request operation=\"UNSUBSCRIBE\" subscription_id=\"" + m_subscriptionId + "\"/>";
}
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<std::mutex> 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<std::mutex> lock(m_mutex);
if (m_subscriptionId.empty())
return;
data = "<request operation=\"POLL\" subscription_id=\"" + m_subscriptionId + "\"/>";
}
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<std::mutex> lock(m_mutex);
m_subscriptionId.clear();
}
}
else
LOG(LWARNING, ("Poll returned", response.m_status));
}
} // namespace traffxml

View File

@@ -0,0 +1,546 @@
#pragma once
#include "traffxml/traff_model.hpp"
#include "base/thread.hpp"
#include "indexer/mwm_set.hpp"
#include "platform/http_client.hpp"
#include <chrono>
#include <set>
#include <string>
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:
virtual ~TraffSourceManager() {}
/**
* @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<MwmSet::MwmId> & 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<TraffSource> 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<SomeTraffSource> source = std::unique_ptr<SomeTraffSource>(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
};
virtual ~TraffSource() {}
/**
* @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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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<std::chrono::time_point<std::chrono::steady_clock>> 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<std::chrono::time_point<std::chrono::steady_clock>> 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<std::chrono::time_point<std::chrono::steady_clock>> 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<int> 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<bool> 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<Availability> 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<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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);
};
/**
* @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
* @param url The URL for the TraFF service API.
*/
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.
*
* @param mwms The MWMs for which data is needed.
*/
virtual void Subscribe(std::set<MwmSet::MwmId> & 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<MwmSet::MwmId> & 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 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

View File

@@ -0,0 +1,76 @@
#include "traffxml/traff_storage.hpp"
#include "platform/platform.hpp"
#include "coding/internal/file_data.hpp"
#include "base/logging.hpp"
#include <string>
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<std::mutex> 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<std::mutex> 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<std::mutex> 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

View File

@@ -0,0 +1,77 @@
#pragma once
#include <mutex>
#include <pugixml.hpp>
/*
* 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 const & 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

Some files were not shown because too many files have changed in this diff Show More