Compare commits

..

235 Commits

Author SHA1 Message Date
mvglasow
426a9661ab Merge commit 'd473361e5' into traffic 2025-12-14 12:40:00 +02:00
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
183 changed files with 14102 additions and 1635 deletions

1
.gitignore vendored
View File

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

View File

@@ -28,3 +28,8 @@
# R8 crypts the source line numbers in all log messages. # R8 crypts the source line numbers in all log messages.
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926 # https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
-dontoptimize -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" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
</intent> </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> </queries>
<supports-screens <supports-screens

View File

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

View File

@@ -3,12 +3,19 @@ package app.organicmaps.settings;
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE; import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.TwoStatePreference; import androidx.preference.TwoStatePreference;
@@ -28,6 +35,7 @@ import app.organicmaps.sdk.routing.RoutingOptions;
import app.organicmaps.sdk.search.SearchRecents; import app.organicmaps.sdk.search.SearchRecents;
import app.organicmaps.sdk.settings.MapLanguageCode; import app.organicmaps.sdk.settings.MapLanguageCode;
import app.organicmaps.sdk.settings.UnitLocale; import app.organicmaps.sdk.settings.UnitLocale;
import app.organicmaps.sdk.traffxml.AndroidTransport;
import app.organicmaps.sdk.util.Config; import app.organicmaps.sdk.util.Config;
import app.organicmaps.sdk.util.NetworkPolicy; import app.organicmaps.sdk.util.NetworkPolicy;
import app.organicmaps.sdk.util.PowerManagment; 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.sdk.util.log.LogsManager;
import app.organicmaps.util.ThemeSwitcher; import app.organicmaps.util.ThemeSwitcher;
import app.organicmaps.util.Utils; import app.organicmaps.util.Utils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
{ {
@@ -61,6 +71,10 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
initAutoDownloadPrefsCallbacks(); initAutoDownloadPrefsCallbacks();
initLargeFontSizePrefsCallbacks(); initLargeFontSizePrefsCallbacks();
initTransliterationPrefsCallbacks(); initTransliterationPrefsCallbacks();
initTrafficHttpEnabledPrefsCallbacks();
initTrafficHttpUrlPrefsCallbacks();
initTrafficAppsPrefs();
initTrafficLegacyEnabledPrefsCallbacks();
init3dModePrefsCallbacks(); init3dModePrefsCallbacks();
initPerspectivePrefsCallbacks(); initPerspectivePrefsCallbacks();
initAutoZoomPrefsCallbacks(); initAutoZoomPrefsCallbacks();
@@ -136,6 +150,46 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
pref.setSummary(locale.getDisplayLanguage()); 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() private void updateRoutingSettingsPrefsSummary()
{ {
final Preference pref = getPreference(getString(R.string.prefs_routing)); final Preference pref = getPreference(getString(R.string.prefs_routing));
@@ -163,6 +217,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
updateVoiceInstructionsPrefsSummary(); updateVoiceInstructionsPrefsSummary();
updateRoutingSettingsPrefsSummary(); updateRoutingSettingsPrefsSummary();
updateMapLanguageCodeSummary(); updateMapLanguageCodeSummary();
updateTrafficHttpUrlSummary();
updateTrafficAppsSummary();
} }
@Override @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() private void initUseMobileDataPrefsCallbacks()
{ {
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data)); final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));

View File

@@ -90,7 +90,8 @@ public enum ThemeSwitcher
{ {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES); uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
if (RoutingController.get().isVehicleNavigation()) if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleDark; style = MapStyle.VehicleDark;
@@ -103,7 +104,8 @@ public enum ThemeSwitcher
{ {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO); uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); else
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
if (RoutingController.get().isVehicleNavigation()) if (RoutingController.get().isVehicleNavigation())
style = MapStyle.VehicleClear; style = MapStyle.VehicleClear;

View File

@@ -154,7 +154,6 @@ public class PlacePageView extends Fragment
private MaterialTextView mTvLastChecked; private MaterialTextView mTvLastChecked;
private View mEditPlace; private View mEditPlace;
private View mAddPlace; private View mAddPlace;
private View mMapTooOld;
private View mEditTopSpace; private View mEditTopSpace;
private ShapeableImageView mColorIcon; private ShapeableImageView mColorIcon;
private MaterialTextView mTvCategory; private MaterialTextView mTvCategory;
@@ -319,7 +318,6 @@ public class PlacePageView extends Fragment
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked); mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
mEditPlace = mFrame.findViewById(R.id.ll__place_editor); mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
mAddPlace = mFrame.findViewById(R.id.ll__place_add); mAddPlace = mFrame.findViewById(R.id.ll__place_add);
mMapTooOld = mFrame.findViewById(R.id.cv__map_too_old);
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space); mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
latlon.setOnLongClickListener(this); latlon.setOnLongClickListener(this);
address.setOnLongClickListener(this); address.setOnLongClickListener(this);
@@ -686,7 +684,7 @@ public class PlacePageView extends Fragment
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning()) if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
{ {
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld); UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
} }
else else
{ {
@@ -694,59 +692,31 @@ public class PlacePageView extends Fragment
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace); UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor); MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add); MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
mTvEditPlace.setOnClickListener(this);
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace(); mTvAddPlace.setOnClickListener(this);
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
if (shouldEnableEditPlace) mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
{ final int editTextButtonColor =
mTvEditPlace.setOnClickListener(this); Editor.nativeShouldEnableEditPlace()
mTvAddPlace.setOnClickListener(this);
}
else
{
mTvEditPlace.setOnClickListener((v) -> {
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
});
mTvAddPlace.setOnClickListener((v) -> {
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
});
CountryItem map = CountryItem.fill(MapManager.nativeGetSelectedCountry());
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|| map.status == CountryItem.STATUS_FAILED)
{
mMapTooOld.setVisibility(VISIBLE);
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
if (canUpdateMap)
{
mTvUpdateTooOldMap.setOnClickListener((v) -> {
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
mMapTooOld.setVisibility(GONE);
});
}
else
{
mTvUpdateTooOldMap.setVisibility(GONE);
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
}
}
}
final int editButtonColor =
shouldEnableEditPlace
? ContextCompat.getColor( ? ContextCompat.getColor(
getContext(), getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)) UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled); : ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
final ColorStateList editStrokeButtonColor = new ColorStateList(
mTvEditPlace.setTextColor(editButtonColor); new int[][]{
mTvAddPlace.setTextColor(editButtonColor); new int[]{android.R.attr.state_enabled}, // enabled
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor)); new int[]{-android.R.attr.state_enabled} // disabled
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor)); },
new int[]{
ContextCompat.getColor(
getContext(),
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)),
ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled)
});
mTvEditPlace.setTextColor(editTextButtonColor);
mTvAddPlace.setTextColor(editTextButtonColor);
mTvEditPlace.setStrokeColor(editStrokeButtonColor);
mTvAddPlace.setStrokeColor(editStrokeButtonColor);
UiUtils.showIf( UiUtils.showIf(
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace), UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
mEditTopSpace); mEditTopSpace);

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M453,680L513,680L513,440L453,440L453,680ZM479.98,366Q494,366 503.5,356.8Q513,347.6 513,334Q513,319.55 503.52,309.78Q494.04,300 480.02,300Q466,300 456.5,309.78Q447,319.55 447,334Q447,347.6 456.48,356.8Q465.96,366 479.98,366ZM480.27,880Q397.53,880 324.77,848.5Q252,817 197.5,762.5Q143,708 111.5,635.16Q80,562.32 80,479.5Q80,396.68 111.5,323.84Q143,251 197.5,197Q252,143 324.84,111.5Q397.68,80 480.5,80Q563.32,80 636.16,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,479.73Q880,562.47 848.5,635.23Q817,708 763,762.32Q709,816.63 636,848.32Q563,880 480.27,880ZM480.5,820Q622,820 721,720.5Q820,621 820,479.5Q820,338 721.19,239Q622.38,140 480,140Q339,140 239.5,238.81Q140,337.62 140,480Q140,621 239.5,720.5Q339,820 480.5,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -26,7 +26,7 @@
app:tint="?colorSecondary"/> app:tint="?colorSecondary"/>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/head_message" android:id="@+id/head_message"
style="@style/TextAppearance.Headline6" style="?fontHeadline6"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base" android:layout_marginTop="@dimen/margin_base"
@@ -36,7 +36,7 @@
android:text="@string/download_map_title" /> android:text="@string/download_map_title" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/download_message" android:id="@+id/download_message"
style="@style/TextAppearance.Body2" style="?fontBody2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base" android:layout_marginStart="@dimen/margin_base"

View File

@@ -28,7 +28,7 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="@string/aa_connected_title" android:text="@string/aa_connected_title"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Headline4" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textStyle="bold" /> android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
@@ -36,7 +36,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/car_used_on_the_car_screen" android:text="@string/car_used_on_the_car_screen"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Body1" /> android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@@ -20,7 +20,7 @@
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:text="@string/aa_request_permission_activity_text" android:text="@string/aa_request_permission_activity_text"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Headline4" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textStyle="bold" /> android:textStyle="bold" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@@ -22,7 +22,7 @@
android:layout_marginStart="@dimen/margin_base" android:layout_marginStart="@dimen/margin_base"
android:maxLines="3" android:maxLines="3"
android:ellipsize="end" android:ellipsize="end"
android:textAppearance="@style/TextAppearance.Headline6" android:textAppearance="?fontHeadline6"
tools:text="Title" /> tools:text="Title" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView

View File

@@ -12,7 +12,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_base" android:layout_marginStart="@dimen/margin_base"
android:text="@string/download_country_ask" android:text="@string/download_country_ask"
android:textAppearance="@style/TextAppearance.Body2" android:textAppearance="?fontBody2"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"/> tools:visibility="visible"/>
<com.google.android.material.progressindicator.LinearProgressIndicator <com.google.android.material.progressindicator.LinearProgressIndicator

View File

@@ -14,6 +14,7 @@
android:textAppearance="@style/MwmTextAppearance.Title" /> android:textAppearance="@style/MwmTextAppearance.Title" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/et__input_layout" android:id="@+id/et__input_layout"
style="?fontBody1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:hintEnabled="false"> app:hintEnabled="false">

View File

@@ -27,7 +27,7 @@
android:layout_marginTop="@dimen/margin_base"> android:layout_marginTop="@dimen/margin_base">
<RadioButton <RadioButton
style="@style/TextAppearance.Subtitle1" style="?fontSubtitle1"
android:id="@+id/sort_by_default" android:id="@+id/sort_by_default"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
app:buttonTint="@null"/> app:buttonTint="@null"/>
<RadioButton <RadioButton
style="@style/TextAppearance.Subtitle1" style="?fontSubtitle1"
android:id="@+id/sort_by_name" android:id="@+id/sort_by_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -47,7 +47,7 @@
app:buttonTint="@null"/> app:buttonTint="@null"/>
<RadioButton <RadioButton
style="@style/TextAppearance.Subtitle1" style="?fontSubtitle1"
android:id="@+id/sort_by_type" android:id="@+id/sort_by_type"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -57,7 +57,7 @@
app:buttonTint="@null"/> app:buttonTint="@null"/>
<RadioButton <RadioButton
style="@style/TextAppearance.Subtitle1" style="?fontSubtitle1"
android:id="@+id/sort_by_distance" android:id="@+id/sort_by_distance"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -67,7 +67,7 @@
app:buttonTint="@null"/> app:buttonTint="@null"/>
<RadioButton <RadioButton
style="@style/TextAppearance.Subtitle1" style="?fontSubtitle1"
android:id="@+id/sort_by_time" android:id="@+id/sort_by_time"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -17,6 +17,7 @@
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/edit_bookmark_name_input" android:id="@+id/edit_bookmark_name_input"
style="?fontBody1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/name" android:hint="@string/name"
@@ -87,6 +88,7 @@
tools:src="@drawable/ic_bookmark_none" /> tools:src="@drawable/ic_bookmark_none" />
</RelativeLayout> </RelativeLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="?fontBody1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_half" android:layout_marginEnd="@dimen/margin_half"

View File

@@ -17,7 +17,7 @@
android:layout_marginEnd="@dimen/margin_base" android:layout_marginEnd="@dimen/margin_base"
android:paddingTop="@dimen/margin_half" android:paddingTop="@dimen/margin_half"
android:paddingBottom="@dimen/margin_base" android:paddingBottom="@dimen/margin_base"
android:textAppearance="@style/TextAppearance.Headline6" android:textAppearance="?fontHeadline6"
tools:text="Xindian Shitoushan Trail" /> tools:text="Xindian Shitoushan Trail" />
<include <include
layout="@layout/item_divider" layout="@layout/item_divider"

View File

@@ -16,7 +16,7 @@
android:layout_marginStart="@dimen/margin_base" android:layout_marginStart="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_base" android:layout_marginTop="@dimen/margin_base"
android:text="@string/layers_title" android:text="@string/layers_title"
android:textAppearance="@style/TextAppearance.Headline6" android:textAppearance="?fontHeadline6"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@@ -30,7 +30,7 @@
android:ellipsize="middle" android:ellipsize="middle"
android:singleLine="true" android:singleLine="true"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.Body1" android:textAppearance="?fontBody1"
tools:text="Bookmark name looooooooooooooooooongasdasdasd" /> tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
<LinearLayout <LinearLayout
android:id="@+id/bottom_line_container" android:id="@+id/bottom_line_container"

View File

@@ -25,7 +25,7 @@
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin" android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
android:ellipsize="middle" android:ellipsize="middle"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.Body1" android:textAppearance="?fontBody1"
tools:text="Bookmark name looooooooooooooooooongasdasdasd" /> tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/size" android:id="@+id/size"
@@ -37,7 +37,7 @@
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
style="@style/TextAppearance.Body2" style="?fontBody2"
tools:text="42000000" /> tools:text="42000000" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -12,7 +12,7 @@
android:paddingBottom="@dimen/margin_half_plus"> android:paddingBottom="@dimen/margin_half_plus">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_message" android:id="@+id/text_message"
style="@style/TextAppearance.Headline6" style="?fontHeadline6"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"

View File

@@ -2,7 +2,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text" android:id="@+id/text"
style="@style/TextAppearance.Headline6" style="?fontHeadline6"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?windowBackgroundForced" android:background="?windowBackgroundForced"

View File

@@ -14,7 +14,7 @@
android:gravity="center" android:gravity="center"
android:maxWidth="500dp" android:maxWidth="500dp"
android:text="@string/editor_category_unsuitable_title" android:text="@string/editor_category_unsuitable_title"
android:textAppearance="@style/TextAppearance.Headline6" android:textAppearance="@style/TextAppearance.MdcTypographyStyles.Headline6"
android:textStyle="bold" /> android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/editor_category_unsuitable_text" android:id="@+id/editor_category_unsuitable_text"

View File

@@ -10,6 +10,7 @@
android:orientation="horizontal"> android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_layout" android:id="@+id/input_layout"
style="?fontBody1"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"

View File

@@ -147,12 +147,12 @@
<LinearLayout <LinearLayout
android:id="@+id/allday" android:id="@+id/allday"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="@dimen/editor_height_allday"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:background="?clickableBackground" android:background="?clickableBackground"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="@dimen/margin_half"> android:padding="@dimen/margin_half_plus">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:layout_width="0dp" android:layout_width="0dp"
@@ -164,8 +164,7 @@
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sw__allday" android:id="@+id/sw__allday"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
android:layout_marginEnd="@dimen/margin_quarter"/>
</LinearLayout> </LinearLayout>

View File

@@ -29,7 +29,7 @@
android:layout_marginTop="@dimen/margin_base" android:layout_marginTop="@dimen/margin_base"
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin" android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.Body1" android:textAppearance="?fontBody1"
tools:text="@string/tracks_title" /> tools:text="@string/tracks_title" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/tv__bookmark_distance" android:id="@+id/tv__bookmark_distance"
@@ -39,7 +39,7 @@
android:layout_marginBottom="@dimen/margin_half_plus" android:layout_marginBottom="@dimen/margin_half_plus"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textAppearance="@style/TextAppearance.Body2" android:textAppearance="?fontBody2"
tools:text="@string/by_distance" /> tools:text="@string/by_distance" />
</LinearLayout> </LinearLayout>
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView

View File

@@ -82,15 +82,13 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/place_page_last_checked" android:id="@+id/place_page_last_checked"
style="@style/TextAppearance.Caption" style="?fontCaption"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_half" android:paddingBottom="@dimen/margin_half"
android:paddingHorizontal="@dimen/margin_base" android:paddingHorizontal="@dimen/margin_base"
tools:text="Existence confirmed 1 month ago"/> tools:text="Existence confirmed 1 month ago"/>
<include android:visibility="gone" layout="@layout/place_page_map_too_old"/>
<include android:visibility="gone" layout="@layout/place_page_editor"/> <include android:visibility="gone" layout="@layout/place_page_editor"/>
<include android:visibility="gone" layout="@layout/place_page_add"/> <include android:visibility="gone" layout="@layout/place_page_add"/>

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cv__map_too_old"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_base"
android:layout_marginTop="@dimen/margin_half"
app:strokeWidth="1dp"
app:strokeColor="@color/base_accent"
app:cardBackgroundColor="@color/bg_cards">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/margin_base"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_base"
app:srcCompat="@drawable/info_icon"
app:tint="@color/base_accent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_title"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textStyle="bold"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tv__map_too_old_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_quarter"
android:text="@string/place_page_map_too_old_description"
android:fontFamily="@string/robotoRegular"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textColor="?android:textColorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mb__update_too_old_map"
style="@style/MwmWidget.M3.Button.Primary"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="end"
android:text="@string/place_page_update_too_old_map"/>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -65,7 +65,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/oh_check_date" android:id="@+id/oh_check_date"
style="@style/TextAppearance.Caption" style="?fontCaption"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"

View File

@@ -88,19 +88,19 @@
<item name="android:textAppearanceSmall">@style/TextAppearance.Small</item> <item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
<item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item> <item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
<item name="android:textAppearanceLarge">@style/TextAppearance.Large</item> <item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
<item name="fontHeadline1">@style/TextAppearance.Headline1</item> <item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.Headline2</item> <item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.Headline3</item> <item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.Headline4</item> <item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item> <item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
<item name="fontHeadline6">@style/TextAppearance.Headline6</item> <item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item> <item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item> <item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.Body1</item> <item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
<item name="fontBody2">@style/TextAppearance.Body2</item> <item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
<item name="fontButton">@style/TextAppearance.Button</item> <item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
<item name="fontCaption">@style/TextAppearance.Caption</item> <item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
<item name="fontOverline">@style/TextAppearance.Overline</item> <item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item> <item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item> <item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
<item name="elevationProfilePropIconTint">@color/white_secondary</item> <item name="elevationProfilePropIconTint">@color/white_secondary</item>

View File

@@ -130,6 +130,7 @@
<!-- Editor --> <!-- Editor -->
<dimen name="editor_height_days">72dp</dimen> <dimen name="editor_height_days">72dp</dimen>
<dimen name="editor_height_closed">72dp</dimen> <dimen name="editor_height_closed">72dp</dimen>
<dimen name="editor_height_allday">56dp</dimen>
<dimen name="editor_height_field">64dp</dimen> <dimen name="editor_height_field">64dp</dimen>
<dimen name="editor_margin_left">56dp</dimen> <dimen name="editor_margin_left">56dp</dimen>
<dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen> <dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen>

View File

@@ -35,6 +35,11 @@
<string name="pref_settings_general" translatable="false">GeneralSettings</string> <string name="pref_settings_general" translatable="false">GeneralSettings</string>
<string name="pref_navigation" translatable="false">Navigation</string> <string name="pref_navigation" translatable="false">Navigation</string>
<string name="pref_information" translatable="false">Information</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_transliteration" translatable="false">Transliteration</string>
<string name="pref_power_management" translatable="false">PowerManagment</string> <string name="pref_power_management" translatable="false">PowerManagment</string>
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string> <string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>

View File

@@ -215,6 +215,7 @@
<!-- Settings information group in settings screen --> <!-- Settings information group in settings screen -->
<string name="prefs_group_information">Information</string> <string name="prefs_group_information">Information</string>
<string name="prefs_group_route">Navigation</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_title">Zoom buttons</string>
<string name="pref_zoom_summary">Display on the map</string> <string name="pref_zoom_summary">Display on the map</string>
<!-- Settings «Map» category: «Night style» title --> <!-- Settings «Map» category: «Night style» title -->
@@ -573,16 +574,6 @@
<string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string> <string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string>
<string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string> <string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string>
<string name="placepage_add_place_button">Add Place to OpenStreetMap</string> <string name="placepage_add_place_button">Add Place to OpenStreetMap</string>
<!-- Title of info shown when the map is older than 3 to 6 months -->
<string name="place_page_map_too_old_title">Map data outdated</string>
<!-- Description of info shown when the map is older than 3 months -->
<string name="place_page_map_too_old_description"> Your current map data is very old, please update the map.</string>
<!-- Description of info shown when the app and the map are older than 6 months -->
<string name="place_page_app_too_old_description"> Your current map data is very old, please update the CoMaps app.</string>
<!-- Button to update map region, part of the map too old info -->
<string name="place_page_update_too_old_map">Update map region</string>
<!-- Toast shown after pressing add place / edit OpenStreetMap when editing is disabled -->
<string name="place_page_too_old_to_edit">OpenStreetMap editing is disabled because the map data is too old.</string>
<string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string> <string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string>
<string name="osm_note_toast">Note will be sent to OpenStreetMap</string> <string name="osm_note_toast">Note will be sent to OpenStreetMap</string>
<!-- Displayed when saving some edits to the map to warn against publishing personal data --> <!-- Displayed when saving some edits to the map to warn against publishing personal data -->
@@ -799,6 +790,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> <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! --> <!-- Current language of the map! -->
<string name="change_map_locale">Map language</string> <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 --> <!-- OpenStreetMap text on splash screen -->
<string name="splash_subtitle">Map data from OpenStreetMap</string> <string name="splash_subtitle">Map data from OpenStreetMap</string>
<!-- Telegram group url for the "?" About page --> <!-- Telegram group url for the "?" About page -->

View File

@@ -6,6 +6,11 @@
<item name="android:textStyle">normal</item> <item name="android:textStyle">normal</item>
</style> </style>
<style name="MwmTextAppearance.Display1">
<item name="android:textSize">@dimen/text_size_display_1</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<style name="MwmTextAppearance.Title"> <style name="MwmTextAppearance.Title">
<item name="android:textSize">@dimen/text_size_title</item> <item name="android:textSize">@dimen/text_size_title</item>
<item name="android:textColor">?android:textColorPrimary</item> <item name="android:textColor">?android:textColorPrimary</item>
@@ -185,91 +190,91 @@
<item name="android:textColor">@color/white_secondary</item> <item name="android:textColor">@color/white_secondary</item>
</style> </style>
<style name="TextAppearance.Headline1" <style name="TextAppearance.MdcTypographyStyles.Headline1"
parent="TextAppearance.Material3.DisplayLarge"> parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">@string/robotoLight</item> <item name="fontFamily">@string/robotoLight</item>
<item name="android:fontFamily">@string/robotoLight</item> <item name="android:fontFamily">@string/robotoLight</item>
<item name="android:textSize">96sp</item> <item name="android:textSize">96sp</item>
</style> </style>
<style name="TextAppearance.Headline2" <style name="TextAppearance.MdcTypographyStyles.Headline2"
parent="TextAppearance.Material3.DisplayLarge"> parent="TextAppearance.MaterialComponents.Headline2">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">60sp</item> <item name="android:textSize">60sp</item>
</style> </style>
<style name="TextAppearance.Headline3" <style name="TextAppearance.MdcTypographyStyles.Headline3"
parent="TextAppearance.Material3.DisplayMedium"> parent="TextAppearance.MaterialComponents.Headline3">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">48sp</item> <item name="android:textSize">48sp</item>
</style> </style>
<style name="TextAppearance.Headline4" <style name="TextAppearance.MdcTypographyStyles.Headline4"
parent="TextAppearance.Material3.HeadlineLarge"> parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">34sp</item> <item name="android:textSize">34sp</item>
</style> </style>
<style name="TextAppearance.Headline5" <style name="TextAppearance.MdcTypographyStyles.Headline5"
parent="TextAppearance.Material3.HeadlineMedium"> parent="TextAppearance.MaterialComponents.Headline5">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">24sp</item> <item name="android:textSize">24sp</item>
</style> </style>
<style name="TextAppearance.Headline6" <style name="TextAppearance.MdcTypographyStyles.Headline6"
parent="TextAppearance.Material3.HeadlineSmall"> parent="TextAppearance.MaterialComponents.Headline6">
<item name="fontFamily">@string/robotoMedium</item> <item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item> <item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">20sp</item> <item name="android:textSize">20sp</item>
</style> </style>
<style name="TextAppearance.Subtitle1" <style name="TextAppearance.MdcTypographyStyles.Subtitle1"
parent="TextAppearance.Material3.TitleMedium"> parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="TextAppearance.Subtitle2" <style name="TextAppearance.MdcTypographyStyles.Subtitle2"
parent="TextAppearance.Material3.TitleSmall"> parent="TextAppearance.MaterialComponents.Subtitle2">
<item name="fontFamily">@string/robotoMedium</item> <item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item> <item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
</style> </style>
<style name="TextAppearance.Body1" <style name="TextAppearance.MdcTypographyStyles.Body1"
parent="TextAppearance.Material3.BodyLarge"> parent="TextAppearance.MaterialComponents.Body1">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="TextAppearance.Body2" <style name="TextAppearance.MdcTypographyStyles.Body2"
parent="TextAppearance.Material3.BodyMedium"> parent="TextAppearance.MaterialComponents.Body2">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
</style> </style>
<style name="TextAppearance.Button" <style name="TextAppearance.MdcTypographyStyles.Button"
parent="TextAppearance.MaterialComponents.Button"> parent="TextAppearance.MaterialComponents.Button">
<item name="fontFamily">@string/robotoMedium</item> <item name="fontFamily">@string/robotoMedium</item>
<item name="android:fontFamily">@string/robotoMedium</item> <item name="android:fontFamily">@string/robotoMedium</item>
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
</style> </style>
<style name="TextAppearance.Caption" <style name="TextAppearance.MdcTypographyStyles.Caption"
parent="TextAppearance.Material3.LabelMedium"> parent="TextAppearance.MaterialComponents.Caption">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
</style> </style>
<style name="TextAppearance.Overline" <style name="TextAppearance.MdcTypographyStyles.Overline"
parent="TextAppearance.MaterialComponents.Overline"> parent="TextAppearance.MaterialComponents.Overline">
<item name="fontFamily">@string/robotoRegular</item> <item name="fontFamily">@string/robotoRegular</item>
<item name="android:fontFamily">@string/robotoRegular</item> <item name="android:fontFamily">@string/robotoRegular</item>

View File

@@ -139,13 +139,12 @@
<item name="cornerSize">50%</item> <item name="cornerSize">50%</item>
</style> </style>
<style name="MwmWidget.ToolbarStyle" parent="Widget.Material3.Toolbar"> <style name="MwmWidget.ToolbarStyle" parent="ThemeOverlay.Material3.Dark.ActionBar">
<item name="android:background">?colorPrimary</item> <item name="android:background">?colorPrimary</item>
<item name="android:displayOptions">homeAsUp|showTitle</item> <item name="android:displayOptions">homeAsUp|showTitle</item>
<item name="contentInsetStart">0dp</item> <item name="contentInsetStart">0dp</item>
<item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item> <item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
<item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item> <item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
<item name="buttonGravity">center_vertical</item>
</style> </style>
<style name="MwmWidget.ToolbarStyle.Light"> <style name="MwmWidget.ToolbarStyle.Light">

View File

@@ -93,19 +93,19 @@
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item> <item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item> <item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item> <item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
<item name="fontHeadline1">@style/TextAppearance.Headline1</item> <item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
<item name="fontHeadline2">@style/TextAppearance.Headline2</item> <item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
<item name="fontHeadline3">@style/TextAppearance.Headline3</item> <item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
<item name="fontHeadline4">@style/TextAppearance.Headline4</item> <item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item> <item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
<item name="fontHeadline6">@style/TextAppearance.Headline6</item> <item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item> <item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item> <item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
<item name="fontBody1">@style/TextAppearance.Body1</item> <item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
<item name="fontBody2">@style/TextAppearance.Body2</item> <item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
<item name="fontButton">@style/TextAppearance.Button</item> <item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
<item name="fontCaption">@style/TextAppearance.Caption</item> <item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
<item name="fontOverline">@style/TextAppearance.Overline</item> <item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item> <item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item> <item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
<item name="elevationProfilePropIconTint">@color/black_secondary</item> <item name="elevationProfilePropIconTint">@color/black_secondary</item>

View File

@@ -191,6 +191,36 @@
android:widgetLayout="@layout/preference_switch" android:widgetLayout="@layout/preference_switch"
android:order="5"/> android:order="5"/>
</PreferenceCategory> </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 <androidx.preference.PreferenceCategory
android:key="@string/pref_privacy" android:key="@string/pref_privacy"
android:title="@string/privacy" android:title="@string/privacy"

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
#include "app/organicmaps/sdk/Framework.hpp" #include "app/organicmaps/sdk/Framework.hpp"
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp" #include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
#include "app/organicmaps/sdk/core/jni_helper.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(); JNIEnv * env = jni::GetEnv();
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V"); jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
env->CallVoidMethod(*onComplete, methodId); 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()->SaveTransliteration(value);
frm()->AllowTransliteration(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" } // 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. * True if the first start animation has been seen.
*/ */
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen"; 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() {} private Config() {}
@@ -393,6 +403,63 @@ public final class Config
nativeSetTransliteration(value); nativeSetTransliteration(value);
} }
public static boolean getTrafficHttpEnabled()
{
return nativeGetTrafficHttpEnabled();
}
public static void setTrafficHttpEnabled(boolean value)
{
nativeSetTrafficHttpEnabled(value);
}
public static String getTrafficHttpUrl()
{
return nativeGetTrafficHttpUrl();
}
public static void setTrafficHttpUrl(String value)
{
nativeSetTrafficHttpUrl(value);
}
public static 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 @NonNull
public static String getDonateUrl() public static String getDonateUrl()
{ {
@@ -536,4 +603,10 @@ public final class Config
private static native void nativeSetLargeFontsSize(boolean value); private static native void nativeSetLargeFontsSize(boolean value);
private static native boolean nativeGetTransliteration(); private static native boolean nativeGetTransliteration();
private static native void nativeSetTransliteration(boolean value); 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 CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways"
#define TEMP_ADDR_EXTENSION ".tempaddr" #define TEMP_ADDR_EXTENSION ".tempaddr"
#define TRAFFIC_FILE_EXTENSION ".traffic"
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json" #define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv" #define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"

View File

@@ -207,8 +207,6 @@ set(SRC
tesselator.hpp tesselator.hpp
towns_dumper.cpp towns_dumper.cpp
towns_dumper.hpp towns_dumper.hpp
traffic_generator.cpp
traffic_generator.hpp
transit_generator.cpp transit_generator.cpp
transit_generator.hpp transit_generator.hpp
transit_generator_experimental.cpp transit_generator_experimental.cpp

View File

@@ -26,7 +26,6 @@
#include "generator/routing_world_roads_generator.hpp" #include "generator/routing_world_roads_generator.hpp"
#include "generator/search_index_builder.hpp" #include "generator/search_index_builder.hpp"
#include "generator/statistics.hpp" #include "generator/statistics.hpp"
#include "generator/traffic_generator.hpp"
#include "generator/transit_generator.hpp" #include "generator/transit_generator.hpp"
#include "generator/transit_generator_experimental.hpp" #include "generator/transit_generator_experimental.hpp"
#include "generator/unpack_mwm.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(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_bool(check_mwm, false, "Check map file to be correct.");
DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container."); 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"); 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); 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); string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION);

View File

@@ -134,8 +134,6 @@ TagMapping const kDefaultTagMapping = {
{OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private}, {OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private},
{OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private}, {OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private},
{OsmElement::Tag("locked", "yes"), RoadAccess::Type::Locked}, {OsmElement::Tag("locked", "yes"), RoadAccess::Type::Locked},
{OsmElement::Tag("service", "parking_aisle"), RoadAccess::Type::Private},
{OsmElement::Tag("amenity", "parking_entrance"), RoadAccess::Type::Private},
}; };
// Removed secondary, tertiary from car list. Example https://www.openstreetmap.org/node/8169922700 // Removed secondary, tertiary from car list. Example https://www.openstreetmap.org/node/8169922700

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) NS_SWIFT_NAME(SettingsBridge)
@interface MWMSettings : NSObject @interface MWMSettings : NSObject
+ (BOOL)liveTrafficEnabled;
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
+ (NSURL *)liveTrafficUrl;
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
+ (BOOL)buildings3dViewEnabled; + (BOOL)buildings3dViewEnabled;
+ (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled; + (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled;

View File

@@ -27,6 +27,40 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
@implementation MWMSettings @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)buildings3dViewEnabled;
{ {
bool _ = true, on = true; bool _ = true, on = true;

View File

@@ -717,6 +717,13 @@
"editor_place_doesnt_exist" = "Place does not exist"; "editor_place_doesnt_exist" = "Place does not exist";
"text_more_button" = "…more"; "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 */ /* Phone number error message */
"error_enter_correct_phone" = "Enter a valid phone number"; "error_enter_correct_phone" = "Enter a valid phone number";
"error_enter_correct_web" = "Enter a valid web address"; "error_enter_correct_web" = "Enter a valid web address";
@@ -816,6 +823,7 @@
"privacy_policy" = "Privacy policy"; "privacy_policy" = "Privacy policy";
"terms_of_use" = "Terms of use"; "terms_of_use" = "Terms of use";
"button_layer_subway" = "Metro"; "button_layer_subway" = "Metro";
"button_layer_traffic" = "Traffic";
"layers_title" = "Map Styles and Layers"; "layers_title" = "Map Styles and Layers";
"subway_data_unavailable" = "Metro map is unavailable"; "subway_data_unavailable" = "Metro map is unavailable";
"title_error_downloading_bookmarks" = "An error occurred"; "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"; "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"; "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 */ /* Phone number error message */
"error_enter_correct_phone" = "Enter a valid phone number"; "error_enter_correct_phone" = "Enter a valid phone number";
"error_enter_correct_web" = "Enter a valid web address"; "error_enter_correct_web" = "Enter a valid web address";
@@ -837,6 +844,7 @@
"privacy_policy" = "Privacy policy"; "privacy_policy" = "Privacy policy";
"terms_of_use" = "Terms of use"; "terms_of_use" = "Terms of use";
"button_layer_subway" = "Subway"; "button_layer_subway" = "Subway";
"button_layer_traffic" = "Traffic";
"layers_title" = "Map Styles and Layers"; "layers_title" = "Map Styles and Layers";
"subway_data_unavailable" = "Subway map is unavailable"; "subway_data_unavailable" = "Subway map is unavailable";
"title_error_downloading_bookmarks" = "An error occurred"; "title_error_downloading_bookmarks" = "An error occurred";

View File

@@ -18,6 +18,7 @@
272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; }; 272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; };
272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; }; 272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; };
272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.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 */; }; 2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; }; 2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
@@ -1792,6 +1794,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */,
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */, FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */, FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */, FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
@@ -2029,6 +2032,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = { 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
274720592E439FBA00C516DF /* libtraffxml.a */,
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */, FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
FA456C3B26BDC6AD00B83C20 /* Chart.framework */, FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
FA853BF226BC5DE50026D455 /* libshaders.a */, 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 // MARK: Methods

View File

@@ -18,6 +18,11 @@ class BottomMenuLayersCell: UITableViewCell {
updateOutdoorButton() updateOutdoorButton()
} }
} }
@IBOutlet private var trafficButton: BottomMenuLayerButton! {
didSet {
updateTrafficButton()
}
}
var onClose: (()->())? var onClose: (()->())?
@@ -32,6 +37,7 @@ class BottomMenuLayersCell: UITableViewCell {
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor")) outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines")) isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway")) subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
trafficButton.setupWith(image: UIImage(resource: .btnMenuTraffic), text: L("button_layer_traffic"))
} }
deinit { deinit {
@@ -56,6 +62,11 @@ class BottomMenuLayersCell: UITableViewCell {
let enabled = MapOverlayManager.outdoorEnabled() let enabled = MapOverlayManager.outdoorEnabled()
outdoorButton.setStyleAndApply(styleFor(enabled)) outdoorButton.setStyleAndApply(styleFor(enabled))
} }
private func updateTrafficButton() {
let enabled = MapOverlayManager.trafficEnabled()
trafficButton.setStyleAndApply(styleFor(enabled))
}
@IBAction func onCloseButtonPressed(_ sender: Any) { @IBAction func onCloseButtonPressed(_ sender: Any) {
onClose?() onClose?()
@@ -75,6 +86,11 @@ class BottomMenuLayersCell: UITableViewCell {
let enable = !MapOverlayManager.outdoorEnabled() let enable = !MapOverlayManager.outdoorEnabled()
MapOverlayManager.setOutdoorEnabled(enable) MapOverlayManager.setOutdoorEnabled(enable)
} }
@IBAction func onTrafficButton(_ sender: Any) {
let enable = !MapOverlayManager.trafficEnabled()
MapOverlayManager.setTrafficEnabled(enable)
}
} }
extension BottomMenuLayersCell: MapOverlayManagerObserver { extension BottomMenuLayersCell: MapOverlayManagerObserver {
@@ -89,6 +105,10 @@ extension BottomMenuLayersCell: MapOverlayManagerObserver {
func onOutdoorStateUpdated() { func onOutdoorStateUpdated() {
updateOutdoorButton() updateOutdoorButton()
} }
func onTrafficStateUpdated() {
updateTrafficButton()
}
} }
private extension BottomMenuLayersCell { private extension BottomMenuLayersCell {

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"/> <device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <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="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@@ -81,26 +81,35 @@
<rect key="frame" x="16" y="58" width="308" height="64"/> <rect key="frame" x="16" y="58" width="308" height="64"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target"> <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"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections> <connections>
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/> <action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
</connections> </connections>
</view> </view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target"> <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"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections> <connections>
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/> <action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
</connections> </connections>
</view> </view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target"> <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"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections> <connections>
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/> <action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
</connections> </connections>
</view> </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> </subviews>
<constraints> <constraints>
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/> <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="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/> <outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/> <outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
<outlet property="trafficButton" destination="95L-lU-yCQ" id="O9W-En-8Rc"/>
</connections> </connections>
<point key="canvasLocation" x="137.6953125" y="201.953125"/> <point key="canvasLocation" x="137.6953125" y="201.953125"/>
</tableViewCell> </tableViewCell>

View File

@@ -60,6 +60,14 @@ struct SettingsNavigationView: View {
@State var forceRefreshDate: Date = Date.now @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 /// The actual view
var body: some View { var body: some View {
List { List {
@@ -212,6 +220,24 @@ struct SettingsNavigationView: View {
} header: { } header: {
Text("driving_options_title") 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) .accentColor(.accent)
.navigationViewStyle(StackNavigationViewStyle()) .navigationViewStyle(StackNavigationViewStyle())
@@ -229,6 +255,8 @@ struct SettingsNavigationView: View {
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
hasLiveTraffic = Settings.hasLiveTraffic
liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? ""
} }
.onChange(of: scenePhase) { _ in .onChange(of: scenePhase) { _ in
forceRefreshDate = Date.now forceRefreshDate = Date.now
@@ -276,6 +304,15 @@ struct SettingsNavigationView: View {
} }
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in .onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting 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(storage)
add_subdirectory(tracking) add_subdirectory(tracking)
add_subdirectory(traffic) add_subdirectory(traffic)
add_subdirectory(traffxml)
add_subdirectory(transit) add_subdirectory(transit)

View File

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

View File

@@ -243,12 +243,15 @@ void ChangesetWrapper::Modify(editor::XMLFeature node)
void ChangesetWrapper::AddChangesetTag(std::string key, std::string value) void ChangesetWrapper::AddChangesetTag(std::string key, std::string value)
{ {
// Truncate to 254 characters as OSM has a length limit of 255
if (strings::Truncate(value, kMaximumOsmChars))
value += "";
value = strings::EscapeForXML(value); value = strings::EscapeForXML(value);
//OSM has a length limit of 255 characters
if (value.length() > kMaximumOsmChars)
{
LOG(LWARNING, ("value is too long for OSM 255 char limit: ", value));
value = value.substr(0, kMaximumOsmChars - 3).append("...");
}
m_changesetComments.insert_or_assign(std::move(key), std::move(value)); m_changesetComments.insert_or_assign(std::move(key), std::move(value));
} }

View File

@@ -143,7 +143,6 @@ void CreateCafeAtPoint(m2::PointD const & point, MwmSet::MwmId const & mwmId, os
editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo); editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo);
emo.SetHouseNumber("12"); emo.SetHouseNumber("12");
emo.LogDiffInJournal({});
TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ()); TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ());
} }
@@ -158,7 +157,6 @@ void GenerateUploadedFeature(MwmSet::MwmId const & mwmId, osm::EditableMapObject
pugi::xml_node created = mwmNode.append_child("create"); pugi::xml_node created = mwmNode.append_child("create");
editor::XMLFeature xf = editor::ToXML(emo, true); editor::XMLFeature xf = editor::ToXML(emo, true);
xf.SetEditJournal(emo.GetJournal());
xf.SetMWMFeatureIndex(emo.GetID().m_index); xf.SetMWMFeatureIndex(emo.GetID().m_index);
xf.SetModificationTime(time(nullptr)); xf.SetModificationTime(time(nullptr));
xf.SetUploadTime(time(nullptr)); xf.SetUploadTime(time(nullptr));
@@ -872,7 +870,7 @@ void EditorTest::CreateNoteTest()
}; };
// Should match a piece of text in the editor note. // Should match a piece of text in the editor note.
constexpr char const * kPlaceDoesNotExistMessage = "This place does not exist:"; constexpr char const * kPlaceDoesNotExistMessage = "The place has gone or never existed";
ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft) ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft)
{ {

View File

@@ -115,42 +115,6 @@ UNIT_TEST(XMLFeature_UintLang)
TEST_EQUAL(f2.GetName("int_name"), "Gorky Park", ()); TEST_EQUAL(f2.GetName("int_name"), "Gorky Park", ());
} }
UNIT_TEST(XMLFeature_SetOSMTagsForType)
{
XMLFeature restaurantFeature(XMLFeature::Type::Node);
restaurantFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-restaurant"));
ASSERT(restaurantFeature.HasTag("amenity"), ());
TEST_EQUAL(restaurantFeature.GetTagValue("amenity"), "restaurant", ());
XMLFeature officeFeature(XMLFeature::Type::Node);
officeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("office"));
ASSERT(officeFeature.HasTag("office"), ());
TEST_EQUAL(officeFeature.GetTagValue("office"), "yes", ());
XMLFeature touristOfficeFeature(XMLFeature::Type::Node);
touristOfficeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("tourism-information-office"));
ASSERT(touristOfficeFeature.HasTag("tourism"), ());
TEST_EQUAL(touristOfficeFeature.GetTagValue("tourism"), "information", ());
XMLFeature addressFeature(XMLFeature::Type::Node);
addressFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("building-address"));
ASSERT(!addressFeature.HasAnyTags(), ("Addresses should not have a category tag"));
XMLFeature recyclingCenterFeature(XMLFeature::Type::Node);
recyclingCenterFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-centre"));
ASSERT(recyclingCenterFeature.HasTag("amenity"), ());
TEST_EQUAL(recyclingCenterFeature.GetTagValue("amenity"), "recycling", ());
ASSERT(recyclingCenterFeature.HasTag("recycling_type"), ());
TEST_EQUAL(recyclingCenterFeature.GetTagValue("recycling_type"), "centre", ());
XMLFeature recyclingContainerFeature(XMLFeature::Type::Node);
recyclingContainerFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-container"));
ASSERT(recyclingContainerFeature.HasTag("amenity"), ());
TEST_EQUAL(recyclingContainerFeature.GetTagValue("amenity"), "recycling", ());
ASSERT(recyclingContainerFeature.HasTag("recycling_type"), ());
TEST_EQUAL(recyclingContainerFeature.GetTagValue("recycling_type"), "container", ());
}
UNIT_TEST(XMLFeature_ToOSMString) UNIT_TEST(XMLFeature_ToOSMString)
{ {
XMLFeature feature(XMLFeature::Type::Node); XMLFeature feature(XMLFeature::Type::Node);
@@ -303,52 +267,104 @@ UNIT_TEST(XMLFeature_Geometry)
TEST_EQUAL(feature.GetGeometry(), geometry, ()); TEST_EQUAL(feature.GetGeometry(), geometry, ());
} }
UNIT_TEST(XMLFeature_ApplyPatch)
{
auto const kOsmFeature = R"(<?xml version="1.0"?>
<osm>
<node id="1" lat="1" lon="2" timestamp="2015-11-27T21:13:32Z" version="1">
<tag k="amenity" v="cafe"/>
</node>
</osm>
)";
auto const kPatch = R"(<?xml version="1.0"?>
<node lat="1" lon="2" timestamp="2015-11-27T21:13:32Z">
<tag k="website" v="maps.me"/>
</node>
)";
XMLFeature const baseOsmFeature = XMLFeature::FromOSM(kOsmFeature).front();
{
XMLFeature noAnyTags = baseOsmFeature;
noAnyTags.ApplyPatch(XMLFeature(kPatch));
TEST(noAnyTags.HasKey("website"), ());
}
{
XMLFeature hasMainTag = baseOsmFeature;
hasMainTag.SetTagValue("website", "mapswith.me");
hasMainTag.ApplyPatch(XMLFeature(kPatch));
TEST_EQUAL(hasMainTag.GetTagValue("website"), "maps.me", ());
size_t tagsCount = 0;
hasMainTag.ForEachTag([&tagsCount](std::string const &, std::string const &) { ++tagsCount; });
TEST_EQUAL(2, tagsCount, ("website should be replaced, not duplicated."));
}
{
XMLFeature hasAltTag = baseOsmFeature;
hasAltTag.SetTagValue("contact:website", "mapswith.me");
hasAltTag.ApplyPatch(XMLFeature(kPatch));
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasAltTag.GetTagValue("contact:website"), "maps.me", ());
}
{
XMLFeature hasAltTag = baseOsmFeature;
hasAltTag.SetTagValue("url", "mapswithme.com");
hasAltTag.ApplyPatch(XMLFeature(kPatch));
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasAltTag.GetTagValue("url"), "maps.me", ());
}
{
XMLFeature hasTwoAltTags = baseOsmFeature;
hasTwoAltTags.SetTagValue("contact:website", "mapswith.me");
hasTwoAltTags.SetTagValue("url", "mapswithme.com");
hasTwoAltTags.ApplyPatch(XMLFeature(kPatch));
TEST(!hasTwoAltTags.HasTag("website"), ("Existing alt tag should be used."));
TEST_EQUAL(hasTwoAltTags.GetTagValue("contact:website"), "maps.me", ());
TEST_EQUAL(hasTwoAltTags.GetTagValue("url"), "mapswithme.com", ());
}
{
XMLFeature hasMainAndAltTag = baseOsmFeature;
hasMainAndAltTag.SetTagValue("website", "osmrulezz.com");
hasMainAndAltTag.SetTagValue("url", "mapswithme.com");
hasMainAndAltTag.ApplyPatch(XMLFeature(kPatch));
TEST_EQUAL(hasMainAndAltTag.GetTagValue("website"), "maps.me", ());
TEST_EQUAL(hasMainAndAltTag.GetTagValue("url"), "mapswithme.com", ());
}
}
UNIT_TEST(XMLFeature_FromXMLAndBackToXML) UNIT_TEST(XMLFeature_FromXMLAndBackToXML)
{ {
classificator::Load(); classificator::Load();
std::string const xmlNoTypeStr = R"(<?xml version="1.0"?> std::string const xmlNoTypeStr = R"(<?xml version="1.0"?>
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z"> <node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
<tag k="leisure" v="park" /> <tag k="name" v="Gorki Park" />
<tag k="name" v="Gorki Park" /> <tag k="name:en" v="Gorki Park" />
<tag k="name:en" v="Gorki Park" /> <tag k="name:ru" v="Парк Горького" />
<tag k="name:ru" v="Парк Горького" /> <tag k="addr:housenumber" v="10" />
<tag k="addr:housenumber" v="10" />
<journal version="1.0">
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
<data key="name:en" old_value="" new_value="Gorki Park" />
</entry>
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
<data key="name:ru" old_value="xxx" new_value="Парк Горького" />
</entry>
</journal>
<journalHistory version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="leisure-park" geomType="Point" lat="55.7978998" lon="37.474528" />
</entry>
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
<data key="addr:housenumber" old_value="43" new_value="10" />
</entry>
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
<data key="name" old_value="" new_value="Gorki Park" />
</entry>
</journalHistory>
</node> </node>
)"; )";
char const kTimestamp[] = "2015-11-27T21:13:32Z"; char const kTimestamp[] = "2015-11-27T21:13:32Z";
editor::XMLFeature xmlFeature(xmlNoTypeStr); editor::XMLFeature xmlNoType(xmlNoTypeStr);
editor::XMLFeature xmlWithType = xmlNoType;
xmlWithType.SetTagValue("amenity", "atm");
osm::EditableMapObject emo; osm::EditableMapObject emo;
osm::EditJournal journal = xmlFeature.GetEditJournal(); editor::FromXML(xmlWithType, emo);
emo.ApplyEditsFromJournal(journal); auto fromFtWithType = editor::ToXML(emo, true);
emo.SetJournal(std::move(journal)); fromFtWithType.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(fromFtWithType, xmlWithType, ());
auto xmlFromMapObject = editor::ToXML(emo, true); auto fromFtWithoutType = editor::ToXML(emo, false);
xmlFromMapObject.SetEditJournal(emo.GetJournal()); fromFtWithoutType.SetAttribute("timestamp", kTimestamp);
xmlFromMapObject.SetAttribute("timestamp", kTimestamp); TEST_EQUAL(fromFtWithoutType, xmlNoType, ());
TEST_EQUAL(xmlFromMapObject, xmlFeature, ());
} }
UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml) UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
@@ -357,14 +373,8 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
{ {
std::string const recyclingCentreStr = R"(<?xml version="1.0"?> std::string const recyclingCentreStr = R"(<?xml version="1.0"?>
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z"> <node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
<tag k="amenity" v="recycling" /> <tag k="amenity" v="recycling" />
<tag k="recycling_type" v="centre" /> <tag k="recycling_type" v="centre" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-recycling-centre" geomType="Point" lat="55.8047445" lon="37.5865532" />
</entry>
</journal>
<journalHistory version="1.0" />
</node> </node>
)"; )";
@@ -373,30 +383,21 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
editor::XMLFeature xmlFeature(recyclingCentreStr); editor::XMLFeature xmlFeature(recyclingCentreStr);
osm::EditableMapObject emo; osm::EditableMapObject emo;
osm::EditJournal journal = xmlFeature.GetEditJournal(); editor::FromXML(xmlFeature, emo);
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto const th = emo.GetTypes(); auto const th = emo.GetTypes();
TEST_EQUAL(th.Size(), 1, ()); TEST_EQUAL(th.Size(), 1, ());
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ()); TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ());
auto convertedFt = editor::ToXML(emo, true); auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
convertedFt.SetAttribute("timestamp", kTimestamp); convertedFt.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(xmlFeature, convertedFt, ()); TEST_EQUAL(xmlFeature, convertedFt, ());
} }
{ {
std::string const recyclingContainerStr = R"(<?xml version="1.0"?> std::string const recyclingContainerStr = R"(<?xml version="1.0"?>
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z"> <node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
<tag k="amenity" v="recycling" /> <tag k="amenity" v="recycling" />
<tag k="recycling_type" v="container" /> <tag k="recycling_type" v="container" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-recycling-container" geomType="Point" lat="55.8047445" lon="37.5865532" />
</entry>
</journal>
<journalHistory version="1.0" />
</node> </node>
)"; )";
@@ -405,16 +406,13 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
editor::XMLFeature xmlFeature(recyclingContainerStr); editor::XMLFeature xmlFeature(recyclingContainerStr);
osm::EditableMapObject emo; osm::EditableMapObject emo;
osm::EditJournal journal = xmlFeature.GetEditJournal(); editor::FromXML(xmlFeature, emo);
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal));
auto const th = emo.GetTypes(); auto const th = emo.GetTypes();
TEST_EQUAL(th.Size(), 1, ()); TEST_EQUAL(th.Size(), 1, ());
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ()); TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ());
auto convertedFt = editor::ToXML(emo, true); auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
convertedFt.SetAttribute("timestamp", kTimestamp); convertedFt.SetAttribute("timestamp", kTimestamp);
TEST_EQUAL(xmlFeature, convertedFt, ()); TEST_EQUAL(xmlFeature, convertedFt, ());
} }
@@ -468,43 +466,58 @@ UNIT_TEST(XMLFeature_Diet)
TEST_EQUAL(ft.GetCuisine(), "", ()); TEST_EQUAL(ft.GetCuisine(), "", ());
} }
UNIT_TEST(XMLFeature_SocialContactsProcessing_clean) UNIT_TEST(XMLFeature_SocialContactsProcessing)
{ {
{ {
std::string const nightclubStr = R"(<?xml version="1.0"?> std::string const nightclubStr = R"(<?xml version="1.0"?>
<node lat="40.82862" lon="20.30017" timestamp="2022-02-24T05:07:00Z"> <node lat="50.4082862" lon="30.5130017" timestamp="2022-02-24T05:07:00Z">
<tag k="amenity" v="bar" /> <tag k="amenity" v="nightclub" />
<tag k="name" v="Irish Pub" /> <tag k="name" v="Stereo Plaza" />
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" /> <tag k="contact:facebook" v="http://www.facebook.com/pages/Stereo-Plaza/118100041593935" />
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" /> <tag k="contact:instagram" v="https://www.instagram.com/p/CSy87IhMhfm/" />
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" /> <tag k="contact:line" v="liff.line.me/1645278921-kWRPP32q/?accountId=673watcr" />
<journal version="1.0">
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
<data type="amenity-nightclub" geomType="Point" lat="50.4082862" lon="30.5130017" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:facebook" old_value="" new_value="PierreCardinPeru.oficial" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:instagram" old_value="" new_value="fraback.genusswelt" />
</entry>
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
<data key="contact:line" old_value="" new_value="015qevdv" />
</entry>
</journal>
<journalHistory version="1.0" />
</node> </node>
)"; )";
editor::XMLFeature xmlFeature(nightclubStr); editor::XMLFeature xmlFeature(nightclubStr);
osm::EditableMapObject emo; osm::EditableMapObject emo;
osm::EditJournal journal = xmlFeature.GetEditJournal(); editor::FromXML(xmlFeature, emo);
emo.ApplyEditsFromJournal(journal);
emo.SetJournal(std::move(journal)); auto convertedFt = editor::ToXML(emo, true);
TEST(convertedFt.HasTag("contact:facebook"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "https://facebook.com/pages/Stereo-Plaza/118100041593935",
());
TEST(convertedFt.HasTag("contact:instagram"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:instagram"), "https://instagram.com/p/CSy87IhMhfm", ());
TEST(convertedFt.HasTag("contact:line"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:line"), "https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr",
());
}
}
UNIT_TEST(XMLFeature_SocialContactsProcessing_clean)
{
{
std::string const nightclubStr = R"(<?xml version="1.0"?>
<node lat="40.82862" lon="20.30017" timestamp="2022-02-24T05:07:00Z">
<tag k="amenity" v="bar" />
<tag k="name" v="Irish Pub" />
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" />
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" />
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" />
</node>
)";
editor::XMLFeature xmlFeature(nightclubStr);
osm::EditableMapObject emo;
editor::FromXML(xmlFeature, emo);
auto convertedFt = editor::ToXML(emo, true); auto convertedFt = editor::ToXML(emo, true);
convertedFt.SetEditJournal(emo.GetJournal());
TEST(convertedFt.HasTag("contact:facebook"), ()); TEST(convertedFt.HasTag("contact:facebook"), ());
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ()); TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ());

View File

@@ -610,101 +610,221 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString())); LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
// Don't use new editor for Legacy Objects
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
bool useNewEditor =
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
try try
{ {
switch (fti.m_status) if (useNewEditor)
{ {
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue; LOG(LDEBUG, ("New Editor used\n"));
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created: // fallthrough switch (fti.m_status)
case FeatureStatus::Modified: {
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created: // fallthrough
case FeatureStatus::Modified:
{
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
switch (fti.m_object.GetEditingLifecycle())
{
case EditingLifecycle::CREATED:
{
// Generate XMLFeature for new object
JournalEntry const & createEntry = journal.front();
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
("First item should have type ObjectCreated"));
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
XMLFeature feature =
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
// Check if place already exists
bool mergeSameLocation = false;
try
{
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
// precision of OSM coordinates (WGS 84), ~= 1 cm
constexpr double tolerance = 0.0000001;
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
{
changeset.AddChangesetTag("info:merged_same_location", "yes");
feature = osmFeature;
mergeSameLocation = true;
}
else
{
changeset.AddChangesetTag("info:feature_close_by", "yes");
}
}
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
{}
catch (ChangesetWrapper::EmptyFeatureException const &)
{}
// Add tags to XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
if (!mergeSameLocation)
changeset.Create(feature);
else
changeset.Modify(feature);
break;
}
case EditingLifecycle::MODIFIED:
{
// Load existing OSM object (Throws, see catch below)
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
// Update tags of XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
changeset.Modify(feature);
break;
}
case EditingLifecycle::IN_SYNC:
{
CHECK(false, ("Object already IN_SYNC should not be here"));
continue;
}
}
break;
}
case FeatureStatus::Deleted:
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
break;
}
}
else // Use old editor
{ {
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal(); // Todo: Remove old editor after transition period
switch (fti.m_status)
switch (fti.m_object.GetEditingLifecycle())
{ {
case EditingLifecycle::CREATED: case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
case FeatureStatus::Created:
{ {
// Generate XMLFeature for new object XMLFeature feature = editor::ToXML(fti.m_object, true);
JournalEntry const & createEntry = journal.front(); if (!fti.m_street.empty())
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated, feature.SetTagValue(kAddrStreetTag, fti.m_street);
("First item should have type ObjectCreated"));
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
XMLFeature feature =
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
// Check if place already exists ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
bool mergeSameLocation = false; ("Linear and area features creation is not supported yet."));
try try
{ {
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator); auto const center = fti.m_object.GetMercator();
// Throws, see catch below.
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
// precision of OSM coordinates (WGS 84), ~= 1 cm // If we are here, it means that object already exists at the given point.
constexpr double tolerance = 0.0000001; // To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
XMLFeature const osmFeatureCopy = osmFeature;
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance)) osmFeature.ApplyPatch(feature);
// Check to avoid uploading duplicates into OSM.
if (osmFeature == osmFeatureCopy)
{ {
changeset.AddChangesetTag("info:merged_same_location", "yes"); LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
feature = osmFeature; // Don't delete this local change right now for user to see it in profile.
mergeSameLocation = true; // It will be automatically deleted by migration code on the next maps update.
} }
else else
{ {
changeset.AddChangesetTag("info:feature_close_by", "yes"); LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.AddChangesetTag("info:features_merged", "yes");
changeset.Modify(osmFeature);
} }
} }
catch (ChangesetWrapper::OsmObjectWasDeletedException const &) catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
{} {
catch (ChangesetWrapper::EmptyFeatureException const &) // Object was never created by anyone else - it's safe to create it.
{} changeset.AddChangesetTag("info:old_editor", "yes");
// Add tags to XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
if (!mergeSameLocation)
changeset.Create(feature); changeset.Create(feature);
}
catch (ChangesetWrapper::EmptyFeatureException const &)
{
// There is another node nearby, but it should be safe to create a new one.
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Create(feature);
}
catch (...)
{
// Pass network or other errors to outside exception handler.
throw;
}
}
break;
case FeatureStatus::Modified:
{
// Do not serialize feature's type to avoid breaking OSM data.
// TODO: Implement correct types matching when we support modifying existing feature types.
XMLFeature feature = editor::ToXML(fti.m_object, false);
if (!fti.m_street.empty())
feature.SetTagValue(kAddrStreetTag, fti.m_street);
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr)
{
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue;
}
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
XMLFeature const osmFeatureCopy = osmFeature;
osmFeature.ApplyPatch(feature);
// Check to avoid uploading duplicates into OSM.
if (osmFeature == osmFeatureCopy)
{
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
// Don't delete this local change right now for user to see it in profile.
// It will be automatically deleted by migration code on the next maps update.
}
else else
changeset.Modify(feature); {
break; LOG(LDEBUG, ("Uploading patched feature", osmFeature));
} changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Modify(osmFeature);
case EditingLifecycle::MODIFIED: }
{
// Load existing OSM object (Throws, see catch below)
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
// Update tags of XMLFeature
UpdateXMLFeatureTags(feature, journal, changeset);
// Upload XMLFeature to OSM
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
changeset.AddChangesetTag("info:new_editor", "yes");
changeset.Modify(feature);
break;
}
case EditingLifecycle::IN_SYNC:
{
CHECK(false, ("Object already IN_SYNC should not be here"));
continue;
}
} }
break; break;
}
case FeatureStatus::Deleted: case FeatureStatus::Deleted:
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID()); auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
if (!originalObjectPtr) if (!originalObjectPtr)
{ {
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded.")); LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
GetPlatform().RunTask(Platform::Thread::Gui, GetPlatform().RunTask(Platform::Thread::Gui,
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); }); [this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
continue; continue;
}
changeset.AddChangesetTag("info:old_editor", "yes");
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
break;
} }
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
break;
} }
uploadInfo.m_uploadStatus = kUploaded; uploadInfo.m_uploadStatus = kUploaded;
uploadInfo.m_uploadError.clear(); uploadInfo.m_uploadError.clear();
@@ -787,7 +907,23 @@ void Editor::SaveUploadedInformation(FeatureID const & fid, UploadInfo const & u
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid, bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
FeatureTypeInfo & fti) const FeatureTypeInfo & fti) const
{ {
if (status != FeatureStatus::Created) EditJournal journal = xml.GetEditJournal();
// Do not load Legacy Objects form Journal
auto const & journalHistory = journal.GetJournalHistory();
bool loadFromJournal =
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
LOG(LDEBUG, ("loadFromJournal: ", loadFromJournal));
if (status == FeatureStatus::Created)
{
if (loadFromJournal)
fti.m_object.ApplyEditsFromJournal(journal);
else
editor::FromXML(xml, fti.m_object);
}
else
{ {
auto const originalObjectPtr = GetOriginalMapObject(fid); auto const originalObjectPtr = GetOriginalMapObject(fid);
if (!originalObjectPtr) if (!originalObjectPtr)
@@ -797,10 +933,12 @@ bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, Featu
} }
fti.m_object = *originalObjectPtr; fti.m_object = *originalObjectPtr;
}
EditJournal journal = xml.GetEditJournal(); if (loadFromJournal)
fti.m_object.ApplyEditsFromJournal(journal); fti.m_object.ApplyEditsFromJournal(journal);
else
editor::ApplyPatch(xml, fti.m_object);
}
fti.m_object.SetJournal(std::move(journal)); fti.m_object.SetJournal(std::move(journal));
fti.m_object.SetID(fid); fti.m_object.SetID(fid);

View File

@@ -178,6 +178,38 @@ string XMLFeature::ToOSMString() const
return ost.str(); return ost.str();
} }
void XMLFeature::ApplyPatch(XMLFeature const & featureWithChanges)
{
// TODO(mgsergio): Get these alt tags from the config.
base::StringIL const alternativeTags[] = {{"phone", "contact:phone", "contact:mobile", "mobile"},
{"website", "contact:website", "url"},
{"fax", "contact:fax"},
{"email", "contact:email"}};
featureWithChanges.ForEachTag([&alternativeTags, this](string_view k, string_view v)
{
// Avoid duplication for similar alternative osm tags.
for (auto const & alt : alternativeTags)
{
auto it = alt.begin();
ASSERT(it != alt.end(), ());
if (k == *it)
{
for (auto const & tag : alt)
{
// Reuse already existing tag if it's present.
if (HasTag(tag))
{
SetTagValue(tag, v);
return;
}
}
}
}
SetTagValue(k, v);
});
}
m2::PointD XMLFeature::GetMercatorCenter() const m2::PointD XMLFeature::GetMercatorCenter() const
{ {
return mercator::FromLatLon(GetLatLonFromNode(GetRootNode())); return mercator::FromLatLon(GetLatLonFromNode(GetRootNode()));
@@ -638,40 +670,6 @@ void XMLFeature::RemoveTag(string_view key)
GetRootNode().remove_child(tag); GetRootNode().remove_child(tag);
} }
void XMLFeature::SetOSMTagsForType(uint32_t type)
{
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
SetTagValue("amenity", "recycling");
SetTagValue("recycling_type", "centre");
}
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
SetTagValue("amenity", "recycling");
SetTagValue("recycling_type", "container");
}
else if (ftypes::IsAddressChecker::Instance()(type))
{
// Addresses don't have a category tag
}
else
{
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
if (++iter)
{
// Main type is stored as "k=amenity v=restaurant"
SetTagValue(k, *iter);
}
else {
// Main type is stored as "k=building v=yes"
SetTagValue(k, kYes);
}
}
}
void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value) void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
{ {
if (value.empty()) if (value.empty())
@@ -809,6 +807,26 @@ XMLFeature::Type XMLFeature::StringToType(string const & type)
return Type::Unknown; return Type::Unknown;
} }
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object)
{
xml.ForEachName([&object](string_view lang, string_view name)
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
string const house = xml.GetHouse();
if (!house.empty())
object.SetHouseNumber(house);
auto const cuisineStr = xml.GetCuisine();
if (!cuisineStr.empty())
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
xml.ForEachTag([&object](string_view k, string v)
{
// Skip result because we iterate via *all* tags here.
(void)object.UpdateMetadataValue(k, std::move(v));
});
}
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType) XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
{ {
bool const isPoint = object.GetGeomType() == feature::GeomType::Point; bool const isPoint = object.GetGeomType() == feature::GeomType::Point;
@@ -824,15 +842,6 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
toFeature.SetGeometry(begin(triangles), end(triangles)); toFeature.SetGeometry(begin(triangles), end(triangles));
} }
if (serializeType)
{
feature::TypesHolder types = object.GetTypes();
types.SortBySpec();
ASSERT(!types.Empty(), ("Feature does not have a type"));
uint32_t mainType = types.front();
toFeature.SetOSMTagsForType(mainType);
}
object.GetNameMultilang().ForEach([&toFeature](uint8_t const & lang, string_view name) object.GetNameMultilang().ForEach([&toFeature](uint8_t const & lang, string_view name)
{ toFeature.SetName(lang, name); }); { toFeature.SetName(lang, name); });
@@ -847,8 +856,61 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
toFeature.SetCuisine(cuisineStr); toFeature.SetCuisine(cuisineStr);
} }
if (serializeType)
{
feature::TypesHolder th = object.GetTypes();
// TODO(mgsergio): Use correct sorting instead of SortBySpec based on the config.
th.SortBySpec();
// TODO(mgsergio): Either improve "OSM"-compatible serialization for more complex types,
// or save all our types directly, to restore and reuse them in migration of modified features.
for (uint32_t const type : th)
{
if (ftypes::IsCuisineChecker::Instance()(type))
continue;
if (ftypes::IsRecyclingTypeChecker::Instance()(type))
continue;
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "centre");
continue;
}
if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "container");
continue;
}
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
if (++iter)
{
// First (main) type is always stored as "k=amenity v=restaurant".
// Any other "k=amenity v=atm" is replaced by "k=atm v=yes".
if (toFeature.GetTagValue(k).empty())
toFeature.SetTagValue(k, *iter);
else
toFeature.SetTagValue(*iter, kYes);
}
else
{
// We're editing building, generic craft, shop, office, amenity etc.
// Skip it's serialization.
// TODO(mgsergio): Correcly serialize all types back and forth.
LOG(LDEBUG, ("Skipping type serialization:", k));
}
}
}
object.ForEachMetadataItem([&toFeature](string_view tag, string_view value) object.ForEachMetadataItem([&toFeature](string_view tag, string_view value)
{ {
if (osm::isSocialContactTag(tag) && value.find('/') != std::string::npos)
toFeature.SetTagValue(tag, osm::socialContactToURL(tag, value));
else
toFeature.SetTagValue(tag, value); toFeature.SetTagValue(tag, value);
}); });
@@ -861,11 +923,105 @@ XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD merca
XMLFeature toFeature(XMLFeature::Type::Node); XMLFeature toFeature(XMLFeature::Type::Node);
toFeature.SetCenter(mercator); toFeature.SetCenter(mercator);
toFeature.SetOSMTagsForType(type); // Set Type
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "centre");
}
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
{
toFeature.SetTagValue("amenity", "recycling");
toFeature.SetTagValue("recycling_type", "container");
}
else if (ftypes::IsAddressChecker::Instance()(type))
{
// Addresses don't have a category tag
}
else
{
string const strType = classif().GetReadableObjectName(type);
strings::SimpleTokenizer iter(strType, "-");
string_view const k = *iter;
CHECK(++iter, ("Processing Type failed: ", strType));
// Main type is always stored as "k=amenity v=restaurant".
toFeature.SetTagValue(k, *iter);
ASSERT(!(++iter), ("Can not process 3-arity/complex types: ", strType));
}
return toFeature; return toFeature;
} }
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object)
{
ASSERT_EQUAL(XMLFeature::Type::Node, xml.GetType(), ("At the moment only new nodes (points) can be created."));
object.SetPointType();
object.SetMercator(xml.GetMercatorCenter());
xml.ForEachName([&object](string_view lang, string_view name)
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
string const house = xml.GetHouse();
if (!house.empty())
object.SetHouseNumber(house);
auto const cuisineStr = xml.GetCuisine();
if (!cuisineStr.empty())
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
feature::TypesHolder types = object.GetTypes();
Classificator const & cl = classif();
xml.ForEachTag([&](string_view k, string_view v)
{
if (object.UpdateMetadataValue(k, string(v)))
return;
// Cuisines are already processed before this loop.
if (k == "cuisine")
return;
// We process recycling_type tag together with "amenity"="recycling" later.
// We currently ignore recycling tag because it's our custom tag and we cannot
// import it to osm directly.
if (k == "recycling" || k == "recycling_type")
return;
uint32_t type = 0;
if (k == "amenity" && v == "recycling" && xml.HasTag("recycling_type"))
{
auto const typeValue = xml.GetTagValue("recycling_type");
if (typeValue == "centre")
type = ftypes::IsRecyclingCentreChecker::Instance().GetType();
else if (typeValue == "container")
type = ftypes::IsRecyclingContainerChecker::Instance().GetType();
}
// Simple heuristics. It works for types converted from osm with short mapcss rules
// where k=v from osm is converted to our k-v type (amenity=restaurant, shop=convenience etc.).
if (type == 0)
type = cl.GetTypeByPathSafe({k, v});
if (type == 0)
type = cl.GetTypeByPathSafe({k}); // building etc.
if (type == 0)
type = cl.GetTypeByPathSafe({"amenity", k}); // atm=yes, toilet=yes etc.
if (type && types.Size() >= feature::kMaxTypesCount)
LOG(LERROR, ("Can't add type:", k, v, ". Types limit exceeded."));
else if (type)
types.Add(type);
else
{
// LOG(LWARNING, ("Can't load/parse type:", k, v));
/// @todo Refactor to make one ForEachTag loop. Now we have separate ForEachName,
/// so we can't log any suspicious tag here ...
}
});
object.SetTypes(types);
return types.Size() > 0;
}
string DebugPrint(XMLFeature const & feature) string DebugPrint(XMLFeature const & feature)
{ {
std::ostringstream ost; std::ostringstream ost;

View File

@@ -73,6 +73,9 @@ public:
void Save(std::ostream & ost) const; void Save(std::ostream & ost) const;
std::string ToOSMString() const; std::string ToOSMString() const;
/// Tags from featureWithChanges are applied to this(osm) feature.
void ApplyPatch(XMLFeature const & featureWithChanges);
Type GetType() const; Type GetType() const;
std::string GetTypeString() const; std::string GetTypeString() const;
@@ -182,8 +185,6 @@ public:
void SetTagValue(std::string_view key, std::string_view value); void SetTagValue(std::string_view key, std::string_view value);
void RemoveTag(std::string_view key); void RemoveTag(std::string_view key);
/// Add the OSM tags for a feature type
void SetOSMTagsForType(uint32_t type);
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags /// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
void UpdateOSMTag(std::string_view key, std::string_view value); void UpdateOSMTag(std::string_view key, std::string_view value);
/// Replace an old business with a new business /// Replace an old business with a new business
@@ -204,15 +205,22 @@ private:
pugi::xml_document m_document; pugi::xml_document m_document;
}; };
/// @param serializeType if false, type is not serialized. /// Rewrites all but geometry and types.
/// This function converts the current state of a MapObject to a format similar to OSM style XML. /// Should be applied to existing features only (in mwm files).
/// Tags written in this function are used to see POI details when debugging. Only the data stored void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object);
/// in the EditJournal is used for OSM editing.
/// @param serializeType if false, types are not serialized.
/// Useful for applying modifications to existing OSM features, to avoid issues when someone
/// has changed a type in OSM, but our users uploaded invalid outdated type after modifying feature.
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType); XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
/// Used to generate XML for created objects in the new editor /// Used to generate XML for created objects in the new editor
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator); XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
/// Creates new feature, including geometry and types.
/// @Note: only nodes (points) are supported at the moment.
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object);
std::string DebugPrint(XMLFeature const & feature); std::string DebugPrint(XMLFeature const & feature);
std::string DebugPrint(XMLFeature::Type const type); std::string DebugPrint(XMLFeature::Type const type);
} // namespace editor } // namespace editor

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp" #include "geometry/point2d.hpp"
#include "base/math.hpp" #include "base/math.hpp"
@@ -9,17 +10,20 @@
namespace m2 namespace m2
{ {
// This class holds a parametrization of the /**
// line segment between two points p0 and p1. * @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. * The parametrization is of the form
// Other conditions: * `p(t) = p0 + t * dir`.
// dir is the normalized (p1 - p0) vector. *
// length(dir) = 1. * Other conditions:
// p(0) = p0. * * `dir` is the normalized `(p1 - p0)` vector.
// p(T) = p1 with T = length(p1 - p0). * * `length(dir) = 1`.
// * * `p(0) = p0`.
// The points with t in [0, T] are the points of the segment. * * `p(T) = p1` with `T = length(p1 - p0)`.
*
* The points with `t` in `[0, T]` are the points of the segment.
*/
template <typename Point> template <typename Point>
class ParametrizedSegment class ParametrizedSegment
{ {
@@ -36,7 +40,9 @@ public:
m_d = m_d / m_length; 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 double SquaredDistanceToPoint(Point const & p) const
{ {
m2::PointD const diff(p - m_p0); m2::PointD const diff(p - m_p0);
@@ -52,9 +58,22 @@ public:
return math::Pow2(CrossProduct(diff, m_d)); 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); m2::PointD const diff(p - m_p0);
double const t = DotProduct(m_d, diff); double const t = DotProduct(m_d, diff);

View File

@@ -19,20 +19,31 @@ public:
/// Registers a new map. /// Registers a new map.
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile); std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
/// Deregisters a map from internal records. /**
/// * @brief Deregisters a map from internal records.
/// \param countryFile A countryFile denoting a map to be deregistered. * @param countryFile A `CountryFile` denoting a map to be deregistered.
/// \return True if the map was successfully deregistered. If map is locked * @return True if the map was successfully deregistered, false if the map is locked now.
/// now, returns false. */
bool DeregisterMap(platform::CountryFile const & countryFile); bool DeregisterMap(platform::CountryFile const & countryFile);
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale, void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
covering::CoveringMode mode = covering::ViewportWithLowLevels) const; covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) 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 * @brief Iterates over features within a given distance of a center point.
// hierarchy and there is no fast way to merge frozen and edited features. *
* 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, void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
double sizeM, int scale) const; double sizeM, int scale) const;
void ForEachInScale(FeatureCallback const & f, int scale) const; void ForEachInScale(FeatureCallback const & f, int scale) const;
@@ -66,18 +77,24 @@ private:
std::unique_ptr<FeatureSourceFactory> m_factory; 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 class FrozenDataSource : public DataSource
{ {
public: public:
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {} 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 * @brief Guard for loading features from particular MWM by demand.
/// a unique FeaturesLoaderGuard instance for every thread. *
/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest. * @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 class FeaturesLoaderGuard
{ {
public: public:

View File

@@ -479,9 +479,6 @@ bool EditableMapObject::CheckHouseNumberWhenIsAddress() const
// static // static
bool EditableMapObject::ValidateFlats(string const & flats) bool EditableMapObject::ValidateFlats(string const & flats)
{ {
if (strings::CountChar(flats) > kMaximumOsmChars)
return false;
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it) for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
{ {
string_view token = *it; string_view token = *it;
@@ -519,9 +516,6 @@ bool EditableMapObject::ValidatePhoneList(string const & phone)
if (phone.empty()) if (phone.empty())
return true; return true;
if (strings::CountChar(phone) > kMaximumOsmChars)
return false;
auto constexpr kMaxNumberLen = 15; auto constexpr kMaxNumberLen = 15;
auto constexpr kMinNumberLen = 5; auto constexpr kMinNumberLen = 5;
@@ -562,9 +556,6 @@ bool EditableMapObject::ValidateEmail(string const & email)
if (email.empty()) if (email.empty())
return true; return true;
if (strings::CountChar(email) > kMaximumOsmChars)
return false;
if (strings::IsASCIIString(email)) if (strings::IsASCIIString(email))
{ {
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)"); static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
@@ -598,9 +589,6 @@ bool EditableMapObject::ValidateLevel(string const & level)
if (level.empty()) if (level.empty())
return true; return true;
if (strings::CountChar(level) > kMaximumOsmChars)
return false;
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos) if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
return false; return false;
@@ -645,9 +633,6 @@ bool EditableMapObject::ValidateName(string const & name)
if (name.empty()) if (name.empty())
return true; return true;
if (strings::CountChar(name) > kMaximumOsmChars)
return false;
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×"; static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×";
using Iter = utf8::unchecked::iterator<string::const_iterator>; using Iter = utf8::unchecked::iterator<string::const_iterator>;

View File

@@ -70,7 +70,6 @@ class EditableMapObject : public MapObject
{ {
public: public:
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50; static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
static int constexpr kMaximumOsmChars = 255;
bool IsNameEditable() const; bool IsNameEditable() const;
bool IsAddressEditable() const; bool IsAddressEditable() const;

View File

@@ -76,6 +76,18 @@ public:
// (number of points in inner triangle-strips). // (number of points in inner triangle-strips).
using PointsBufferT = buffer_vector<m2::PointD, 32>; 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 & GetPoints(int scale);
PointsBufferT const & GetTrianglesAsPoints(int scale); PointsBufferT const & GetTrianglesAsPoints(int scale);
@@ -85,6 +97,13 @@ public:
void ParseHeader2(); void ParseHeader2();
void ParseRelations(); void ParseRelations();
void ParseAllBeforeGeometry() { 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 ResetGeometry();
void ParseGeometry(int scale); void ParseGeometry(int scale);
void ParseTriangles(int scale); void ParseTriangles(int scale);

View File

@@ -701,24 +701,46 @@ double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType);
uint64_t GetPopulationByRadius(double r); 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 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, Motorway,
Trunk, Trunk,
Primary, Primary,
Secondary, Secondary,
Tertiary, Tertiary,
/**
* Unclassified, residential, living street and `highway=road`.
*/
LivingStreet, LivingStreet,
/**
* Service, track, busway and `man_made=pier`.
*/
Service, Service,
// OSM highway=service type is widely used even for _significant_ roads. // OSM highway=service type is widely used even for _significant_ roads.
// Adding a new type to distinguish mapped driveway or parking_aisle. // Adding a new type to distinguish mapped driveway or parking_aisle.
ServiceMinor, ServiceMinor,
/**
* Anything not intended for motorized traffic: pedestrian, footway, bridleway, steps, cycleway,
* path and also `highway=construction`.
*/
Pedestrian, 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); std::string DebugPrint(HighwayClass const cls);

View File

@@ -6,8 +6,6 @@ UNIT_TEST(RoadShields_Smoke)
{ {
using namespace ftypes; using namespace ftypes;
// TODO: Fix broken tests to make code compile
/*
auto shields = GetRoadShields("France", "D 116A"); auto shields = GetRoadShields("France", "D 116A");
TEST_EQUAL(shields.size(), 1, ()); TEST_EQUAL(shields.size(), 1, ());
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ()); TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
@@ -57,5 +55,4 @@ UNIT_TEST(RoadShields_Smoke)
shields = GetRoadShields("Estonia", "ee:national/27;ee:local/7841171"); shields = GetRoadShields("Estonia", "ee:national/27;ee:local/7841171");
TEST_EQUAL(shields.size(), 1, ()); TEST_EQUAL(shields.size(), 1, ());
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ()); TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
*/
} }

View File

@@ -157,8 +157,10 @@ public:
explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {} explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {}
virtual ~MwmSet() = default; 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 class MwmHandle
{ {
public: public:
@@ -243,19 +245,26 @@ public:
BadFile BadFile
}; };
// An Observer interface to MwmSet. Note that these functions can /**
// be called from *ANY* thread because most signals are sent when * @brief An Observer interface to `MwmSet`.
// some thread releases its MwmHandle, so overrides must be as fast *
// as possible and non-blocking when it's possible. * 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 class Observer
{ {
public: public:
virtual ~Observer() = default; 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 */) {} 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 */) {} 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. /// @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; 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 Clear();
void ClearCache(); void ClearCache();
@@ -345,10 +361,18 @@ private:
ProcessEventList(events); 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); 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); void ProcessEventList(EventList & events);
/// @precondition This function is always called under mutex m_lock. /// @precondition This function is always called under mutex m_lock.

View File

@@ -408,10 +408,6 @@ bool ValidateWebsite(string const & site)
auto const startPos = GetProtocolNameLength(site); auto const startPos = GetProtocolNameLength(site);
// check lengt and leave room for addition of 'http://'
if (strings::CountChar(site) > (IsProtocolSpecified(site) ? kMaximumOsmChars : kMaximumOsmChars - 7))
return false;
if (startPos >= site.size()) if (startPos >= site.size())
return false; return false;
@@ -433,9 +429,6 @@ bool ValidateFacebookPage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Check if 'page' contains valid Facebook username or page name. // Check if 'page' contains valid Facebook username or page name.
// * length >= 5 // * length >= 5
// * no forbidden symbols in the string // * no forbidden symbols in the string
@@ -459,9 +452,6 @@ bool ValidateInstagramPage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Rules are defined here: https://blog.jstassen.com/2016/03/code-regex-for-instagram-username-and-hashtags/ // Rules are defined here: https://blog.jstassen.com/2016/03/code-regex-for-instagram-username-and-hashtags/
if (regex_match(page, s_instaRegex)) if (regex_match(page, s_instaRegex))
return true; return true;
@@ -478,9 +468,6 @@ bool ValidateTwitterPage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
if (!ValidateWebsite(page)) if (!ValidateWebsite(page))
return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044 return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044
@@ -493,9 +480,6 @@ bool ValidateVkPage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
{ {
// Check that page contains valid username. Rules took here: https://vk.com/faq18038 // Check that page contains valid username. Rules took here: https://vk.com/faq18038
// The page name must be between 5 and 32 characters. // The page name must be between 5 and 32 characters.
@@ -529,9 +513,6 @@ bool ValidateLinePage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
{ {
// Check that linePage contains valid page name. // Check that linePage contains valid page name.
// Rules are defined here: https://help.line.me/line/?contentId=10009904 // Rules are defined here: https://help.line.me/line/?contentId=10009904
@@ -555,9 +536,6 @@ bool ValidateFediversePage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Match @username@instance.name format // Match @username@instance.name format
if (regex_match(page, s_fediverseRegex)) if (regex_match(page, s_fediverseRegex))
return true; return true;
@@ -597,9 +575,6 @@ bool ValidateBlueskyPage(string const & page)
if (page.empty()) if (page.empty())
return true; return true;
if (strings::CountChar(page) > kMaximumOsmChars)
return false;
// Match {@?}{user/domain.name} format // Match {@?}{user/domain.name} format
if (regex_match(page, s_blueskyRegex)) if (regex_match(page, s_blueskyRegex))
return true; return true;
@@ -626,6 +601,12 @@ bool ValidateBlueskyPage(string const & page)
return false; return false;
} }
bool isSocialContactTag(string_view tag)
{
return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine || tag == kFediverse ||
tag == kBluesky || tag == kPanoramax;
}
bool isSocialContactTag(MapObject::MetadataID const metaID) bool isSocialContactTag(MapObject::MetadataID const metaID)
{ {
return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM || return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM ||
@@ -637,6 +618,35 @@ bool isSocialContactTag(MapObject::MetadataID const metaID)
// Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name // Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name
// from OSM data and user input. This function prepends domain name to generate full URL. // from OSM data and user input. This function prepends domain name to generate full URL.
string socialContactToURL(string_view tag, string_view value)
{
ASSERT(!value.empty(), ());
if (tag == kInstagram)
return string{kUrlInstagram}.append(value);
if (tag == kFacebook)
return string{kUrlFacebook}.append(value);
if (tag == kTwitter)
return string{kUrlTwitter}.append(value);
if (tag == kVk)
return string{kUrlVk}.append(value);
if (tag == kFediverse)
return fediverseHandleToUrl(value);
if (tag == kBluesky) // In future
return string{kUrlBluesky}.append(value);
if (tag == kLine)
{
if (value.find('/') == string::npos) // 'value' is a username.
return string{kUrlLine}.append(value);
else // 'value' is an URL.
return string{kHttps}.append(value);
}
if (tag == kPanoramax)
return string{kUrlPanoramax}.append(value);
return string{value};
}
string socialContactToURL(MapObject::MetadataID metaID, string_view value) string socialContactToURL(MapObject::MetadataID metaID, string_view value)
{ {
ASSERT(!value.empty(), ()); ASSERT(!value.empty(), ());

View File

@@ -6,8 +6,6 @@
namespace osm namespace osm
{ {
static int constexpr kMaximumOsmChars = 255;
std::string ValidateAndFormat_website(std::string const & v); std::string ValidateAndFormat_website(std::string const & v);
std::string ValidateAndFormat_facebook(std::string const & v); std::string ValidateAndFormat_facebook(std::string const & v);
std::string ValidateAndFormat_instagram(std::string const & v); std::string ValidateAndFormat_instagram(std::string const & v);
@@ -26,6 +24,8 @@ bool ValidateLinePage(std::string const & v);
bool ValidateFediversePage(std::string const & v); bool ValidateFediversePage(std::string const & v);
bool ValidateBlueskyPage(std::string const & v); bool ValidateBlueskyPage(std::string const & v);
bool isSocialContactTag(std::string_view tag);
bool isSocialContactTag(osm::MapObject::MetadataID const metaID); bool isSocialContactTag(osm::MapObject::MetadataID const metaID);
std::string socialContactToURL(std::string_view tag, std::string_view value);
std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value); std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value);
} // namespace osm } // namespace osm

View File

@@ -7,7 +7,9 @@
#include "ge0/url_generator.hpp" #include "ge0/url_generator.hpp"
#include "routing/index_router.hpp"
#include "routing/route.hpp" #include "routing/route.hpp"
#include "routing/routing_helpers.hpp"
#include "routing/speed_camera_prohibition.hpp" #include "routing/speed_camera_prohibition.hpp"
#include "routing_common/num_mwm_id.hpp" #include "routing_common/num_mwm_id.hpp"
@@ -17,13 +19,18 @@
#include "search/locality_finder.hpp" #include "search/locality_finder.hpp"
#include "storage/country_info_getter.hpp" #include "storage/country_info_getter.hpp"
#include "storage/routing_helpers.hpp"
#include "storage/storage.hpp" #include "storage/storage.hpp"
#include "storage/storage_helpers.hpp" #include "storage/storage_helpers.hpp"
#include "traffxml/traff_source.hpp"
#include "drape_frontend/color_constants.hpp" #include "drape_frontend/color_constants.hpp"
#include "drape_frontend/gps_track_point.hpp" #include "drape_frontend/gps_track_point.hpp"
#include "drape_frontend/visual_params.hpp" #include "drape_frontend/visual_params.hpp"
#include "editor/editable_data_source.hpp"
#include "descriptions/loader.hpp" #include "descriptions/loader.hpp"
#include "indexer/categories_holder.hpp" #include "indexer/categories_holder.hpp"
@@ -39,6 +46,7 @@
#include "indexer/scales.hpp" #include "indexer/scales.hpp"
#include "indexer/transliteration_loader.hpp" #include "indexer/transliteration_loader.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/localization.hpp" #include "platform/localization.hpp"
#include "platform/measurement_utils.hpp" #include "platform/measurement_utils.hpp"
#include "platform/mwm_version.hpp" #include "platform/mwm_version.hpp"
@@ -61,11 +69,13 @@
#include "base/logging.hpp" #include "base/logging.hpp"
#include "base/math.hpp" #include "base/math.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp" #include "base/string_utils.hpp"
#include "std/target_os.hpp" #include "std/target_os.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "private.h"
#include <algorithm> #include <algorithm>
@@ -93,6 +103,8 @@ std::string_view constexpr kAllow3dKey = "Allow3d";
std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d"; std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d";
std::string_view constexpr kAllowAutoZoom = "AutoZoom"; std::string_view constexpr kAllowAutoZoom = "AutoZoom";
std::string_view constexpr kTrafficEnabledKey = "TrafficEnabled"; 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 kTransitSchemeEnabledKey = "TransitSchemeEnabled";
std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled"; std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled";
std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled"; std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled";
@@ -273,8 +285,11 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
[this]() -> StringsBundle const & [this]() -> StringsBundle const &
{ return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }), { return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }),
static_cast<RoutingManager::Delegate &>(*this)) static_cast<RoutingManager::Delegate &>(*this))
, m_trafficManager(bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes, , m_trafficManager(m_featuresFetcher.GetDataSource(),
m_routingManager.RoutingSession()) [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_lastReportedCountry(kInvalidCountryId)
, m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG) , m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG)
, m_descriptionsLoader(std::make_unique<descriptions::Loader>(m_featuresFetcher.GetDataSource())) , 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.SetDelegate(make_unique<search::EditorDelegate>(m_featuresFetcher.GetDataSource()));
editor.SetInvalidateFn([this]() { InvalidateRect(GetCurrentViewport()); }); editor.SetInvalidateFn([this]() { InvalidateRect(GetCurrentViewport()); });
/// @todo Uncomment when we will integrate a traffic provider. if (params.m_trafficTestMode)
// m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion()); m_trafficManager.SetTestMode();
// m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors()); m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
// m_trafficManager.SetEnabled(LoadTrafficEnabled()); m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
m_isolinesManager.SetEnabled(LoadIsolinesEnabled()); m_isolinesManager.SetEnabled(LoadIsolinesEnabled());
@@ -383,6 +398,19 @@ Framework::~Framework()
m_featuresFetcher.SetOnMapDeregisteredCallback(nullptr); 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) void Framework::ShowNode(storage::CountryId const & countryId)
{ {
StopLocationFollow(); StopLocationFollow();
@@ -398,14 +426,14 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, storage::Loc
m2::RectD rect = mercator::Bounds::FullRect(); m2::RectD rect = mercator::Bounds::FullRect();
if (localFile && localFile->OnDisk(MapFileType::Map)) if (localFile && localFile->OnDisk(MapFileType::Map))
{ m_trafficManager.RunSynchronized([this, localFile, &rect](){
auto const res = RegisterMap(*localFile); auto const res = RegisterMap(*localFile);
MwmSet::MwmId const & id = res.first; MwmSet::MwmId const & id = res.first;
if (id.IsAlive()) if (id.IsAlive())
rect = id.GetInfo()->m_bordersRect; rect = id.GetInfo()->m_bordersRect;
} m_trafficManager.Invalidate(id);
});
m_trafficManager.Invalidate();
m_transitManager.Invalidate(); m_transitManager.Invalidate();
m_isolinesManager.Invalidate(); m_isolinesManager.Invalidate();
@@ -487,6 +515,8 @@ void Framework::LoadMapsSync()
LOG(LDEBUG, ("Editor initialized")); LOG(LDEBUG, ("Editor initialized"));
GetStorage().RestoreDownloadQueue(); GetStorage().RestoreDownloadQueue();
InitializeTraffic();
} }
// Small copy-paste with LoadMapsSync, but I don't have a better solution. // 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(); GetStorage().RestoreDownloadQueue();
InitializeTraffic();
callback(); callback();
}); });
}).detach(); }).detach();
@@ -2475,6 +2507,42 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled)
settings::Set(kTrafficEnabledKey, 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 Framework::LoadTrafficSimplifiedColors()
{ {
bool simplified; bool simplified;

View File

@@ -103,11 +103,22 @@ class Loader;
/// build version for screenshots. /// build version for screenshots.
// #define FIXED_LOCATION // #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 struct FrameworkParams
{ {
bool m_enableDiffs = true; bool m_enableDiffs = true;
size_t m_numSearchAPIThreads = 1; size_t m_numSearchAPIThreads = 1;
/**
* @brief Whether the traffic manager should start in test mode.
*/
bool m_trafficTestMode = false;
FrameworkParams() = default; FrameworkParams() = default;
FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {} FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {}
}; };
@@ -233,7 +244,34 @@ public:
/// \note It works for group and leaf node. /// \note It works for group and leaf node.
bool HasUnsavedEdits(storage::CountryId const & countryId); 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(); 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); void LoadMapsAsync(std::function<void()> && callback);
/// Registers all local map files in internal indexes. /// Registers all local map files in internal indexes.
@@ -384,6 +422,16 @@ private:
private: private:
std::vector<m2::TriangleD> GetSelectedFeatureTriangles() const; 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: public:
/// @name GPS location updates routine. /// @name GPS location updates routine.
void OnLocationError(location::TLocationError error); void OnLocationError(location::TLocationError error);
@@ -489,11 +537,18 @@ private:
std::unique_ptr<descriptions::Loader> m_descriptionsLoader; std::unique_ptr<descriptions::Loader> m_descriptionsLoader;
public: 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); 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 ShowSearchResult(search::Result const & res, bool animation = true);
void UpdateViewport(search::Results const & results); void UpdateViewport(search::Results const & results);
@@ -721,6 +776,14 @@ public:
bool LoadTrafficEnabled(); bool LoadTrafficEnabled();
void SaveTrafficEnabled(bool trafficEnabled); 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(); bool LoadTrafficSimplifiedColors();
void SaveTrafficSimplifiedColors(bool simplified); 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 "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/point2d.hpp"
#include "geometry/polyline2d.hpp" #include "geometry/polyline2d.hpp"
#include "geometry/screenbase.hpp" #include "geometry/screenbase.hpp"
@@ -28,21 +37,68 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
class TrafficManager final class TrafficManager final : public traffxml::TraffSourceManager
{ {
public: 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 enum class TrafficState
{ {
/** Traffic is disabled, no traffic data will be retrieved or considered for routing. */
Disabled, Disabled,
/** Traffic is enabled and working normally (the first request may not have been scheduled yet). */
Enabled, Enabled,
/** At least one request is currently pending. */
WaitingData, WaitingData,
/** At least one MWM has stale traffic data. */
Outdated, Outdated,
/** Traffic data for at least one MWM was invalid or not found on the server. */
NoData, NoData,
/** At least one request failed or timed out. */
NetworkError, NetworkError,
/** Traffic data could not be retrieved because the map data is outdated. */
ExpiredData, ExpiredData,
/** Traffic data could not be retrieved because the app version is outdated. */
ExpiredApp 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 struct MyPosition
{ {
m2::PointD m_position = m2::PointD(0.0, 0.0); m2::PointD m_position = m2::PointD(0.0, 0.0);
@@ -55,25 +111,122 @@ public:
using TrafficStateChangedFn = std::function<void(TrafficState)>; using TrafficStateChangedFn = std::function<void(TrafficState)>;
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>; using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes, TrafficManager(DataSource & dataSource,
traffic::TrafficObserver & observer); CountryInfoGetterFn countryInfoGetter,
CountryParentNameGetterFn const & countryParentNameGetter,
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
routing::RoutingSession & routingSession);
~TrafficManager(); ~TrafficManager();
void Teardown(); 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; TrafficState GetState() const;
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn); void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine); void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
/**
* @brief Sets the version of the MWM used locally.
*/
void SetCurrentDataVersion(int64_t dataVersion); 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); void SetEnabled(bool enabled);
/**
* @brief Whether the traffic manager is enabled.
*
* @return True if enabled, false if not
*/
bool IsEnabled() const; 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 UpdateViewport(ScreenBase const & screen);
void UpdateMyPosition(MyPosition const & myPosition); 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 OnDestroySurface();
void OnRecoverSurface(); void OnRecoverSurface();
@@ -85,57 +238,295 @@ public:
void SetSimplifiedColorScheme(bool simplified); void SetSimplifiedColorScheme(bool simplified);
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; } 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(); std::lock_guard<std::mutex> lock(m_mutex);
explicit CacheEntry(std::chrono::time_point<std::chrono::steady_clock> const & requestTime); f();
}
bool m_isLoaded; private:
size_t m_dataSize;
std::chrono::time_point<std::chrono::steady_clock> m_lastActiveTime; /**
std::chrono::time_point<std::chrono::steady_clock> m_lastRequestTime; * @brief Recalculates the TraFF subscription area.
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime; *
* 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(); 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. * @brief Processes new traffic data.
/// \note |lastMwmsByRect|/|activeMwms| may be either |m_lastDrapeMwmsByRect/|m_activeDrapeMwms| *
/// or |m_lastRoutingMwmsByRect|/|m_activeRoutingMwms|. * The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`.
/// \note |m_mutex| is locked inside the method. So the method should be called without |m_mutex|. * `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, void UpdateActiveMwms(m2::RectD const & rect, std::vector<MwmSet::MwmId> & lastMwmsByRect,
std::set<MwmSet::MwmId> & activeMwms); std::set<MwmSet::MwmId> & activeMwms);
// This is a group of methods that haven't their own synchronization inside. // 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); void ChangeState(TrafficState newState);
bool IsInvalidState() const; 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; 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(); 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(); 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> template <class F>
void ForEachActiveMwm(F && f) const void ForEachActiveMwm(F && f) const
{ {
@@ -144,44 +535,233 @@ private:
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(f)); 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; 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; df::DrapeEngineSafePtr m_drapeEngine;
std::atomic<int64_t> m_currentDataVersion; std::atomic<int64_t> m_currentDataVersion;
// These fields have a flag of their initialization. // 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_currentPosition = {MyPosition(), false};
std::pair<MyPosition, bool> m_currentPositionLazy = m_currentPosition;
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false}; 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; std::atomic<TrafficState> m_state;
TrafficStateChangedFn m_onStateChangedFn; TrafficStateChangedFn m_onStateChangedFn;
bool m_hasSimplifiedColorScheme = true; bool m_hasSimplifiedColorScheme = true;
size_t m_maxCacheSizeBytes; /**
size_t m_currentCacheSizeBytes = 0; * @brief The TraFF sources from which we get traffic information.
*
std::map<MwmSet::MwmId, CacheEntry> m_mwmCache; * Threads must lock `m_trafficSourceMutex` prior to accessing this member.
*/
std::vector<std::unique_ptr<traffxml::TraffSource>> m_trafficSources;
bool m_isRunning; bool m_isRunning;
std::condition_variable m_condition; 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::vector<MwmSet::MwmId> m_lastDrapeMwmsByRect;
std::set<MwmSet::MwmId> m_activeDrapeMwms; 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; 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, * @brief Whether active MWMs have changed since the last request.
// which allows a client to make conditional requests. */
std::map<MwmSet::MwmId, std::string> m_trafficETags; bool m_activeMwmsChanged = false;
std::atomic<bool> m_isPaused; 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; 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; 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); extern std::string DebugPrint(TrafficManager::TrafficState state);

View File

@@ -36,20 +36,61 @@ public:
RoadWarningFirstFerry, RoadWarningFirstFerry,
}; };
/**
* @brief User mark types.
*
* `UserMark` subclasses are assigned a value from this enum.
*/
enum Type : uint32_t enum Type : uint32_t
{ {
/**
* `Bookmark`
*/
BOOKMARK, // Should always be the first one BOOKMARK, // Should always be the first one
/**
* `ApiMarkPoint`
*/
API, API,
/**
* `SearchMarkPoint`
*/
SEARCH, SEARCH,
/**
* `StaticMarkPoint`
*/
STATIC, STATIC,
/**
* `RouteMarkPoint`
*/
ROUTING, ROUTING,
/**
* `SpeedCameraMark`
*/
SPEED_CAM, SPEED_CAM,
/**
* `RoadWarningMark`
*/
ROAD_WARNING, ROAD_WARNING,
/**
* `TransitMark`
*/
TRANSIT, TRANSIT,
LOCAL_ADS, LOCAL_ADS,
/**
* `TrackInfoMark`
*/
TRACK_INFO, TRACK_INFO,
/**
* `TrackSelectionMark`
*/
TRACK_SELECTION, TRACK_SELECTION,
/**
* `DebugMarkPoint`
*/
DEBUG_MARK, // Plain "DEBUG" results in a name collision. DEBUG_MARK, // Plain "DEBUG" results in a name collision.
/**
* `ColoredMarkPoint`
*/
COLORED, COLORED,
USER_MARK_TYPES_COUNT, USER_MARK_TYPES_COUNT,
USER_MARK_TYPES_COUNT_MAX = 1000, USER_MARK_TYPES_COUNT_MAX = 1000,
@@ -133,6 +174,9 @@ private:
bool m_hasPosition = false; bool m_hasPosition = false;
}; };
/**
* @brief A mark in the shape of a dot.
*/
class DebugMarkPoint : public UserMark class DebugMarkPoint : public UserMark
{ {
public: public:
@@ -141,6 +185,9 @@ public:
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override; 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 class ColoredMarkPoint : public UserMark
{ {
public: public:

View File

@@ -13,53 +13,88 @@
namespace platform namespace platform
{ {
// This class represents a path to disk files corresponding to some /**
// country region. * @brief Represents a path to disk files corresponding to some country region.
// *
// This class also wraps World.mwm and WorldCoasts.mwm * This class also wraps World.mwm and WorldCoasts.mwm files from resource bundle, when they can't
// files from resource bundle, when they can't be found in a data * be found in a data directory. In this exceptional case, directory will be empty and
// directory. In this exceptional case, directory will be empty and * `SyncWithDisk()`/`DeleteFromDisk()`/`GetPath()`/`GetSize()` will return incorrect results.
// 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()`.
// In any case, when you're going to read a file LocalCountryFile points to, */
// use platform::GetCountryReader().
class LocalCountryFile class LocalCountryFile
{ {
public: public:
LocalCountryFile(); LocalCountryFile();
// Creates an instance holding a path to countryFile's in a /**
// directory. Note that no disk operations are not performed until * @brief Creates an instance holding a path to countryFile's in a directory.
// SyncWithDisk() is called. *
// The directory must contain a full path to the country file. * 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); 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 * @brief Syncs internal state like availability of files, their sizes etc. with disk.
// this method but it's not implemented by performance reasons. This check is done on *
// building routes stage. * 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(); 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; 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 GetPath(MapFileType type) const;
std::string GetFileName(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; 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; 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 OnDisk(MapFileType type) const;
bool IsInBundle() const { return m_directory.empty(); } bool IsInBundle() const { return m_directory.empty(); }
@@ -74,8 +109,17 @@ public:
bool ValidateIntegrity() const; 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); static LocalCountryFile MakeForTesting(std::string countryFileName, int64_t version = 0);
// Used in generator only to simplify getting instance from path. // 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) void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate)
{ {
m_regions.clear();
if (m_routerThread) if (m_routerThread)
{ {
m_routerThread->Cancel(); m_routerThread->Cancel();
@@ -48,18 +50,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set<std::string> & regions)
void AbsentRegionsFinder::GetAllRegions(std::set<std::string> & countries) 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) for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
return; {
if (!mwmName.empty())
m_regions.emplace(mwmName);
}
m_routerThread->Join(); m_routerThread.reset();
}
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames()) countries = m_regions;
if (!mwmName.empty())
countries.emplace(mwmName);
m_routerThread.reset();
} }
bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const

View File

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

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