mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 04:53:36 +00:00
Compare commits
234 Commits
6a20269819
...
traffic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dbde46dca | ||
|
|
7ed4a6ddd9 | ||
|
|
645ca792f7 | ||
|
|
cd64d14830 | ||
|
|
67b1d918ba | ||
|
|
48a3e5d4b0 | ||
|
|
d4c002851b | ||
|
|
9d4801886e | ||
|
|
5a26b72431 | ||
|
|
859b89e127 | ||
|
|
8fcf00b0ca | ||
|
|
5c2cedb19a | ||
|
|
67a3866529 | ||
|
|
90fdf0ed72 | ||
|
|
2b7ee4ba44 | ||
|
|
cd1201cb02 | ||
|
|
e6937b211a | ||
|
|
27ad70d729 | ||
|
|
a9700156db | ||
|
|
1d87e0e987 | ||
|
|
f7882636cd | ||
|
|
40fbea11e7 | ||
|
|
e87727c168 | ||
|
|
d82b545e30 | ||
|
|
781d973faa | ||
|
|
d6818786f7 | ||
|
|
615f57c604 | ||
|
|
af20e2b42b | ||
|
|
05f6dfad7b | ||
|
|
2b867a64a0 | ||
|
|
c0fd405798 | ||
|
|
207d6c833d | ||
|
|
d47aa09053 | ||
|
|
fbc150fae2 | ||
|
|
d88ed01bc1 | ||
|
|
3ec32e4415 | ||
|
|
9065f45b21 | ||
|
|
46d363ae24 | ||
|
|
0dc62c47dd | ||
|
|
3c9eeb9a75 | ||
|
|
ae23afd72e | ||
|
|
800cc0641b | ||
|
|
1b74062447 | ||
|
|
6a694c5d3e | ||
|
|
dda13b8d3d | ||
|
|
c1340a9941 | ||
|
|
a313526aed | ||
|
|
32d1a3a36e | ||
|
|
3a45740884 | ||
|
|
4e624bd04b | ||
|
|
a3d1ed83c3 | ||
|
|
38dbad0f7e | ||
|
|
7fe5823140 | ||
|
|
091d510ba1 | ||
|
|
f07c8d66d8 | ||
|
|
c9f50cdc72 | ||
|
|
2ed9bc1880 | ||
|
|
d098ecae15 | ||
|
|
9e06ec815e | ||
|
|
bf6cf27f8c | ||
|
|
bf36c875d5 | ||
|
|
30b2df89cd | ||
|
|
ba2d653a30 | ||
|
|
d6eacd7364 | ||
|
|
07c75e627e | ||
|
|
e7bde9aa05 | ||
|
|
5ba708caff | ||
|
|
48e8f32e01 | ||
|
|
38e98df6cc | ||
|
|
0713e22328 | ||
|
|
3ed31a575f | ||
|
|
ac87e3c585 | ||
|
|
ef806cf18a | ||
|
|
d46c0fec76 | ||
|
|
1d42d3b431 | ||
|
|
2663eda820 | ||
|
|
16708aae7f | ||
|
|
6963637b1b | ||
|
|
57f55f1022 | ||
|
|
01476d3dc7 | ||
|
|
90d7cadc3f | ||
|
|
3a6f21dbd1 | ||
|
|
221fe69840 | ||
|
|
167f0b8377 | ||
|
|
df7d507e1b | ||
|
|
fe737602d8 | ||
|
|
daa147a721 | ||
|
|
bd555afe61 | ||
|
|
73a70d943e | ||
|
|
dde50bd0a1 | ||
|
|
0106dc3fe5 | ||
|
|
034856f033 | ||
|
|
f53c794fdd | ||
|
|
dcab6ee5a0 | ||
|
|
edc15ac982 | ||
|
|
871cd73592 | ||
|
|
958be3dee6 | ||
|
|
06f63dcb9a | ||
|
|
bebac8d8a7 | ||
|
|
a25602dbe0 | ||
|
|
98796cd6f8 | ||
|
|
2729d07732 | ||
|
|
61b15d623d | ||
|
|
03d6847be3 | ||
|
|
3b1fca01e3 | ||
|
|
be3792b93a | ||
|
|
7283e4ecb4 | ||
|
|
75c7d146af | ||
|
|
d988ab3326 | ||
|
|
798affe0ef | ||
|
|
93a1f9d1a6 | ||
|
|
3f58c6ee20 | ||
|
|
a20d1453e0 | ||
|
|
e825753487 | ||
|
|
cc58eaa50a | ||
|
|
75197a11a8 | ||
|
|
d03b47bee0 | ||
|
|
daf344b27f | ||
|
|
121bdc4af8 | ||
|
|
6656c7e441 | ||
|
|
f32493faaa | ||
|
|
4f4d376a4a | ||
|
|
4324e329e5 | ||
|
|
9eeac05fdf | ||
|
|
b418cf659c | ||
|
|
964368f5d4 | ||
|
|
23922f1c2b | ||
|
|
f02b1538e7 | ||
|
|
dd65e89f8f | ||
|
|
f132022e60 | ||
|
|
247f88254e | ||
|
|
81a31d6b42 | ||
|
|
89d1365fee | ||
|
|
9fb08bdc56 | ||
|
|
371a58f6f9 | ||
|
|
af8b748c59 | ||
|
|
a43e83d280 | ||
|
|
04b2059ca0 | ||
|
|
ed15925251 | ||
|
|
173b5e1718 | ||
|
|
26aa5e5f54 | ||
|
|
f31541efb2 | ||
|
|
db3ed87b92 | ||
|
|
fbaa5470fd | ||
|
|
0681171d69 | ||
|
|
e3d86be324 | ||
|
|
ef3de2c781 | ||
|
|
d574b536ba | ||
|
|
2d3ca8014b | ||
|
|
df13e279b6 | ||
|
|
b48310e6a5 | ||
|
|
b98fe1999c | ||
|
|
d47713516d | ||
|
|
d72bd9e00e | ||
|
|
8cffe8fa64 | ||
|
|
ae5dea4a53 | ||
|
|
588332a23b | ||
|
|
3eb99e952c | ||
|
|
9f4b6d73ce | ||
|
|
488159e2f9 | ||
|
|
f30316d868 | ||
|
|
daaf52d27d | ||
|
|
ba9980ba36 | ||
|
|
5531b1129b | ||
|
|
394a6673e5 | ||
|
|
62ee9d5b46 | ||
|
|
2592bcf042 | ||
|
|
dd7ed98c1a | ||
|
|
76fce016bb | ||
|
|
7db32a9922 | ||
|
|
fa5608d874 | ||
|
|
185febd8d8 | ||
|
|
7a5ea64ea0 | ||
|
|
a4106505af | ||
|
|
083845a502 | ||
|
|
c6de2a25aa | ||
|
|
4c5fb21c33 | ||
|
|
f566f6f0ef | ||
|
|
9afb28aaa1 | ||
|
|
bd178932c1 | ||
|
|
2894218573 | ||
|
|
63f0799161 | ||
|
|
5b67d668bd | ||
|
|
f7adea08a2 | ||
|
|
c0c8d5da58 | ||
|
|
2ed300ca08 | ||
|
|
52a915211e | ||
|
|
18f1dfac45 | ||
|
|
a7897e2347 | ||
|
|
e3f5dd3ca8 | ||
|
|
de03995e77 | ||
|
|
74d79e5c8e | ||
|
|
e2aff53291 | ||
|
|
a39bdee0d1 | ||
|
|
136293c308 | ||
|
|
356b051036 | ||
|
|
c8d5a07262 | ||
|
|
e94c23d538 | ||
|
|
2ba3030366 | ||
|
|
3455050876 | ||
|
|
cf57942a0b | ||
|
|
3a713c477a | ||
|
|
edb1b7e784 | ||
|
|
53e80b9283 | ||
|
|
7107314e2f | ||
|
|
fafec070c9 | ||
|
|
d7facd5732 | ||
|
|
382e46af63 | ||
|
|
d0a9c564e4 | ||
|
|
a9ceec3995 | ||
|
|
73d61ff655 | ||
|
|
5cdf14386d | ||
|
|
2f6a8564cb | ||
|
|
80a7ed503e | ||
|
|
6e65e60c3d | ||
|
|
f041f910e7 | ||
|
|
dbf253c9d1 | ||
|
|
24d65bd37f | ||
|
|
16cb70a952 | ||
|
|
8827ec3c09 | ||
|
|
7be0b8a256 | ||
|
|
f0f847b214 | ||
|
|
2017907b1f | ||
|
|
bb410fc3bc | ||
|
|
6e8d400611 | ||
|
|
7b420def17 | ||
|
|
737d7b5643 | ||
|
|
9c93f421ac | ||
|
|
1574b5b7cb | ||
|
|
932dda6552 | ||
|
|
6f2f61b30a | ||
|
|
20c9fc5f45 | ||
|
|
be3e3d773b | ||
|
|
0fd7f8d573 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,6 +21,7 @@ data/symbols/**/symbols.sdf
|
||||
|
||||
data/bookmarks
|
||||
data/edits.xml
|
||||
data/traffic.xml
|
||||
data/World.mwm
|
||||
data/WorldCoasts.mwm
|
||||
data/world_mwm/*
|
||||
|
||||
5
android/app/proguard-rules.pro
vendored
5
android/app/proguard-rules.pro
vendored
@@ -28,3 +28,8 @@
|
||||
# R8 crypts the source line numbers in all log messages.
|
||||
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
|
||||
-dontoptimize
|
||||
|
||||
# Keep classes for Android TraFF support
|
||||
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_7 { *; }
|
||||
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_8 { *; }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: 2025.03.02-7-FDroid+25030207
|
||||
version: 2025.07.23-4-FDroid+25072304
|
||||
|
||||
@@ -62,6 +62,21 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.GET_CAPABILITIES"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.POLL"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.SUBSCRIBE"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.SUBSCRIPTION_CHANGE"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.UNSUBSCRIBE"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<supports-screens
|
||||
|
||||
@@ -12,6 +12,7 @@ public class LayersUtils
|
||||
availableLayers.add(Mode.OUTDOORS);
|
||||
availableLayers.add(Mode.ISOLINES);
|
||||
availableLayers.add(Mode.SUBWAY);
|
||||
availableLayers.add(Mode.TRAFFIC);
|
||||
return availableLayers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,19 @@ package app.organicmaps.settings;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
@@ -28,6 +35,7 @@ import app.organicmaps.sdk.routing.RoutingOptions;
|
||||
import app.organicmaps.sdk.search.SearchRecents;
|
||||
import app.organicmaps.sdk.settings.MapLanguageCode;
|
||||
import app.organicmaps.sdk.settings.UnitLocale;
|
||||
import app.organicmaps.sdk.traffxml.AndroidTransport;
|
||||
import app.organicmaps.sdk.util.Config;
|
||||
import app.organicmaps.sdk.util.NetworkPolicy;
|
||||
import app.organicmaps.sdk.util.PowerManagment;
|
||||
@@ -35,11 +43,13 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
|
||||
import app.organicmaps.sdk.util.log.LogsManager;
|
||||
import app.organicmaps.util.ThemeSwitcher;
|
||||
import app.organicmaps.util.Utils;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
|
||||
{
|
||||
@@ -61,6 +71,10 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
initAutoDownloadPrefsCallbacks();
|
||||
initLargeFontSizePrefsCallbacks();
|
||||
initTransliterationPrefsCallbacks();
|
||||
initTrafficHttpEnabledPrefsCallbacks();
|
||||
initTrafficHttpUrlPrefsCallbacks();
|
||||
initTrafficAppsPrefs();
|
||||
initTrafficLegacyEnabledPrefsCallbacks();
|
||||
init3dModePrefsCallbacks();
|
||||
initPerspectivePrefsCallbacks();
|
||||
initAutoZoomPrefsCallbacks();
|
||||
@@ -136,6 +150,46 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
pref.setSummary(locale.getDisplayLanguage());
|
||||
}
|
||||
|
||||
private void updateTrafficHttpUrlSummary()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||
String summary = Config.getTrafficHttpUrl();
|
||||
if (summary.length() == 0)
|
||||
pref.setSummary(R.string.traffic_http_url_not_set);
|
||||
else
|
||||
pref.setSummary(summary);
|
||||
}
|
||||
|
||||
private void updateTrafficAppsSummary()
|
||||
{
|
||||
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||
/*
|
||||
* If the preference is disabled, it has not been initialized. This is the case if no TraFF
|
||||
* apps were found. The code below would crash when trying to access the entries, and there
|
||||
* is no need to update the summary if the setting cannot be changed.
|
||||
*/
|
||||
if (!pref.isEnabled())
|
||||
return;
|
||||
String[] apps = Config.getTrafficApps();
|
||||
if (apps.length == 0)
|
||||
pref.setSummary(R.string.traffic_apps_none_selected);
|
||||
else
|
||||
{
|
||||
String summary = "";
|
||||
for (int i = 0; i < apps.length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
summary = summary + ", ";
|
||||
int index = pref.findIndexOfValue(apps[i]);
|
||||
if (i >= 0)
|
||||
summary = summary + pref.getEntries()[index];
|
||||
else
|
||||
summary = summary + apps[i];
|
||||
}
|
||||
pref.setSummary(summary);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRoutingSettingsPrefsSummary()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.prefs_routing));
|
||||
@@ -163,6 +217,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
updateVoiceInstructionsPrefsSummary();
|
||||
updateRoutingSettingsPrefsSummary();
|
||||
updateMapLanguageCodeSummary();
|
||||
updateTrafficHttpUrlSummary();
|
||||
updateTrafficAppsSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -224,6 +280,91 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficHttpEnabledPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_enabled));
|
||||
|
||||
((TwoStatePreference)pref).setChecked(Config.getTrafficHttpEnabled());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final boolean oldVal = Config.getTrafficHttpEnabled();
|
||||
final boolean newVal = (Boolean) newValue;
|
||||
if (oldVal != newVal)
|
||||
Config.setTrafficHttpEnabled(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficHttpUrlPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||
|
||||
((EditTextPreference)pref).setText(Config.getTrafficHttpUrl());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final String oldVal = Config.getTrafficHttpUrl();
|
||||
final String newVal = (String) newValue;
|
||||
if (!oldVal.equals(newVal))
|
||||
Config.setTrafficHttpUrl(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficAppsPrefs()
|
||||
{
|
||||
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0);
|
||||
|
||||
if (receivers == null || receivers.isEmpty())
|
||||
{
|
||||
pref.setSummary(R.string.traffic_apps_not_available);
|
||||
pref.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
pref.setEnabled(true);
|
||||
|
||||
List<String> entryList = new ArrayList<>(receivers.size());
|
||||
List<String> valueList = new ArrayList<>(receivers.size());
|
||||
|
||||
for (ResolveInfo receiver : receivers)
|
||||
{
|
||||
// friendly name
|
||||
entryList.add(receiver.loadLabel(pm).toString());
|
||||
// actual value (we just need the package name, broadcasts are sent to any receiver in the package)
|
||||
valueList.add(receiver.activityInfo.applicationInfo.packageName);
|
||||
}
|
||||
|
||||
pref.setEntries(entryList.toArray(new CharSequence[0]));
|
||||
pref.setEntryValues(valueList.toArray(new CharSequence[0]));
|
||||
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// newValue is a Set<String>, each item is a package ID
|
||||
String[] apps = ((Set<String>)newValue).toArray(new String[0]);
|
||||
Config.setTrafficApps(apps);
|
||||
updateTrafficAppsSummary();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficLegacyEnabledPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_legacy_enabled));
|
||||
|
||||
((TwoStatePreference)pref).setChecked(Config.getTrafficLegacyEnabled());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final boolean oldVal = Config.getTrafficLegacyEnabled();
|
||||
final boolean newVal = (Boolean) newValue;
|
||||
if (oldVal != newVal)
|
||||
Config.setTrafficLegacyEnabled(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initUseMobileDataPrefsCallbacks()
|
||||
{
|
||||
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
|
||||
<string name="pref_navigation" translatable="false">Navigation</string>
|
||||
<string name="pref_information" translatable="false">Information</string>
|
||||
<string name="pref_traffic" translatable="false">Traffic</string>
|
||||
<string name="pref_traffic_http_enabled" translatable="false">TrafficHttpEnabled</string>
|
||||
<string name="pref_traffic_http_url" translatable="false">TrafficHttpUrl</string>
|
||||
<string name="pref_traffic_apps" translatable="false">TrafficApps</string>
|
||||
<string name="pref_traffic_legacy_enabled" translatable="false">TrafficLegacyEnabled</string>
|
||||
<string name="pref_transliteration" translatable="false">Transliteration</string>
|
||||
<string name="pref_power_management" translatable="false">PowerManagment</string>
|
||||
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
<!-- Settings information group in settings screen -->
|
||||
<string name="prefs_group_information">Information</string>
|
||||
<string name="prefs_group_route">Navigation</string>
|
||||
<string name="prefs_group_traffic">Traffic information</string>
|
||||
<string name="pref_zoom_title">Zoom buttons</string>
|
||||
<string name="pref_zoom_summary">Display on the map</string>
|
||||
<!-- Settings «Map» category: «Night style» title -->
|
||||
@@ -787,6 +788,24 @@
|
||||
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
|
||||
<!-- Current language of the map! -->
|
||||
<string name="change_map_locale">Map language</string>
|
||||
<!-- Enable live traffic data via HTTP (title) -->
|
||||
<string name="traffic_http_enabled">Enable live traffic data</string>
|
||||
<!-- Enable live traffic data via HTTP (description) -->
|
||||
<string name="traffic_http_enabled_description">When enabled, the app will periodically retrieve traffic information from the configured URL.</string>
|
||||
<!-- URL for live traffic data -->
|
||||
<string name="traffic_http_url">Traffic service URL</string>
|
||||
<!-- Status message indicating that user did not set a traffic URL yet. -->
|
||||
<string name="traffic_http_url_not_set">Not set</string>
|
||||
<!-- TraFF 0.8 apps from which to receive data (title) -->
|
||||
<string name="traffic_apps">Use data from TraFF applications</string>
|
||||
<!-- Status message indicating that no TraFF 0.8 apps are installed -->
|
||||
<string name="traffic_apps_not_available">No apps installed</string>
|
||||
<!-- Status message indicating that no TraFF 0.8 apps are currently selected -->
|
||||
<string name="traffic_apps_none_selected">No apps salected</string>
|
||||
<!-- Enable traffic data from TraFF 0.7 apps (title) -->
|
||||
<string name="traffic_legacy_enabled">Use data from legacy TraFF applications</string>
|
||||
<!-- Enable traffic data from TraFF 0.7 apps (description) -->
|
||||
<string name="traffic_legacy_enabled_description">When enabled, the app will receive and process traffic data from legacy TraFF applications.</string>
|
||||
<!-- OpenStreetMap text on splash screen -->
|
||||
<string name="splash_subtitle">Map data from OpenStreetMap</string>
|
||||
<!-- Telegram group url for the "?" About page -->
|
||||
|
||||
@@ -175,6 +175,36 @@
|
||||
</intent>
|
||||
</PreferenceScreen>
|
||||
</androidx.preference.PreferenceCategory>
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:key="@string/pref_traffic"
|
||||
android:title="@string/prefs_group_traffic"
|
||||
android:order="4">
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_traffic_http_enabled"
|
||||
android:title="@string/traffic_http_enabled"
|
||||
app:singleLineTitle="false"
|
||||
android:summary="@string/traffic_http_enabled_description"
|
||||
android:defaultValue="true"
|
||||
android:order="1"/>
|
||||
<EditTextPreference
|
||||
android:key="@string/pref_traffic_http_url"
|
||||
android:title="@string/traffic_http_url"
|
||||
app:singleLineTitle="false"
|
||||
android:order="2"/>
|
||||
<MultiSelectListPreference
|
||||
android:key="@string/pref_traffic_apps"
|
||||
android:title="@string/traffic_apps"
|
||||
app:singleLineTitle="false"
|
||||
android:order="3"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_traffic_legacy_enabled"
|
||||
android:title="@string/traffic_legacy_enabled"
|
||||
app:singleLineTitle="false"
|
||||
android:summary="@string/traffic_legacy_enabled_description"
|
||||
android:defaultValue="true"
|
||||
android:order="4"/>
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:key="@string/pref_privacy"
|
||||
android:title="@string/privacy"
|
||||
|
||||
@@ -17,6 +17,7 @@ set(SRC
|
||||
app/organicmaps/sdk/opengl/gl3stub.h
|
||||
app/organicmaps/sdk/platform/GuiThread.hpp
|
||||
app/organicmaps/sdk/platform/AndroidPlatform.hpp
|
||||
app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp
|
||||
app/organicmaps/sdk/util/Distance.hpp
|
||||
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
|
||||
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
|
||||
@@ -76,6 +77,8 @@ set(SRC
|
||||
app/organicmaps/sdk/platform/PThreadImpl.cpp
|
||||
app/organicmaps/sdk/platform/SecureStorage.cpp
|
||||
app/organicmaps/sdk/platform/SocketImpl.cpp
|
||||
app/organicmaps/sdk/traffxml/AndroidTraffSource.cpp
|
||||
app/organicmaps/sdk/traffxml/SourceImpl.cpp
|
||||
app/organicmaps/sdk/util/Config.cpp
|
||||
app/organicmaps/sdk/util/GeoUtils.cpp
|
||||
app/organicmaps/sdk/util/HttpClient.cpp
|
||||
@@ -127,6 +130,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||
# icu
|
||||
# agg
|
||||
# vulkan_wrapper
|
||||
traffxml
|
||||
|
||||
# Android libs
|
||||
log
|
||||
|
||||
@@ -182,6 +182,8 @@ public:
|
||||
void Set3dMode(bool allow3d, bool allow3dBuildings);
|
||||
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
|
||||
|
||||
TrafficManager & GetTrafficManager() { return m_work.GetTrafficManager(); }
|
||||
|
||||
void SetMapLanguageCode(std::string const & languageCode);
|
||||
std::string GetMapLanguageCode();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "app/organicmaps/sdk/Framework.hpp"
|
||||
|
||||
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
|
||||
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
|
||||
|
||||
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
||||
|
||||
@@ -34,6 +35,26 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_OrganicMaps_nativeInitFramework(
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
|
||||
env->CallVoidMethod(*onComplete, methodId);
|
||||
|
||||
ASSERT(g_framework, ("g_framework must be non-null"));
|
||||
|
||||
/*
|
||||
* Add traffic sources for Android.
|
||||
*/
|
||||
jclass configClass = env->FindClass("app/organicmaps/sdk/util/Config");
|
||||
jmethodID const getTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||
"getTrafficLegacyEnabled", "()Z");
|
||||
jmethodID const applyTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||
"applyTrafficLegacyEnabled", "(Z)V");
|
||||
jmethodID const getTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||
"getTrafficApps", "()[Ljava/lang/String;");
|
||||
jmethodID const applyTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||
"applyTrafficApps", "([Ljava/lang/String;)V");
|
||||
|
||||
env->CallStaticVoidMethod(configClass, applyTrafficLegacyEnabledId,
|
||||
env->CallStaticBooleanMethod(configClass, getTrafficLegacyEnabledId));
|
||||
env->CallStaticVoidMethod(configClass, applyTrafficAppsId,
|
||||
(jobjectArray)env->CallStaticObjectMethod(configClass, getTrafficAppsId));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -119,4 +119,74 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_util_Config_nativeSetTranslitera
|
||||
frm()->SaveTransliteration(value);
|
||||
frm()->AllowTransliteration(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz)
|
||||
{
|
||||
return frm()->LoadTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz,
|
||||
jboolean value)
|
||||
{
|
||||
frm()->SaveTrafficHttpEnabled(value);
|
||||
frm()->SetTrafficHttpEnabled(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz)
|
||||
{
|
||||
std::string value = frm()->LoadTrafficHttpUrl();
|
||||
return jni::ToJavaString(env, value);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz,
|
||||
jstring value)
|
||||
{
|
||||
frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||
frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_applyTrafficLegacyEnabled(JNIEnv * env, jclass thiz,
|
||||
jboolean value)
|
||||
{
|
||||
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||
if (traffxml::AndroidTraffSourceV0_7* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_7*>(source))
|
||||
{
|
||||
traffSource->Close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
if (value)
|
||||
traffxml::AndroidTraffSourceV0_7::Create(tm);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_applyTrafficApps(JNIEnv * env, jclass thiz, jobjectArray value)
|
||||
{
|
||||
jsize valueLen = env->GetArrayLength(value);
|
||||
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||
if (traffxml::AndroidTraffSourceV0_8* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_8*>(source))
|
||||
{
|
||||
traffSource->Close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
for (jsize i = 0; i < valueLen; i++)
|
||||
{
|
||||
jstring jAppId = (jstring)env->GetObjectArrayElement(value, i);
|
||||
std::string appId = jni::ToNativeString(env, jAppId);
|
||||
traffxml::AndroidTraffSourceV0_8::Create(tm, appId);
|
||||
env->DeleteLocalRef(jAppId);
|
||||
}
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright © 2017–2020 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright © 2019–2020 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];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 don’t recognize, or if we start keeping a persistent list of
|
||||
* subscriptions, we need to delete the subscription from our list. Until then, there is
|
||||
* nothing to do here: either the subscription isn’t in the list, or we are about to shut
|
||||
* down and the whole list is about to get discarded.
|
||||
*/
|
||||
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_HEARTBEAT)) {
|
||||
String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
|
||||
if (subscriptionId.equals(this.subscriptionId)) {
|
||||
Logger.d(this.getClass().getSimpleName(),
|
||||
String.format("got a heartbeat from %s for subscription %s; sending result",
|
||||
intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE), subscriptionId));
|
||||
this.setResult(AndroidTransport.RESULT_OK, null, null);
|
||||
}
|
||||
} // intent.getAction()
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches TraFF messages from a content provider.
|
||||
*
|
||||
* @param context The context to use for the content resolver
|
||||
* @param uri The content provider URI
|
||||
*/
|
||||
private void fetchMessages(Context context, Uri uri) {
|
||||
try {
|
||||
Cursor cursor = context.getContentResolver().query(uri, new String[] {AndroidTransport.COLUMN_DATA}, null, null, null);
|
||||
if (cursor == null)
|
||||
return;
|
||||
if (cursor.getCount() < 1) {
|
||||
cursor.close();
|
||||
return;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder("<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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright © 2019–2020 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;
|
||||
}
|
||||
@@ -70,6 +70,16 @@ public final class Config
|
||||
* True if the first start animation has been seen.
|
||||
*/
|
||||
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen";
|
||||
|
||||
/**
|
||||
* Whether feeds from legacy TraFF applications (TraFF 0.7, Android transport) are enabled.
|
||||
*/
|
||||
private static final String KEY_TRAFFIC_LEGACY_ENABLED = "TrafficLegacyEnabled";
|
||||
|
||||
/**
|
||||
* TraFF (0.8+) applications from which to request traffic data.
|
||||
*/
|
||||
private static final String KEY_TRAFFIC_APPS = "TrafficApps";
|
||||
|
||||
private Config() {}
|
||||
|
||||
@@ -388,6 +398,63 @@ public final class Config
|
||||
nativeSetTransliteration(value);
|
||||
}
|
||||
|
||||
public static boolean getTrafficHttpEnabled()
|
||||
{
|
||||
return nativeGetTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
public static void setTrafficHttpEnabled(boolean value)
|
||||
{
|
||||
nativeSetTrafficHttpEnabled(value);
|
||||
}
|
||||
|
||||
public static String getTrafficHttpUrl()
|
||||
{
|
||||
return nativeGetTrafficHttpUrl();
|
||||
}
|
||||
|
||||
public static void setTrafficHttpUrl(String value)
|
||||
{
|
||||
nativeSetTrafficHttpUrl(value);
|
||||
}
|
||||
|
||||
public static String[] getTrafficApps()
|
||||
{
|
||||
String appString = getString(KEY_TRAFFIC_APPS, "");
|
||||
if (appString.length() == 0)
|
||||
return new String[0];
|
||||
return appString.split(",");
|
||||
}
|
||||
|
||||
public static void setTrafficApps(String[] value)
|
||||
{
|
||||
String valueString = "";
|
||||
for (int i = 0; i < value.length; i++)
|
||||
{
|
||||
valueString = valueString + value[i];
|
||||
if ((i + 1) < value.length)
|
||||
valueString = valueString + ",";
|
||||
}
|
||||
setString(KEY_TRAFFIC_APPS, valueString);
|
||||
applyTrafficApps(value);
|
||||
}
|
||||
|
||||
public static boolean getTrafficLegacyEnabled()
|
||||
{
|
||||
return getBool(KEY_TRAFFIC_LEGACY_ENABLED, false);
|
||||
}
|
||||
|
||||
public static void setTrafficLegacyEnabled(boolean value)
|
||||
{
|
||||
setBool(KEY_TRAFFIC_LEGACY_ENABLED, value);
|
||||
applyTrafficLegacyEnabled(value);
|
||||
}
|
||||
|
||||
public static boolean isNY()
|
||||
{
|
||||
return getBool("NY");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getDonateUrl()
|
||||
{
|
||||
@@ -531,4 +598,10 @@ public final class Config
|
||||
private static native void nativeSetLargeFontsSize(boolean value);
|
||||
private static native boolean nativeGetTransliteration();
|
||||
private static native void nativeSetTransliteration(boolean value);
|
||||
private static native boolean nativeGetTrafficHttpEnabled();
|
||||
private static native void nativeSetTrafficHttpEnabled(boolean value);
|
||||
private static native String nativeGetTrafficHttpUrl();
|
||||
private static native void nativeSetTrafficHttpUrl(String value);
|
||||
private static native void applyTrafficApps(String[] value);
|
||||
private static native void applyTrafficLegacyEnabled(boolean value);
|
||||
}
|
||||
|
||||
17
data/test_data/traff/DE-A10-Werder-GrossKreutz.xml
Normal file
17
data/test_data/traff/DE-A10-Werder-GrossKreutz.xml
Normal 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>
|
||||
17
data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml
Normal file
17
data/test_data/traff/DE-A115-PotsdamDrewitz-Nuthetal.xml
Normal 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>
|
||||
@@ -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>
|
||||
19
data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml
Normal file
19
data/test_data/traff/DE-B2R-SendlingSued-Passauerstrasse.xml
Normal 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>
|
||||
22
data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml
Normal file
22
data/test_data/traff/DE-GM4-UrbanDualCarriagewayEndpoint.xml
Normal 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>
|
||||
58
data/test_data/traff/DE-RoundaboutEndpoint.xml
Executable file
58
data/test_data/traff/DE-RoundaboutEndpoint.xml
Executable 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>
|
||||
23
data/test_data/traff/LT-A5-Kaunas-Eastbound.xml
Normal file
23
data/test_data/traff/LT-A5-Kaunas-Eastbound.xml
Normal 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>
|
||||
@@ -108,8 +108,6 @@ auto constexpr TMP_OFFSETS_EXT = OFFSET_EXT EXTENSION_TMP;
|
||||
#define CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways"
|
||||
#define TEMP_ADDR_EXTENSION ".tempaddr"
|
||||
|
||||
#define TRAFFIC_FILE_EXTENSION ".traffic"
|
||||
|
||||
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
|
||||
|
||||
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"
|
||||
|
||||
@@ -207,8 +207,6 @@ set(SRC
|
||||
tesselator.hpp
|
||||
towns_dumper.cpp
|
||||
towns_dumper.hpp
|
||||
traffic_generator.cpp
|
||||
traffic_generator.hpp
|
||||
transit_generator.cpp
|
||||
transit_generator.hpp
|
||||
transit_generator_experimental.cpp
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "generator/routing_world_roads_generator.hpp"
|
||||
#include "generator/search_index_builder.hpp"
|
||||
#include "generator/statistics.hpp"
|
||||
#include "generator/traffic_generator.hpp"
|
||||
#include "generator/transit_generator.hpp"
|
||||
#include "generator/transit_generator_experimental.hpp"
|
||||
#include "generator/unpack_mwm.hpp"
|
||||
@@ -171,7 +170,6 @@ DEFINE_string(unpack_borders, "", "Convert packed_polygons to a directory of pol
|
||||
DEFINE_bool(unpack_mwm, false, "Unpack each section of mwm into a separate file with name filePath.sectionName.");
|
||||
DEFINE_bool(check_mwm, false, "Check map file to be correct.");
|
||||
DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container.");
|
||||
DEFINE_bool(generate_traffic_keys, false, "Generate keys for the traffic map (road segment -> speed group).");
|
||||
|
||||
DEFINE_bool(dump_mwm_tmp, false, "Prints feature builder objects from .mwm.tmp");
|
||||
|
||||
@@ -546,12 +544,6 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
|
||||
BuildPopularPlacesFromDescriptions(dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_generate_traffic_keys)
|
||||
{
|
||||
if (!traffic::GenerateTrafficKeysFromDataFile(dataFile))
|
||||
LOG(LCRITICAL, ("Error generating traffic keys."));
|
||||
}
|
||||
}
|
||||
|
||||
string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION);
|
||||
|
||||
@@ -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
|
||||
@@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace traffic
|
||||
{
|
||||
bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath);
|
||||
} // namespace traffic
|
||||
@@ -1,6 +1,12 @@
|
||||
NS_SWIFT_NAME(SettingsBridge)
|
||||
@interface MWMSettings : NSObject
|
||||
|
||||
+ (BOOL)liveTrafficEnabled;
|
||||
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||
|
||||
+ (NSURL *)liveTrafficUrl;
|
||||
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||
|
||||
+ (BOOL)buildings3dViewEnabled;
|
||||
+ (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled;
|
||||
|
||||
|
||||
@@ -27,6 +27,40 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
|
||||
|
||||
@implementation MWMSettings
|
||||
|
||||
+ (BOOL)liveTrafficEnabled;
|
||||
{
|
||||
return GetFramework().LoadTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||
{
|
||||
auto &f = GetFramework();
|
||||
f.SaveTrafficHttpEnabled(liveTrafficEnabled);
|
||||
f.SetTrafficHttpEnabled(liveTrafficEnabled);
|
||||
}
|
||||
|
||||
+ (NSURL *)liveTrafficUrl;
|
||||
{
|
||||
NSString * link = @(GetFramework().LoadTrafficHttpUrl().c_str());
|
||||
if ([link length] == 0) {
|
||||
return nil;
|
||||
} else {
|
||||
return [NSURL URLWithString:link];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||
{
|
||||
auto &f = GetFramework();
|
||||
if (liveTrafficUrl == nil) {
|
||||
f.SaveTrafficHttpUrl(@"".UTF8String);
|
||||
f.SetTrafficHttpUrl(@"".UTF8String);
|
||||
} else {
|
||||
f.SaveTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||
f.SetTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)buildings3dViewEnabled;
|
||||
{
|
||||
bool _ = true, on = true;
|
||||
|
||||
@@ -717,6 +717,13 @@
|
||||
"editor_place_doesnt_exist" = "Place does not exist";
|
||||
"text_more_button" = "…more";
|
||||
|
||||
/* Live traffic data */
|
||||
"traffic_http" = "Live Traffic";
|
||||
"traffic_http_enabled" = "Enable live traffic data";
|
||||
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||
"traffic_http_url" = "Traffic service URL";
|
||||
"traffic_http_url_not_set" = "Not set";
|
||||
|
||||
/* Phone number error message */
|
||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||
"error_enter_correct_web" = "Enter a valid web address";
|
||||
@@ -816,6 +823,7 @@
|
||||
"privacy_policy" = "Privacy policy";
|
||||
"terms_of_use" = "Terms of use";
|
||||
"button_layer_subway" = "Metro";
|
||||
"button_layer_traffic" = "Traffic";
|
||||
"layers_title" = "Map Styles and Layers";
|
||||
"subway_data_unavailable" = "Metro map is unavailable";
|
||||
"title_error_downloading_bookmarks" = "An error occurred";
|
||||
|
||||
@@ -737,6 +737,13 @@
|
||||
"editor_place_doesnt_exist_description" = "Describe what the place looks like now to send an error note to the OpenStreetMap community";
|
||||
"text_more_button" = "…more";
|
||||
|
||||
/* Live traffic data */
|
||||
"traffic_http" = "Live Traffic";
|
||||
"traffic_http_enabled" = "Enable live traffic data";
|
||||
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||
"traffic_http_url" = "Traffic service URL";
|
||||
"traffic_http_url_not_set" = "Not set";
|
||||
|
||||
/* Phone number error message */
|
||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||
"error_enter_correct_web" = "Enter a valid web address";
|
||||
@@ -837,6 +844,7 @@
|
||||
"privacy_policy" = "Privacy policy";
|
||||
"terms_of_use" = "Terms of use";
|
||||
"button_layer_subway" = "Subway";
|
||||
"button_layer_traffic" = "Traffic";
|
||||
"layers_title" = "Map Styles and Layers";
|
||||
"subway_data_unavailable" = "Subway map is unavailable";
|
||||
"title_error_downloading_bookmarks" = "An error occurred";
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; };
|
||||
272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; };
|
||||
272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.swift */; };
|
||||
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 274720592E439FBA00C516DF /* libtraffxml.a */; };
|
||||
2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
|
||||
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
|
||||
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; };
|
||||
@@ -767,6 +768,7 @@
|
||||
272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = "<group>"; };
|
||||
272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
274720592E439FBA00C516DF /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2752B6C92E31197000887CC4 /* MapLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLanguage.swift; sourceTree = "<group>"; };
|
||||
2752B6CD2E3121D800887CC4 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = "<group>"; };
|
||||
2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
|
||||
@@ -1792,6 +1794,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */,
|
||||
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
|
||||
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
|
||||
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
|
||||
@@ -2029,6 +2032,7 @@
|
||||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
274720592E439FBA00C516DF /* libtraffxml.a */,
|
||||
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
|
||||
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
|
||||
FA853BF226BC5DE50026D455 /* libshaders.a */,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
updateOutdoorButton()
|
||||
}
|
||||
}
|
||||
@IBOutlet private var trafficButton: BottomMenuLayerButton! {
|
||||
didSet {
|
||||
updateTrafficButton()
|
||||
}
|
||||
}
|
||||
|
||||
var onClose: (()->())?
|
||||
|
||||
@@ -32,6 +37,7 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
|
||||
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
|
||||
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
|
||||
trafficButton.setupWith(image: UIImage(resource: .btnMenuTraffic), text: L("button_layer_traffic"))
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -56,6 +62,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
let enabled = MapOverlayManager.outdoorEnabled()
|
||||
outdoorButton.setStyleAndApply(styleFor(enabled))
|
||||
}
|
||||
|
||||
private func updateTrafficButton() {
|
||||
let enabled = MapOverlayManager.trafficEnabled()
|
||||
trafficButton.setStyleAndApply(styleFor(enabled))
|
||||
}
|
||||
|
||||
@IBAction func onCloseButtonPressed(_ sender: Any) {
|
||||
onClose?()
|
||||
@@ -75,6 +86,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
let enable = !MapOverlayManager.outdoorEnabled()
|
||||
MapOverlayManager.setOutdoorEnabled(enable)
|
||||
}
|
||||
|
||||
@IBAction func onTrafficButton(_ sender: Any) {
|
||||
let enable = !MapOverlayManager.trafficEnabled()
|
||||
MapOverlayManager.setTrafficEnabled(enable)
|
||||
}
|
||||
}
|
||||
|
||||
extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
||||
@@ -89,6 +105,10 @@ extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
||||
func onOutdoorStateUpdated() {
|
||||
updateOutdoorButton()
|
||||
}
|
||||
|
||||
func onTrafficStateUpdated() {
|
||||
updateTrafficButton()
|
||||
}
|
||||
}
|
||||
|
||||
private extension BottomMenuLayersCell {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -81,26 +81,35 @@
|
||||
<rect key="frame" x="16" y="58" width="308" height="64"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
|
||||
<rect key="frame" x="1024" y="0.0" width="1024" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
|
||||
<rect key="frame" x="2048" y="0.0" width="1024" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" id="95L-lU-yCQ" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="-358" y="-352" width="1024" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="ZQs-ua-gDZ"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onTrafficButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="tie-i7-JaB"/>
|
||||
</connections>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
|
||||
@@ -144,6 +153,7 @@
|
||||
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
|
||||
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
|
||||
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
|
||||
<outlet property="trafficButton" destination="95L-lU-yCQ" id="O9W-En-8Rc"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
|
||||
</tableViewCell>
|
||||
|
||||
@@ -60,6 +60,14 @@ struct SettingsNavigationView: View {
|
||||
@State var forceRefreshDate: Date = Date.now
|
||||
|
||||
|
||||
/// If live traffic data should be used
|
||||
@State var hasLiveTraffic: Bool = false
|
||||
|
||||
|
||||
/// The url of the live traffic data server
|
||||
@State var liveTrafficServerUrlString: String = ""
|
||||
|
||||
|
||||
/// The actual view
|
||||
var body: some View {
|
||||
List {
|
||||
@@ -212,6 +220,24 @@ struct SettingsNavigationView: View {
|
||||
} header: {
|
||||
Text("driving_options_title")
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $hasLiveTraffic) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("traffic_http_enabled")
|
||||
|
||||
Text("traffic_http_enabled_description")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.tint(.accent)
|
||||
|
||||
TextField("traffic_http_url", text: $liveTrafficServerUrlString, prompt: Text("traffic_http_url_not_set"))
|
||||
.tint(.accent)
|
||||
} header: {
|
||||
Text("traffic_http")
|
||||
}
|
||||
}
|
||||
.accentColor(.accent)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
@@ -229,6 +255,8 @@ struct SettingsNavigationView: View {
|
||||
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
|
||||
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
|
||||
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
|
||||
hasLiveTraffic = Settings.hasLiveTraffic
|
||||
liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? ""
|
||||
}
|
||||
.onChange(of: scenePhase) { _ in
|
||||
forceRefreshDate = Date.now
|
||||
@@ -276,6 +304,15 @@ struct SettingsNavigationView: View {
|
||||
}
|
||||
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
|
||||
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
|
||||
.onChange(of: hasLiveTraffic) { changedHasLiveTraffic in
|
||||
Settings.hasLiveTraffic = changedHasLiveTraffic
|
||||
}
|
||||
.onChange(of: liveTrafficServerUrlString) { changedLiveTrafficServerUrlString in
|
||||
if !changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, let changedLiveTrafficServerUrl = URL(string: changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||
Settings.liveTrafficServerUrl = changedLiveTrafficServerUrl
|
||||
} else {
|
||||
Settings.liveTrafficServerUrl = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,5 @@ add_subdirectory(shaders)
|
||||
add_subdirectory(storage)
|
||||
add_subdirectory(tracking)
|
||||
add_subdirectory(traffic)
|
||||
add_subdirectory(traffxml)
|
||||
add_subdirectory(transit)
|
||||
|
||||
@@ -38,9 +38,9 @@ namespace
|
||||
{
|
||||
// The first zoom level in kAverageSegmentsCount.
|
||||
int constexpr kFirstZoomInAverageSegments = 10;
|
||||
std::array<size_t, 10> constexpr kAverageSegmentsCount = {
|
||||
// 10 11 12 13 14 15 16 17 18 19
|
||||
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500};
|
||||
std::array<size_t, 11> constexpr kAverageSegmentsCount = {
|
||||
// 10 11 12 13 14 15 16 17 18 19 20
|
||||
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500, 500};
|
||||
|
||||
double constexpr kMetersPerLevel = 3.0;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
@@ -9,17 +10,20 @@
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// This class holds a parametrization of the
|
||||
// line segment between two points p0 and p1.
|
||||
// The parametrization is of the form
|
||||
// p(t) = p0 + t * dir.
|
||||
// Other conditions:
|
||||
// dir is the normalized (p1 - p0) vector.
|
||||
// length(dir) = 1.
|
||||
// p(0) = p0.
|
||||
// p(T) = p1 with T = length(p1 - p0).
|
||||
//
|
||||
// The points with t in [0, T] are the points of the segment.
|
||||
/**
|
||||
* @brief This class holds a parametrization of the line segment between two points `p0` and `p1`.
|
||||
*
|
||||
* The parametrization is of the form
|
||||
* `p(t) = p0 + t * dir`.
|
||||
*
|
||||
* Other conditions:
|
||||
* * `dir` is the normalized `(p1 - p0)` vector.
|
||||
* * `length(dir) = 1`.
|
||||
* * `p(0) = p0`.
|
||||
* * `p(T) = p1` with `T = length(p1 - p0)`.
|
||||
*
|
||||
* The points with `t` in `[0, T]` are the points of the segment.
|
||||
*/
|
||||
template <typename Point>
|
||||
class ParametrizedSegment
|
||||
{
|
||||
@@ -36,7 +40,9 @@ public:
|
||||
m_d = m_d / m_length;
|
||||
}
|
||||
|
||||
// Returns the squared (euclidean) distance from the segment to |p|.
|
||||
/**
|
||||
* @brief Returns the squared (euclidean) distance from the segment to `p`.
|
||||
*/
|
||||
double SquaredDistanceToPoint(Point const & p) const
|
||||
{
|
||||
m2::PointD const diff(p - m_p0);
|
||||
@@ -52,9 +58,22 @@ public:
|
||||
return math::Pow2(CrossProduct(diff, m_d));
|
||||
}
|
||||
|
||||
// Returns the point of the segment that is closest to |p|.
|
||||
m2::PointD ClosestPointTo(Point const & p) const
|
||||
/**
|
||||
* @brief Returns the point of the segment that is closest to `p`.
|
||||
*
|
||||
* @param p The checkpoint
|
||||
* @param snapToEnds If true, the result is the endpoint of the segment which is closest to `p`
|
||||
*/
|
||||
m2::PointD ClosestPointTo(Point const & p, bool snapToEnds = false) const
|
||||
{
|
||||
if (snapToEnds)
|
||||
{
|
||||
if (mercator::DistanceOnEarth(p, m_p0) < mercator::DistanceOnEarth(p, m_p1))
|
||||
return m_p0;
|
||||
else
|
||||
return m_p1;
|
||||
}
|
||||
|
||||
m2::PointD const diff(p - m_p0);
|
||||
double const t = DotProduct(m_d, diff);
|
||||
|
||||
|
||||
@@ -19,20 +19,31 @@ public:
|
||||
/// Registers a new map.
|
||||
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
||||
|
||||
/// Deregisters a map from internal records.
|
||||
///
|
||||
/// \param countryFile A countryFile denoting a map to be deregistered.
|
||||
/// \return True if the map was successfully deregistered. If map is locked
|
||||
/// now, returns false.
|
||||
/**
|
||||
* @brief Deregisters a map from internal records.
|
||||
* @param countryFile A `CountryFile` denoting a map to be deregistered.
|
||||
* @return True if the map was successfully deregistered, false if the map is locked now.
|
||||
*/
|
||||
bool DeregisterMap(platform::CountryFile const & countryFile);
|
||||
|
||||
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
|
||||
covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
|
||||
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const;
|
||||
// Calls |f| for features closest to |center| until |stopCallback| returns true or distance
|
||||
// |sizeM| from has been reached. Then for EditableDataSource calls |f| for each edited feature
|
||||
// inside square with center |center| and side |2 * sizeM|. Edited features are not in the same
|
||||
// hierarchy and there is no fast way to merge frozen and edited features.
|
||||
|
||||
/**
|
||||
* @brief Iterates over features within a given distance of a center point.
|
||||
*
|
||||
* Calls `f` for features closest to `center` until `stopCallback` returns true or distance
|
||||
* `sizeM` from has been reached. Then for EditableDataSource calls `f` for each edited feature
|
||||
* inside square with center `center` and side `2 * sizeM`. Edited features are not in the same
|
||||
* hierarchy and there is no fast way to merge frozen and edited features.
|
||||
*
|
||||
* @brief f Callback function that is called on each feature.
|
||||
* @brief stopCallback Callback function which decides whether to continue searching or stop.
|
||||
* @brief center The center of the search area.
|
||||
* @brief sizeM The size of the search area, as a distance from the center point.
|
||||
* @brief scale
|
||||
*/
|
||||
void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
|
||||
double sizeM, int scale) const;
|
||||
void ForEachInScale(FeatureCallback const & f, int scale) const;
|
||||
@@ -66,18 +77,24 @@ private:
|
||||
std::unique_ptr<FeatureSourceFactory> m_factory;
|
||||
};
|
||||
|
||||
// DataSource which operates with features from mwm file and does not support features creation
|
||||
// deletion or modification.
|
||||
/**
|
||||
* @brief A `DataSource` which operates with features from an MWM file and does not support
|
||||
* creation, deletion or modification of features.
|
||||
*/
|
||||
class FrozenDataSource : public DataSource
|
||||
{
|
||||
public:
|
||||
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {}
|
||||
};
|
||||
|
||||
/// Guard for loading features from particular MWM by demand.
|
||||
/// @note If you need to work with FeatureType from different threads you need to use
|
||||
/// a unique FeaturesLoaderGuard instance for every thread.
|
||||
/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest.
|
||||
/**
|
||||
* @brief Guard for loading features from particular MWM by demand.
|
||||
*
|
||||
* @note If you need to work with `FeatureType` from different threads, you need to use
|
||||
* a unique `FeaturesLoaderGuard` instance for every thread.
|
||||
* For an example of concurrent extracting feature details please see `ConcurrentFeatureParsingTest`
|
||||
* in `routing/routing_integration_tests`.
|
||||
*/
|
||||
class FeaturesLoaderGuard
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -76,6 +76,18 @@ public:
|
||||
// (number of points in inner triangle-strips).
|
||||
using PointsBufferT = buffer_vector<m2::PointD, 32>;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the points of the feature.
|
||||
*
|
||||
* Depending on `scale`, the geometry may be simplified by reducing groups of nearby points to
|
||||
* one point. If `scale` equals `FeatureType::BEST_GEOMETRY`, no such simplification takes place.
|
||||
*
|
||||
* Points are cached between calls and `scale` may not be honored if cached points are returned.
|
||||
* To reliably enforce `scale`, call `ResetGemoetry()` immediately prior to `GetPoints()`.
|
||||
*
|
||||
* @param scale The map scale
|
||||
* @return The points of the feature, simplified according to `scale`.
|
||||
*/
|
||||
PointsBufferT const & GetPoints(int scale);
|
||||
PointsBufferT const & GetTrianglesAsPoints(int scale);
|
||||
|
||||
@@ -85,6 +97,13 @@ public:
|
||||
void ParseHeader2();
|
||||
void ParseRelations();
|
||||
void ParseAllBeforeGeometry() { ParseRelations(); }
|
||||
|
||||
/**
|
||||
* @brief Resets the geometry.
|
||||
*
|
||||
* This discards any cached points, resulting in points being re-fetched the next time
|
||||
* `GetPoints()` or `GetTrianglesAsPoints()` is called.
|
||||
*/
|
||||
void ResetGeometry();
|
||||
void ParseGeometry(int scale);
|
||||
void ParseTriangles(int scale);
|
||||
|
||||
@@ -701,24 +701,46 @@ double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType);
|
||||
uint64_t GetPopulationByRadius(double r);
|
||||
//@}
|
||||
|
||||
// Highway class. The order is important.
|
||||
// The enum values follow from the biggest roads (Trunk) to the smallest ones (Service).
|
||||
/**
|
||||
* @brief Highway class.
|
||||
*
|
||||
* The order is important. The enum values follow from the biggest roads (Trunk) to the smallest ones (Service).
|
||||
*/
|
||||
enum class HighwayClass
|
||||
{
|
||||
Undefined = 0, // There has not been any attempt of calculating HighwayClass.
|
||||
/**
|
||||
* Used when there has not been any attempt of calculating HighwayClass.
|
||||
*/
|
||||
Undefined = 0,
|
||||
Motorway,
|
||||
Trunk,
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
/**
|
||||
* Unclassified, residential, living street and `highway=road`.
|
||||
*/
|
||||
LivingStreet,
|
||||
/**
|
||||
* Service, track, busway and `man_made=pier`.
|
||||
*/
|
||||
Service,
|
||||
// OSM highway=service type is widely used even for _significant_ roads.
|
||||
// Adding a new type to distinguish mapped driveway or parking_aisle.
|
||||
ServiceMinor,
|
||||
/**
|
||||
* Anything not intended for motorized traffic: pedestrian, footway, bridleway, steps, cycleway,
|
||||
* path and also `highway=construction`.
|
||||
*/
|
||||
Pedestrian,
|
||||
Transported, // Vehicles are transported by train or ferry.
|
||||
Count // This value is used for internals only.
|
||||
/**
|
||||
* Vehicles are transported by train or ferry.
|
||||
*/
|
||||
Transported,
|
||||
/**
|
||||
* This value is used for internals only.
|
||||
*/
|
||||
Count
|
||||
};
|
||||
|
||||
std::string DebugPrint(HighwayClass const cls);
|
||||
|
||||
@@ -157,8 +157,10 @@ public:
|
||||
explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {}
|
||||
virtual ~MwmSet() = default;
|
||||
|
||||
// Mwm handle, which is used to refer to mwm and prevent it from
|
||||
// deletion when its FileContainer is used.
|
||||
/**
|
||||
* @brief Mwm handle, which is used to refer to mwm and prevent it from deletion when its
|
||||
* FileContainer is used.
|
||||
*/
|
||||
class MwmHandle
|
||||
{
|
||||
public:
|
||||
@@ -243,19 +245,26 @@ public:
|
||||
BadFile
|
||||
};
|
||||
|
||||
// An Observer interface to MwmSet. Note that these functions can
|
||||
// be called from *ANY* thread because most signals are sent when
|
||||
// some thread releases its MwmHandle, so overrides must be as fast
|
||||
// as possible and non-blocking when it's possible.
|
||||
/**
|
||||
* @brief An Observer interface to `MwmSet`.
|
||||
*
|
||||
* Note that these functions can be called from *ANY* thread because most signals are sent when
|
||||
* some thread releases its MwmHandle, so overrides must be as fast as possible and non-blocking
|
||||
* when it's possible.
|
||||
*/
|
||||
class Observer
|
||||
{
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
|
||||
// Called when a map is registered for the first time and can be used.
|
||||
/**
|
||||
* @brief Called when a map is registered for the first time and can be used.
|
||||
*/
|
||||
virtual void OnMapRegistered(platform::LocalCountryFile const & /* localFile */) {}
|
||||
|
||||
// Called when a map is deregistered and can no longer be used.
|
||||
/**
|
||||
* @brief Called when a map is deregistered and can no longer be used.
|
||||
*/
|
||||
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {}
|
||||
};
|
||||
|
||||
@@ -304,7 +313,14 @@ public:
|
||||
/// @todo In fact, std::shared_ptr<MwmInfo> is a MwmId. Seems like better to make vector<MwmId> interface.
|
||||
void GetMwmsInfo(std::vector<std::shared_ptr<MwmInfo>> & info) const;
|
||||
|
||||
// Clears caches and mwm's registry. All known mwms won't be marked as DEREGISTERED.
|
||||
/**
|
||||
* @brief Clears caches and mwm's registry.
|
||||
*
|
||||
* All known mwms won't be marked as DEREGISTERED.
|
||||
*
|
||||
* @todo what does “all won’t be marked” mean? Not all will be marked/some might not be marked?
|
||||
* Or all will be unmarked?
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
void ClearCache();
|
||||
@@ -345,10 +361,18 @@ private:
|
||||
ProcessEventList(events);
|
||||
}
|
||||
|
||||
// Sets |status| in |info|, adds corresponding event to |event|.
|
||||
/**
|
||||
* @brief Sets `status` in `info`, adds corresponding event to `event`.
|
||||
* @param info
|
||||
* @param status
|
||||
* @param events
|
||||
*/
|
||||
void SetStatus(MwmInfo & info, MwmInfo::Status status, EventList & events);
|
||||
|
||||
// Triggers observers on each event in |events|.
|
||||
/**
|
||||
* @brief Triggers observers on each event in `events`.
|
||||
* @param events
|
||||
*/
|
||||
void ProcessEventList(EventList & events);
|
||||
|
||||
/// @precondition This function is always called under mutex m_lock.
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
#include "ge0/url_generator.hpp"
|
||||
|
||||
#include "routing/index_router.hpp"
|
||||
#include "routing/route.hpp"
|
||||
#include "routing/routing_helpers.hpp"
|
||||
#include "routing/speed_camera_prohibition.hpp"
|
||||
|
||||
#include "routing_common/num_mwm_id.hpp"
|
||||
@@ -17,13 +19,18 @@
|
||||
#include "search/locality_finder.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
#include "storage/routing_helpers.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
#include "storage/storage_helpers.hpp"
|
||||
|
||||
#include "traffxml/traff_source.hpp"
|
||||
|
||||
#include "drape_frontend/color_constants.hpp"
|
||||
#include "drape_frontend/gps_track_point.hpp"
|
||||
#include "drape_frontend/visual_params.hpp"
|
||||
|
||||
#include "editor/editable_data_source.hpp"
|
||||
|
||||
#include "descriptions/loader.hpp"
|
||||
|
||||
#include "indexer/categories_holder.hpp"
|
||||
@@ -39,6 +46,7 @@
|
||||
#include "indexer/scales.hpp"
|
||||
#include "indexer/transliteration_loader.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/localization.hpp"
|
||||
#include "platform/measurement_utils.hpp"
|
||||
#include "platform/mwm_version.hpp"
|
||||
@@ -61,11 +69,13 @@
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "std/target_os.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
#include "private.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -93,6 +103,8 @@ std::string_view constexpr kAllow3dKey = "Allow3d";
|
||||
std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d";
|
||||
std::string_view constexpr kAllowAutoZoom = "AutoZoom";
|
||||
std::string_view constexpr kTrafficEnabledKey = "TrafficEnabled";
|
||||
std::string_view constexpr kTrafficHttpEnabledKey = "TrafficHttpEnabled";
|
||||
std::string_view constexpr kTrafficHttpUrlKey = "TrafficHttpUrl";
|
||||
std::string_view constexpr kTransitSchemeEnabledKey = "TransitSchemeEnabled";
|
||||
std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled";
|
||||
std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled";
|
||||
@@ -273,8 +285,11 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
|
||||
[this]() -> StringsBundle const &
|
||||
{ return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }),
|
||||
static_cast<RoutingManager::Delegate &>(*this))
|
||||
, m_trafficManager(bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes,
|
||||
m_routingManager.RoutingSession())
|
||||
, m_trafficManager(m_featuresFetcher.GetDataSource(),
|
||||
[this]() -> storage::CountryInfoGetter const & { return GetCountryInfoGetter(); },
|
||||
[this](string const & id) -> string { return m_storage.GetParentIdFor(id); },
|
||||
bind(&Framework::GetMwmsByRect, this, _1, false /* rough */),
|
||||
kMaxTrafficCacheSizeBytes, m_routingManager.RoutingSession())
|
||||
, m_lastReportedCountry(kInvalidCountryId)
|
||||
, m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG)
|
||||
, m_descriptionsLoader(std::make_unique<descriptions::Loader>(m_featuresFetcher.GetDataSource()))
|
||||
@@ -348,10 +363,10 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
|
||||
editor.SetDelegate(make_unique<search::EditorDelegate>(m_featuresFetcher.GetDataSource()));
|
||||
editor.SetInvalidateFn([this]() { InvalidateRect(GetCurrentViewport()); });
|
||||
|
||||
/// @todo Uncomment when we will integrate a traffic provider.
|
||||
// m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
|
||||
// m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
|
||||
// m_trafficManager.SetEnabled(LoadTrafficEnabled());
|
||||
if (params.m_trafficTestMode)
|
||||
m_trafficManager.SetTestMode();
|
||||
m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
|
||||
m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
|
||||
|
||||
m_isolinesManager.SetEnabled(LoadIsolinesEnabled());
|
||||
|
||||
@@ -383,6 +398,19 @@ Framework::~Framework()
|
||||
m_featuresFetcher.SetOnMapDeregisteredCallback(nullptr);
|
||||
}
|
||||
|
||||
void Framework::InitializeTraffic()
|
||||
{
|
||||
m_trafficManager.SetEnabled(LoadTrafficEnabled());
|
||||
if (!m_trafficManager.IsTestMode() && LoadTrafficHttpEnabled())
|
||||
// TODO handle invalid URLs
|
||||
traffxml::HttpTraffSource::Create(m_trafficManager, LoadTrafficHttpUrl());
|
||||
|
||||
/*
|
||||
* MockTraffSource for debugging purposes.
|
||||
*/
|
||||
//traffxml::MockTraffSource::Create(m_trafficManager);
|
||||
}
|
||||
|
||||
void Framework::ShowNode(storage::CountryId const & countryId)
|
||||
{
|
||||
StopLocationFollow();
|
||||
@@ -398,14 +426,14 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, storage::Loc
|
||||
m2::RectD rect = mercator::Bounds::FullRect();
|
||||
|
||||
if (localFile && localFile->OnDisk(MapFileType::Map))
|
||||
{
|
||||
auto const res = RegisterMap(*localFile);
|
||||
MwmSet::MwmId const & id = res.first;
|
||||
if (id.IsAlive())
|
||||
rect = id.GetInfo()->m_bordersRect;
|
||||
}
|
||||
m_trafficManager.RunSynchronized([this, localFile, &rect](){
|
||||
auto const res = RegisterMap(*localFile);
|
||||
MwmSet::MwmId const & id = res.first;
|
||||
if (id.IsAlive())
|
||||
rect = id.GetInfo()->m_bordersRect;
|
||||
m_trafficManager.Invalidate(id);
|
||||
});
|
||||
|
||||
m_trafficManager.Invalidate();
|
||||
m_transitManager.Invalidate();
|
||||
m_isolinesManager.Invalidate();
|
||||
|
||||
@@ -487,6 +515,8 @@ void Framework::LoadMapsSync()
|
||||
LOG(LDEBUG, ("Editor initialized"));
|
||||
|
||||
GetStorage().RestoreDownloadQueue();
|
||||
|
||||
InitializeTraffic();
|
||||
}
|
||||
|
||||
// Small copy-paste with LoadMapsSync, but I don't have a better solution.
|
||||
@@ -509,6 +539,8 @@ void Framework::LoadMapsAsync(std::function<void()> && callback)
|
||||
|
||||
GetStorage().RestoreDownloadQueue();
|
||||
|
||||
InitializeTraffic();
|
||||
|
||||
callback();
|
||||
});
|
||||
}).detach();
|
||||
@@ -2475,6 +2507,42 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled)
|
||||
settings::Set(kTrafficEnabledKey, trafficEnabled);
|
||||
}
|
||||
|
||||
void Framework::SetTrafficHttpEnabled(bool enabled)
|
||||
{
|
||||
m_trafficManager.SetHttpTraffSource(enabled, LoadTrafficHttpUrl());
|
||||
}
|
||||
|
||||
bool Framework::LoadTrafficHttpEnabled()
|
||||
{
|
||||
bool enabled;
|
||||
if (!settings::Get(kTrafficHttpEnabledKey, enabled))
|
||||
enabled = false;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void Framework::SaveTrafficHttpEnabled(bool trafficHttpEnabled)
|
||||
{
|
||||
settings::Set(kTrafficHttpEnabledKey, trafficHttpEnabled);
|
||||
}
|
||||
|
||||
void Framework::SetTrafficHttpUrl(std::string url)
|
||||
{
|
||||
m_trafficManager.SetHttpTraffSource(LoadTrafficHttpEnabled(), url);
|
||||
}
|
||||
|
||||
std::string Framework::LoadTrafficHttpUrl()
|
||||
{
|
||||
std::string url;
|
||||
if (!settings::Get(kTrafficHttpUrlKey, url))
|
||||
url = TRAFFIC_HTTP_URL_DEFAULT;
|
||||
return url;
|
||||
}
|
||||
|
||||
void Framework::SaveTrafficHttpUrl(std::string trafficHttpUrl)
|
||||
{
|
||||
settings::Set(kTrafficHttpUrlKey, trafficHttpUrl);
|
||||
}
|
||||
|
||||
bool Framework::LoadTrafficSimplifiedColors()
|
||||
{
|
||||
bool simplified;
|
||||
|
||||
@@ -103,11 +103,22 @@ class Loader;
|
||||
/// build version for screenshots.
|
||||
// #define FIXED_LOCATION
|
||||
|
||||
/**
|
||||
* @brief Initialization parameters for the framework.
|
||||
*
|
||||
* `FrameworkParams` is intended for parameters which are hardcoded rather than read from a
|
||||
* configuration. It allows test cases to run on a tailored configuration.
|
||||
*/
|
||||
struct FrameworkParams
|
||||
{
|
||||
bool m_enableDiffs = true;
|
||||
size_t m_numSearchAPIThreads = 1;
|
||||
|
||||
/**
|
||||
* @brief Whether the traffic manager should start in test mode.
|
||||
*/
|
||||
bool m_trafficTestMode = false;
|
||||
|
||||
FrameworkParams() = default;
|
||||
FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {}
|
||||
};
|
||||
@@ -233,7 +244,34 @@ public:
|
||||
/// \note It works for group and leaf node.
|
||||
bool HasUnsavedEdits(storage::CountryId const & countryId);
|
||||
|
||||
/**
|
||||
* @brief Loads maps synchronously.
|
||||
*
|
||||
* Maps are loaded on the calling thread.
|
||||
*
|
||||
* This function also performs certain initialization operations which depend on map data being
|
||||
* available, such as search, traffic and the download queue.
|
||||
*
|
||||
* @note This function is not suitable for use on platforms which enforce restrictions on
|
||||
* time-consuming or potentially blocking operations on the UI thread (as Android does). On such
|
||||
* platforms, `LoadMapsAsync()` should be used instead.
|
||||
*/
|
||||
void LoadMapsSync();
|
||||
|
||||
/**
|
||||
* @brief Loads maps asynchronously.
|
||||
*
|
||||
* Maps are loaded on a new thread. Some operations are executed as part of a separate task which
|
||||
* is posted to the GUI thread.
|
||||
*
|
||||
* This function also performs certain initialization operations which depend on map data being
|
||||
* available, such as search, traffic and the download queue.
|
||||
*
|
||||
* After finishing initialization, the caller-supplied callback function is called. This function
|
||||
* also runs on the GUI thread and should therefore not perform any time-consuming operations.
|
||||
*
|
||||
* @param callback A callback function to run at the end of initialization.
|
||||
*/
|
||||
void LoadMapsAsync(std::function<void()> && callback);
|
||||
|
||||
/// Registers all local map files in internal indexes.
|
||||
@@ -384,6 +422,16 @@ private:
|
||||
private:
|
||||
std::vector<m2::TriangleD> GetSelectedFeatureTriangles() const;
|
||||
|
||||
/**
|
||||
* @brief Initializes the traffic manager.
|
||||
*
|
||||
* This enables the traffic manager if defined in settings. If the traffic manager is not in test
|
||||
* mode, all cunfigured sources are also added here.
|
||||
*
|
||||
* Maps must be loaded prior to calling this method.
|
||||
*/
|
||||
void InitializeTraffic();
|
||||
|
||||
public:
|
||||
/// @name GPS location updates routine.
|
||||
void OnLocationError(location::TLocationError error);
|
||||
@@ -489,11 +537,18 @@ private:
|
||||
std::unique_ptr<descriptions::Loader> m_descriptionsLoader;
|
||||
|
||||
public:
|
||||
// Moves viewport to the search result and taps on it.
|
||||
/**
|
||||
* @brief Moves viewport to the search result and taps on it.
|
||||
* @param res
|
||||
* @param animation
|
||||
*/
|
||||
void SelectSearchResult(search::Result const & res, bool animation);
|
||||
|
||||
// Cancels all searches, stops location follow and then selects
|
||||
// search result.
|
||||
/**
|
||||
* @brief Cancels all searches, stops location follow and then selects search result.
|
||||
* @param res
|
||||
* @param animation
|
||||
*/
|
||||
void ShowSearchResult(search::Result const & res, bool animation = true);
|
||||
|
||||
void UpdateViewport(search::Results const & results);
|
||||
@@ -721,6 +776,14 @@ public:
|
||||
bool LoadTrafficEnabled();
|
||||
void SaveTrafficEnabled(bool trafficEnabled);
|
||||
|
||||
void SetTrafficHttpEnabled(bool enabled);
|
||||
bool LoadTrafficHttpEnabled();
|
||||
void SaveTrafficHttpEnabled(bool trafficHttpEnabled);
|
||||
|
||||
void SetTrafficHttpUrl(std::string url);
|
||||
std::string LoadTrafficHttpUrl();
|
||||
void SaveTrafficHttpUrl(std::string trafficHttpUrl);
|
||||
|
||||
bool LoadTrafficSimplifiedColors();
|
||||
void SaveTrafficSimplifiedColors(bool simplified);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,15 @@
|
||||
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
#include "routing/routing_session.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "traffxml/traff_decoder.hpp"
|
||||
#include "traffxml/traff_model.hpp"
|
||||
#include "traffxml/traff_source.hpp"
|
||||
#include "traffxml/traff_storage.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/polyline2d.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
@@ -28,21 +37,68 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class TrafficManager final
|
||||
class TrafficManager final : public traffxml::TraffSourceManager
|
||||
{
|
||||
public:
|
||||
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
|
||||
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
|
||||
using TrafficUpdateCallbackFn = std::function<void(bool)>;
|
||||
|
||||
/**
|
||||
* @brief Global state of traffic information.
|
||||
*/
|
||||
/*
|
||||
* TODO clean out obsolete states.
|
||||
* Only `Disabled` and `Enabled` are currently used, but some might be reactivated in the future
|
||||
* and platforms (android/iphone) still evaluate all states.
|
||||
* `ExpiredData` is definitely obsolete, as traffic data is no longer dependent on a particular
|
||||
* map version, but still evaluated by android/iphone code.
|
||||
*/
|
||||
enum class TrafficState
|
||||
{
|
||||
/** Traffic is disabled, no traffic data will be retrieved or considered for routing. */
|
||||
Disabled,
|
||||
/** Traffic is enabled and working normally (the first request may not have been scheduled yet). */
|
||||
Enabled,
|
||||
/** At least one request is currently pending. */
|
||||
WaitingData,
|
||||
/** At least one MWM has stale traffic data. */
|
||||
Outdated,
|
||||
/** Traffic data for at least one MWM was invalid or not found on the server. */
|
||||
NoData,
|
||||
/** At least one request failed or timed out. */
|
||||
NetworkError,
|
||||
/** Traffic data could not be retrieved because the map data is outdated. */
|
||||
ExpiredData,
|
||||
/** Traffic data could not be retrieved because the app version is outdated. */
|
||||
ExpiredApp
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The mode for the traffic manager.
|
||||
*
|
||||
* Future versions may introduce further test modes. Therefore, always use `TrafficManager::IsTestMode()`
|
||||
* to verify if the traffic manager is running in test mode.
|
||||
*/
|
||||
enum class Mode
|
||||
{
|
||||
/**
|
||||
* Traffic manager mode for normal operation.
|
||||
*
|
||||
* This is the default mode unless something else is explicitly set.
|
||||
*/
|
||||
Normal,
|
||||
/**
|
||||
* Test mode.
|
||||
*
|
||||
* This mode will prevent the traffic manager from automatically subscribing to sources and
|
||||
* polling them. It will still receive and process push feeds.
|
||||
*
|
||||
* Future versions may introduce further behavior changes, and/or introduce more test modes.
|
||||
*/
|
||||
Test
|
||||
};
|
||||
|
||||
struct MyPosition
|
||||
{
|
||||
m2::PointD m_position = m2::PointD(0.0, 0.0);
|
||||
@@ -55,25 +111,122 @@ public:
|
||||
using TrafficStateChangedFn = std::function<void(TrafficState)>;
|
||||
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
|
||||
|
||||
TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
|
||||
traffic::TrafficObserver & observer);
|
||||
TrafficManager(DataSource & dataSource,
|
||||
CountryInfoGetterFn countryInfoGetter,
|
||||
CountryParentNameGetterFn const & countryParentNameGetter,
|
||||
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
|
||||
routing::RoutingSession & routingSession);
|
||||
~TrafficManager();
|
||||
|
||||
void Teardown();
|
||||
|
||||
/**
|
||||
* @brief Returns a copy of the cache of all currently active TraFF messages.
|
||||
*
|
||||
* For testing purposes.
|
||||
*
|
||||
* Keys are message IDs, values are messages.
|
||||
*
|
||||
* This method is safe to call from any thread.
|
||||
*/
|
||||
std::map<std::string, traffxml::TraffMessage> GetMessageCache();
|
||||
|
||||
TrafficState GetState() const;
|
||||
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
|
||||
|
||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
||||
/**
|
||||
* @brief Sets the version of the MWM used locally.
|
||||
*/
|
||||
void SetCurrentDataVersion(int64_t dataVersion);
|
||||
|
||||
/**
|
||||
* @brief Enables or disables the traffic manager.
|
||||
*
|
||||
* This sets the internal state and notifies the drape engine.
|
||||
*
|
||||
* Upon creation, the traffic manager is disabled. MWMs must be loaded before first enabling the
|
||||
* traffic manager.
|
||||
*
|
||||
* While disabled, the traffic manager will not update its subscription area (upon being enabled
|
||||
* again, it will do so if necessary). It will not poll any sources or process any messages. Feeds
|
||||
* added via `ReceiveFeed()` will be added to the queue but will not be processed until the
|
||||
* traffic manager is re-enabled.
|
||||
*
|
||||
* Calling this function with `enabled` identical to the current state is a no-op.
|
||||
*
|
||||
* @todo Currently, all MWMs must be loaded before calling `SetEnabled()`, as MWMs loaded after
|
||||
* that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added
|
||||
* (and removed) – note that this affects the `DataSource`, not the set of active MWMs.
|
||||
* See `Framework::OnMapDeregistered()` implementation for the opposite case (MWM deregistered).
|
||||
*
|
||||
* @param enabled True to enable, false to disable
|
||||
*/
|
||||
void SetEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Whether the traffic manager is enabled.
|
||||
*
|
||||
* @return True if enabled, false if not
|
||||
*/
|
||||
bool IsEnabled() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the enabled state and URL for the `HttpTraffSource`.
|
||||
*
|
||||
* If the traffic manager is in test mode, this function is a no-op.
|
||||
*
|
||||
* Otherwise this function is expected to be called only if the enabled state and/or URL have
|
||||
* actually changed. Setting both to the current state will remove the current source and create
|
||||
* a new one with identical settings.
|
||||
*
|
||||
* This function currently assumes that there is never more than one `HttpTraffSource` configured
|
||||
* at the same time.
|
||||
*
|
||||
* @param enabled Whether the HTTP TraFF source is enabled.
|
||||
* @param url The URL for the TraFF API.
|
||||
*/
|
||||
void SetHttpTraffSource(bool enabled, std::string url);
|
||||
|
||||
/**
|
||||
* @brief Removes all `TraffSource` instances which satisfy a predicate.
|
||||
*
|
||||
* This method iterates over all currently configured `TraffSource` instances and calls the
|
||||
* caller-suppplied predicate function `pred` on each of them. If `pred` returns true, the source
|
||||
* is removed, else it is kept.
|
||||
*
|
||||
* @todo For now, `pred` deliberately takes a non-const argument so we can do cleanup inside
|
||||
* `pred`. If we manage to move any such cleanup into the destructor of the `TraffSource` subclass
|
||||
* and get rid of any `Close()` methods in subclasses (which is preferable for other reasons as
|
||||
* well), the argument can be made const.
|
||||
*
|
||||
* @param pred The predicate function, see description.
|
||||
*/
|
||||
void RemoveTraffSourceIf(const std::function<bool(traffxml::TraffSource*)>& pred);
|
||||
|
||||
/**
|
||||
* @brief Starts the traffic manager.
|
||||
*
|
||||
*/
|
||||
void Start();
|
||||
|
||||
void UpdateViewport(ScreenBase const & screen);
|
||||
void UpdateMyPosition(MyPosition const & myPosition);
|
||||
|
||||
void Invalidate();
|
||||
/**
|
||||
* @brief Invalidates traffic information for the specified MWM.
|
||||
*
|
||||
* Invalidation of traffic data is always per MWM and affects locations which refer to any version
|
||||
* of this MWM, or whose enclosing rectangle overlaps with that of the MWM. The decoded segments
|
||||
* for these locations are discarded and decoded again, ensuring they are based on the new MWM.
|
||||
* The TraFF messages themselves remain unchanged.
|
||||
*
|
||||
* This method must either be called from a lambda function passed to `RunSynchronized()`,
|
||||
* or the caller must explicitly lock the private `m_mutex` prior to calling this method.
|
||||
*
|
||||
* @param mwmId The newly addded MWM.
|
||||
*/
|
||||
void Invalidate(MwmSet::MwmId const & mwmId);
|
||||
|
||||
void OnDestroySurface();
|
||||
void OnRecoverSurface();
|
||||
@@ -85,57 +238,295 @@ public:
|
||||
void SetSimplifiedColorScheme(bool simplified);
|
||||
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; }
|
||||
|
||||
private:
|
||||
struct CacheEntry
|
||||
/**
|
||||
* @brief Whether the traffic manager is operating in test mode.
|
||||
*/
|
||||
bool IsTestMode() { return m_mode != Mode::Normal; }
|
||||
|
||||
/**
|
||||
* @brief Switches the traffic manager into test mode.
|
||||
*
|
||||
* The mode can only be set before the traffic manager is first enabled. After that, this method
|
||||
* will log a warning but otherwise do nothing.
|
||||
*
|
||||
* In test mode, the traffic manager will not subscribe to sources or poll them automatically.
|
||||
* Expired messages will not get purged automatically, but `PurgeExpiredMessages()` can be called
|
||||
* to purge expired messages once. The traffic manager will still receive and process push feeds.
|
||||
*
|
||||
* Future versions may introduce further behavior changes.
|
||||
*/
|
||||
void SetTestMode();
|
||||
|
||||
/**
|
||||
* @brief Processes a traffic feed.
|
||||
*
|
||||
* The feed may be a result of a pull operation, or received through a push operation.
|
||||
* (Push operations are not supported by all sources.)
|
||||
*
|
||||
* This method is safe to call from any thread.
|
||||
*
|
||||
* @param feed The traffic feed.
|
||||
*/
|
||||
virtual void ReceiveFeed(traffxml::TraffFeed feed) override;
|
||||
|
||||
/**
|
||||
* @brief Registers a `TraffSource`.
|
||||
* @param source The source.
|
||||
*/
|
||||
virtual void RegisterSource(std::unique_ptr<traffxml::TraffSource> source) override;
|
||||
|
||||
/**
|
||||
* @brief Retrieves all currently active MWMs.
|
||||
*
|
||||
* This method retrieves all MWMs in the viewport, within a certain distance of the current
|
||||
* position (if there is a valid position) or part of the route (if any), and stores them in
|
||||
* `activeMwms`.
|
||||
*
|
||||
* This method locks `m_mutex` and is therefore safe to call from any thread. Callers which
|
||||
* already hold `m_mutex` can use the private `UniteActiveMwms()` method instead.
|
||||
*
|
||||
* @param activeMwms Retrieves the list of active MWMs.
|
||||
*/
|
||||
virtual void GetActiveMwms(std::set<MwmSet::MwmId> & activeMwms) override;
|
||||
|
||||
/**
|
||||
* @brief Purges expired messages from the cache.
|
||||
*
|
||||
* This method is safe to call from any thread, except for the traffic worker thread.
|
||||
*/
|
||||
void PurgeExpiredMessages();
|
||||
|
||||
/**
|
||||
* @brief Clears the traffic message cache and feed queue.
|
||||
*
|
||||
* This is intended for testing purposes and clears the message cache, as well as the feed queue.
|
||||
* Subscriptions are not changed.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Registers a callback function which gets called on traffic updates.
|
||||
*
|
||||
* Intended for testing.
|
||||
*
|
||||
* @param fn The callback function.
|
||||
*/
|
||||
void SetTrafficUpdateCallbackFn(TrafficUpdateCallbackFn && fn);
|
||||
|
||||
/**
|
||||
* @brief Runs a function guarded by the traffic manager mutex.
|
||||
*
|
||||
* This locks `m_mutex`, then runs `f` and releases the mutex.
|
||||
*
|
||||
* @param f
|
||||
*/
|
||||
void RunSynchronized(std::function<void()> f)
|
||||
{
|
||||
CacheEntry();
|
||||
explicit CacheEntry(std::chrono::time_point<std::chrono::steady_clock> const & requestTime);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
f();
|
||||
}
|
||||
|
||||
bool m_isLoaded;
|
||||
size_t m_dataSize;
|
||||
private:
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastActiveTime;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastRequestTime;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
|
||||
/**
|
||||
* @brief Recalculates the TraFF subscription area.
|
||||
*
|
||||
* The subscription area needs to be recalculated when the traffic manager goes from disabled to
|
||||
* enabled, or when it is resumed after being paused, as the subscription area is not updated
|
||||
* while the traffic manager is disabled or paused.
|
||||
*
|
||||
* If the subscription area has changed, or if `forceRenewal` is true, TraFF subscriptions are
|
||||
* renewed by calling `SubscribeOrChangeSubscription()`.
|
||||
*
|
||||
* No traffic data is discarded, but sources will be polled for an update, which may turn out
|
||||
* larger than usual if the traffic manager was in disabled/paused state for an extended period of
|
||||
* time or the subscription area has changed.
|
||||
*
|
||||
* @param forceRenewal If true, renew subscriptions even if the subscription area has not changed.
|
||||
*/
|
||||
void RecalculateSubscription(bool forceRenewal);
|
||||
|
||||
int m_retriesCount;
|
||||
bool m_isWaitingForResponse;
|
||||
/**
|
||||
* @brief Ensures every TraFF source has a subscription covering all currently active MWMs.
|
||||
*
|
||||
* This method cycles through all TraFF sources in `m_trafficSources` and calls
|
||||
* `SubscribeOrChangeSubscription()` on each of them.
|
||||
*/
|
||||
void SubscribeOrChangeSubscription();
|
||||
|
||||
traffic::TrafficInfo::Availability m_lastAvailability;
|
||||
};
|
||||
/**
|
||||
* @brief Unsubscribes from all traffic services we are subscribed to.
|
||||
*
|
||||
* This method cycles through all TraFF sources in `m_trafficSources` and calls `Unsubscribe()`
|
||||
* on each of them.
|
||||
*/
|
||||
void Unsubscribe();
|
||||
|
||||
/**
|
||||
* @brief Restores the message cache from file storage.
|
||||
*
|
||||
* @note The caller must lock `m_mutex` prior to calling this function, as it makes unprotected
|
||||
* changes to shared data structures.
|
||||
*
|
||||
* @note The return value indicates whether actions related to a traffic update should be taken,
|
||||
* such as notifying the routing and drape engine. It is true if at least one message with a
|
||||
* decoded location was read, and no messages without decoded locations. If messages without a
|
||||
* decoded location were read, the return value is false, as the location decoding will trigger
|
||||
* updates by itself. If errors occurred and no messages are read, the return value is also false.
|
||||
*
|
||||
* @return True if a traffic update needs to be sent, false if not
|
||||
*/
|
||||
bool RestoreCache();
|
||||
|
||||
/**
|
||||
* @brief Polls all traffic services for updates.
|
||||
*
|
||||
* This method cycles through all TraFF sources in `m_trafficSources` and calls `IsPollNeeded()`
|
||||
* on each of them. If this method returns true, it then calls `Poll()` on the source.
|
||||
*/
|
||||
void Poll();
|
||||
|
||||
/**
|
||||
* @brief Purges expired messages from the cache.
|
||||
*
|
||||
* This is the internal conterpart of `PurgeExpiredMessages()`. It is safe to call from any
|
||||
* thread. Unlike `PurgeExpiredMessages()`, it does not wake the worker thread, making it suitable
|
||||
* for use on the worker thread.
|
||||
*
|
||||
* @return true if messages were purged, false if not
|
||||
*/
|
||||
bool PurgeExpiredMessagesImpl();
|
||||
|
||||
/**
|
||||
* @brief Consolidates the feed queue.
|
||||
*
|
||||
* If multiple feeds in the queue have the same message ID, only the message with the newest
|
||||
* update time is kept (if two messages have the same ID and update time, the one in the feed
|
||||
* with the higher index is kept); other messages with the same ID are discarded. Empty feeds
|
||||
* are discarded.
|
||||
*/
|
||||
void ConsolidateFeedQueue();
|
||||
|
||||
/**
|
||||
* @brief Removes the first message from the first feed and decodes it.
|
||||
*/
|
||||
void DecodeFirstMessage();
|
||||
|
||||
/**
|
||||
* @brief Event loop for the traffic worker thread.
|
||||
*
|
||||
* This method runs an event loop, which blocks until woken up or a timeout equivalent to the
|
||||
* update interval elapses. It cycles through the list of MWMs for which updates have been
|
||||
* scheduled, triggering a network request for each and processing the result.
|
||||
*/
|
||||
void ThreadRoutine();
|
||||
bool WaitForRequest(std::vector<MwmSet::MwmId> & mwms);
|
||||
|
||||
void OnTrafficDataResponse(traffic::TrafficInfo && info);
|
||||
void OnTrafficRequestFailed(traffic::TrafficInfo && info);
|
||||
/**
|
||||
* @brief Blocks until a request for traffic data is received or a timeout expires.
|
||||
*
|
||||
* This method acts as the loop condition for `ThreadRoutine()`. It blocks until woken up or the
|
||||
* update interval expires. In the latter case, it calls `RequestTrafficData()` to insert all
|
||||
* currently active MWMs into the list of MWMs to update; otherwise, it leaves the list as it is.
|
||||
* In either case, it populates `mwms` with the list and returns.
|
||||
*
|
||||
* @return `true` during normal operation, `false` during teardown (signaling the event loop to exit).
|
||||
*/
|
||||
bool WaitForRequest();
|
||||
|
||||
/// \brief Updates |activeMwms| and request traffic data.
|
||||
/// \param rect is a rectangle covering a new active mwm set.
|
||||
/// \note |lastMwmsByRect|/|activeMwms| may be either |m_lastDrapeMwmsByRect/|m_activeDrapeMwms|
|
||||
/// or |m_lastRoutingMwmsByRect|/|m_activeRoutingMwms|.
|
||||
/// \note |m_mutex| is locked inside the method. So the method should be called without |m_mutex|.
|
||||
/**
|
||||
* @brief Processes new traffic data.
|
||||
*
|
||||
* The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`.
|
||||
* `m_allMwmColoring` is rebuilt from per-message colorings in `m_messageCache` as needed.
|
||||
*
|
||||
* This method is normally called from the traffic worker thread. Test tools may also call it from
|
||||
* other threads.
|
||||
*/
|
||||
void OnTrafficDataUpdate();
|
||||
|
||||
/**
|
||||
* @brief Updates `activeMwms` and requests traffic data.
|
||||
*
|
||||
* The old and new list of active MWMs may refer either to those used by the rendering engine
|
||||
* (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those around the current position.
|
||||
* (`m_lastPositionMwmsByRect`/`m_activePositionMwms`).
|
||||
*
|
||||
* The method first determines the list of MWMs overlapping with `rect`. If it is identical to
|
||||
* `lastMwmsByRect`, the method returns immediately. Otherwise, it stores the new set in
|
||||
* `lastMwmsByRect` and populates `activeMwms` with the elements.
|
||||
*
|
||||
* This method locks `m_mutex` while populating `activeMwms`. There is no need for the caller to
|
||||
* do that.
|
||||
*
|
||||
* @param rect Rectangle covering the new active MWM set.
|
||||
* @param lastMwmsByRect Set of active MWMs, see description.
|
||||
* @param activeMwms Vector of active MWMs, see description.
|
||||
*/
|
||||
void UpdateActiveMwms(m2::RectD const & rect, std::vector<MwmSet::MwmId> & lastMwmsByRect,
|
||||
std::set<MwmSet::MwmId> & activeMwms);
|
||||
|
||||
// This is a group of methods that haven't their own synchronization inside.
|
||||
void RequestTrafficData();
|
||||
void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force);
|
||||
|
||||
void Clear();
|
||||
void ClearCache(MwmSet::MwmId const & mwmId);
|
||||
void ShrinkCacheToAllowableSize();
|
||||
|
||||
void UpdateState();
|
||||
void ChangeState(TrafficState newState);
|
||||
|
||||
bool IsInvalidState() const;
|
||||
|
||||
void OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current);
|
||||
|
||||
/**
|
||||
* @brief Retrieves all currently active MWMs.
|
||||
*
|
||||
* This method retrieves all MWMs in the viewport, within a certain distance of the current
|
||||
* position (if there is a valid position) or part of the route (if any), and stores them in
|
||||
* `activeMwms`.
|
||||
*
|
||||
* The caller must hold `m_mutex` prior to calling this method. `GetActiveMwms()` is available
|
||||
* as a convenience wrapper which locks `m_mutex`, calls this method and releases it.
|
||||
*
|
||||
* @param activeMwms Retrieves the list of active MWMs.
|
||||
*/
|
||||
void UniteActiveMwms(std::set<MwmSet::MwmId> & activeMwms) const;
|
||||
|
||||
/**
|
||||
* @brief Pauses the traffic manager.
|
||||
*
|
||||
* Upon creation, the traffic manager is not paused.
|
||||
*
|
||||
* While the traffic manager is paused and no route is active, the traffic manager will not update
|
||||
* its subscription area (upon resuming, it will do so if necessary). It will not poll any sources
|
||||
* or process any messages. Feeds added via `ReceiveFeed()` will be added to the queue but will
|
||||
* not be processed until the traffic manager is resumed.
|
||||
*
|
||||
* Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except
|
||||
* it does not change the external state, and an active route effectively overrides the paused
|
||||
* state. It is intended for internal use by the framework.
|
||||
*/
|
||||
void Pause();
|
||||
|
||||
/**
|
||||
* @brief Resumes the traffic manager.
|
||||
*
|
||||
* Upon creation, the traffic manager is not paused. Resuming a traffic manager that is not paused
|
||||
* is a no-op.
|
||||
*
|
||||
* Upon resume, the traffic manager will recalculate its subscription area and change its
|
||||
* subscription if necessary. It will continue processing feeds in the queue, including those
|
||||
* received before or while the traffic manager was paused.
|
||||
*
|
||||
* Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except
|
||||
* it does not change the external state, and an active route effectively overrides the paused
|
||||
* state. It is intended for internal use by the framework.
|
||||
*/
|
||||
void Resume();
|
||||
|
||||
template <class F>
|
||||
void ForEachMwm(F && f) const
|
||||
{
|
||||
std::vector<std::shared_ptr<MwmInfo>> allMwmInfo;
|
||||
m_dataSource.GetMwmsInfo(allMwmInfo);
|
||||
std::for_each(allMwmInfo.begin(), allMwmInfo.end(), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void ForEachActiveMwm(F && f) const
|
||||
{
|
||||
@@ -144,44 +535,233 @@ private:
|
||||
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether updates to the observer are currently inhibited.
|
||||
*
|
||||
* Updates are inhibited while a route calculation is in progress. In this state, the observer
|
||||
* receives traffic updates only if the queue has run empty, not if nore locations are waiting
|
||||
* to be decoded.
|
||||
*
|
||||
* Inhibtiting the observer is necessary as traffic updates during route calculation will cause
|
||||
* it to restart from scratch. Once the route has been calculated, updates will trigger a
|
||||
* recalculation, which is much faster (seconds or less).
|
||||
*/
|
||||
bool IsObserverInhibited() const { return (m_routingSessionState == routing::SessionState::RouteBuilding)
|
||||
|| (m_routingSessionState == routing::SessionState::RouteRebuilding); }
|
||||
|
||||
/**
|
||||
* @brief Whether we are currently routing.
|
||||
*/
|
||||
bool IsRouting() const { return m_routingSessionState != routing::SessionState::NoValidRoute; }
|
||||
|
||||
/**
|
||||
* @brief Whether the traffic manager is paused and not routing.
|
||||
*
|
||||
* This is used to inhibit polling and message decoding.
|
||||
*/
|
||||
bool IsPausedAndNotRouting() const { return m_isPaused && !IsRouting(); }
|
||||
|
||||
DataSource & m_dataSource;
|
||||
CountryInfoGetterFn m_countryInfoGetterFn;
|
||||
CountryParentNameGetterFn m_countryParentNameGetterFn;
|
||||
GetMwmsByRectFn m_getMwmsByRectFn;
|
||||
traffic::TrafficObserver & m_observer;
|
||||
|
||||
/*
|
||||
* Originally this was m_observer, of type traffic::TrafficObserver. Since routing::RoutingSession
|
||||
* inherits from that class, and an interface to the routing session is needed in order to
|
||||
* determine the MWMs for which we need traffic information, the type was changed and the member
|
||||
* renamed to reflect that.
|
||||
*/
|
||||
routing::RoutingSession & m_routingSession;
|
||||
|
||||
/**
|
||||
* @brief Cached state of the routing session.
|
||||
*
|
||||
* `m_routingSession` methods which query the state may only be called from the GUI thread,
|
||||
* therefore we are caching this value when we get notified of a change.
|
||||
*/
|
||||
routing::SessionState m_routingSessionState = routing::SessionState::NoValidRoute;
|
||||
|
||||
df::DrapeEngineSafePtr m_drapeEngine;
|
||||
std::atomic<int64_t> m_currentDataVersion;
|
||||
|
||||
// These fields have a flag of their initialization.
|
||||
/*
|
||||
* The lazy ones get updated only if they are not initialized, or if their new position is more
|
||||
* than a certain distance from the previously stored one.
|
||||
*/
|
||||
std::pair<MyPosition, bool> m_currentPosition = {MyPosition(), false};
|
||||
std::pair<MyPosition, bool> m_currentPositionLazy = m_currentPosition;
|
||||
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false};
|
||||
std::pair<ScreenBase, bool> m_currentModelViewLazy = m_currentModelView;
|
||||
|
||||
/**
|
||||
* The mode in which the traffic manager is running.
|
||||
*/
|
||||
Mode m_mode = Mode::Normal;
|
||||
|
||||
/**
|
||||
* Whether the traffic manager accepts mode changes.
|
||||
*
|
||||
* Mode cannot be set after the traffic manager has been enabled for the first time.
|
||||
*/
|
||||
bool m_canSetMode = true;
|
||||
|
||||
std::atomic<TrafficState> m_state;
|
||||
TrafficStateChangedFn m_onStateChangedFn;
|
||||
|
||||
bool m_hasSimplifiedColorScheme = true;
|
||||
|
||||
size_t m_maxCacheSizeBytes;
|
||||
size_t m_currentCacheSizeBytes = 0;
|
||||
|
||||
std::map<MwmSet::MwmId, CacheEntry> m_mwmCache;
|
||||
/**
|
||||
* @brief The TraFF sources from which we get traffic information.
|
||||
*
|
||||
* Threads must lock `m_trafficSourceMutex` prior to accessing this member.
|
||||
*/
|
||||
std::vector<std::unique_ptr<traffxml::TraffSource>> m_trafficSources;
|
||||
|
||||
bool m_isRunning;
|
||||
std::condition_variable m_condition;
|
||||
|
||||
/*
|
||||
* To determine for which MWMs we need traffic data, we need to keep track of 3 groups of MWMs:
|
||||
* those used by the renderer (i.e. in or just around the viewport), those within a certain area
|
||||
* around the current position, and those used by the routing engine (only if currently routing).
|
||||
*
|
||||
* Routing MWMs are stored as a set.
|
||||
*
|
||||
* The other groups are stored twice: as a set and as a vector. The set always holds the MWMs which
|
||||
* were last seen in use. Both get updated together when active MWMs are added or removed.
|
||||
* However, the vector is used as a reference to detect changes. Clear() clears the vector but not
|
||||
* the set, invalidating the set without destroying its contents.
|
||||
*
|
||||
* Methods which use only the set:
|
||||
*
|
||||
* * RequestTrafficSubscription(), exits if empty, otherwise cycles through the set.
|
||||
* * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription()).
|
||||
*
|
||||
* Methods which use both, but in a different way:
|
||||
*
|
||||
* * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it
|
||||
* updates both vector and set, but adds MWMs to the set only if they are alive.
|
||||
*/
|
||||
std::vector<MwmSet::MwmId> m_lastDrapeMwmsByRect;
|
||||
std::set<MwmSet::MwmId> m_activeDrapeMwms;
|
||||
std::vector<MwmSet::MwmId> m_lastRoutingMwmsByRect;
|
||||
std::vector<MwmSet::MwmId> m_lastPositionMwmsByRect;
|
||||
std::set<MwmSet::MwmId> m_activePositionMwms;
|
||||
std::set<MwmSet::MwmId> m_activeRoutingMwms;
|
||||
|
||||
// The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.
|
||||
// It is one of several mechanisms that HTTP provides for web cache validation,
|
||||
// which allows a client to make conditional requests.
|
||||
std::map<MwmSet::MwmId, std::string> m_trafficETags;
|
||||
/**
|
||||
* @brief Whether active MWMs have changed since the last request.
|
||||
*/
|
||||
bool m_activeMwmsChanged = false;
|
||||
|
||||
std::atomic<bool> m_isPaused;
|
||||
|
||||
std::vector<MwmSet::MwmId> m_requestedMwms;
|
||||
/**
|
||||
* @brief Mutex for access to shared members.
|
||||
*
|
||||
* Threads which access shared members (see documentation) must lock this mutex while doing so.
|
||||
*
|
||||
* @note To access `m_trafficSource`, lock `m_trafficSourceMutex`, not this mutex.
|
||||
*/
|
||||
std::mutex m_mutex;
|
||||
|
||||
/**
|
||||
* @brief Mutex for access to `m_trafficSources`.
|
||||
*
|
||||
* Threads which access `m_trafficSources` must lock this mutex while doing so.
|
||||
*/
|
||||
std::mutex m_trafficSourceMutex;
|
||||
|
||||
/**
|
||||
* @brief Worker thread which fetches traffic updates.
|
||||
*/
|
||||
threads::SimpleThread m_thread;
|
||||
|
||||
/**
|
||||
* @brief When the last response was received.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
|
||||
|
||||
/**
|
||||
* @brief When the last update notification to the Drape engine was posted.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastDrapeUpdate;
|
||||
|
||||
/**
|
||||
* @brief When the last update notification to the traffic observer was posted.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastObserverUpdate;
|
||||
|
||||
/**
|
||||
* @brief When the cache file was last updated.
|
||||
*/
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_lastStorageUpdate;
|
||||
|
||||
/**
|
||||
* @brief Whether a poll operation is needed.
|
||||
*
|
||||
* Used in the worker thread to indicate we need to poll all sources. The poll operation may still
|
||||
* be inhibited for individual sources.
|
||||
*/
|
||||
bool m_isPollNeeded;
|
||||
|
||||
/**
|
||||
* @brief Queue of feeds waiting to be processed.
|
||||
*
|
||||
* Threads must lock `m_mutex` before accessing `m_feedQueue`, as some platforms may receive feeds
|
||||
* on multiple threads.
|
||||
*/
|
||||
std::vector<traffxml::TraffFeed> m_feedQueue;
|
||||
|
||||
/**
|
||||
* @brief Whether the feed queue needs to be resorted.
|
||||
*
|
||||
* Resorting is needed when a new feed is added, or the current position or the viewport center
|
||||
* has changed by more than a certain threshold.
|
||||
*/
|
||||
std::atomic<bool> m_isFeedQueueSortInvalid = false;
|
||||
|
||||
/**
|
||||
* @brief Cache of all currently active TraFF messages.
|
||||
*
|
||||
* Keys are message IDs, values are messages.
|
||||
*
|
||||
* Threads must lock `m_mutex` before accessing `m_messageCache`, as access can happen from
|
||||
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
|
||||
* thread).
|
||||
*/
|
||||
std::map<std::string, traffxml::TraffMessage> m_messageCache;
|
||||
|
||||
/**
|
||||
* @brief The storage instance.
|
||||
*
|
||||
* Used to persist the TraFF message cache between sessions.
|
||||
*/
|
||||
std::unique_ptr<traffxml::LocalStorage> m_storage;
|
||||
|
||||
/**
|
||||
* @brief The TraFF decoder instance.
|
||||
*
|
||||
* Used to decode TraFF locations into road segments on the map.
|
||||
*/
|
||||
std::unique_ptr<traffxml::DefaultTraffDecoder> m_traffDecoder;
|
||||
|
||||
/**
|
||||
* @brief Map between MWM IDs and their colorings.
|
||||
*
|
||||
* Threads must lock `m_mutex` before accessing `m_allMwmColoring`, as access can happen from
|
||||
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
|
||||
* thread).
|
||||
*/
|
||||
std::map<MwmSet::MwmId, traffic::TrafficInfo::Coloring> m_allMwmColoring;
|
||||
|
||||
/**
|
||||
* @brief Callback function which gets called on traffic updates.
|
||||
*
|
||||
* Intended for testing.
|
||||
*/
|
||||
std::optional<TrafficUpdateCallbackFn> m_trafficUpdateCallbackFn;
|
||||
};
|
||||
|
||||
extern std::string DebugPrint(TrafficManager::TrafficState state);
|
||||
|
||||
@@ -36,20 +36,61 @@ public:
|
||||
RoadWarningFirstFerry,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief User mark types.
|
||||
*
|
||||
* `UserMark` subclasses are assigned a value from this enum.
|
||||
*/
|
||||
enum Type : uint32_t
|
||||
{
|
||||
/**
|
||||
* `Bookmark`
|
||||
*/
|
||||
BOOKMARK, // Should always be the first one
|
||||
/**
|
||||
* `ApiMarkPoint`
|
||||
*/
|
||||
API,
|
||||
/**
|
||||
* `SearchMarkPoint`
|
||||
*/
|
||||
SEARCH,
|
||||
/**
|
||||
* `StaticMarkPoint`
|
||||
*/
|
||||
STATIC,
|
||||
/**
|
||||
* `RouteMarkPoint`
|
||||
*/
|
||||
ROUTING,
|
||||
/**
|
||||
* `SpeedCameraMark`
|
||||
*/
|
||||
SPEED_CAM,
|
||||
/**
|
||||
* `RoadWarningMark`
|
||||
*/
|
||||
ROAD_WARNING,
|
||||
/**
|
||||
* `TransitMark`
|
||||
*/
|
||||
TRANSIT,
|
||||
LOCAL_ADS,
|
||||
/**
|
||||
* `TrackInfoMark`
|
||||
*/
|
||||
TRACK_INFO,
|
||||
/**
|
||||
* `TrackSelectionMark`
|
||||
*/
|
||||
TRACK_SELECTION,
|
||||
/**
|
||||
* `DebugMarkPoint`
|
||||
*/
|
||||
DEBUG_MARK, // Plain "DEBUG" results in a name collision.
|
||||
/**
|
||||
* `ColoredMarkPoint`
|
||||
*/
|
||||
COLORED,
|
||||
USER_MARK_TYPES_COUNT,
|
||||
USER_MARK_TYPES_COUNT_MAX = 1000,
|
||||
@@ -133,6 +174,9 @@ private:
|
||||
bool m_hasPosition = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mark in the shape of a dot.
|
||||
*/
|
||||
class DebugMarkPoint : public UserMark
|
||||
{
|
||||
public:
|
||||
@@ -141,6 +185,9 @@ public:
|
||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mark in the shape of a dot, of caller-defined color and radius.
|
||||
*/
|
||||
class ColoredMarkPoint : public UserMark
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -13,53 +13,88 @@
|
||||
|
||||
namespace platform
|
||||
{
|
||||
// This class represents a path to disk files corresponding to some
|
||||
// country region.
|
||||
//
|
||||
// This class also wraps World.mwm and WorldCoasts.mwm
|
||||
// files from resource bundle, when they can't be found in a data
|
||||
// directory. In this exceptional case, directory will be empty and
|
||||
// SyncWithDisk()/DeleteFromDisk()/GetPath()/GetSize() will return
|
||||
// incorrect results.
|
||||
//
|
||||
// In any case, when you're going to read a file LocalCountryFile points to,
|
||||
// use platform::GetCountryReader().
|
||||
/**
|
||||
* @brief Represents a path to disk files corresponding to some country region.
|
||||
*
|
||||
* This class also wraps World.mwm and WorldCoasts.mwm files from resource bundle, when they can't
|
||||
* be found in a data directory. In this exceptional case, directory will be empty and
|
||||
* `SyncWithDisk()`/`DeleteFromDisk()`/`GetPath()`/`GetSize()` will return incorrect results.
|
||||
*
|
||||
* In any case, when you're going to read a file LocalCountryFile points to, use
|
||||
* `platform::GetCountryReader()`.
|
||||
*/
|
||||
class LocalCountryFile
|
||||
{
|
||||
public:
|
||||
LocalCountryFile();
|
||||
|
||||
// Creates an instance holding a path to countryFile's in a
|
||||
// directory. Note that no disk operations are not performed until
|
||||
// SyncWithDisk() is called.
|
||||
// The directory must contain a full path to the country file.
|
||||
/**
|
||||
* @brief Creates an instance holding a path to countryFile's in a directory.
|
||||
*
|
||||
* Note that no disk operations are performed until `SyncWithDisk()` is called.
|
||||
*
|
||||
* @param directory full path to the country file
|
||||
* @param countryFile
|
||||
* @param version
|
||||
*/
|
||||
LocalCountryFile(std::string directory, CountryFile countryFile, int64_t version);
|
||||
|
||||
// Syncs internal state like availability of files, their sizes etc. with disk.
|
||||
// Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in
|
||||
// this method but it's not implemented by performance reasons. This check is done on
|
||||
// building routes stage.
|
||||
/**
|
||||
* @brief Syncs internal state like availability of files, their sizes etc. with disk.
|
||||
*
|
||||
* Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in
|
||||
* this method but it's not implemented by performance reasons. This check is done on
|
||||
* building routes stage.
|
||||
*/
|
||||
void SyncWithDisk();
|
||||
|
||||
// Removes specified file from disk if it is known for LocalCountryFile, i.e.
|
||||
// it was found by a previous SyncWithDisk() call.
|
||||
/**
|
||||
* @brief Deletes a file from disk.
|
||||
*
|
||||
* Removes the specified file from disk for `LocalCountryFile`, if it is known, i.e. it was found
|
||||
* by a previous SyncWithDisk() call.
|
||||
* @param type
|
||||
*/
|
||||
void DeleteFromDisk(MapFileType type) const;
|
||||
|
||||
// Returns path to a file.
|
||||
// Return value may be empty until SyncWithDisk() is called.
|
||||
/**
|
||||
* @brief Returns the path to a file.
|
||||
*
|
||||
* Return value may be empty until SyncWithDisk() is called.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
std::string GetPath(MapFileType type) const;
|
||||
std::string GetFileName(MapFileType type) const;
|
||||
|
||||
// Returns size of a file.
|
||||
// Return value may be zero until SyncWithDisk() is called.
|
||||
/**
|
||||
* @brief Returns the size of a file.
|
||||
*
|
||||
* Return value may be zero until SyncWithDisk() is called.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
uint64_t GetSize(MapFileType type) const;
|
||||
|
||||
// Returns true when some files are found during SyncWithDisk.
|
||||
// Return value is false until SyncWithDisk() is called.
|
||||
/**
|
||||
* @brief Returns true when files are found during `SyncWithDisk()`.
|
||||
*
|
||||
* Return value is false until `SyncWithDisk()` is called.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
bool HasFiles() const;
|
||||
|
||||
// Checks whether files specified in filesMask are on disk.
|
||||
// Return value will be false until SyncWithDisk() is called.
|
||||
/**
|
||||
* @brief Checks whether files specified in filesMask are on disk.
|
||||
*
|
||||
* Return value will be false until SyncWithDisk() is called.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
bool OnDisk(MapFileType type) const;
|
||||
|
||||
bool IsInBundle() const { return m_directory.empty(); }
|
||||
@@ -74,8 +109,17 @@ public:
|
||||
|
||||
bool ValidateIntegrity() const;
|
||||
|
||||
// Creates LocalCountryFile for test purposes, for a country region
|
||||
// with countryFileName (without any extensions). Automatically performs sync with disk.
|
||||
//
|
||||
/**
|
||||
* @brief Creates a `LocalCountryFile` for test purposes.
|
||||
*
|
||||
* Creates a `LocalCountryFile` for test purposes, for a country region with `countryFileName`.
|
||||
* Automatically performs sync with disk.
|
||||
*
|
||||
* @param countryFileName The filename, without any extension.
|
||||
* @param version The data version.
|
||||
* @return
|
||||
*/
|
||||
static LocalCountryFile MakeForTesting(std::string countryFileName, int64_t version = 0);
|
||||
|
||||
// Used in generator only to simplify getting instance from path.
|
||||
|
||||
@@ -17,6 +17,8 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile
|
||||
|
||||
void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate)
|
||||
{
|
||||
m_regions.clear();
|
||||
|
||||
if (m_routerThread)
|
||||
{
|
||||
m_routerThread->Cancel();
|
||||
@@ -48,18 +50,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set<std::string> & regions)
|
||||
|
||||
void AbsentRegionsFinder::GetAllRegions(std::set<std::string> & countries)
|
||||
{
|
||||
countries.clear();
|
||||
// Note: if called from `RoutingSession` callback, m_state will still have its pre-update value.
|
||||
if (m_routerThread)
|
||||
{
|
||||
m_routerThread->Join();
|
||||
|
||||
if (!m_routerThread)
|
||||
return;
|
||||
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
|
||||
{
|
||||
if (!mwmName.empty())
|
||||
m_regions.emplace(mwmName);
|
||||
}
|
||||
|
||||
m_routerThread->Join();
|
||||
m_routerThread.reset();
|
||||
}
|
||||
|
||||
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
|
||||
if (!mwmName.empty())
|
||||
countries.emplace(mwmName);
|
||||
|
||||
m_routerThread.reset();
|
||||
countries = m_regions;
|
||||
}
|
||||
|
||||
bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const
|
||||
|
||||
@@ -14,19 +14,45 @@ namespace routing
|
||||
{
|
||||
using LocalFileCheckerFn = std::function<bool(std::string const &)>;
|
||||
|
||||
// Encapsulates generation of mwm names of absent regions needed for building the route between
|
||||
// |checkpoints|. For this purpose the new thread is used.
|
||||
/**
|
||||
* @brief Generates a list of MWMs needed to build a route.
|
||||
*
|
||||
* The `AbsentRegionsFinder` class encapsulates generation of MWM names of absent regions needed
|
||||
* for building the route between `checkpoints`. For this purpose a separate worker thread is used.
|
||||
*/
|
||||
class AbsentRegionsFinder
|
||||
{
|
||||
public:
|
||||
AbsentRegionsFinder(CountryFileGetterFn const & countryFileGetter, LocalFileCheckerFn const & localFileChecker,
|
||||
std::shared_ptr<NumMwmIds> numMwmIds, DataSource & dataSource);
|
||||
|
||||
// Creates new thread |m_routerThread| and starts routing in it.
|
||||
/**
|
||||
* @brief Creates new thread `m_routerThread` and starts routing in it.
|
||||
* @param checkpoints The checkpoints of the route (start, optional intermediate points, destination)
|
||||
* @param delegate
|
||||
*/
|
||||
void GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate);
|
||||
// Waits for the routing thread |m_routerThread| to finish and returns results from it.
|
||||
|
||||
/**
|
||||
* @brief Retrieves the MWMs needed to build the route.
|
||||
*
|
||||
* When called for the first time after `GenerateAbsentRegions()`, this method waits for the
|
||||
* routing thread `m_routerThread` to finish and returns results from it. Results are cached and
|
||||
* subsequent calls are served from the cache.
|
||||
*
|
||||
* @param countries Receives the list of MWM names.
|
||||
*/
|
||||
void GetAllRegions(std::set<std::string> & countries);
|
||||
// Waits for the results from GetAllRegions() and returns only regions absent on the device.
|
||||
|
||||
/**
|
||||
* @brief Retrieves the missing MWMs needed to build the route.
|
||||
*
|
||||
* This calls `GetAllRegions()` and strips from the result all regions already present on the
|
||||
* device, leaving only the missing ones. If the call to `GetAllRegions()` is the first one after
|
||||
* calling `GenerateAbsentRegions()`, this involves waiting for the router thread to finish.
|
||||
*
|
||||
* @param absentCountries Receives the list of missing MWM names.
|
||||
*/
|
||||
void GetAbsentRegions(std::set<std::string> & absentCountries);
|
||||
|
||||
private:
|
||||
@@ -39,5 +65,19 @@ private:
|
||||
DataSource & m_dataSource;
|
||||
|
||||
std::unique_ptr<threads::Thread> m_routerThread;
|
||||
|
||||
/**
|
||||
* @brief Mutex for access to `m_regions`.
|
||||
*
|
||||
* Threads which access `m_regions` must lock this mutex while doing so.
|
||||
*/
|
||||
std::mutex m_mutex;
|
||||
|
||||
/**
|
||||
* @brief Regions required for building the last route.
|
||||
*
|
||||
* This member is cleared by `GenerateAbsentRegions()` and populated by `GetAllRegions()`.
|
||||
*/
|
||||
std::set<std::string> m_regions;
|
||||
};
|
||||
} // namespace routing
|
||||
|
||||
@@ -80,6 +80,13 @@ bool AsyncRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin
|
||||
return m_router->FindClosestProjectionToRoad(point, direction, radius, proj);
|
||||
}
|
||||
|
||||
void AsyncRouter::GetAllRegions(std::set<std::string> & countries)
|
||||
{
|
||||
if (!m_absentRegionsFinder)
|
||||
return;
|
||||
m_absentRegionsFinder->GetAllRegions(countries);
|
||||
}
|
||||
|
||||
void AsyncRouter::RouterDelegateProxy::OnProgress(float progress)
|
||||
{
|
||||
ProgressCallback onProgress = nullptr;
|
||||
|
||||
@@ -23,11 +23,15 @@
|
||||
namespace routing
|
||||
{
|
||||
|
||||
/// Dispatches a route calculation on a worker thread
|
||||
/**
|
||||
* @brief The AsyncRouter class is a wrapper class to run routing routines in a different thread.
|
||||
*
|
||||
* It encapsulates an `IRouter` (or subclass) instance, set with `SetRouter()`, and runs it in a
|
||||
* separate worker thread to calculate the route.
|
||||
*/
|
||||
class AsyncRouter final
|
||||
{
|
||||
public:
|
||||
/// AsyncRouter is a wrapper class to run routing routines in the different thread
|
||||
AsyncRouter(PointCheckCallback const & pointCheckCallback);
|
||||
~AsyncRouter();
|
||||
|
||||
@@ -59,6 +63,15 @@ public:
|
||||
bool FindClosestProjectionToRoad(m2::PointD const & point, m2::PointD const & direction, double radius,
|
||||
EdgeProj & proj);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the MWMs needed to build the route.
|
||||
*
|
||||
* Waits for the routing thread to finish and returns the list of MWM names from it.
|
||||
*
|
||||
* @param countries Receives the list of MWM names.
|
||||
*/
|
||||
void GetAllRegions(std::set<std::string> & countries);
|
||||
|
||||
private:
|
||||
/// Worker thread function
|
||||
void ThreadFunc();
|
||||
|
||||
@@ -85,6 +85,7 @@ void DirectionsEngine::LoadPathAttributes(FeatureID const & featureId, LoadedPat
|
||||
pathSegment.m_isOneWay = m_onewayChecker(types);
|
||||
|
||||
pathSegment.m_roadNameInfo.m_isLink = pathSegment.m_isLink;
|
||||
pathSegment.m_roadNameInfo.m_onRoundabout = pathSegment.m_onRoundabout;
|
||||
pathSegment.m_roadNameInfo.m_junction_ref = ft->GetMetadata(feature::Metadata::FMD_JUNCTION_REF);
|
||||
pathSegment.m_roadNameInfo.m_destination_ref = ft->GetMetadata(feature::Metadata::FMD_DESTINATION_REF);
|
||||
pathSegment.m_roadNameInfo.m_destination = ft->GetMetadata(feature::Metadata::FMD_DESTINATION);
|
||||
|
||||
@@ -33,9 +33,18 @@ public:
|
||||
|
||||
// @TODO(bykoianko) Method Generate() should fill
|
||||
// vector<RouteSegment> instead of corresponding arguments.
|
||||
/// \brief Generates all args which are passed by reference.
|
||||
/// \param path is points of the route. It should not be empty.
|
||||
/// \returns true if fields passed by reference are filled correctly and false otherwise.
|
||||
/**
|
||||
* @brief Calculates segments from a path on a route graph.
|
||||
*
|
||||
* Segments are calculated from `graph` (the route graph) and `path` (points on the route); each
|
||||
* pair of consecutive points becomes a segment.
|
||||
*
|
||||
* @param graph The route graph
|
||||
* @param path The route path, an ordered list of points on the route
|
||||
* @param cancellable
|
||||
* @param routeSegments Receives the list of segments
|
||||
* @return true on successful completion, false if cancelled or an error occurred
|
||||
*/
|
||||
bool Generate(IndexRoadGraph const & graph, std::vector<geometry::PointWithAltitude> const & path,
|
||||
base::Cancellable const & cancellable, std::vector<RouteSegment> & routeSegments);
|
||||
void Clear();
|
||||
|
||||
@@ -22,41 +22,170 @@ class TrafficStash;
|
||||
class EdgeEstimator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief The purpose for which cost calculations are to be used.
|
||||
*
|
||||
* A number of cost estimation functions take `Purpose` as an argument and may return different
|
||||
* values depending on the value of that argument.
|
||||
*/
|
||||
enum class Purpose
|
||||
{
|
||||
/**
|
||||
* @brief Indicates that cost calculations are for the purpose of choosing the best route.
|
||||
*/
|
||||
Weight,
|
||||
/**
|
||||
* @brief Indicates that cost calculations are for the purpose of calculating the estimated time
|
||||
* of arrival.
|
||||
*/
|
||||
ETA
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constructs a new `EdgeEstimator`.
|
||||
*
|
||||
* @param vehicleType The vehicle type.
|
||||
* @param maxWeightSpeedKMpH The maximum speed for the vehicle on a road.
|
||||
* @param offroadSpeedKMpH The maximum speed for the vehicle on an off-road link.
|
||||
* @param dataSourcePtr
|
||||
* @param numMwmIds
|
||||
*/
|
||||
EdgeEstimator(VehicleType vehicleType, double maxWeightSpeedKMpH, SpeedKMpH const & offroadSpeedKMpH,
|
||||
DataSource * dataSourcePtr = nullptr, std::shared_ptr<NumMwmIds> numMwmIds = nullptr);
|
||||
virtual ~EdgeEstimator() = default;
|
||||
|
||||
/**
|
||||
* @brief Calculates the heuristic for two points.
|
||||
*
|
||||
* The heuristic is used by the A* routing algorithm when choosing the next point to examine. It
|
||||
* must be less than, or equal to, the lowest possible cost of traveling from one point to the
|
||||
* other. Zero is an admissible heuristic, but effectively downgrades the A* algorithm to behave
|
||||
* exactly like the Dijkstra algorithm, of which A* is an improved version. A good heuristic is as
|
||||
* close as possible to the actual cost, without violating the aforementioned requirement.
|
||||
*
|
||||
* @param from The start point for the part of the route for which the heuristic is to be calculated.
|
||||
* @param to The destination point for the part of the route for which the heuristic is to be calculated.
|
||||
* @return The heuristic, expressed as travel time in seconds.
|
||||
*/
|
||||
double CalcHeuristic(ms::LatLon const & from, ms::LatLon const & to) const;
|
||||
// Estimates time in seconds it takes to go from point |from| to point |to| along a leap (fake)
|
||||
// edge |from|-|to| using real features.
|
||||
// Note 1. The result of the method should be used if it's necessary to add a leap (fake) edge
|
||||
// (|from|, |to|) in road graph.
|
||||
// Note 2. The result of the method should be less or equal to CalcHeuristic(|from|, |to|).
|
||||
// Note 3. It's assumed here that CalcLeapWeight(p1, p2) == CalcLeapWeight(p2, p1).
|
||||
|
||||
/**
|
||||
* @brief Estimates travel time between two points along a leap (fake) edge using real features.
|
||||
*
|
||||
* Estimates time in seconds it takes to go from point `from` to point `to` along a leap (fake)
|
||||
* edge `from`-`to` using real features.
|
||||
*
|
||||
* Note 1. The result of the method should be used if it is necessary to add a leap (fake) edge
|
||||
* (`from`, `to`) in road graph.
|
||||
*
|
||||
* Note 2. The result of the method should be less or equal to `CalcHeuristic(from, to)`.
|
||||
*
|
||||
* Note 3. It is assumed here that `CalcLeapWeight(p1, p2) == CalcLeapWeight(p2, p1)`.
|
||||
*
|
||||
* @todo Note 2 looks like a typo, presumably the result of this method should be no less than the
|
||||
* heuristic (otherwise the heuristic might not satisfy the requirements of A*).
|
||||
*
|
||||
* @param from The start point.
|
||||
* @param to The destination point.
|
||||
* @param mwmId
|
||||
* @return Travel time in seconds.
|
||||
*/
|
||||
double CalcLeapWeight(ms::LatLon const & from, ms::LatLon const & to, NumMwmId mwmId = kFakeNumMwmId);
|
||||
|
||||
/**
|
||||
* @brief Returns the maximum speed this `EdgeEstimator` instance assumes for any road.
|
||||
* @return The speed in m/s.
|
||||
*/
|
||||
double GetMaxWeightSpeedMpS() const;
|
||||
|
||||
// Estimates time in seconds it takes to go from point |from| to point |to| along direct fake edge.
|
||||
double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const;
|
||||
/**
|
||||
* @brief Estimates travel time between two points along a direct fake edge.
|
||||
*
|
||||
* Estimates time in seconds it takes to go from point `from` to point `to` along direct fake edge.
|
||||
*
|
||||
* @param from The start point.
|
||||
* @param to The destination point.
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @return Travel time in seconds.
|
||||
*/
|
||||
virtual double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose purpose) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the travel time along a segment.
|
||||
*
|
||||
* @param segment The segment.
|
||||
* @param road The road geometry (speed, restrictions, points) for the road which the segment is a part of.
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @return Travel time in seconds.
|
||||
*/
|
||||
virtual double CalcSegmentWeight(Segment const & segment, RoadGeometry const & road, Purpose purpose) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the penalty for making a U turn.
|
||||
*
|
||||
* The penalty is a fixed amount of time, determined by the implementation.
|
||||
*
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @return The penalty in seconds.
|
||||
*/
|
||||
virtual double GetUTurnPenalty(Purpose purpose) const = 0;
|
||||
|
||||
virtual double GetTurnPenalty(Purpose purpose, double angle, RoadGeometry const & from_road,
|
||||
RoadGeometry const & to_road, bool is_left_hand_traffic = false) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the penalty for using a ferry or rail transit link.
|
||||
*
|
||||
* The penalty is a fixed amount of time, determined by the implementation. It applies once per
|
||||
* link, hence it needs to cover the sum of the time for boarding and unboarding.
|
||||
*
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @return The penalty in seconds.
|
||||
*/
|
||||
virtual double GetFerryLandingPenalty(Purpose purpose) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Whether access restrictions are ignored.
|
||||
*
|
||||
* A return value of false indicates that access restrictions should be observed, which is the
|
||||
* default behavior for a routing use case. If true, it indicates that routing should ignore
|
||||
* access restrictions. This is needed to resolve traffic message locations; it could also be
|
||||
* used e.g. for emergency vehicle use cases.
|
||||
*
|
||||
* This implementation always returns false.
|
||||
*/
|
||||
virtual bool IsAccessIgnored() { return false; }
|
||||
|
||||
/**
|
||||
* @brief Creates an `EdgeEstimator` based on maximum speeds.
|
||||
*
|
||||
* @param vehicleType The vehicle type.
|
||||
* @param maxWeighSpeedKMpH The maximum speed for the vehicle on a road.
|
||||
* @param offroadSpeedKMpH The maximum speed for the vehicle on an off-road link.
|
||||
* @param trafficStash The traffic stash (used only for some vehicle types).
|
||||
* @param dataSourcePtr
|
||||
* @param numMwmIds
|
||||
* @return The `EdgeEstimator` instance.
|
||||
*/
|
||||
static std::shared_ptr<EdgeEstimator> Create(VehicleType vehicleType, double maxWeighSpeedKMpH,
|
||||
SpeedKMpH const & offroadSpeedKMpH,
|
||||
std::shared_ptr<TrafficStash> trafficStash, DataSource * dataSourcePtr,
|
||||
std::shared_ptr<NumMwmIds> numMwmIds);
|
||||
|
||||
/**
|
||||
* @brief Creates an `EdgeEstimator` based on a vehicle model.
|
||||
*
|
||||
* This is a convenience wrapper around `Create(VehicleType, double, SpeedKMpH const &,
|
||||
* std::shared_ptr<TrafficStash>, DataSource *, std::shared_ptr<NumMwmIds>)`, which takes a
|
||||
* `VehicleModel` and derives the maximum speeds for the vehicle from that.
|
||||
*
|
||||
* @param vehicleType The vehicle type.
|
||||
* @param vehicleModel
|
||||
* @param trafficStash The traffic stash (used only for some vehicle types).
|
||||
* @param dataSourcePtr
|
||||
* @param numMwmIds
|
||||
* @return The `EdgeEstimator` instance.
|
||||
*/
|
||||
static std::shared_ptr<EdgeEstimator> Create(VehicleType vehicleType, VehicleModelInterface const & vehicleModel,
|
||||
std::shared_ptr<TrafficStash> trafficStash, DataSource * dataSourcePtr,
|
||||
std::shared_ptr<NumMwmIds> numMwmIds);
|
||||
@@ -74,13 +203,70 @@ private:
|
||||
// std::shared_ptr<NumMwmIds> m_numMwmIds;
|
||||
// std::unordered_map<NumMwmId, double> m_leapWeightSpeedMpS;
|
||||
|
||||
/**
|
||||
* @brief Computes the default speed for leap (fake) segments.
|
||||
*
|
||||
* The result is used by `GetLeapWeightSpeed()`.
|
||||
*
|
||||
* @return Speed in m/s.
|
||||
*/
|
||||
double ComputeDefaultLeapWeightSpeed() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the deafult speed for leap (fake) segments for a given MWM.
|
||||
* @param mwmId
|
||||
* @return Speed in m/s.
|
||||
*/
|
||||
double GetLeapWeightSpeed(NumMwmId mwmId);
|
||||
// double LoadLeapWeightSpeed(NumMwmId mwmId);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Calculates the climb penalty for pedestrians.
|
||||
*
|
||||
* The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill
|
||||
* or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed.
|
||||
*
|
||||
* The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the
|
||||
* altitude (allowing for different penalties at greater altitudes).
|
||||
*
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent).
|
||||
* @param altitudeM The altitude in meters.
|
||||
* @return The climb penalty, as a factor.
|
||||
*/
|
||||
double GetPedestrianClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM);
|
||||
|
||||
/**
|
||||
* @brief Calculates the climb penalty for cyclists.
|
||||
*
|
||||
* The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill
|
||||
* or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed.
|
||||
*
|
||||
* The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the
|
||||
* altitude (allowing for different penalties at greater altitudes).
|
||||
*
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent).
|
||||
* @param altitudeM The altitude in meters.
|
||||
* @return The climb penalty, as a factor.
|
||||
*/
|
||||
double GetBicycleClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM);
|
||||
|
||||
/**
|
||||
* @brief Calculates the climb penalty for cars.
|
||||
*
|
||||
* The climb penalty is a factor which can be multiplied with the cost of an edge which goes uphill
|
||||
* or downhill. The factor for no penalty is 1, i.e. the cost of the edge is not changed.
|
||||
*
|
||||
* The climb penalty may depend on the mode of transportation, the ascent or descent, as well as the
|
||||
* altitude (allowing for different penalties at greater altitudes).
|
||||
*
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @param tangent The tangent of the ascent or descent (10% would be 0.1 for ascent, -0.1 for descent).
|
||||
* @param altitudeM The altitude in meters.
|
||||
* @return The climb penalty, as a factor.
|
||||
*/
|
||||
double GetCarClimbPenalty(EdgeEstimator::Purpose purpose, double tangent, geometry::Altitude altitudeM);
|
||||
|
||||
} // namespace routing
|
||||
|
||||
@@ -18,12 +18,12 @@ using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
LatLonWithAltitude CalcProjectionToSegment(LatLonWithAltitude const & begin, LatLonWithAltitude const & end,
|
||||
m2::PointD const & point)
|
||||
m2::PointD const & point, bool snapToEnds)
|
||||
{
|
||||
m2::ParametrizedSegment<m2::PointD> segment(mercator::FromLatLon(begin.GetLatLon()),
|
||||
mercator::FromLatLon(end.GetLatLon()));
|
||||
|
||||
auto const projectedPoint = segment.ClosestPointTo(point);
|
||||
auto const projectedPoint = segment.ClosestPointTo(point, snapToEnds);
|
||||
auto const distBeginToEnd = ms::DistanceOnEarth(begin.GetLatLon(), end.GetLatLon());
|
||||
|
||||
auto const projectedLatLon = mercator::ToLatLon(projectedPoint);
|
||||
@@ -45,7 +45,8 @@ bool Projection::operator==(Projection const & other) const
|
||||
tie(other.m_segment, other.m_isOneWay, other.m_segmentFront, other.m_segmentBack, other.m_junction);
|
||||
}
|
||||
|
||||
FakeEnding MakeFakeEnding(vector<Segment> const & segments, m2::PointD const & point, WorldGraph & graph)
|
||||
FakeEnding MakeFakeEnding(vector<Segment> const & segments, m2::PointD const & point,
|
||||
WorldGraph & graph, bool snapToEnds)
|
||||
{
|
||||
FakeEnding ending;
|
||||
double averageAltitude = 0.0;
|
||||
@@ -57,7 +58,7 @@ FakeEnding MakeFakeEnding(vector<Segment> const & segments, m2::PointD const & p
|
||||
bool const oneWay = graph.IsOneWay(segment.GetMwmId(), segment.GetFeatureId());
|
||||
auto const & frontJunction = graph.GetJunction(segment, true /* front */);
|
||||
auto const & backJunction = graph.GetJunction(segment, false /* front */);
|
||||
auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point);
|
||||
auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point, snapToEnds);
|
||||
|
||||
ending.m_projections.emplace_back(segment, oneWay, frontJunction, backJunction, projectedJunction);
|
||||
|
||||
@@ -69,13 +70,14 @@ FakeEnding MakeFakeEnding(vector<Segment> const & segments, m2::PointD const & p
|
||||
return ending;
|
||||
}
|
||||
|
||||
FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph)
|
||||
FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph,
|
||||
bool snapToEnds)
|
||||
{
|
||||
auto const & road = graph.GetRoadGeometry(segment.GetFeatureId());
|
||||
bool const oneWay = road.IsOneWay();
|
||||
auto const & frontJunction = road.GetJunction(segment.GetPointId(true /* front */));
|
||||
auto const & backJunction = road.GetJunction(segment.GetPointId(false /* front */));
|
||||
auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point);
|
||||
auto const & projectedJunction = CalcProjectionToSegment(backJunction, frontJunction, point, snapToEnds);
|
||||
|
||||
FakeEnding ending;
|
||||
ending.m_originJunction = LatLonWithAltitude(mercator::ToLatLon(point), projectedJunction.GetAltitude());
|
||||
|
||||
@@ -40,9 +40,11 @@ struct FakeEnding final
|
||||
std::vector<Projection> m_projections;
|
||||
};
|
||||
|
||||
FakeEnding MakeFakeEnding(std::vector<Segment> const & segments, m2::PointD const & point, WorldGraph & graph);
|
||||
FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph);
|
||||
FakeEnding MakeFakeEnding(std::vector<Segment> const & segments, m2::PointD const & point,
|
||||
WorldGraph & graph, bool snapToEnds = false);
|
||||
FakeEnding MakeFakeEnding(Segment const & segment, m2::PointD const & point, IndexGraph & graph,
|
||||
bool snapToEnds = false);
|
||||
|
||||
LatLonWithAltitude CalcProjectionToSegment(LatLonWithAltitude const & begin, LatLonWithAltitude const & end,
|
||||
m2::PointD const & point);
|
||||
m2::PointD const & point, bool snapToEnds = false);
|
||||
} // namespace routing
|
||||
|
||||
@@ -143,9 +143,10 @@ void FeaturesRoadGraphBase::ForEachFeatureClosestToCross(m2::PointD const & cros
|
||||
}
|
||||
|
||||
void FeaturesRoadGraphBase::FindClosestEdges(m2::RectD const & rect, uint32_t count,
|
||||
vector<pair<Edge, geometry::PointWithAltitude>> & vicinities) const
|
||||
vector<pair<Edge, geometry::PointWithAltitude>> & vicinities,
|
||||
bool snapToEnds) const
|
||||
{
|
||||
NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */);
|
||||
NearestEdgeFinder finder(rect.Center(), nullptr /* IsEdgeProjGood */, snapToEnds);
|
||||
|
||||
m_dataSource.ForEachStreet([&](FeatureType & ft)
|
||||
{
|
||||
|
||||
@@ -84,8 +84,21 @@ public:
|
||||
/// @name IRoadGraph overrides
|
||||
/// @{
|
||||
void ForEachFeatureClosestToCross(m2::PointD const & cross, ICrossEdgesLoader & edgesLoader) const override;
|
||||
|
||||
/**
|
||||
* @brief Finds the closest edges to a reference point within a given distance.
|
||||
*
|
||||
* @param rect A rectangle. Its center is the reference point; the search distance is expressed
|
||||
* through the height and width.
|
||||
* @param count The number of results to return.
|
||||
* @param vicinities Receives the results.
|
||||
* @param snapToEnds If true, the projection point (the point on the edge closest to the reference
|
||||
* point) is constrained to one of the edge endpoints; if false, it can be anywhere on the edge.
|
||||
*/
|
||||
void FindClosestEdges(m2::RectD const & rect, uint32_t count,
|
||||
std::vector<std::pair<Edge, geometry::PointWithAltitude>> & vicinities) const override;
|
||||
std::vector<std::pair<Edge, geometry::PointWithAltitude>> & vicinities,
|
||||
bool snapToEnds = false) const override;
|
||||
|
||||
std::vector<IRoadGraph::FullRoadInfo> FindRoads(m2::RectD const & rect,
|
||||
IsGoodFeatureFn const & isGoodFeature) const override;
|
||||
void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const override;
|
||||
|
||||
@@ -217,6 +217,9 @@ template <typename AccessPositionType>
|
||||
bool IndexGraph::IsAccessNoForSure(AccessPositionType const & accessPositionType, RouteWeight const & weight,
|
||||
bool useAccessConditional) const
|
||||
{
|
||||
if (m_estimator->IsAccessIgnored())
|
||||
return false;
|
||||
|
||||
auto const [accessType, confidence] = useAccessConditional
|
||||
? m_roadAccess.GetAccess(accessPositionType, weight)
|
||||
: m_roadAccess.GetAccessWithoutConditional(accessPositionType);
|
||||
|
||||
@@ -123,10 +123,10 @@ unique_ptr<DirectionsEngine> CreateDirectionsEngine(VehicleType vehicleType, sha
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
shared_ptr<TrafficStash> CreateTrafficStash(VehicleType, shared_ptr<NumMwmIds>, traffic::TrafficCache const &)
|
||||
shared_ptr<TrafficStash> CreateTrafficStash(VehicleType vehicleType, shared_ptr<NumMwmIds> numMwmIds,
|
||||
traffic::TrafficCache const & trafficCache)
|
||||
{
|
||||
return nullptr;
|
||||
// return (vehicleType == VehicleType::Car ? make_shared<TrafficStash>(trafficCache, numMwmIds) : nullptr);
|
||||
return (vehicleType == VehicleType::Car ? make_shared<TrafficStash>(trafficCache, numMwmIds) : nullptr);
|
||||
}
|
||||
|
||||
void PushPassedSubroutes(Checkpoints const & checkpoints, vector<Route::SubrouteAttrs> & subroutes)
|
||||
@@ -262,6 +262,37 @@ IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes,
|
||||
CHECK(m_directionsEngine, ());
|
||||
}
|
||||
|
||||
IndexRouter::IndexRouter(VehicleType vehicleType, bool loadAltitudes,
|
||||
CountryParentNameGetterFn const & countryParentNameGetterFn,
|
||||
TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn,
|
||||
shared_ptr<NumMwmIds> numMwmIds, unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
|
||||
std::shared_ptr<EdgeEstimator> estimator, DataSource & dataSource)
|
||||
: m_vehicleType(vehicleType)
|
||||
, m_loadAltitudes(loadAltitudes)
|
||||
, m_name("astar-bidirectional-" + ToString(m_vehicleType))
|
||||
, m_dataSource(dataSource, numMwmIds)
|
||||
, m_vehicleModelFactory(CreateVehicleModelFactory(m_vehicleType, countryParentNameGetterFn))
|
||||
, m_countryFileFn(countryFileFn)
|
||||
, m_countryRectFn(countryRectFn)
|
||||
, m_numMwmIds(std::move(numMwmIds))
|
||||
, m_numMwmTree(std::move(numMwmTree))
|
||||
, m_trafficStash(nullptr)
|
||||
, m_roadGraph(m_dataSource,
|
||||
vehicleType == VehicleType::Pedestrian || vehicleType == VehicleType::Transit
|
||||
? IRoadGraph::Mode::IgnoreOnewayTag
|
||||
: IRoadGraph::Mode::ObeyOnewayTag,
|
||||
m_vehicleModelFactory)
|
||||
, m_estimator(estimator)
|
||||
, m_directionsEngine(CreateDirectionsEngine(m_vehicleType, m_numMwmIds, m_dataSource))
|
||||
, m_countryParentNameGetterFn(countryParentNameGetterFn)
|
||||
{
|
||||
CHECK(!m_name.empty(), ());
|
||||
CHECK(m_numMwmIds, ());
|
||||
CHECK(m_numMwmTree, ());
|
||||
CHECK(m_estimator, ());
|
||||
CHECK(m_directionsEngine, ());
|
||||
}
|
||||
|
||||
unique_ptr<WorldGraph> IndexRouter::MakeSingleMwmWorldGraph()
|
||||
{
|
||||
auto worldGraph = MakeWorldGraph();
|
||||
@@ -283,7 +314,7 @@ bool IndexRouter::FindClosestProjectionToRoad(m2::PointD const & point, m2::Poin
|
||||
std::vector<EdgeProjectionT> candidates;
|
||||
|
||||
uint32_t const count = direction.IsAlmostZero() ? 1 : 4;
|
||||
m_roadGraph.FindClosestEdges(rect, count, candidates);
|
||||
m_roadGraph.FindClosestEdges(rect, count, candidates, (GetMode() == Mode::Decoding));
|
||||
|
||||
if (candidates.empty())
|
||||
return false;
|
||||
@@ -516,7 +547,7 @@ RouterResultCode IndexRouter::DoCalculateRoute(Checkpoints const & checkpoints,
|
||||
guidesMwmId = m_numMwmIds->GetId(country);
|
||||
}
|
||||
|
||||
if (!route.GetAbsentCountries().empty())
|
||||
if ((GetMode() == Mode::Navigation) && !route.GetAbsentCountries().empty())
|
||||
return RouterResultCode::NeedMoreMaps;
|
||||
|
||||
TrafficStash::Guard guard(m_trafficStash);
|
||||
@@ -1060,10 +1091,15 @@ RouterResultCode IndexRouter::AdjustRoute(Checkpoints const & checkpoints, m2::P
|
||||
return RouterResultCode::NoError;
|
||||
}
|
||||
|
||||
RoutingOptions IndexRouter::GetRoutingOptions()
|
||||
{
|
||||
return RoutingOptions::LoadCarOptionsFromSettings();
|
||||
}
|
||||
|
||||
unique_ptr<WorldGraph> IndexRouter::MakeWorldGraph()
|
||||
{
|
||||
// Use saved routing options for all types (car, bicycle, pedestrian).
|
||||
RoutingOptions const routingOptions = RoutingOptions::LoadCarOptionsFromSettings();
|
||||
RoutingOptions const routingOptions = GetRoutingOptions();
|
||||
/// @DebugNote
|
||||
// Add avoid roads here for debug purpose.
|
||||
// routingOptions.Add(RoutingOptions::Road::Motorway);
|
||||
@@ -1110,10 +1146,10 @@ int IndexRouter::PointsOnEdgesSnapping::Snap(m2::PointD const & start, m2::Point
|
||||
|
||||
// One of startEnding or finishEnding will be empty here.
|
||||
if (startEnding.m_projections.empty())
|
||||
startEnding = MakeFakeEnding(m_startSegments, start, m_graph);
|
||||
startEnding = MakeFakeEnding(m_startSegments, start, m_graph, (m_router.GetMode() == Mode::Decoding));
|
||||
|
||||
if (finishEnding.m_projections.empty())
|
||||
finishEnding = MakeFakeEnding(finishSegments, finish, m_graph);
|
||||
finishEnding = MakeFakeEnding(finishSegments, finish, m_graph, (m_router.GetMode() == Mode::Decoding));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1195,12 +1231,11 @@ bool IndexRouter::PointsOnEdgesSnapping::IsFencedOff(m2::PointD const & point, E
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
void IndexRouter::PointsOnEdgesSnapping::RoadsToNearestEdges(m2::PointD const & point, vector<RoadInfoT> const & roads,
|
||||
IsEdgeProjGood const & isGood,
|
||||
vector<EdgeProjectionT> & edgeProj)
|
||||
{
|
||||
NearestEdgeFinder finder(point, isGood);
|
||||
NearestEdgeFinder finder(point, isGood, (m_router.GetMode() == Mode::Decoding));
|
||||
for (auto const & road : roads)
|
||||
finder.AddInformationSource(road);
|
||||
|
||||
@@ -1324,7 +1359,7 @@ bool IndexRouter::PointsOnEdgesSnapping::FindBestEdges(m2::PointD const & checkp
|
||||
}
|
||||
|
||||
// Removing all candidates which are fenced off by the road graph (|closestRoads|) from |checkpoint|.
|
||||
return !IsFencedOff(checkpoint, edgeProj, closestRoads);
|
||||
return (m_router.GetMode() == Mode::Decoding) || !IsFencedOff(checkpoint, edgeProj, closestRoads);
|
||||
};
|
||||
|
||||
// Getting closest edges from |closestRoads| if they are correct according to isGood() function.
|
||||
|
||||
@@ -44,6 +44,24 @@ class IndexGraphStarter;
|
||||
class IndexRouter : public IRouter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Indicates the mode in which the router is operating.
|
||||
*
|
||||
* The mode controls some aspects of router behavior, such as asking for additional maps or how
|
||||
* checkpoints are matched to nearby segments.
|
||||
*/
|
||||
enum Mode
|
||||
{
|
||||
/**
|
||||
* Router mode for navigation, i.e. user-initiated route guidance.
|
||||
*/
|
||||
Navigation,
|
||||
/**
|
||||
* Router mode for location decoding.
|
||||
*/
|
||||
Decoding
|
||||
};
|
||||
|
||||
class BestEdgeComparator final
|
||||
{
|
||||
public:
|
||||
@@ -68,6 +86,24 @@ public:
|
||||
m2::PointD const m_direction;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Creates a new `IndexRouter` instance.
|
||||
*
|
||||
* This is the constructor intended for normal routing. It requires a `TrafficCache` argument,
|
||||
* from which it may create a traffic stash so the traffic situation can be considered for the
|
||||
* route, depending on the vehicle type.
|
||||
*
|
||||
* @param vehicleType The vehichle type
|
||||
* @param loadAltitudes Whether to load altitudes
|
||||
* @param countryParentNameGetterFn Function which converts a country name into the name of its parent country)
|
||||
* @param countryFileFn Function which converts a pointer to its country name
|
||||
* @param countryRectFn Function which returns the rect for a country
|
||||
* @param numMwmIds MWMs to use for route calculation (this should include all MWMs, whether or
|
||||
* not we have the file locally, but not World or WorldCoasts)
|
||||
* @param numMwmTree
|
||||
* @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`)
|
||||
* @param dataSource The MWM data source
|
||||
*/
|
||||
IndexRouter(VehicleType vehicleType, bool loadAltitudes, CountryParentNameGetterFn const & countryParentNameGetterFn,
|
||||
TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn,
|
||||
std::shared_ptr<NumMwmIds> numMwmIds, std::unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
|
||||
@@ -90,6 +126,63 @@ public:
|
||||
|
||||
VehicleType GetVehicleType() const { return m_vehicleType; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Creates a new `IndexRouter` instance.
|
||||
*
|
||||
* This constructor is intended for use by the TraFF decoder, not for normal routing. It differs
|
||||
* from the general-purpose constructor in two ways.
|
||||
*
|
||||
* It takes an explicit `EdgeEstimator` argument, instance, which gives the caller fine-grained
|
||||
* control over the cost calculations used for routing by supplying an `EdgeEstimator` of their
|
||||
* choice.
|
||||
*
|
||||
* It also lacks the `TrafficCache` argument and never creates a traffic stash. This creates a
|
||||
* router instance which ignores the traffic situation, regardless of the vehicle type.
|
||||
*
|
||||
* @param vehicleType The vehichle type
|
||||
* @param loadAltitudes Whether to load altitudes
|
||||
* @param countryParentNameGetterFn Function which converts a country name into the name of its parent country)
|
||||
* @param countryFileFn Function which converts a pointer to its country name
|
||||
* @param countryRectFn Function which returns the rect for a country
|
||||
* @param numMwmIds
|
||||
* @param numMwmTree
|
||||
* @param estimator An edge estimator
|
||||
* @param dataSource The MWM data source
|
||||
*/
|
||||
IndexRouter(VehicleType vehicleType, bool loadAltitudes,
|
||||
CountryParentNameGetterFn const & countryParentNameGetterFn,
|
||||
TCountryFileFn const & countryFileFn, CountryRectFn const & countryRectFn,
|
||||
std::shared_ptr<NumMwmIds> numMwmIds, std::unique_ptr<m4::Tree<NumMwmId>> numMwmTree,
|
||||
std::shared_ptr<EdgeEstimator> estimator, DataSource & dataSource);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the mode in which the router is operating.
|
||||
*
|
||||
* The `IndexRouter` always returns `Mode::Navigation`; subclasses may override this method and
|
||||
* return different values.
|
||||
*
|
||||
* In navigation mode, the router may exit with `RouterResultCode::NeedMoreMaps` if it determines
|
||||
* that a better route can be calculated with additional maps. When snapping endpoints to edges,
|
||||
* it will consider only edges which are not “fenced off” by other edges, i.e. which can be
|
||||
* reached from the endpoint without crossing other edges. This decreases the number of fake
|
||||
* endings and thus speeds up routing, without any undesirable side effects for that use case.
|
||||
*
|
||||
* In decoding mode, the router will never exit with `RouterResultCode::NeedMoreMaps`: it will try
|
||||
* to find a route with the existing maps, or exit without finding a route. When snapping
|
||||
* endpoints to edges, it considers all edges within the given radius, fenced off or not.
|
||||
*/
|
||||
virtual Mode GetMode() { return Mode::Navigation; }
|
||||
|
||||
/**
|
||||
* @brief Returns current routing options.
|
||||
*
|
||||
* In this class, the routing options are the one set in the GUI. Subclasses may override this
|
||||
* method to provide different routing options.
|
||||
*/
|
||||
virtual RoutingOptions GetRoutingOptions();
|
||||
|
||||
private:
|
||||
RouterResultCode CalculateSubrouteJointsMode(IndexGraphStarter & starter, RouterDelegate const & delegate,
|
||||
std::shared_ptr<AStarProgress> const & progress,
|
||||
@@ -154,8 +247,8 @@ private:
|
||||
static bool IsFencedOff(m2::PointD const & point, EdgeProjectionT const & edgeProjection,
|
||||
std::vector<RoadInfoT> const & fences);
|
||||
|
||||
static void RoadsToNearestEdges(m2::PointD const & point, std::vector<RoadInfoT> const & roads,
|
||||
IsEdgeProjGood const & isGood, std::vector<EdgeProjectionT> & edgeProj);
|
||||
void RoadsToNearestEdges(m2::PointD const & point, std::vector<RoadInfoT> const & roads,
|
||||
IsEdgeProjGood const & isGood, std::vector<EdgeProjectionT> & edgeProj);
|
||||
|
||||
Segment GetSegmentByEdge(Edge const & edge) const;
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ namespace routing
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
NearestEdgeFinder::NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood)
|
||||
NearestEdgeFinder::NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood,
|
||||
bool snapToEnds)
|
||||
: m_point(point)
|
||||
, m_isEdgeProjGood(isEdgeProjGood)
|
||||
, m_snapToEnds(snapToEnds)
|
||||
{}
|
||||
|
||||
void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & roadInfo)
|
||||
@@ -28,7 +30,7 @@ void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & ro
|
||||
{
|
||||
m2::ParametrizedSegment<m2::PointD> segment(junctions[i - 1].GetPoint(), junctions[i].GetPoint());
|
||||
|
||||
m2::PointD const closestPoint = segment.ClosestPointTo(m_point);
|
||||
m2::PointD const closestPoint = segment.ClosestPointTo(m_point, m_snapToEnds);
|
||||
double const squaredDist = m_point.SquaredLength(closestPoint);
|
||||
|
||||
if (squaredDist < res.m_squaredDist)
|
||||
@@ -48,7 +50,7 @@ void NearestEdgeFinder::AddInformationSource(IRoadGraph::FullRoadInfo const & ro
|
||||
geometry::Altitude const startAlt = segStart.GetAltitude();
|
||||
geometry::Altitude const endAlt = segEnd.GetAltitude();
|
||||
m2::ParametrizedSegment<m2::PointD> segment(junctions[idx - 1].GetPoint(), junctions[idx].GetPoint());
|
||||
m2::PointD const closestPoint = segment.ClosestPointTo(m_point);
|
||||
m2::PointD const closestPoint = segment.ClosestPointTo(m_point, m_snapToEnds);
|
||||
|
||||
double const segLenM = mercator::DistanceOnEarth(segStart.GetPoint(), segEnd.GetPoint());
|
||||
geometry::Altitude projPointAlt = geometry::kDefaultAltitudeMeters;
|
||||
|
||||
@@ -27,7 +27,12 @@ using IsEdgeProjGood = std::function<bool(std::pair<Edge, geometry::PointWithAlt
|
||||
class NearestEdgeFinder
|
||||
{
|
||||
public:
|
||||
NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood);
|
||||
/**
|
||||
* @param snapToEnds If false, projections of `point` can be anywhere on a segment;
|
||||
* if true, `point` is projected onto the nearest segment endpoint.
|
||||
*/
|
||||
NearestEdgeFinder(m2::PointD const & point, IsEdgeProjGood const & isEdgeProjGood,
|
||||
bool snapToEnds = false);
|
||||
|
||||
inline bool HasCandidates() const { return !m_candidates.empty(); }
|
||||
|
||||
@@ -56,6 +61,7 @@ private:
|
||||
std::vector<EdgeProjectionT> & res) const;
|
||||
|
||||
m2::PointD const m_point;
|
||||
bool m_snapToEnds;
|
||||
std::vector<Candidate> m_candidates;
|
||||
IsEdgeProjGood m_isEdgeProjGood;
|
||||
};
|
||||
|
||||
@@ -263,7 +263,7 @@ public:
|
||||
/// then returns empty array.
|
||||
using EdgeProjectionT = std::pair<Edge, JunctionPointT>;
|
||||
virtual void FindClosestEdges(m2::RectD const & /*rect*/, uint32_t /*count*/,
|
||||
std::vector<EdgeProjectionT> & /*vicinities*/) const
|
||||
std::vector<EdgeProjectionT> & /*vicinities*/, bool snapToEnds) const
|
||||
{}
|
||||
|
||||
/// \returns Vector of pairs FeatureID and corresponding RoadInfo for road features
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
|
||||
namespace routing
|
||||
{
|
||||
// RoadPoint is a unique identifier for any road point in mwm file.
|
||||
//
|
||||
// Contains feature id and point id.
|
||||
// Point id is the ordinal number of the point in the road.
|
||||
/**
|
||||
* @brief A unique identifier for any point on a road in an mwm file.
|
||||
*
|
||||
* It contains a feature id and point id. The point id is the ordinal number of the point in the road.
|
||||
*/
|
||||
class RoadPoint final
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -39,8 +39,21 @@ namespace routing
|
||||
using SubrouteUid = uint64_t;
|
||||
SubrouteUid constexpr kInvalidSubrouteId = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
/// \brief The route is composed of one or several subroutes. Every subroute is composed of segments.
|
||||
/// For every Segment is kept some attributes in the structure SegmentInfo.
|
||||
/**
|
||||
* @brief A segment of the route.
|
||||
*
|
||||
* The route is composed of one or several subroutes. Every subroute is composed of segments.
|
||||
*
|
||||
* For every Segment, some attributes are kept in the `SegmentInfo` structure.
|
||||
*
|
||||
* @todo the statement regarding `SegmentInfo` seems to be outdated, is `SegmentInfo` a prececessor
|
||||
* of `RouteSegment`?
|
||||
*
|
||||
* Segment data which is actually related to a point, such as junction, distance and time, refer to
|
||||
* the end which is closer to the end of the route. For the first segment, distance and time are the
|
||||
* length and travel time of the segment itself. For the last segment, distance and time are the
|
||||
* length and travel time of the entore route, and the junction is the finish point.
|
||||
*/
|
||||
class RouteSegment final
|
||||
{
|
||||
public:
|
||||
@@ -69,17 +82,50 @@ public:
|
||||
uint8_t m_maxSpeedKmPH = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Holds structured information about a road.
|
||||
*
|
||||
* `m_ref` and `m_name` refer to the road itself.
|
||||
*
|
||||
* This structure is only populated for the first segment of a feature (segment index is either 0,
|
||||
* or one less than the segment count of the feature, or the segment is the first segment of the
|
||||
* route which is not a fake segment). For subsequent segments of the same feature, it is empty.
|
||||
*/
|
||||
struct RoadNameInfo
|
||||
{
|
||||
// This is for street/road. |m_ref| |m_name|.
|
||||
std::string m_name; // E.g "Johnson Ave.".
|
||||
std::string m_destination_ref; // Number of next road, e.g. "CA 85", Sometimes "CA 85 South". Usually match |m_ref|
|
||||
// of next main road.
|
||||
// This is for 1st segment of link after junction. Exit |junction_ref| to |m_destination_ref| for |m_destination|.
|
||||
std::string m_junction_ref; // Number of junction e.g. "398B".
|
||||
std::string m_destination; // E.g. "Cupertino".
|
||||
std::string m_ref; // Number of street/road e.g. "CA 85".
|
||||
/**
|
||||
* @brief The name of the road, e.g. “Johnson Ave”.
|
||||
*/
|
||||
std::string m_name;
|
||||
/**
|
||||
* @brief The number of the next road.
|
||||
*
|
||||
* This usually matches `m_ref` of the naxt main road, e.g. “CA 85”, sometimes “CA 85 South”.
|
||||
*/
|
||||
std::string m_destination_ref;
|
||||
/**
|
||||
* @brief The junction number, e.g. “398B”.
|
||||
*
|
||||
* This is used for the first link segment after a junction (exit `junction_ref` to
|
||||
* `m_destination_ref` for `m_destination`).
|
||||
*/
|
||||
std::string m_junction_ref;
|
||||
/**
|
||||
* @brief The destination of the road, e.g. “Cupertino”.
|
||||
*/
|
||||
std::string m_destination;
|
||||
/**
|
||||
* @brief The number of the road, e.g. “CA85”.
|
||||
*/
|
||||
std::string m_ref;
|
||||
/**
|
||||
* @brief Whether the road is of a link type.
|
||||
*/
|
||||
bool m_isLink = false;
|
||||
/**
|
||||
* @brief Whether the road is part of a roundabout.
|
||||
*/
|
||||
bool m_onRoundabout = false;
|
||||
|
||||
RoadNameInfo() = default;
|
||||
RoadNameInfo(std::string name) : m_name(std::move(name)) {}
|
||||
@@ -158,8 +204,30 @@ public:
|
||||
turns::TurnItem const & GetTurn() const { return m_turn; }
|
||||
void ClearTurnLanes() { m_turn.m_lanes.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Returns distance from the beginning of the route in meters.
|
||||
*
|
||||
* Distance is measured up to the end of the current segment, i.e. including the segment. For the
|
||||
* first segment, this is identical to the length of the segment; for the last segment, it is
|
||||
* identical to the length of the entire route.
|
||||
*
|
||||
* Note that the first and last real (non-fake) segment on the route may not report the actual
|
||||
* length between their endpoints, but only the part which is also part of the route.
|
||||
*/
|
||||
double GetDistFromBeginningMeters() const { return m_distFromBeginningMeters; }
|
||||
double GetDistFromBeginningMerc() const { return m_distFromBeginningMerc; }
|
||||
|
||||
/**
|
||||
* @brief Returns travel time from the beginning of the route.
|
||||
*
|
||||
* Travel time is the ETA from the beginning of the route to the end of the current segment, i.e.
|
||||
* including the segment. For the first segment, this is identical to the travel time along the
|
||||
* segment; for the last segment, it is identical to the travel time along the entire route.
|
||||
*
|
||||
* Note that the first and last real (non-fake) segment on the route may report time based not
|
||||
* upon the actual length between their endpoints, but upon the part which is also part of the
|
||||
* route.
|
||||
*/
|
||||
double GetTimeFromBeginningSec() const { return m_timeFromBeginningS; }
|
||||
|
||||
bool HasTransitInfo() const { return m_transitInfo.HasTransitInfo(); }
|
||||
|
||||
@@ -45,16 +45,49 @@ enum class RouterResultCode
|
||||
|
||||
enum class SessionState
|
||||
{
|
||||
NoValidRoute, // No valid route: no route after application launching or the route was removed.
|
||||
RouteBuilding, // We requested a route and wait when it will be built. User may be following
|
||||
// the previous route.
|
||||
RouteNotStarted, // Route is built but the user isn't on it.
|
||||
OnRoute, // User follows the route.
|
||||
RouteNeedRebuild, // User left the route.
|
||||
RouteFinished, // Destination point is reached but the session isn't closed.
|
||||
RouteNoFollowing, // Route is built but following mode has been disabled.
|
||||
RouteRebuilding, // We requested a route rebuild and wait when it will be rebuilt.
|
||||
// User may following the previous route.
|
||||
/**
|
||||
* No valid route: no route after application launching, or the route was removed.
|
||||
*
|
||||
* This is the initial state at launch; in order to get out of it, a destination must be set AND
|
||||
* the current location must be obtained.
|
||||
*/
|
||||
NoValidRoute,
|
||||
|
||||
/**
|
||||
* We have requested a route and are waiting for it to be built. User may be following the previous route.
|
||||
*/
|
||||
RouteBuilding,
|
||||
|
||||
/**
|
||||
* Route is built but the user isn't on it.
|
||||
*/
|
||||
RouteNotStarted,
|
||||
|
||||
/**
|
||||
* User is following the route.
|
||||
*/
|
||||
OnRoute,
|
||||
|
||||
/**
|
||||
* User has left the route.
|
||||
*/
|
||||
RouteNeedRebuild,
|
||||
|
||||
/**
|
||||
* Destination point has been reached but the session isn’t closed yet.
|
||||
*/
|
||||
RouteFinished,
|
||||
|
||||
/**
|
||||
* Route has been built but following mode has been disabled.
|
||||
*/
|
||||
RouteNoFollowing,
|
||||
|
||||
/**
|
||||
* We have requested a route rebuild and are waiting for it to be rebuilt. User may be following
|
||||
* the previous route.
|
||||
*/
|
||||
RouteRebuilding,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -61,6 +61,26 @@ bool IsRoad(Types const & types)
|
||||
|
||||
void FillSegmentInfo(std::vector<double> const & times, std::vector<RouteSegment> & routeSegments);
|
||||
|
||||
/**
|
||||
* @brief Constructs or reconstructs a route.
|
||||
*
|
||||
* This function populates `route` with segments and geometry. Segments are calculated from `graph`
|
||||
* (the route graph) and `path` (points on the route); each pair of consecutive points becomes a
|
||||
* segment. The actual calculation is delegated to `engine` and can be influenced by passing a
|
||||
* different directions engine. Segment information is then enriched with the length of each segment
|
||||
* (calculated directly) and the estimated travel time specified in `times`. Geometry is calculated
|
||||
* from `path` by extracting latitude and longitude from each item.
|
||||
*
|
||||
* The number of items in `times` must be equal to the number of segments, or the number of items in
|
||||
* `points` minus 1. The items of `times` are travel times from start, therefore no value can be
|
||||
* less than the previous one.
|
||||
*
|
||||
* @param engine The directions engine
|
||||
* @param graph The route graph
|
||||
* @param path The route path, an ordered list of points on the route
|
||||
* @param times Travel times (from start) for each segment
|
||||
* @param route The route
|
||||
*/
|
||||
void ReconstructRoute(DirectionsEngine & engine, IndexRoadGraph const & graph, base::Cancellable const & cancellable,
|
||||
std::vector<geometry::PointWithAltitude> const & path, std::vector<double> const & times,
|
||||
Route & route);
|
||||
|
||||
@@ -445,6 +445,13 @@ double RoutingSession::GetCompletionPercent() const
|
||||
return percent;
|
||||
}
|
||||
|
||||
void RoutingSession::GetAllRegions(std::set<std::string> & countries)
|
||||
{
|
||||
if (!m_router)
|
||||
return;
|
||||
m_router->GetAllRegions(countries);
|
||||
}
|
||||
|
||||
void RoutingSession::PassCheckpoints()
|
||||
{
|
||||
CHECK_THREAD_CHECKER(m_threadChecker, ());
|
||||
|
||||
@@ -40,9 +40,12 @@ class RouteMatchingInfo;
|
||||
|
||||
namespace routing
|
||||
{
|
||||
/// \breaf This class is responsible for the route built in the program.
|
||||
/// \note All method of this class should be called from ui thread if there's no
|
||||
/// a special note near a method.
|
||||
/**
|
||||
* @brief This class is responsible for the route built in the program.
|
||||
*
|
||||
* @note Methods of this class may only be called from the UI thread, unless the method
|
||||
* documentation states otherwise.
|
||||
*/
|
||||
class RoutingSession
|
||||
: public traffic::TrafficObserver
|
||||
, public traffic::TrafficCache
|
||||
@@ -66,24 +69,105 @@ public:
|
||||
m2::PointD GetStartPoint() const;
|
||||
m2::PointD GetEndPoint() const;
|
||||
|
||||
/**
|
||||
* @brief Whether routing is currently active.
|
||||
*
|
||||
* A route is considered active if a destination has been entered and the route has ben built or
|
||||
* is currently being built or rebuilt, even if the user is not currently following it.
|
||||
*
|
||||
* @return True if active, false if not
|
||||
*/
|
||||
bool IsActive() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the route is currently navigable.
|
||||
*
|
||||
* The route is considered navigable if it has been built and following mode is enabled, and the
|
||||
* user either has not yet started following the route, is currently following it or has reached
|
||||
* their destination but not yet closed the session. The route is no longer navigable if the user
|
||||
* leaves it.
|
||||
*
|
||||
* @return True if navigable, false if not.
|
||||
*/
|
||||
bool IsNavigable() const;
|
||||
|
||||
/**
|
||||
* @brief Whether a built route exists.
|
||||
*
|
||||
* A route is considered built if it is navigable (see `IsNavigable()`), but retains that state
|
||||
* even if the user leaves the route.
|
||||
*
|
||||
* @return True if built, false if not.
|
||||
*/
|
||||
bool IsBuilt() const;
|
||||
/// \returns true if a new route is in process of building rebuilding or
|
||||
/// if a route is being rebuilt in case the user left the route, and false otherwise.
|
||||
|
||||
/**
|
||||
* @brief Whether a route is currently being built or rebuilt.
|
||||
*
|
||||
* This is the case if a new route is in process of building, rebuilding, or if a route is being
|
||||
* rebuilt after the user has left the route.
|
||||
*
|
||||
* @return True if building or rebuilding, false otherwise.
|
||||
*/
|
||||
bool IsBuilding() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the route is currently being built from scratch.
|
||||
*
|
||||
* This is the case if a new route is currently being built, but not if an already existing route
|
||||
* is being rebuilt.
|
||||
*
|
||||
* @return True if building, false otherwise (also if rebuilding).
|
||||
*/
|
||||
bool IsBuildingOnly() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the route is currently being rebuilt.
|
||||
*
|
||||
* This is the case if an already existing route is being rebuilt, but not if a new route is
|
||||
* currently being built.
|
||||
*
|
||||
* @return True if rebuilding, false otherwise (also if building from scratch).
|
||||
*/
|
||||
bool IsRebuildingOnly() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the route is finished.
|
||||
*
|
||||
* The route is considered finished if the destination point has been reached but the session has
|
||||
* not been closed.
|
||||
*
|
||||
* @return True if finished, false otherwise.
|
||||
*/
|
||||
bool IsFinished() const;
|
||||
|
||||
/**
|
||||
* @brief Whether a route has been built while following mode is disabled.
|
||||
*
|
||||
* @return True if a route has been built and following mode disabled, false otherwise.
|
||||
*/
|
||||
bool IsNoFollowing() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the user is currently following a previously built route.
|
||||
*
|
||||
* This is the case if the route has been built, route following is enabled, the user has started
|
||||
* following the route and not left it.
|
||||
*
|
||||
* @return True if the user is following the route, false otherwise.
|
||||
*/
|
||||
bool IsOnRoute() const;
|
||||
|
||||
bool IsFollowing() const;
|
||||
void Reset();
|
||||
|
||||
void SetState(SessionState state);
|
||||
|
||||
/// \returns true if altitude information along |m_route| is available and
|
||||
/// false otherwise.
|
||||
/**
|
||||
* @brief Whether altitude information along the route is available.
|
||||
*
|
||||
* @return True if altitude information along `m_route` is available, false otherwise.
|
||||
*/
|
||||
bool HasRouteAltitude() const;
|
||||
bool IsRouteId(uint64_t routeId) const;
|
||||
bool IsRouteValid() const;
|
||||
@@ -172,6 +256,15 @@ public:
|
||||
|
||||
double GetCompletionPercent() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the MWMs needed to build the route.
|
||||
*
|
||||
* Waits for the `RegionsRouter` thread to finish and returns the list of MWM names from it.
|
||||
*
|
||||
* @param countries Receives the list of MWM names.
|
||||
*/
|
||||
void GetAllRegions(std::set<std::string> & countries);
|
||||
|
||||
private:
|
||||
struct DoReadyCallback
|
||||
{
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
|
||||
namespace routing
|
||||
{
|
||||
// This is directed road segment used as vertex in index graph.
|
||||
//
|
||||
// You can imagine the segment as a material arrow.
|
||||
// Head of each arrow is connected to fletchings of next arrows with invisible links:
|
||||
// these are the edges of the graph.
|
||||
//
|
||||
// Position of the segment is a position of the arrowhead: GetPointId(true).
|
||||
// This position is used in heuristic and edges weight calculations.
|
||||
/**
|
||||
* @brief A directed road segment used as a vertex in the index graph.
|
||||
*
|
||||
* You can imagine the segment as a material arrow.
|
||||
* Head of each arrow is connected to fletchings of next arrows with invisible links:
|
||||
* these are the edges of the graph.
|
||||
*
|
||||
* Position of the segment is a position of the arrowhead: GetPointId(true).
|
||||
* This position is used in heuristic and edges weight calculations.
|
||||
*/
|
||||
class Segment final
|
||||
{
|
||||
public:
|
||||
@@ -78,7 +80,9 @@ public:
|
||||
bool operator<(SegmentEdge const & edge) const;
|
||||
|
||||
private:
|
||||
// Target is vertex going to for outgoing edges, vertex going from for ingoing edges.
|
||||
/**
|
||||
* @brief Vertex going to for outgoing edges, vertex going from for ingoing edges.
|
||||
*/
|
||||
Segment m_target;
|
||||
RouteWeight m_weight;
|
||||
};
|
||||
|
||||
@@ -218,19 +218,53 @@ public:
|
||||
MaxspeedType GetForward() const { return m_forward; }
|
||||
MaxspeedType GetBackward() const { return m_backward; }
|
||||
|
||||
/**
|
||||
* @brief Whether the maxspeed is valid for the forward direction.
|
||||
*
|
||||
* Valid maxspeeds include numeric values as well as `kNoneMaxSpeed` and `kWalkMaxSpeed`.
|
||||
*
|
||||
* Only the forward direction is evaluated, maxspeed for the backward direction may still be invalid.
|
||||
* To check for valid maxspeed in both directions, use `IsBidirectional()`.
|
||||
*
|
||||
* @return true if valid, false if not.
|
||||
*/
|
||||
bool IsValid() const { return m_forward != kInvalidSpeed; }
|
||||
/// \returns true if Maxspeed is considered as Bidirectional(). It means different
|
||||
/// speed is set for forward and backward direction. Otherwise returns false. It means
|
||||
/// |m_forward| speed should be used for the both directions.
|
||||
|
||||
/**
|
||||
* @brief Whether the maxspeed is valid for both directions.
|
||||
*
|
||||
* @return true if valid for both directions, false if not.
|
||||
*
|
||||
* @todo The documentation previously stated:
|
||||
* “[Returns] true if Maxspeed is considered as Bidirectional(). It means different speed is set
|
||||
* for forward and backward direction. Otherwise returns false. It means `m_forward` speed should
|
||||
* be used for the both directions.” However, this is at odds with the actual code, which just
|
||||
* checks for validity, not identity.
|
||||
*/
|
||||
bool IsBidirectional() const { return IsValid() && m_backward != kInvalidSpeed; }
|
||||
|
||||
/// \brief returns speed according to |m_units|. |kInvalidSpeed|, |kNoneMaxSpeed| or
|
||||
/// |kWalkMaxSpeed| may be returned.
|
||||
/**
|
||||
* @brief Returns maxspeed in native units.
|
||||
*
|
||||
* Native units are the units returned by `GetUnits()`.
|
||||
*
|
||||
* @param forward Whether to return maxspeed for the forward or backward direction.
|
||||
*
|
||||
* @return Speed in native units, or `kInvalidSpeed`, `kNoneMaxSpeed` or `kWalkMaxSpeed`.
|
||||
*/
|
||||
MaxspeedType GetSpeedInUnits(bool forward) const;
|
||||
|
||||
/// \brief returns speed in km per hour. If it's not valid |kInvalidSpeed| is
|
||||
/// returned. Otherwise forward or backward speed in km per hour is returned. |kNoneMaxSpeed| and
|
||||
/// |kWalkMaxSpeed| are converted to some numbers.
|
||||
/**
|
||||
* @brief Returns maxspeed in km per hour.
|
||||
*
|
||||
* If the maxspeed is not valid `kInvalidSpeed` is returned. Otherwise forward or backward speed
|
||||
* in km per hour is returned. `kNoneMaxSpeed` and `kWalkMaxSpeed` are converted to actual speeds
|
||||
* which can be used directly.
|
||||
*
|
||||
* @param forward Whether to return maxspeed for the forward or backward direction.
|
||||
*
|
||||
* @return Speed in km/h, or `kInvalidSpeed`.
|
||||
*/
|
||||
MaxspeedType GetSpeedKmPH(bool forward) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -17,11 +17,21 @@ using NumMwmId = std::uint16_t;
|
||||
NumMwmId constexpr kFakeNumMwmId = std::numeric_limits<NumMwmId>::max();
|
||||
NumMwmId constexpr kGeneratorMwmId = 0;
|
||||
|
||||
/**
|
||||
* @brief A numbered list of country files.
|
||||
*/
|
||||
class NumMwmIds final
|
||||
{
|
||||
public:
|
||||
bool IsEmpty() const { return m_idToFile.empty(); }
|
||||
|
||||
/**
|
||||
* @brief Registers a file, i.e. adds it to the instance.
|
||||
*
|
||||
* If the instance already contains the file, this is a no-op.
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
void RegisterFile(platform::CountryFile const & file)
|
||||
{
|
||||
if (ContainsFile(file))
|
||||
@@ -34,16 +44,36 @@ public:
|
||||
// LOG(LDEBUG, ("MWM:", file.GetName(), "=", id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether this instance contains a given file.
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
bool ContainsFile(platform::CountryFile const & file) const { return m_fileToId.find(file) != m_fileToId.cend(); }
|
||||
|
||||
/**
|
||||
* @brief Whether this instance contains a file at a given index.
|
||||
* @param mwmId The index.
|
||||
* @return
|
||||
*/
|
||||
bool ContainsFileForMwm(NumMwmId mwmId) const { return mwmId < m_idToFile.size(); }
|
||||
|
||||
/**
|
||||
* @brief Returns a file by index.
|
||||
* @param mwmId The index.
|
||||
* @return
|
||||
*/
|
||||
platform::CountryFile const & GetFile(NumMwmId mwmId) const
|
||||
{
|
||||
ASSERT_LESS(mwmId, m_idToFile.size(), ());
|
||||
return m_idToFile[mwmId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the index for a given file.
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
NumMwmId GetId(platform::CountryFile const & file) const
|
||||
{
|
||||
auto const it = m_fileToId.find(file);
|
||||
|
||||
@@ -147,14 +147,15 @@ struct NodeStatuses
|
||||
bool m_groupNode;
|
||||
};
|
||||
|
||||
// This class is used for downloading, updating and deleting maps.
|
||||
// Storage manages a queue of mwms to be downloaded.
|
||||
// Every operation with this queue must be executed
|
||||
// on the storage thread. In the current implementation, the storage
|
||||
// thread coincides with the main (UI) thread.
|
||||
// Downloading of only one mwm at a time is supported, so while the
|
||||
// mwm at the top of the queue is being downloaded (or updated by
|
||||
// applying a diff file) all other mwms have to wait.
|
||||
/**
|
||||
* @brief The Storage class is used for downloading, updating and deleting maps.
|
||||
*
|
||||
* Storage manages a queue of mwms to be downloaded. Every operation with this queue must be
|
||||
* executed on the storage thread. In the current implementation, the storage thread coincides with
|
||||
* the main (UI) thread. Downloading of only one mwm at a time is supported, so while the mwm at the
|
||||
* top of the queue is being downloaded (or updated by applying a diff file) all other mwms have to
|
||||
* wait.
|
||||
*/
|
||||
class Storage final : public QueuedCountry::Subscriber
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -103,6 +103,11 @@ boost::python::list GenerateTrafficKeys(std::string const & mwmPath)
|
||||
return pyhelpers::StdVectorToPythonList(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* We no longer separate keys (segments IDs) from values (their speed groups).
|
||||
* See if we can refactor this into something meaningful and useful, else remove.
|
||||
*/
|
||||
std::vector<uint8_t> GenerateTrafficValues(std::vector<traffic::TrafficInfo::RoadSegmentId> const & keys,
|
||||
boost::python::dict const & segmentMappingDict, uint8_t useTempBlock)
|
||||
{
|
||||
@@ -139,6 +144,13 @@ std::vector<uint8_t> GenerateTrafficValues(std::vector<traffic::TrafficInfo::Roa
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* We no longer separate keys (segments IDs) from values (their speed groups), nor do we store
|
||||
* either in binary files: Segment/speed group pairs are generated from TraFF data and cached in
|
||||
* XML format (using a custom extension to TraFF).
|
||||
* See if we can refactor this into something meaningful and useful, else remove.
|
||||
*/
|
||||
std::vector<uint8_t> GenerateTrafficValuesFromList(boost::python::list const & keys,
|
||||
boost::python::dict const & segmentMappingDict)
|
||||
{
|
||||
@@ -148,6 +160,13 @@ std::vector<uint8_t> GenerateTrafficValuesFromList(boost::python::list const & k
|
||||
return GenerateTrafficValues(keysVec, segmentMappingDict, 1 /* useTempBlock */);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* We no longer separate keys (segments IDs) from values (their speed groups), nor do we store
|
||||
* either in binary files: Segment/speed group pairs are generated from TraFF data and cached in
|
||||
* XML format (using a custom extension to TraFF).
|
||||
* See if we can refactor this into something meaningful and useful, else remove.
|
||||
*/
|
||||
std::vector<uint8_t> GenerateTrafficValuesFromBinary(std::vector<uint8_t> const & keysBlob,
|
||||
boost::python::dict const & segmentMappingDict,
|
||||
uint8_t useTempBlock = 1)
|
||||
@@ -201,7 +220,9 @@ BOOST_PYTHON_MODULE(pytraffic)
|
||||
|
||||
def("load_classificator", LoadClassificator);
|
||||
def("generate_traffic_keys", GenerateTrafficKeys);
|
||||
// TODO obsolete, see function definition
|
||||
def("generate_traffic_values_from_list", GenerateTrafficValuesFromList);
|
||||
// TODO obsolete, see function definition
|
||||
def("generate_traffic_values_from_binary", GenerateTrafficValuesFromBinary,
|
||||
(arg("keysBlob"), arg("segmentMappingDict"), arg("useTempBlock") = 1));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
|
||||
namespace traffic
|
||||
{
|
||||
/**
|
||||
* A bucket for the ratio of the speed of moving traffic to the posted speed limit.
|
||||
*
|
||||
* Let Vmax be the posted speed limit and Vreal the speed at which traffic is currently flowing
|
||||
* or expected to flow. The possible ratios (Vreal/Vmax) are grouped into buckets and, from then
|
||||
* on, only the bucket number is used.
|
||||
*
|
||||
* The threshold ratios for the individual values are defined in `kSpeedGroupThresholdPercentage`.
|
||||
*/
|
||||
enum class SpeedGroup : uint8_t
|
||||
{
|
||||
G0 = 0,
|
||||
@@ -20,24 +29,33 @@ enum class SpeedGroup : uint8_t
|
||||
|
||||
static_assert(static_cast<uint8_t>(SpeedGroup::Count) <= 8, "");
|
||||
|
||||
// Let M be the maximal speed that is possible on a free road
|
||||
// and let V be the maximal speed that is possible on this road when
|
||||
// taking the traffic data into account.
|
||||
// We group all possible ratios (V/M) into a small number of
|
||||
// buckets and only use the number of a bucket everywhere.
|
||||
// That is, we forget the specific values of V when transmitting and
|
||||
// displaying traffic information. The value M of a road is known at the
|
||||
// stage of building the mwm containing this road.
|
||||
//
|
||||
// kSpeedGroupThresholdPercentage[g] denotes the maximal value of (V/M)
|
||||
// that is possible for group |g|. Values falling on a border of two groups
|
||||
// may belong to either group.
|
||||
//
|
||||
// The threshold percentage is defined to be 100 for the
|
||||
// special groups where V is unknown or not defined.
|
||||
/**
|
||||
* Threshold ratios for the individual values of `SpeedGroup`.
|
||||
*
|
||||
* Let Vmax be the posted speed limit and Vreal the speed at which traffic is currently flowing
|
||||
* or expected to flow. The possible ratios (Vreal/Vmax) are grouped into buckets and, from then
|
||||
* on, only the bucket number is used.
|
||||
*
|
||||
* `kSpeedGroupThresholdPercentage[g]` is the maximum percentage of Vreal/Vmax for group g. Values
|
||||
* falling on the border of two groups may belong to either group.
|
||||
*
|
||||
* For special groups, where Vreal/Vmax is unknown or undefined, the threshold is 100%.
|
||||
*/
|
||||
extern uint32_t const kSpeedGroupThresholdPercentage[static_cast<size_t>(SpeedGroup::Count)];
|
||||
|
||||
/// \note This method is used in traffic jam generation.
|
||||
/**
|
||||
* Converts the ratio between speed of flowing traffic and the posted limit to a `SpeedGroup`.
|
||||
*
|
||||
* This method is used in traffic jam generation: Let Vmax be the posted speed limit and Vreal the
|
||||
* speed at which traffic is currently flowing or expected to flow. The possible ratios
|
||||
* (Vreal/Vmax) are grouped into buckets and, from then on, only the bucket number is used.
|
||||
*
|
||||
* This method performs the conversion from the ratio to a `SpeedGroup` bucket.
|
||||
*
|
||||
* @param p Vreal / Vmax * 100% (ratio expressed in percent)
|
||||
*
|
||||
* @return the `SpeedGroup` value which corresponds to `p`
|
||||
*/
|
||||
SpeedGroup GetSpeedGroupByPercentage(double p);
|
||||
|
||||
std::string DebugPrint(SpeedGroup const & group);
|
||||
|
||||
@@ -33,49 +33,6 @@ namespace traffic
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ReadRemoteFile(string const & url, vector<uint8_t> & contents, int & errorCode)
|
||||
{
|
||||
platform::HttpClient request(url);
|
||||
if (!request.RunHttpRequest())
|
||||
{
|
||||
errorCode = request.ErrorCode();
|
||||
LOG(LINFO, ("Couldn't run traffic request", url, ". Error:", errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
errorCode = request.ErrorCode();
|
||||
|
||||
string const & result = request.ServerResponse();
|
||||
contents.resize(result.size());
|
||||
memcpy(contents.data(), result.data(), result.size());
|
||||
|
||||
if (errorCode != 200)
|
||||
{
|
||||
LOG(LINFO, ("Traffic request", url, "failed. HTTP Error:", errorCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string MakeRemoteURL(string const & name, uint64_t version)
|
||||
{
|
||||
if (string(TRAFFIC_DATA_BASE_URL).empty())
|
||||
return {};
|
||||
|
||||
stringstream ss;
|
||||
ss << TRAFFIC_DATA_BASE_URL;
|
||||
if (version != 0)
|
||||
ss << version << "/";
|
||||
ss << url::UrlEncode(name) << TRAFFIC_FILE_EXTENSION;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
char constexpr kETag[] = "etag";
|
||||
} // namespace
|
||||
|
||||
// TrafficInfo::RoadSegmentId -----------------------------------------------------------------
|
||||
TrafficInfo::RoadSegmentId::RoadSegmentId() : m_fid(0), m_idx(0), m_dir(0) {}
|
||||
|
||||
@@ -88,46 +45,11 @@ TrafficInfo::RoadSegmentId::RoadSegmentId(uint32_t fid, uint16_t idx, uint8_t di
|
||||
uint8_t const TrafficInfo::kLatestKeysVersion = 0;
|
||||
uint8_t const TrafficInfo::kLatestValuesVersion = 0;
|
||||
|
||||
TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion)
|
||||
TrafficInfo::TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring)
|
||||
: m_mwmId(mwmId)
|
||||
, m_currentDataVersion(currentDataVersion)
|
||||
, m_coloring(std::move(coloring))
|
||||
{
|
||||
if (!mwmId.IsAlive())
|
||||
{
|
||||
LOG(LWARNING, ("Attempt to create a traffic info for dead mwm."));
|
||||
return;
|
||||
}
|
||||
string const mwmPath = mwmId.GetInfo()->GetLocalFile().GetPath(MapFileType::Map);
|
||||
try
|
||||
{
|
||||
FilesContainerR rcont(mwmPath);
|
||||
if (rcont.IsExist(TRAFFIC_KEYS_FILE_TAG))
|
||||
{
|
||||
auto reader = rcont.GetReader(TRAFFIC_KEYS_FILE_TAG);
|
||||
vector<uint8_t> buf(static_cast<size_t>(reader.Size()));
|
||||
reader.Read(0, buf.data(), buf.size());
|
||||
LOG(LINFO, ("Reading keys for", mwmId, "from section"));
|
||||
try
|
||||
{
|
||||
DeserializeTrafficKeys(buf, m_keys);
|
||||
}
|
||||
catch (Reader::Exception const & e)
|
||||
{
|
||||
auto const info = mwmId.GetInfo();
|
||||
LOG(LINFO,
|
||||
("Could not read traffic keys from section. MWM:", info->GetCountryName(), "Version:", info->GetVersion()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LINFO, ("Reading traffic keys for", mwmId, "from the web"));
|
||||
ReceiveTrafficKeys();
|
||||
}
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Could not initialize traffic keys"));
|
||||
}
|
||||
m_availability = Availability::IsAvailable;
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -144,19 +66,6 @@ void TrafficInfo::SetTrafficKeysForTesting(vector<RoadSegmentId> const & keys)
|
||||
m_availability = Availability::IsAvailable;
|
||||
}
|
||||
|
||||
bool TrafficInfo::ReceiveTrafficData(string & etag)
|
||||
{
|
||||
vector<SpeedGroup> values;
|
||||
switch (ReceiveTrafficValues(etag, values))
|
||||
{
|
||||
case ServerDataStatus::New: return UpdateTrafficData(values);
|
||||
case ServerDataStatus::NotChanged: return true;
|
||||
case ServerDataStatus::NotFound:
|
||||
case ServerDataStatus::Error: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SpeedGroup TrafficInfo::GetSpeedGroup(RoadSegmentId const & id) const
|
||||
{
|
||||
auto const it = m_coloring.find(id);
|
||||
@@ -216,70 +125,6 @@ void TrafficInfo::CombineColorings(vector<TrafficInfo::RoadSegmentId> const & ke
|
||||
ASSERT_EQUAL(numUnexpectedKeys, 0, ());
|
||||
}
|
||||
|
||||
// static
|
||||
void TrafficInfo::SerializeTrafficKeys(vector<RoadSegmentId> const & keys, vector<uint8_t> & result)
|
||||
{
|
||||
vector<uint32_t> fids;
|
||||
vector<size_t> numSegs;
|
||||
vector<bool> oneWay;
|
||||
for (size_t i = 0; i < keys.size();)
|
||||
{
|
||||
size_t j = i;
|
||||
while (j < keys.size() && keys[i].m_fid == keys[j].m_fid)
|
||||
++j;
|
||||
|
||||
bool ow = true;
|
||||
for (size_t k = i; k < j; ++k)
|
||||
{
|
||||
if (keys[k].m_dir == RoadSegmentId::kReverseDirection)
|
||||
{
|
||||
ow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto const numDirs = ow ? 1 : 2;
|
||||
size_t numSegsForThisFid = j - i;
|
||||
CHECK_GREATER(numDirs, 0, ());
|
||||
CHECK_EQUAL(numSegsForThisFid % numDirs, 0, ());
|
||||
numSegsForThisFid /= numDirs;
|
||||
|
||||
fids.push_back(keys[i].m_fid);
|
||||
numSegs.push_back(numSegsForThisFid);
|
||||
oneWay.push_back(ow);
|
||||
|
||||
i = j;
|
||||
}
|
||||
|
||||
MemWriter<vector<uint8_t>> memWriter(result);
|
||||
WriteToSink(memWriter, kLatestKeysVersion);
|
||||
WriteVarUint(memWriter, fids.size());
|
||||
|
||||
{
|
||||
BitWriter<decltype(memWriter)> bitWriter(memWriter);
|
||||
|
||||
uint32_t prevFid = 0;
|
||||
for (auto const & fid : fids)
|
||||
{
|
||||
uint64_t const fidDiff = static_cast<uint64_t>(fid - prevFid);
|
||||
bool ok = coding::GammaCoder::Encode(bitWriter, fidDiff + 1);
|
||||
ASSERT(ok, ());
|
||||
UNUSED_VALUE(ok);
|
||||
prevFid = fid;
|
||||
}
|
||||
|
||||
for (auto const & s : numSegs)
|
||||
{
|
||||
bool ok = coding::GammaCoder::Encode(bitWriter, s + 1);
|
||||
ASSERT(ok, ());
|
||||
UNUSED_VALUE(ok);
|
||||
}
|
||||
|
||||
for (auto const val : oneWay)
|
||||
bitWriter.Write(val ? 1 : 0, 1 /* numBits */);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TrafficInfo::DeserializeTrafficKeys(vector<uint8_t> const & data, vector<TrafficInfo::RoadSegmentId> & result)
|
||||
{
|
||||
@@ -352,166 +197,6 @@ void TrafficInfo::SerializeTrafficValues(vector<SpeedGroup> const & values, vect
|
||||
deflate(buf.data(), buf.size(), back_inserter(result));
|
||||
}
|
||||
|
||||
// static
|
||||
void TrafficInfo::DeserializeTrafficValues(vector<uint8_t> const & data, vector<SpeedGroup> & result)
|
||||
{
|
||||
using Inflate = coding::ZLib::Inflate;
|
||||
|
||||
vector<uint8_t> decompressedData;
|
||||
|
||||
Inflate inflate(Inflate::Format::ZLib);
|
||||
inflate(data.data(), data.size(), back_inserter(decompressedData));
|
||||
|
||||
MemReaderWithExceptions memReader(decompressedData.data(), decompressedData.size());
|
||||
ReaderSource<decltype(memReader)> src(memReader);
|
||||
|
||||
auto const version = ReadPrimitiveFromSource<uint8_t>(src);
|
||||
CHECK_EQUAL(version, kLatestValuesVersion, ("Unsupported version of traffic keys."));
|
||||
|
||||
auto const n = ReadVarUint<uint32_t>(src);
|
||||
result.resize(n);
|
||||
BitReader<decltype(src)> bitReader(src);
|
||||
for (size_t i = 0; i < static_cast<size_t>(n); ++i)
|
||||
{
|
||||
// SpeedGroup's values fit into 3 bits.
|
||||
result[i] = static_cast<SpeedGroup>(bitReader.Read(3));
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(src.Size(), 0, ());
|
||||
}
|
||||
|
||||
// todo(@m) This is a temporary method. Do not refactor it.
|
||||
bool TrafficInfo::ReceiveTrafficKeys()
|
||||
{
|
||||
if (!m_mwmId.IsAlive())
|
||||
return false;
|
||||
auto const & info = m_mwmId.GetInfo();
|
||||
if (!info)
|
||||
return false;
|
||||
|
||||
string const url = MakeRemoteURL(info->GetCountryName(), info->GetVersion());
|
||||
|
||||
if (url.empty())
|
||||
return false;
|
||||
|
||||
vector<uint8_t> contents;
|
||||
int errorCode;
|
||||
if (!ReadRemoteFile(url + ".keys", contents, errorCode))
|
||||
return false;
|
||||
if (errorCode != 200)
|
||||
{
|
||||
LOG(LWARNING, ("Network error when reading keys"));
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<RoadSegmentId> keys;
|
||||
try
|
||||
{
|
||||
DeserializeTrafficKeys(contents, keys);
|
||||
}
|
||||
catch (Reader::Exception const & e)
|
||||
{
|
||||
LOG(LINFO, ("Could not read traffic keys received from server. MWM:", info->GetCountryName(),
|
||||
"Version:", info->GetVersion()));
|
||||
return false;
|
||||
}
|
||||
m_keys.swap(keys);
|
||||
return true;
|
||||
}
|
||||
|
||||
TrafficInfo::ServerDataStatus TrafficInfo::ReceiveTrafficValues(string & etag, vector<SpeedGroup> & values)
|
||||
{
|
||||
if (!m_mwmId.IsAlive())
|
||||
return ServerDataStatus::Error;
|
||||
|
||||
auto const & info = m_mwmId.GetInfo();
|
||||
if (!info)
|
||||
return ServerDataStatus::Error;
|
||||
|
||||
auto const version = info->GetVersion();
|
||||
string const url = MakeRemoteURL(info->GetCountryName(), version);
|
||||
|
||||
if (url.empty())
|
||||
return ServerDataStatus::Error;
|
||||
|
||||
platform::HttpClient request(url);
|
||||
request.LoadHeaders(true);
|
||||
request.SetRawHeader("If-None-Match", etag);
|
||||
|
||||
if (!request.RunHttpRequest() || request.ErrorCode() != 200)
|
||||
return ProcessFailure(request, version);
|
||||
try
|
||||
{
|
||||
string const & response = request.ServerResponse();
|
||||
vector<uint8_t> contents(response.cbegin(), response.cend());
|
||||
DeserializeTrafficValues(contents, values);
|
||||
}
|
||||
catch (Reader::Exception const & e)
|
||||
{
|
||||
m_availability = Availability::NoData;
|
||||
LOG(LWARNING, ("Could not read traffic values received from server. MWM:", info->GetCountryName(),
|
||||
"Version:", info->GetVersion()));
|
||||
return ServerDataStatus::Error;
|
||||
}
|
||||
// Update ETag for this MWM.
|
||||
auto const & headers = request.GetHeaders();
|
||||
auto const it = headers.find(kETag);
|
||||
if (it != headers.end())
|
||||
etag = it->second;
|
||||
|
||||
m_availability = Availability::IsAvailable;
|
||||
return ServerDataStatus::New;
|
||||
}
|
||||
|
||||
bool TrafficInfo::UpdateTrafficData(vector<SpeedGroup> const & values)
|
||||
{
|
||||
m_coloring.clear();
|
||||
|
||||
if (m_keys.size() != values.size())
|
||||
{
|
||||
LOG(LWARNING, ("The number of received traffic values does not correspond to the number of keys:", m_keys.size(),
|
||||
"keys", values.size(), "values."));
|
||||
m_availability = Availability::NoData;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_keys.size(); ++i)
|
||||
if (values[i] != SpeedGroup::Unknown)
|
||||
m_coloring.emplace(m_keys[i], values[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TrafficInfo::ServerDataStatus TrafficInfo::ProcessFailure(platform::HttpClient const & request,
|
||||
int64_t const mwmVersion)
|
||||
{
|
||||
switch (request.ErrorCode())
|
||||
{
|
||||
case 404: /* Not Found */
|
||||
{
|
||||
int64_t version = 0;
|
||||
VERIFY(strings::to_int64(request.ServerResponse().c_str(), version), ());
|
||||
|
||||
if (version > mwmVersion && version <= m_currentDataVersion)
|
||||
m_availability = Availability::ExpiredData;
|
||||
else if (version > m_currentDataVersion)
|
||||
m_availability = Availability::ExpiredApp;
|
||||
else
|
||||
m_availability = Availability::NoData;
|
||||
return ServerDataStatus::NotFound;
|
||||
}
|
||||
case 304: /* Not Modified */
|
||||
{
|
||||
m_availability = Availability::IsAvailable;
|
||||
return ServerDataStatus::NotChanged;
|
||||
}
|
||||
}
|
||||
|
||||
m_availability = Availability::Unknown;
|
||||
|
||||
return ServerDataStatus::Error;
|
||||
}
|
||||
|
||||
string DebugPrint(TrafficInfo::RoadSegmentId const & id)
|
||||
{
|
||||
string const dir = id.m_dir == TrafficInfo::RoadSegmentId::kForwardDirection ? "Forward" : "Backward";
|
||||
|
||||
@@ -15,23 +15,49 @@ class HttpClient;
|
||||
|
||||
namespace traffic
|
||||
{
|
||||
// This class is responsible for providing the real-time
|
||||
// information about road traffic for one mwm file.
|
||||
/**
|
||||
* @brief The `TrafficInfo` class is responsible for providing the real-time information about road
|
||||
* traffic for one MWM.
|
||||
*/
|
||||
class TrafficInfo
|
||||
{
|
||||
public:
|
||||
static uint8_t const kLatestKeysVersion;
|
||||
static uint8_t const kLatestValuesVersion;
|
||||
|
||||
/**
|
||||
* @brief Whether traffic data is available in this `TrafficInfo` instance.
|
||||
*/
|
||||
/*
|
||||
* TODO A global traffic update would require some 2–3 states:
|
||||
* * IsAvailable
|
||||
* * Data available but not yet decoded
|
||||
* * (possibly) No traffic reports for this MWM
|
||||
*/
|
||||
enum class Availability
|
||||
{
|
||||
/** This `TrafficInfo` instance has data available. */
|
||||
IsAvailable,
|
||||
/** No traffic data is available (file not found on the server, or server returned invalid data). */
|
||||
NoData,
|
||||
/** Traffic data could not be retrieved because the map data is outdated. */
|
||||
ExpiredData,
|
||||
/** Traffic data could not be retrieved because the app version is outdated. */
|
||||
ExpiredApp,
|
||||
/** No traffic data is available because the server responded with an error (other than “not found”), or no request was made yet. */
|
||||
Unknown
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The RoadSegmentId struct models a segment of a road.
|
||||
*
|
||||
* A road segment is the link between two consecutive points of an OSM way. The way must be
|
||||
* tagged with a valid `highway` tag. A segment refers to a single direction.
|
||||
*
|
||||
* Therefore, an OSM way with `n` points has `n - 1` segments if tagged as one-way, `2 (n - 1)`
|
||||
* otherwise (as each pair of adjacent points is connected by two segments, one in each
|
||||
* direction.)
|
||||
*/
|
||||
struct RoadSegmentId
|
||||
{
|
||||
// m_dir can be kForwardDirection or kReverseDirection.
|
||||
@@ -68,84 +94,102 @@ public:
|
||||
uint8_t m_dir : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mapping from feature segments to speed groups (see `speed_groups.hpp`), for one MWM.
|
||||
*/
|
||||
// todo(@m) unordered_map?
|
||||
using Coloring = std::map<RoadSegmentId, SpeedGroup>;
|
||||
|
||||
TrafficInfo() = default;
|
||||
|
||||
TrafficInfo(MwmSet::MwmId const & mwmId, int64_t currentDataVersion);
|
||||
TrafficInfo(MwmSet::MwmId const & mwmId, Coloring && coloring);
|
||||
|
||||
/**
|
||||
* @brief Returns a `TrafficInfo` instance with pre-populated traffic information.
|
||||
* @param coloring The traffic information (road segments and their speed group)
|
||||
* @return The new `TrafficInfo` instance
|
||||
*/
|
||||
static TrafficInfo BuildForTesting(Coloring && coloring);
|
||||
void SetTrafficKeysForTesting(std::vector<RoadSegmentId> const & keys);
|
||||
|
||||
// Fetches the latest traffic data from the server and updates the coloring and ETag.
|
||||
// Construct the url by passing an MwmId.
|
||||
// The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.
|
||||
// It is one of several mechanisms that HTTP provides for web cache validation,
|
||||
// which allows a client to make conditional requests.
|
||||
// *NOTE* This method must not be called on the UI thread.
|
||||
bool ReceiveTrafficData(std::string & etag);
|
||||
|
||||
// Returns the latest known speed group by a feature segment's id
|
||||
// or SpeedGroup::Unknown if there is no information about the segment.
|
||||
/**
|
||||
* @brief Returns the latest known speed group by a feature segment's ID.
|
||||
* @param id The road segment ID.
|
||||
* @return The speed group, or `SpeedGroup::Unknown` if no information is available.
|
||||
*/
|
||||
SpeedGroup GetSpeedGroup(RoadSegmentId const & id) const;
|
||||
|
||||
MwmSet::MwmId const & GetMwmId() const { return m_mwmId; }
|
||||
Coloring const & GetColoring() const { return m_coloring; }
|
||||
Availability GetAvailability() const { return m_availability; }
|
||||
|
||||
// Extracts RoadSegmentIds from mwm and stores them in a sorted order.
|
||||
/**
|
||||
* @brief Extracts RoadSegmentIds from an MWM and stores them in a sorted order.
|
||||
* @param mwmPath Path to the MWM file
|
||||
*
|
||||
* @todo We don’t need this any longer as the API has been reworked: We no longer separate keys
|
||||
* (segment IDs) from values (their speed groups) and no longer have a use case for retrieving a
|
||||
* list of all possible segment IDs – rather, we decode TraFF messages into segments, or have a
|
||||
* cached list of the segments affected by a particular message. However, pytraffic still has some
|
||||
* references to this function. We need to clean those up first, then we can delete this function.
|
||||
*/
|
||||
static void ExtractTrafficKeys(std::string const & mwmPath, std::vector<RoadSegmentId> & result);
|
||||
|
||||
// Adds the unknown values to the partially known coloring map |knownColors|
|
||||
// so that the keys of the resulting map are exactly |keys|.
|
||||
/**
|
||||
* @brief Adds unknown values to a partially known coloring map.
|
||||
*
|
||||
* After this method returns, the keys of `result` will be exactly `keys`. The speed group
|
||||
* associated with each key will be the same as in `knownColors`, or `SpeedGroup::Unknown` for
|
||||
* keys which are not found in `knownColors`.
|
||||
*
|
||||
* Keys in `knownColors` which are not in `keys` will be ignored.
|
||||
*
|
||||
* If `result` contains mappings prior to this method being called, they will be deleted.
|
||||
*
|
||||
* @param keys The keys for the result map.
|
||||
* @param knownColors The map containing the updates.
|
||||
* @param result The map to be updated.
|
||||
*/
|
||||
static void CombineColorings(std::vector<TrafficInfo::RoadSegmentId> const & keys,
|
||||
TrafficInfo::Coloring const & knownColors, TrafficInfo::Coloring & result);
|
||||
|
||||
// Serializes the keys of the coloring map to |result|.
|
||||
// The keys are road segments ids which do not change during
|
||||
// an mwm's lifetime so there's no point in downloading them every time.
|
||||
// todo(@m) Document the format.
|
||||
static void SerializeTrafficKeys(std::vector<RoadSegmentId> const & keys, std::vector<uint8_t> & result);
|
||||
|
||||
/*
|
||||
* TODO We don’t need these any longer as the format is obsolete, but pytraffic still has some
|
||||
* references to these. We need to clean those up first, then we can delete these functions.
|
||||
*/
|
||||
static void DeserializeTrafficKeys(std::vector<uint8_t> const & data, std::vector<RoadSegmentId> & result);
|
||||
|
||||
static void SerializeTrafficValues(std::vector<SpeedGroup> const & values, std::vector<uint8_t> & result);
|
||||
|
||||
static void DeserializeTrafficValues(std::vector<uint8_t> const & data, std::vector<SpeedGroup> & result);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Result of the last request to the server.
|
||||
*/
|
||||
enum class ServerDataStatus
|
||||
{
|
||||
/** New data was returned. */
|
||||
New,
|
||||
/** Data has not changed since the last request. */
|
||||
NotChanged,
|
||||
/** The URL was not found on the server. */
|
||||
NotFound,
|
||||
/** An error prevented data from being requested, or the server responded with an error. */
|
||||
Error,
|
||||
};
|
||||
|
||||
friend void UnitTest_TrafficInfo_UpdateTrafficData();
|
||||
|
||||
// todo(@m) A temporary method. Remove it once the keys are added
|
||||
// to the generator and the data is regenerated.
|
||||
bool ReceiveTrafficKeys();
|
||||
|
||||
// Tries to read the values of the Coloring map from server into |values|.
|
||||
// Returns result of communicating with server as ServerDataStatus.
|
||||
// Otherwise, returns false and does not change m_coloring.
|
||||
ServerDataStatus ReceiveTrafficValues(std::string & etag, std::vector<SpeedGroup> & values);
|
||||
|
||||
// Updates the coloring and changes the availability status if needed.
|
||||
bool UpdateTrafficData(std::vector<SpeedGroup> const & values);
|
||||
|
||||
ServerDataStatus ProcessFailure(platform::HttpClient const & request, int64_t const mwmVersion);
|
||||
|
||||
// The mapping from feature segments to speed groups (see speed_groups.hpp).
|
||||
/**
|
||||
* @brief The mapping from feature segments to speed groups (see speed_groups.hpp).
|
||||
*/
|
||||
Coloring m_coloring;
|
||||
|
||||
// The keys of the coloring map. The values are downloaded periodically
|
||||
// and combined with the keys to form m_coloring.
|
||||
// *NOTE* The values must be received in the exact same order that the
|
||||
// keys are saved in.
|
||||
/**
|
||||
* @brief The keys of the coloring map. The values are downloaded periodically
|
||||
* and combined with the keys to form `m_coloring`.
|
||||
* *NOTE* The values must be received in the exact same order that the keys are saved in.
|
||||
*/
|
||||
std::vector<RoadSegmentId> m_keys;
|
||||
|
||||
MwmSet::MwmId m_mwmId;
|
||||
|
||||
@@ -37,7 +37,11 @@ protected:
|
||||
};
|
||||
} // namespace
|
||||
|
||||
/// @todo Need TRAFFIC_DATA_BASE_URL for this test.
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* Leaving it here for now, maybe we can derive some TraFF tests from it.
|
||||
* This tests retrieval of traffic information from the server.
|
||||
*/
|
||||
/*
|
||||
UNIT_TEST(TrafficInfo_RemoteFile)
|
||||
{
|
||||
@@ -69,6 +73,13 @@ UNIT_TEST(TrafficInfo_RemoteFile)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* Leaving it here for now, maybe we can derive some TraFF tests from it.
|
||||
* This tests serialization of traffic data to files and reading it back, results should be
|
||||
* identical and satisfy whatever constraints there are in the app.
|
||||
*/
|
||||
/*
|
||||
UNIT_TEST(TrafficInfo_Serialization)
|
||||
{
|
||||
TrafficInfo::Coloring coloring = {
|
||||
@@ -114,7 +125,14 @@ UNIT_TEST(TrafficInfo_Serialization)
|
||||
TEST_EQUAL(values, deserializedValues, ());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO Inherited from MWM/OM, no longer works with TraFF logic (API logic changed).
|
||||
* Leaving it here for now, maybe we can derive some TraFF tests from it.
|
||||
* This tests processing of updated traffic data.
|
||||
*/
|
||||
/*
|
||||
UNIT_TEST(TrafficInfo_UpdateTrafficData)
|
||||
{
|
||||
vector<TrafficInfo::RoadSegmentId> const keys = {
|
||||
@@ -147,4 +165,5 @@ UNIT_TEST(TrafficInfo_UpdateTrafficData)
|
||||
for (size_t i = 0; i < keys.size(); ++i)
|
||||
TEST_EQUAL(info.GetSpeedGroup(keys[i]), values2[i], ());
|
||||
}
|
||||
*/
|
||||
} // namespace traffic
|
||||
|
||||
21
libs/traffxml/CMakeLists.txt
Normal file
21
libs/traffxml/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
project(traffxml)
|
||||
|
||||
set(SRC
|
||||
traff_decoder.cpp
|
||||
traff_decoder.hpp
|
||||
traff_model.cpp
|
||||
traff_model.hpp
|
||||
traff_model_xml.cpp
|
||||
traff_model_xml.hpp
|
||||
traff_source.cpp
|
||||
traff_source.hpp
|
||||
traff_storage.cpp
|
||||
traff_storage.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
pugixml
|
||||
coding
|
||||
)
|
||||
1610
libs/traffxml/traff_decoder.cpp
Normal file
1610
libs/traffxml/traff_decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
644
libs/traffxml/traff_decoder.hpp
Normal file
644
libs/traffxml/traff_decoder.hpp
Normal file
@@ -0,0 +1,644 @@
|
||||
#pragma once
|
||||
|
||||
#include "traffxml/traff_model.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
// Only needed for OpenlrTraffDecoder, see below
|
||||
#if 0
|
||||
#include "openlr/openlr_decoder.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#endif
|
||||
|
||||
#include "routing/index_router.hpp"
|
||||
#include "routing/regions_decl.hpp"
|
||||
#include "routing/router.hpp"
|
||||
#include "routing/vehicle_mask.hpp"
|
||||
|
||||
#include "routing_common/num_mwm_id.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
/**
|
||||
* @brief Abstract base class for all TraFF decoder implementations.
|
||||
*
|
||||
* At this point, `TraffDecoder` is single-threaded and not guaranteed to be thread-safe. This means
|
||||
* that all `TraffDecoder` operations should be limited to one thread or use appropriate thread
|
||||
* synchronization mechanisms. In particular, calling `DecodeMessage()` concurrently from multiple
|
||||
* threads is not supported.
|
||||
*/
|
||||
class TraffDecoder
|
||||
{
|
||||
public:
|
||||
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
|
||||
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
|
||||
|
||||
TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||||
std::map<std::string, traffxml::TraffMessage> & messageCache);
|
||||
|
||||
virtual ~TraffDecoder() {}
|
||||
|
||||
/**
|
||||
* @brief Decodes a single message to its segments and their speed groups.
|
||||
*
|
||||
* This method is not guaranteed to be thread-safe. All calls to this method should either be
|
||||
* strictly limited to one designated thread, or be synchronized using an appropriate mechanism.
|
||||
*
|
||||
* In addition to the above, this method may access the message cache which was passed to the
|
||||
* constructor. This is not thread-safe and needs to be synchronized, unless all other operations
|
||||
* on the message cache are guaranteed to happen on the same thread that called this method.
|
||||
*
|
||||
* @param message The message to decode.
|
||||
*/
|
||||
void DecodeMessage(traffxml::TraffMessage & message);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Decodes a TraFF location.
|
||||
*
|
||||
* @param message The message to decode.
|
||||
* @param decoded Receives the decoded segments. The speed group will be `Unknown`.
|
||||
*/
|
||||
virtual void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) = 0;
|
||||
|
||||
/**
|
||||
* @brief Applies traffic impact to a decoded TraFF location.
|
||||
*
|
||||
* Applying impact sets the corresponding speed groups of the decoded segments. Existing speed groups will be overwritten.
|
||||
*
|
||||
* @param impact The traffic impact to apply.
|
||||
* @param decoded The decoded segments.
|
||||
*/
|
||||
void ApplyTrafficImpact(traffxml::TrafficImpact & impact, traffxml::MultiMwmColoring & decoded);
|
||||
|
||||
DataSource & m_dataSource;
|
||||
CountryInfoGetterFn m_countryInfoGetterFn;
|
||||
CountryParentNameGetterFn m_countryParentNameGetterFn;
|
||||
|
||||
/**
|
||||
* @brief Cache of all currently active TraFF messages.
|
||||
*
|
||||
* Keys are message IDs, values are messages.
|
||||
*/
|
||||
std::map<std::string, traffxml::TraffMessage> & m_messageCache;
|
||||
|
||||
/**
|
||||
* @brief Consolidated traffic impact of the message currently being decoded
|
||||
*/
|
||||
std::optional<traffxml::TrafficImpact> m_trafficImpact;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
// Disabled for now, as the OpenLR-based decoder is slow, buggy and not well suited to the task.
|
||||
#if 0
|
||||
/**
|
||||
* @brief A `TraffDecoder` implementation which internally uses the version 3 OpenLR decoder.
|
||||
*/
|
||||
class OpenLrV3TraffDecoder : public TraffDecoder
|
||||
{
|
||||
public:
|
||||
OpenLrV3TraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||||
std::map<std::string, traffxml::TraffMessage> & messageCache);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Decodes a TraFF location.
|
||||
*
|
||||
* @param message The message to decode.
|
||||
* @param decoded Receives the decoded segments. The speed group will be `Unknown`.
|
||||
*/
|
||||
void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Returns the OpenLR functional road class (FRC) matching a TraFF road class.
|
||||
*
|
||||
* @param roadClass The TraFF road class.
|
||||
* @return The FRC.
|
||||
*/
|
||||
static openlr::FunctionalRoadClass GetRoadClassFrc(std::optional<RoadClass> & roadClass);
|
||||
|
||||
/**
|
||||
* @brief Guess the distance between two points.
|
||||
*
|
||||
* If both `p1` and `p2` have the `distance` attribute set, the difference between these two is
|
||||
* evaluated. If it is within a certain tolerance margin of the direct distance between the two
|
||||
* points, this value is returned. Otherwise, the distance is calculated from direct distance,
|
||||
* multiplied with a tolerance factor to account for the fact that the road is not always a
|
||||
* straight line.
|
||||
*
|
||||
* The result can be used to provide some semi-valid DNP values.
|
||||
*
|
||||
* @param p1 The first point.
|
||||
* @param p2 The second point.
|
||||
* @return The approximate distance on the ground, in meters.
|
||||
*/
|
||||
static uint32_t GuessDnp(Point & p1, Point & p2);
|
||||
|
||||
/**
|
||||
* @brief Converts a TraFF point to an OpenLR location reference point.
|
||||
*
|
||||
* Only coordinates are populated.
|
||||
*
|
||||
* @param point The point
|
||||
* @return An OpenLR LRP with the coordinates of the point.
|
||||
*/
|
||||
static openlr::LocationReferencePoint PointToLrp(Point & point);
|
||||
|
||||
/**
|
||||
* @brief Converts a TraFF location to an OpenLR linear location reference.
|
||||
*
|
||||
* @param location The location
|
||||
* @param backwards If true, gnerates a linear location reference for the backwards direction,
|
||||
* with the order of points reversed.
|
||||
* @return An OpenLR linear location reference which corresponds to the location.
|
||||
*/
|
||||
static openlr::LinearLocationReference TraffLocationToLinearLocationReference(TraffLocation & location, bool backwards);
|
||||
|
||||
/**
|
||||
* @brief Converts a TraFF location to a vector of OpenLR segments.
|
||||
*
|
||||
* Depending on the directionality, the resulting vector will hold one or two elements: one for
|
||||
* the forward direction, and for bidirectional locations, a second one for the backward
|
||||
* direction.
|
||||
*
|
||||
* @param location The location
|
||||
* @param messageId The message ID
|
||||
* @return A vector holding the resulting OpenLR segments.
|
||||
*/
|
||||
static std::vector<openlr::LinearSegment> TraffLocationToOpenLrSegments(TraffLocation & location, std::string & messageId);
|
||||
|
||||
/**
|
||||
* @brief The OpenLR decoder instance.
|
||||
*
|
||||
* Used to decode TraFF locations into road segments on the map.
|
||||
*/
|
||||
openlr::OpenLRDecoder m_openLrDecoder;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief A `TraffDecoder` implementation which internally uses the routing engine.
|
||||
*/
|
||||
class RoutingTraffDecoder : public TraffDecoder,
|
||||
public MwmSet::Observer
|
||||
{
|
||||
public:
|
||||
class DecoderRouter : public routing::IndexRouter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a new `DecoderRouter` instance.
|
||||
*
|
||||
* @param countryParentNameGetterFn Function which converts a country name into the name of its parent country)
|
||||
* @param countryFileFn Function which converts a pointer to its country name
|
||||
* @param countryRectFn Function which returns the rect for a country
|
||||
* @param numMwmIds
|
||||
* @param numMwmTree
|
||||
* @param trafficCache The traffic cache (used only if `vehicleType` is `VehicleType::Car`)
|
||||
* @param dataSource The MWM data source
|
||||
* @param decoder The `TraffDecoder` instance to which this router instance is coupled
|
||||
*/
|
||||
DecoderRouter(CountryParentNameGetterFn const & countryParentNameGetterFn,
|
||||
routing::TCountryFileFn const & countryFileFn,
|
||||
routing::CountryRectFn const & countryRectFn,
|
||||
std::shared_ptr<routing::NumMwmIds> numMwmIds,
|
||||
std::unique_ptr<m4::Tree<routing::NumMwmId>> numMwmTree,
|
||||
DataSource & dataSource, RoutingTraffDecoder & decoder);
|
||||
protected:
|
||||
/**
|
||||
* @brief Whether the set of fake endings generated for the check points is restricted.
|
||||
*
|
||||
* The return value is used internally when snapping checkpoints to edges. If this function
|
||||
* returns true, this instructs the `PointsOnEdgesSnapping` instance to consider only edges which
|
||||
* are not fenced off, i.e. can be reached from the respective checkpoint without crossing any
|
||||
* other edges. If it returns false, this restriction does not apply, and all nearby edges are
|
||||
* considered.
|
||||
*
|
||||
* Restricting the set of fake endings in this manner decreases the options considered for routing
|
||||
* and thus processing time, which is desirable for regular routing and has no side effects.
|
||||
* For TraFF location matching, simplification has undesirable side effects: if reference points
|
||||
* are located on one side of the road, the other carriageway may not be considered. This would
|
||||
* lead to situations like these:
|
||||
*
|
||||
* --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<--
|
||||
* -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->--
|
||||
* *< <*
|
||||
*
|
||||
* (-- carriageway, + junction, < > direction, *< end point, <* start point, == route)
|
||||
*
|
||||
* To avoid this, the `DecoderRouter` implementation always returns false.
|
||||
*/
|
||||
/**
|
||||
* @brief Returns the mode in which the router is operating.
|
||||
*
|
||||
* The `DecoderRouter` always returns `Mode::Decoding`.
|
||||
*
|
||||
* In navigation mode, the router may exit with `RouterResultCode::NeedMoreMaps` if it determines
|
||||
* that a better route can be calculated with additional maps. When snapping endpoints to edges,
|
||||
* it will consider only edges which are not “fenced off” by other edges, i.e. which can be
|
||||
* reached from the endpoint without crossing other edges. This decreases the number of fake
|
||||
* endings and thus speeds up routing, without any undesirable side effects for that use case.
|
||||
*
|
||||
* Asking the user to download extra maps is neither practical for a TraFF decoder which runs in
|
||||
* the background and may decode many locations, one by one, nor is it needed (if maps are
|
||||
* missing, we do not need to decode traffic reports for them).
|
||||
*
|
||||
* Eliminating fenced-off edges from the snapping candidates has an undesirable side effect for
|
||||
* TraFF location decoding on dual-carriageway roads: if the reference points are outside the
|
||||
* carriageways, only one direction gets considered for snapping, as the opposite direction is
|
||||
* fenced off by it. This may lead to situations like these:
|
||||
*
|
||||
* ~~~
|
||||
* --<--<-+<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<==<+-<--<--
|
||||
* -->-->-+>==>==>==>==>==>==>-->-->-->-->-->-->-->-->-->-->-->==>==>==>==>==>==>==>==>+->-->--
|
||||
* |< <|
|
||||
*
|
||||
* (-- carriageway, + junction, < > direction, |< end point, <| start point, == route)
|
||||
* ~~~
|
||||
*
|
||||
* Therefore, in decoding mode, the router will never exit with `RouterResultCode::NeedMoreMaps`
|
||||
* but tries to find a route with the existing maps, or exits without a route. When snapping
|
||||
* endpoints to edges, it considers all edges within the given radius, fenced off or not.
|
||||
*/
|
||||
IndexRouter::Mode GetMode() override { return IndexRouter::Mode::Decoding; }
|
||||
|
||||
/**
|
||||
* @brief Returns current routing options.
|
||||
*
|
||||
* For traffic decoding purposes, all roads are allowed.
|
||||
*/
|
||||
routing::RoutingOptions GetRoutingOptions() override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
class TraffEstimator final : public routing::EdgeEstimator
|
||||
{
|
||||
public:
|
||||
TraffEstimator(DataSource * dataSourcePtr, std::shared_ptr<routing::NumMwmIds> numMwmIds,
|
||||
double maxWeightSpeedKMpH,
|
||||
routing::SpeedKMpH const & offroadSpeedKMpH,
|
||||
RoutingTraffDecoder & decoder)
|
||||
: EdgeEstimator(routing::VehicleType::Car, maxWeightSpeedKMpH, offroadSpeedKMpH, dataSourcePtr, numMwmIds)
|
||||
, m_decoder(decoder)
|
||||
{
|
||||
}
|
||||
|
||||
// EdgeEstimator overrides:
|
||||
|
||||
/**
|
||||
* @brief Estimates travel time between two points along a direct fake edge.
|
||||
*
|
||||
* Estimates time in seconds it takes to go from point `from` to point `to` along direct fake edge.
|
||||
*
|
||||
* @param from The start point.
|
||||
* @param to The destination point.
|
||||
* @param purpose The purpose for which the result is to be used.
|
||||
* @return Travel time in seconds.
|
||||
*/
|
||||
double CalcOffroad(ms::LatLon const & from, ms::LatLon const & to, Purpose /* purpose */) const override;
|
||||
double CalcSegmentWeight(routing::Segment const & segment, routing::RoadGeometry const & road, Purpose /* purpose */) const override;
|
||||
|
||||
double GetUTurnPenalty(Purpose /* purpose */) const override;
|
||||
|
||||
/**
|
||||
* @brief Determines the penalty factor for making a turn.
|
||||
*
|
||||
* The turn is at the first or last points of `from_road` and `to_road` and can be determined
|
||||
* by comparing the endpoints of `from_road` and `to_road` for a match.
|
||||
*
|
||||
* @param purpose The purpose for which the penalty is calculated, ignored by this implementation
|
||||
* @param angle The angle in degrees (negative values indicate a right turn)
|
||||
* @param from_road The road (segment between two junctions) before the turn
|
||||
* @param to_road The road (segment between two junctions) after the turn
|
||||
* @param is_left_hand_traffic True for left-hand traffic, false for right-hand traffic
|
||||
*/
|
||||
double GetTurnPenalty(Purpose /* purpose */, double angle, routing::RoadGeometry const & from_road,
|
||||
routing::RoadGeometry const & to_road, bool is_left_hand_traffic = false) const override;
|
||||
double GetFerryLandingPenalty(Purpose /* purpose */) const override;
|
||||
|
||||
/**
|
||||
* @brief Whether access restrictions are ignored.
|
||||
*
|
||||
* A return value of false indicates that access restrictions should be observed, which is the
|
||||
* default behavior for a routing use case. If true, it indicates that routing should ignore
|
||||
* access restrictions. This is needed to resolve traffic message locations; it could also be
|
||||
* used e.g. for emergency vehicle use cases.
|
||||
*
|
||||
* This implementation may return true or false, depending on the location being decoded.
|
||||
*/
|
||||
bool IsAccessIgnored() override;
|
||||
|
||||
private:
|
||||
RoutingTraffDecoder & m_decoder;
|
||||
};
|
||||
|
||||
struct JunctionCandidateInfo
|
||||
{
|
||||
JunctionCandidateInfo(double weight)
|
||||
: m_weight(weight)
|
||||
{}
|
||||
|
||||
double m_weight;
|
||||
size_t m_segmentsIn = 0;
|
||||
size_t m_segmentsOut = 0;
|
||||
size_t m_twoWaySegments = 0;
|
||||
};
|
||||
|
||||
RoutingTraffDecoder(DataSource & dataSource, CountryInfoGetterFn countryInfoGetter,
|
||||
const CountryParentNameGetterFn & countryParentNameGetter,
|
||||
std::map<std::string, traffxml::TraffMessage> & messageCache);
|
||||
|
||||
/**
|
||||
* @brief Called when a map is registered for the first time and can be used.
|
||||
*/
|
||||
void OnMapRegistered(platform::LocalCountryFile const & localFile) override;
|
||||
|
||||
/**
|
||||
* @brief Called when a map is deregistered and can no longer be used.
|
||||
*
|
||||
* This implementation does nothing, as `NumMwmIds` does not support removal.
|
||||
*/
|
||||
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) override {}
|
||||
|
||||
/**
|
||||
* @brief Determines the penalty factor bases on how highway attributes match.
|
||||
*
|
||||
* This compares the highway type of the candidate feature (as retrieved from OSM) against the
|
||||
* road class and ramps attributes of the location.
|
||||
*
|
||||
* Rules are subject to change but principles are:
|
||||
*
|
||||
* Penalties for ramp mismatch and road class mismatch are applied consecutively, thus the maximum
|
||||
* penalty is `kAttributePenalty ^ 2`.
|
||||
*
|
||||
* If ramps mismatch (location specifies a ramp but candidate is not a ramp, or vice versa), the
|
||||
* penalty is `kAttributePenalty`.
|
||||
*
|
||||
* If road classes are similar, the penalty is `kReducedAttributePenalty`. For a complete
|
||||
* mismatch, the penalty is `kAttributePenalty`.
|
||||
*
|
||||
* @param highwayType The OSM highway type of the candidate feature.
|
||||
* @param roadClass The TraFF road class of the location.
|
||||
* @param ramps The ramps atribute of the TraFF location.
|
||||
*
|
||||
* @return 1 for a perfect match (same road class and ramp type), up to `kAttributePenalty ^ 2`
|
||||
* for a mismatch.
|
||||
*/
|
||||
static double GetHighwayTypePenalty(std::optional<routing::HighwayType> highwayType,
|
||||
std::optional<RoadClass> roadClass,
|
||||
Ramps ramps);
|
||||
|
||||
/**
|
||||
* @brief Determines the penalty factor based on how two reference numbers match.
|
||||
*
|
||||
* Rules are subject to change.
|
||||
*
|
||||
* This method takes a vector as an argument, compares each element and returns the penalty for
|
||||
* the best match.
|
||||
*
|
||||
* @param refs A vector of reference numbers of the current segment, compared against `m_roadRef`.
|
||||
*
|
||||
* @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty`
|
||||
* for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for
|
||||
* a partial match (unclear whether both refs refer to the same object).
|
||||
*/
|
||||
double GetRoadRefPenalty(std::vector<std::string> & refs) const;
|
||||
|
||||
/**
|
||||
* @brief Determines the penalty factor based on how two reference numbers match.
|
||||
*
|
||||
* Rules are subject to change.
|
||||
*
|
||||
* @param ref The reference number of the current segment, compared against `m_roadRef`.
|
||||
*
|
||||
* @return 1 for a perfect match (refs are assumed to refer to the same object), `kAttributePenalty`
|
||||
* for a mismatch (refs are assumed to refer to different objects) or`kReducedAttributePenalty` for
|
||||
* a partial match (unclear whether both refs refer to the same object).
|
||||
*/
|
||||
double GetRoadRefPenalty(std::string const & ref) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Initializes the router.
|
||||
*
|
||||
* This is usually done in the constructor but fails if no maps are loaded (attempting to
|
||||
* construct a router without maps results in a crash, hence we check for maps and exit with an
|
||||
* error if we have none). It can be repeated any time.
|
||||
*
|
||||
* Attempting to initialize a router which has already been succesfully initialized is a no-op. It
|
||||
* will be reported as success.
|
||||
*
|
||||
* @return true if successful, false if not.
|
||||
*/
|
||||
bool InitRouter();
|
||||
|
||||
/**
|
||||
* @brief Adds a segment to the decoded segments.
|
||||
*
|
||||
* @param decoded The decoded segments.
|
||||
* @param segment The segment to add.
|
||||
*/
|
||||
void AddDecodedSegment(traffxml::MultiMwmColoring & decoded, routing::Segment & segment);
|
||||
|
||||
/**
|
||||
* @brief Decodes one direction of a TraFF location.
|
||||
*
|
||||
* @param message The message to decode.
|
||||
* @param decoded Receives the decoded segments. The speed group will be `Unknown`.
|
||||
* @param backwards If true, decode the backward direction, else the forward direction.
|
||||
*/
|
||||
void DecodeLocationDirection(traffxml::TraffMessage & message,
|
||||
traffxml::MultiMwmColoring & decoded, bool backwards);
|
||||
|
||||
/**
|
||||
* @brief Decodes a TraFF location.
|
||||
*
|
||||
* @param message The message to decode.
|
||||
* @param decoded Receives the decoded segments. The speed group will be `Unknown`.
|
||||
*/
|
||||
void DecodeLocation(traffxml::TraffMessage & message, traffxml::MultiMwmColoring & decoded) override;
|
||||
|
||||
/**
|
||||
* @brief Truncates the route so its endpoints best match the reference points.
|
||||
*
|
||||
* Leading and trailing fake segments are discarded.
|
||||
*
|
||||
* When building the graph, the router creates fake segments to the nearest roads. These are not
|
||||
* necessarily the best for location decoding, which may result in “heads” or “tails” being added
|
||||
* to the decoded location. This function attempts to detect and remove them.
|
||||
*
|
||||
* To do this, it iterates over the nodes (taken from `rsegments`) and determines if any of them
|
||||
* is a better start/end candidate. This is done by calculating the cost of leaping between the
|
||||
* node and the corresponding checkpoint; if this is cheaper than the stretch of route bypassed
|
||||
* in this way, the node becomes a candidate for the corresponding endpoint. The higher the cost
|
||||
* saving, the better the candidate.
|
||||
*
|
||||
* After identifying the best candidate for each endpoint, segments outside these nodes are
|
||||
* discarded.
|
||||
*
|
||||
* @param rsegments The segments of the route
|
||||
* @param checkpoints The reference points (at least two)
|
||||
* @param backwards True when decoding the backward direction, false when decodign the forward direction.
|
||||
*/
|
||||
void TruncateRoute(std::vector<routing::RouteSegment> & rsegments,
|
||||
routing::Checkpoints const & checkpoints, bool backwards);
|
||||
|
||||
private:
|
||||
static void LogCode(routing::RouterResultCode code, double const elapsedSec);
|
||||
|
||||
/**
|
||||
* @brief Populates the list of candidates for junction points.
|
||||
*
|
||||
* If the location has a fuzziness of `LowRes`, the map is searched for candidates around the
|
||||
* `from` and `to` points, which are taken from the `m_location` member of `m_message`. The weight
|
||||
* for each candidate is calculated based on its distance from the reference point and the match
|
||||
* between the attributes of the segment and the location. Since junction points are part of
|
||||
* multiple segments, the best match wins. Candidates and their weight are stored in
|
||||
* `m_startJunctions` and `m_endJunctions`.
|
||||
*
|
||||
* If the location’s fuzziness attribute is empty or does not equal `LowRes`, `m_startJunctions`
|
||||
* and `m_endJunctions` are cleared.
|
||||
*/
|
||||
void GetJunctionPointCandidates();
|
||||
|
||||
/**
|
||||
* @brief Populates a list of candidates for junction points.
|
||||
*
|
||||
* Implementation for `GetJunctionPointCandidates()`. The map is searched for candidates around
|
||||
* `point`. The weight for each candidate is calculated based on its distance from `point` and
|
||||
* the match between the attributes of the segment and the location of `m_message`. Since junction
|
||||
* points are part of multiple segments, the best match wins. Candidates and their weight are
|
||||
* stored in `junctions`.
|
||||
*
|
||||
* @param point The reference point
|
||||
* @param junctions Receives a list of junction candidates with their weight
|
||||
*/
|
||||
void GetJunctionPointCandidates(Point const & point,
|
||||
std::map<m2::PointD, double> & junctions);
|
||||
|
||||
/**
|
||||
* @brief Mutex for access to shared members.
|
||||
*
|
||||
* This is to prevent adding newly-registered maps while the router is in use.
|
||||
*
|
||||
* @todo As per the `MwmSet::Observer` documentation, implementations should be quick and lean,
|
||||
* as they may be called from any thread. Locking a mutex may be in conflict with this, as it may
|
||||
* mean locking up the caller while a location is being decoded.
|
||||
*/
|
||||
std::mutex m_mutex;
|
||||
|
||||
std::shared_ptr<routing::NumMwmIds> m_numMwmIds = std::make_shared<routing::NumMwmIds>();
|
||||
std::unique_ptr<routing::IRouter> m_router;
|
||||
std::optional<traffxml::TraffMessage> m_message = std::nullopt;
|
||||
|
||||
/**
|
||||
* @brief Junction points near start of location, with their associated offroad weight.
|
||||
*
|
||||
* If the list is empty, no junction alignment at the `from` point will be done and decoding
|
||||
* relies solely on point coordinates.
|
||||
*/
|
||||
std::map<m2::PointD, double> m_startJunctions;
|
||||
|
||||
/**
|
||||
* @brief Junction points near end of location, with their associated offroad weight.
|
||||
*
|
||||
* If the list is empty, no junction alignment at the `to` point will be done and decoding
|
||||
* relies solely on point coordinates.
|
||||
*/
|
||||
std::map<m2::PointD, double> m_endJunctions;
|
||||
|
||||
/**
|
||||
* @brief Radius around reference points in which to search for junctions.
|
||||
*
|
||||
* Determined dynamically, based on distance between reference points.
|
||||
* Maximum distance is never more than half the distance between endpoints.
|
||||
* It should be between `kJunctionRadiusMin` and `kJunctionRadiusMax`, and as close as possible to
|
||||
* 1/3 the distance.
|
||||
*/
|
||||
double m_junctionRadius;
|
||||
|
||||
/**
|
||||
* @brief The road ref of `m_message`, parsed with `ParseRef()`
|
||||
*/
|
||||
std::vector<std::string> m_roadRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The default TraFF decoder implementation, recommended for production use.
|
||||
*/
|
||||
//using DefaultTraffDecoder = OpenLrV3TraffDecoder;
|
||||
using DefaultTraffDecoder = RoutingTraffDecoder;
|
||||
|
||||
traffxml::RoadClass GetRoadClass(routing::HighwayType highwayType);
|
||||
double GetRoadClassPenalty(traffxml::RoadClass lhs, traffxml::RoadClass rhs);
|
||||
bool IsRamp(routing::HighwayType highwayType);
|
||||
|
||||
/**
|
||||
* @brief Breaks down a ref into groups for comparison.
|
||||
*
|
||||
* The result of this function can be used to determine if two reference numbers match partially
|
||||
* (such as `A4`, `A4bis` and `A4.1`).
|
||||
*
|
||||
* Implementation details may change; currently the following applies:
|
||||
*
|
||||
* A whitespace character (or sequence of whitespace characters), or a switch between letters and
|
||||
* digits, starts a new group.
|
||||
*
|
||||
* Letters are converted to lowercase.
|
||||
*
|
||||
* For example, each of `A42`, `A 42` and `-a42` would be broken down into `a, 42`, whereas `A4.2`
|
||||
* would be broken down into `a, 4, 2`.
|
||||
*/
|
||||
std::vector<std::string> ParseRef(std::string const & ref);
|
||||
|
||||
/**
|
||||
* @brief Calculates the segments to truncate at the start of the route.
|
||||
*
|
||||
* The route is not actually truncated by this function.
|
||||
*
|
||||
* `start` and `startSaving` should be 0 when calling this function. After it returns, these values
|
||||
* will indicate the first segment to keep and the cost saved by truncating everything before.
|
||||
*
|
||||
* @param rsegments The segments of the route
|
||||
* @param checkpoints The reference points (at least two)
|
||||
* @param start Index of the first segment to keep
|
||||
* @param startSaving Cost saved by truncating
|
||||
* @param startWeight Weight of the fake segments up to the first real segment
|
||||
* @param junctions Junctions with the weight of their leap segment
|
||||
*/
|
||||
void TruncateStart(std::vector<routing::RouteSegment> & rsegments,
|
||||
routing::Checkpoints const & checkpoints,
|
||||
size_t & start, double & startSaving, double const startWeight,
|
||||
std::map<m2::PointD, double> const & junctions);
|
||||
|
||||
/**
|
||||
* @brief Calculates the segments to truncate at the start of the route.
|
||||
*
|
||||
* The route is not actually truncated by this function.
|
||||
*
|
||||
* `end` should be `rsegments.size() - 1` and `endSaving` should be 0 when calling this function.
|
||||
* After it returns, these values will indicate the last segment to keep and the cost saved by
|
||||
* truncating everything after.
|
||||
*
|
||||
* @param rsegments The segments of the route
|
||||
* @param checkpoints The reference points (at least two)
|
||||
* @param end Index of the last segment to keep
|
||||
* @param endSaving Cost saved by truncating
|
||||
* @param endWeight Total weight of the route, including trailing fake segments
|
||||
* @param junctions Junctions with the weight of their leap segment
|
||||
*/
|
||||
void TruncateEnd(std::vector<routing::RouteSegment> & rsegments,
|
||||
routing::Checkpoints const & checkpoints,
|
||||
size_t & end, double & endSaving, double const endWeight,
|
||||
std::map<m2::PointD, double> const & junctions);
|
||||
} // namespace traffxml
|
||||
635
libs/traffxml/traff_model.cpp
Normal file
635
libs/traffxml/traff_model.cpp
Normal file
@@ -0,0 +1,635 @@
|
||||
#include "traffxml/traff_model.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
const std::unordered_map<EventType, traffic::SpeedGroup> kEventSpeedGroupMap{
|
||||
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
||||
{EventType::CongestionHeavyTraffic, traffic::SpeedGroup::G4},
|
||||
{EventType::CongestionLongQueue, traffic::SpeedGroup::G0},
|
||||
{EventType::CongestionNone, traffic::SpeedGroup::G5},
|
||||
{EventType::CongestionNormalTraffic, traffic::SpeedGroup::G5},
|
||||
{EventType::CongestionQueue, traffic::SpeedGroup::G2},
|
||||
{EventType::CongestionQueueLikely, traffic::SpeedGroup::G3},
|
||||
{EventType::CongestionSlowTraffic, traffic::SpeedGroup::G3},
|
||||
{EventType::CongestionStationaryTraffic, traffic::SpeedGroup::G1},
|
||||
{EventType::CongestionStationaryTrafficLikely, traffic::SpeedGroup::G2},
|
||||
{EventType::CongestionTrafficBuildingUp, traffic::SpeedGroup::G4},
|
||||
{EventType::CongestionTrafficCongestion, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal
|
||||
{EventType::CongestionTrafficFlowingFreely, traffic::SpeedGroup::G5},
|
||||
{EventType::CongestionTrafficHeavierThanNormal, traffic::SpeedGroup::G4},
|
||||
{EventType::CongestionTrafficLighterThanNormal, traffic::SpeedGroup::G5},
|
||||
{EventType::CongestionTrafficMuchHeavierThanNormal, traffic::SpeedGroup::G3},
|
||||
{EventType::CongestionTrafficProblem, traffic::SpeedGroup::G3}, // TODO or G2? Unquantified, below normal
|
||||
// TODO Construction* (not in enum yet)
|
||||
/*
|
||||
* Some delay types have a duration which depends on the route. This is better expressed as a
|
||||
* speed group, although the mapping may be somewhat arbitrary and may need to be corrected.
|
||||
*/
|
||||
{EventType::DelayDelay, traffic::SpeedGroup::G2},
|
||||
{EventType::DelayDelayPossible, traffic::SpeedGroup::G3},
|
||||
{EventType::DelayLongDelay, traffic::SpeedGroup::G1},
|
||||
{EventType::DelayVeryLongDelay, traffic::SpeedGroup::G0},
|
||||
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
||||
// TODO complete Restriction* (not in enum yet)
|
||||
{EventType::RestrictionBlocked, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionBlockedAhead, traffic::SpeedGroup::TempBlock},
|
||||
//{EventType::RestrictionCarriagewayBlocked, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open
|
||||
//{EventType::RestrictionCarriagewayClosed, traffic::SpeedGroup::TempBlock}, // TODO FIXME other carriageways may still be open
|
||||
{EventType::RestrictionClosed, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionClosedAhead, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionEntryBlocked, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionExitBlocked, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionRampBlocked, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionRampClosed, traffic::SpeedGroup::TempBlock},
|
||||
{EventType::RestrictionSpeedLimit, traffic::SpeedGroup::G4},
|
||||
// TODO Security*, Transport*, Weather* (not in enum yet)
|
||||
};
|
||||
|
||||
// none of the currently define events imply an explicit maxspeed
|
||||
#if 0
|
||||
const std::unordered_map<EventType, uint8_t> kEventMaxspeedMap{
|
||||
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
||||
// TODO Construction* (not in enum yet)
|
||||
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
||||
// TODO complete Restriction* (not in enum yet)
|
||||
// TODO Security*, Transport*, Weather* (not in enum yet)
|
||||
};
|
||||
#endif
|
||||
|
||||
const std::unordered_map<EventType, uint16_t> kEventDelayMap{
|
||||
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
||||
// TODO Construction* (not in enum yet)
|
||||
//{EventType::DelayDelay, }, // mapped to speed group
|
||||
//{EventType::DelayDelayPossible, }, // mapped to speed group
|
||||
//{EventType::DelayLongDelay, }, // mapped to speed group
|
||||
{EventType::DelaySeveralHours, 150}, // assumption: 2.5 hours
|
||||
{EventType::DelayUncertainDuration, 60}, // assumption: 1 hour
|
||||
//{EventType::DelayVeryLongDelay, }, // mapped to speed group
|
||||
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
||||
// TODO complete Restriction* (not in enum yet)
|
||||
// TODO Security*, Transport*, Weather* (not in enum yet)
|
||||
};
|
||||
|
||||
std::optional<IsoTime> IsoTime::ParseIsoTime(std::string timeString)
|
||||
{
|
||||
/*
|
||||
* TODO this is ugly because we need to work around some compiler deficiencies.
|
||||
*
|
||||
* Ideally, we would be using `std::chrono::time_point<std::chrono::utc_clock>` and parse the
|
||||
* string using `std::chrono::from_stream`, using `%FT%T%z` for the format string.
|
||||
* This works in GCC 14+ and is pleasantly liberal about the time zone format (all of +01, +0100
|
||||
* and +01:00 are parsed correctly). Alas, Ubuntu 24.04 (currently the default dev platform) comes
|
||||
* with GCC 13.2, which lacks this support. Clang, the only supported compiler for Android (and,
|
||||
* presumably, iOS), as of mid-2025, doesn’t support it at all.
|
||||
*
|
||||
* The workaround is therefore to use `std::chrono::time_point<std::chrono::system_clock>`, which
|
||||
* exposes the same API as its `utc_clock` counterpart, making transition at a later point easy.
|
||||
* In addition, however, it can be constructed from `std::time_t`, which we can generate from
|
||||
* `std::tm`. Unlike the other C legacy functions, gmtime is thread-safe.
|
||||
* Still not the prettiest way (as it relies on legacy C functions which are not
|
||||
* thread-safe), but the best we can get until we have proper compiler support for `from_stream`.
|
||||
*
|
||||
* Should we have support for `std::chrono:clock_cast` but not `std::chrono::from_stream`, we
|
||||
* could build a `std::chrono::sys_seconds` from the constutuent values and use
|
||||
* `std::chrono::clock_cast` to convert it to a `std::chrono::time_point`, based on whatever clock
|
||||
* is supported. This works on Linux (using `utc_clock`) as of mid-2025, but not on the primary
|
||||
* target platforms (Android and iOS) and has therefore been left out for uniformity (and
|
||||
* reproducibility of bugs).
|
||||
*/
|
||||
/*
|
||||
* Regex for ISO 8601 time, with some tolerance for time zone offset. If matched, the matcher
|
||||
* will contain the following items:
|
||||
*
|
||||
* 0: 2019-11-01T11:55:42+01:00 (entire expression)
|
||||
* 1: 2019 (year)
|
||||
* 2: 11 (month)
|
||||
* 3: 01 (day)
|
||||
* 4: 11 (hour, local)
|
||||
* 5: 55 (minute, local)
|
||||
* 6: 42.445 (second, local, float)
|
||||
* 7: .445 (fractional seconds)
|
||||
* 8: +01:00 (complete UTC offset, or Z; blank if not specified)
|
||||
* 9: +01:00 (complete UTC offset, blank for Z or of not specified)
|
||||
* 10: +01 (UTC offset, hours with sign; blank for Z or if not specified)
|
||||
* 11: :00 (UTC offset, minutes, prefixed with separator)
|
||||
* 12: 00 (UTC offset, minutes, unsigned; blank for Z or if not specified)
|
||||
*/
|
||||
static boost::regex iso8601Regex("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}(\\.[0-9]*)?)(Z|(([+-][0-9]{2})(:?([0-9]{2}))?))?");
|
||||
|
||||
boost::smatch iso8601Matcher;
|
||||
if (boost::regex_search(timeString, iso8601Matcher, iso8601Regex))
|
||||
{
|
||||
int offset_h = iso8601Matcher[10].matched ? std::stoi(iso8601Matcher[10]) : 0;
|
||||
int offset_m = iso8601Matcher[12].matched ? std::stoi(iso8601Matcher[12]) : 0;
|
||||
if (offset_h < 0)
|
||||
offset_m *= -1;
|
||||
|
||||
std::tm tm = {};
|
||||
tm.tm_year = std::stoi(iso8601Matcher[1]) - 1900;
|
||||
tm.tm_mon = std::stoi(iso8601Matcher[2]) - 1;
|
||||
tm.tm_mday = std::stoi(iso8601Matcher[3]);
|
||||
tm.tm_hour = std::stoi(iso8601Matcher[4]) - offset_h;
|
||||
tm.tm_min = std::stoi(iso8601Matcher[5]) - offset_m;
|
||||
tm.tm_sec = std::stof(iso8601Matcher[6]) + 0.5f;
|
||||
|
||||
std::time_t tt = timegm(&tm);
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> tp = std::chrono::system_clock::from_time_t(tt);
|
||||
|
||||
IsoTime result(tp);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LINFO, ("Not a valid ISO 8601 timestamp:", timeString));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
IsoTime IsoTime::Now()
|
||||
{
|
||||
return IsoTime(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
IsoTime::IsoTime(std::chrono::time_point<std::chrono::system_clock> tp)
|
||||
: m_tp(tp)
|
||||
{}
|
||||
|
||||
bool IsoTime::IsPast()
|
||||
{
|
||||
return m_tp < std::chrono::system_clock::now();
|
||||
}\
|
||||
|
||||
void IsoTime::Shift(IsoTime nowRef)
|
||||
{
|
||||
auto const offset = std::chrono::system_clock::now() - nowRef.m_tp;
|
||||
m_tp += offset;
|
||||
}
|
||||
|
||||
std::string IsoTime::ToString() const
|
||||
{
|
||||
auto const tp_seconds = time_point_cast<std::chrono::seconds>(m_tp);
|
||||
auto const time_t = std::chrono::system_clock::to_time_t(tp_seconds);
|
||||
std::tm tm = *std::gmtime(&time_t);
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||
ss << "Z";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool IsoTime::operator< (IsoTime & rhs)
|
||||
{
|
||||
return m_tp < rhs.m_tp;
|
||||
}
|
||||
|
||||
bool IsoTime::operator> (IsoTime & rhs)
|
||||
{
|
||||
return m_tp > rhs.m_tp;
|
||||
}
|
||||
|
||||
bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs)
|
||||
{
|
||||
if ((lhs.m_speedGroup == traffic::SpeedGroup::TempBlock)
|
||||
&& (rhs.m_speedGroup == traffic::SpeedGroup::TempBlock))
|
||||
return true;
|
||||
return (lhs.m_speedGroup == rhs.m_speedGroup)
|
||||
&& (lhs.m_maxspeed == rhs.m_maxspeed)
|
||||
&& (lhs.m_delayMins == rhs.m_delayMins);
|
||||
}
|
||||
|
||||
bool operator==(Point const & lhs, Point const & rhs)
|
||||
{
|
||||
return lhs.m_coordinates == rhs.m_coordinates;
|
||||
}
|
||||
|
||||
bool operator==(TraffLocation const & lhs, TraffLocation const & rhs)
|
||||
{
|
||||
return (lhs.m_from == rhs.m_from)
|
||||
&& (lhs.m_at == rhs.m_at)
|
||||
&& (lhs.m_via == rhs.m_via)
|
||||
&& (lhs.m_notVia == rhs.m_notVia)
|
||||
&& (lhs.m_to == rhs.m_to);
|
||||
}
|
||||
|
||||
IsoTime TraffMessage::GetEffectiveExpirationTime()
|
||||
{
|
||||
IsoTime result = m_expirationTime;
|
||||
if (m_startTime && m_startTime.value() > result)
|
||||
result = m_startTime.value();
|
||||
if (m_endTime && m_endTime.value() > result)
|
||||
result = m_endTime.value();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TraffMessage::IsExpired(IsoTime now)
|
||||
{
|
||||
return GetEffectiveExpirationTime() < now;
|
||||
}
|
||||
|
||||
std::optional<TrafficImpact> TraffMessage::GetTrafficImpact()
|
||||
{
|
||||
// no events, no impact
|
||||
if (m_events.empty())
|
||||
return std::nullopt;
|
||||
|
||||
// examine events
|
||||
std::vector<TrafficImpact> impacts;
|
||||
for (auto event : m_events)
|
||||
{
|
||||
TrafficImpact impact;
|
||||
|
||||
if (auto it = kEventSpeedGroupMap.find(event.m_type); it != kEventSpeedGroupMap.end())
|
||||
impact.m_speedGroup = it->second;
|
||||
|
||||
if (event.m_speed)
|
||||
impact.m_maxspeed = event.m_speed.value();
|
||||
// TODO if no explicit speed given, look up in kEventMaxspeedMap (once we have entries)
|
||||
|
||||
if (event.m_class == EventClass::Delay
|
||||
&& event.m_type != EventType::DelayClearance
|
||||
&& event.m_type != EventType::DelayForecastWithdrawn
|
||||
&& event.m_type != EventType::DelaySeveralHours
|
||||
&& event.m_type != EventType::DelayUncertainDuration
|
||||
&& event.m_qDurationMins)
|
||||
impact.m_delayMins = event.m_qDurationMins.value();
|
||||
else if (auto it = kEventDelayMap.find(event.m_type); it != kEventDelayMap.end())
|
||||
impact.m_delayMins = it->second;
|
||||
|
||||
// TempBlock overrules everything else, return immediately
|
||||
if (impact.m_speedGroup == traffic::SpeedGroup::TempBlock)
|
||||
return impact;
|
||||
// if there is no actual impact, discard
|
||||
if ((impact.m_maxspeed < kMaxspeedNone)
|
||||
|| (impact.m_delayMins > 0)
|
||||
|| (impact.m_speedGroup != traffic::SpeedGroup::Unknown))
|
||||
impacts.push_back(impact);
|
||||
}
|
||||
|
||||
if (impacts.empty())
|
||||
return std::nullopt;
|
||||
|
||||
TrafficImpact result;
|
||||
for (auto impact : impacts)
|
||||
{
|
||||
ASSERT(impact.m_speedGroup != traffic::SpeedGroup::TempBlock, ("Got SpeedGroup::TempBlock, which should not happen at this stage"));
|
||||
if (result.m_speedGroup == traffic::SpeedGroup::Unknown)
|
||||
result.m_speedGroup = impact.m_speedGroup;
|
||||
// TempBlock cannot occur here, so we can do just a simple comparison
|
||||
else if ((impact.m_speedGroup != traffic::SpeedGroup::Unknown) && (impact.m_speedGroup < result.m_speedGroup))
|
||||
result.m_speedGroup = impact.m_speedGroup;
|
||||
|
||||
if (impact.m_maxspeed < result.m_maxspeed)
|
||||
result.m_maxspeed = impact.m_maxspeed;
|
||||
|
||||
if (impact.m_delayMins > result.m_delayMins)
|
||||
result.m_delayMins = impact.m_delayMins;
|
||||
}
|
||||
if ((result.m_maxspeed < kMaxspeedNone)
|
||||
|| (result.m_delayMins > 0)
|
||||
|| (result.m_speedGroup != traffic::SpeedGroup::Unknown))
|
||||
return result;
|
||||
else
|
||||
// should never happen, unless we have a bug somewhere
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void TraffMessage::ShiftTimestamps()
|
||||
{
|
||||
IsoTime nowRef = m_updateTime;
|
||||
m_receiveTime.Shift(nowRef);
|
||||
m_updateTime.Shift(nowRef);
|
||||
m_expirationTime.Shift(nowRef);
|
||||
if (m_startTime)
|
||||
m_startTime.value().Shift(nowRef);
|
||||
if (m_endTime)
|
||||
m_endTime.value().Shift(nowRef);
|
||||
}
|
||||
|
||||
void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target)
|
||||
{
|
||||
// for each mwm in delta
|
||||
for (auto [mwmId, coloring] : delta)
|
||||
// if target contains mwm
|
||||
if (auto target_it = target.find(mwmId); target_it != target.end())
|
||||
// for each segment in delta[mwm] (coloring)
|
||||
for (auto [rsid, sg] : coloring)
|
||||
// if target[mwm] contains segment
|
||||
if (auto c_it = target_it->second.find(rsid) ; c_it != target_it->second.end())
|
||||
{
|
||||
// if delta overrules target (target is Unknown, delta is TempBlock or delta is slower than target)
|
||||
if ((sg == traffic::SpeedGroup::TempBlock)
|
||||
|| (c_it->second == traffic::SpeedGroup::Unknown) || (sg < c_it->second))
|
||||
target_it->second[rsid] = sg;
|
||||
}
|
||||
else
|
||||
// if target[mwm] does not contain segment, add speed group
|
||||
target_it->second[rsid] = sg;
|
||||
else
|
||||
// if target does not contain mwm, add coloring
|
||||
target[mwmId] = coloring;
|
||||
}
|
||||
|
||||
/*
|
||||
string DebugPrint(LinearSegmentSource source)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case LinearSegmentSource::NotValid: return "NotValid";
|
||||
case LinearSegmentSource::FromLocationReferenceTag: return "FromLocationReferenceTag";
|
||||
case LinearSegmentSource::FromCoordinatesTag: return "FromCoordinatesTag";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
*/
|
||||
std::string DebugPrint(IsoTime time)
|
||||
{
|
||||
std::ostringstream os;
|
||||
//os << std::put_time(&time.m_tm, "%Y-%m-%d %H:%M:%S %z");
|
||||
// %FT%T%z
|
||||
auto const time_t = std::chrono::system_clock::to_time_t(time.m_tp);
|
||||
std::tm tm = *std::gmtime(&time_t);
|
||||
os << std::put_time(&tm, "%Y-%m-%d %H:%M:%S UTC");
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(Directionality directionality)
|
||||
{
|
||||
switch (directionality)
|
||||
{
|
||||
case Directionality::OneDirection: return "OneDirection";
|
||||
case Directionality::BothDirections: return "BothDirections";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(Fuzziness fuzziness)
|
||||
{
|
||||
switch (fuzziness)
|
||||
{
|
||||
case Fuzziness::LowRes: return "LowRes";
|
||||
case Fuzziness::MediumRes: return "MediumRes";
|
||||
case Fuzziness::EndUnknown: return "EndUnknown";
|
||||
case Fuzziness::StartUnknown: return "StartUnknown";
|
||||
case Fuzziness::ExtentUnknown: return "ExtentUnknown";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(Ramps ramps)
|
||||
{
|
||||
switch (ramps)
|
||||
{
|
||||
case Ramps::All: return "All";
|
||||
case Ramps::Entry: return "Entry";
|
||||
case Ramps::Exit: return "Exit";
|
||||
case Ramps::None: return "None";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(RoadClass roadClass)
|
||||
{
|
||||
switch (roadClass)
|
||||
{
|
||||
case RoadClass::Motorway: return "Motorway";
|
||||
case RoadClass::Trunk: return "Trunk";
|
||||
case RoadClass::Primary: return "Primary";
|
||||
case RoadClass::Secondary: return "Secondary";
|
||||
case RoadClass::Tertiary: return "Tertiary";
|
||||
case RoadClass::Other: return "Other";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(EventClass eventClass)
|
||||
{
|
||||
switch (eventClass)
|
||||
{
|
||||
case EventClass::Invalid: return "Invalid";
|
||||
case EventClass::Activity: return "Activity";
|
||||
case EventClass::Authority: return "Authority";
|
||||
case EventClass::Carpool: return "Carpool";
|
||||
case EventClass::Congestion: return "Congestion";
|
||||
case EventClass::Construction: return "Construction";
|
||||
case EventClass::Delay: return "Delay";
|
||||
case EventClass::Environment: return "Environment";
|
||||
case EventClass::EquipmentStatus: return "EquipmentStatus";
|
||||
case EventClass::Hazard: return "Hazard";
|
||||
case EventClass::Incident: return "Incident";
|
||||
case EventClass::Restriction: return "Restriction";
|
||||
case EventClass::Security: return "Security";
|
||||
case EventClass::Transport: return "Transport";
|
||||
case EventClass::Weather: return "Weather";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(EventType eventType)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType::Invalid: return "Invalid";
|
||||
// TODO Activity*, Authority*, Carpool* (not in enum yet)
|
||||
case EventType::CongestionCleared: return "CongestionCleared";
|
||||
case EventType::CongestionForecastWithdrawn: return "CongestionForecastWithdrawn";
|
||||
case EventType::CongestionHeavyTraffic: return "CongestionHeavyTraffic";
|
||||
case EventType::CongestionLongQueue: return "CongestionLongQueue";
|
||||
case EventType::CongestionNone: return "CongestionNone";
|
||||
case EventType::CongestionNormalTraffic: return "CongestionNormalTraffic";
|
||||
case EventType::CongestionQueue: return "CongestionQueue";
|
||||
case EventType::CongestionQueueLikely: return "CongestionQueueLikely";
|
||||
case EventType::CongestionSlowTraffic: return "CongestionSlowTraffic";
|
||||
case EventType::CongestionStationaryTraffic: return "CongestionStationaryTraffic";
|
||||
case EventType::CongestionStationaryTrafficLikely: return "CongestionStationaryTrafficLikely";
|
||||
case EventType::CongestionTrafficBuildingUp: return "CongestionTrafficBuildingUp";
|
||||
case EventType::CongestionTrafficCongestion: return "CongestionTrafficCongestion";
|
||||
case EventType::CongestionTrafficEasing: return "CongestionTrafficEasing";
|
||||
case EventType::CongestionTrafficFlowingFreely: return "CongestionTrafficFlowingFreely";
|
||||
case EventType::CongestionTrafficHeavierThanNormal: return "CongestionTrafficHeavierThanNormal";
|
||||
case EventType::CongestionTrafficLighterThanNormal: return "CongestionTrafficLighterThanNormal";
|
||||
case EventType::CongestionTrafficMuchHeavierThanNormal: return "CongestionTrafficMuchHeavierThanNormal";
|
||||
case EventType::CongestionTrafficProblem: return "CongestionTrafficProblem";
|
||||
// TODO Construction* (not in enum yet)
|
||||
case EventType::DelayClearance: return "DelayClearance";
|
||||
case EventType::DelayDelay: return "DelayDelay";
|
||||
case EventType::DelayDelayPossible: return "DelayDelayPossible";
|
||||
case EventType::DelayForecastWithdrawn: return "DelayForecastWithdrawn";
|
||||
case EventType::DelayLongDelay: return "DelayLongDelay";
|
||||
case EventType::DelaySeveralHours: return "DelaySeveralHours";
|
||||
case EventType::DelayUncertainDuration: return "DelayUncertainDuration";
|
||||
case EventType::DelayVeryLongDelay: return "DelayVeryLongDelay";
|
||||
// TODO Environment*, EquipmentStatus*, Hazard*, Incident* (not in enum yet)
|
||||
// TODO complete Restriction* (not in enum yet)
|
||||
case EventType::RestrictionBlocked: return "RestrictionBlocked";
|
||||
case EventType::RestrictionBlockedAhead: return "RestrictionBlockedAhead";
|
||||
case EventType::RestrictionCarriagewayBlocked: return "RestrictionCarriagewayBlocked";
|
||||
case EventType::RestrictionCarriagewayClosed: return "RestrictionCarriagewayClosed";
|
||||
case EventType::RestrictionClosed: return "RestrictionClosed";
|
||||
case EventType::RestrictionClosedAhead: return "RestrictionClosedAhead";
|
||||
case EventType::RestrictionEntryBlocked: return "RestrictionEntryBlocked";
|
||||
case EventType::RestrictionEntryReopened: return "RestrictionEntryReopened";
|
||||
case EventType::RestrictionExitBlocked: return "RestrictionExitBlocked";
|
||||
case EventType::RestrictionExitReopened: return "RestrictionExitReopened";
|
||||
case EventType::RestrictionOpen: return "RestrictionOpen";
|
||||
case EventType::RestrictionRampBlocked: return "RestrictionRampBlocked";
|
||||
case EventType::RestrictionRampClosed: return "RestrictionRampClosed";
|
||||
case EventType::RestrictionRampReopened: return "RestrictionRampReopened";
|
||||
case EventType::RestrictionReopened: return "RestrictionReopened";
|
||||
case EventType::RestrictionSpeedLimit: return "RestrictionSpeedLimit";
|
||||
case EventType::RestrictionSpeedLimitLifted: return "RestrictionSpeedLimitLifted";
|
||||
// TODO Security*, Transport*, Weather* (not in enum yet)
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(ResponseStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case ResponseStatus::Ok: return "Ok";
|
||||
case ResponseStatus::InvalidOperation: return "InvalidOperation";
|
||||
case ResponseStatus::SubscriptionRejected: return "SubscriptionRejected";
|
||||
case ResponseStatus::NotCovered: return "NotCovered";
|
||||
case ResponseStatus::PartiallyCovered: return "PartiallyCovered";
|
||||
case ResponseStatus::SubscriptionUnknown: return "SubscriptionUnknown";
|
||||
case ResponseStatus::PushRejected: return "PushRejected";
|
||||
case ResponseStatus::InternalError: return "InternalError";
|
||||
case ResponseStatus::Invalid: return "Invalid";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(TrafficImpact impact)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "TrafficImpact { ";
|
||||
os << "speedGroup: " << DebugPrint(impact.m_speedGroup) << ", ";
|
||||
os << "maxspeed: " << (impact.m_maxspeed == kMaxspeedNone ? "none" : std::to_string(impact.m_maxspeed)) << ", ";
|
||||
os << "delayMins: " << impact.m_delayMins;
|
||||
os << " }";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(Point point)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Point { ";
|
||||
os << "coordinates: " << DebugPrint(point.m_coordinates) << ", ";
|
||||
os << "distance: " << (point.m_distance ? std::to_string(point.m_distance.value()) : "nullopt") << ", ";
|
||||
os << "junctionName: " << point.m_junctionName.value_or("nullopt") << ", ";
|
||||
os << "junctionRef: " << point.m_junctionRef.value_or("nullopt");
|
||||
os << " }";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(TraffLocation location)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "TraffLocation { ";
|
||||
os << "from: " << (location.m_from ? DebugPrint(location.m_from.value()) : "nullopt") << ", ";
|
||||
os << "at: " << (location.m_at ? DebugPrint(location.m_at.value()) : "nullopt") << ", ";
|
||||
os << "via: " << (location.m_via ? DebugPrint(location.m_via.value()) : "nullopt") << ", ";
|
||||
os << "to: " << (location.m_to ? DebugPrint(location.m_to.value()) : "nullopt") << ", ";
|
||||
os << "notVia: " << (location.m_notVia ? DebugPrint(location.m_notVia.value()) : "nullopt") << ", ";
|
||||
os << "fuzziness: " << (location.m_fuzziness ? DebugPrint(location.m_fuzziness.value()) : "nullopt") << ", ";
|
||||
os << "country: " << location.m_country.value_or("nullopt") << ", ";
|
||||
os << "territory: " << location.m_territory.value_or("nullopt") << ", ";
|
||||
os << "town: " << location.m_town.value_or("nullopt") << ", ";
|
||||
os << "roadClass: " << (location.m_roadClass ? DebugPrint(location.m_roadClass.value()) : "nullopt") << ", ";
|
||||
os << "roadRef: " << location.m_roadRef.value_or("nullopt") << ", ";
|
||||
os << "roadName: " << location.m_roadName.value_or("nullopt") << ", ";
|
||||
os << "origin: " << location.m_origin.value_or("nullopt") << ", ";
|
||||
os << "destination: " << location.m_destination.value_or("nullopt") << ", ";
|
||||
os << "direction: " << location.m_direction.value_or("nullopt") << ", ";
|
||||
os << "directionality: " << DebugPrint(location.m_directionality) << ", ";
|
||||
os << "ramps: " << DebugPrint(location.m_ramps);
|
||||
os << " }";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(TraffEvent event)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "TraffEvent { ";
|
||||
os << "class: " << DebugPrint(event.m_class) << ", ";
|
||||
os << "type: " << DebugPrint(event.m_type) << ", ";
|
||||
os << "length: " << (event.m_length ? std::to_string(event.m_length.value()) : "nullopt") << ", ";
|
||||
os << "probability: " << (event.m_probability ? std::to_string(event.m_probability.value()) : "nullopt") << ", ";
|
||||
os << "q_duration: "
|
||||
<< (event.m_qDurationMins
|
||||
? (std::to_string(event.m_qDurationMins.value() / 60) + ":" +
|
||||
(event.m_qDurationMins.value() % 60 < 10 ? "0" : "") +
|
||||
std::to_string(event.m_qDurationMins.value() % 60))
|
||||
: "nullopt")
|
||||
<< ", ";
|
||||
// TODO other quantifiers
|
||||
os << "speed: " << (event.m_speed ? std::to_string(event.m_speed.value()) : "nullopt");
|
||||
// TODO supplementary information
|
||||
os << " }";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(TraffMessage message)
|
||||
{
|
||||
std::string sep;
|
||||
std::ostringstream os;
|
||||
os << "TraffMessage { ";
|
||||
os << "id: " << message.m_id << ", ";
|
||||
|
||||
os << "replaces: [";
|
||||
sep = " ";
|
||||
for (auto const & replacedId : message.m_replaces)
|
||||
{
|
||||
os << sep << replacedId;
|
||||
sep = ", ";
|
||||
}
|
||||
os << " ], ";
|
||||
|
||||
os << "receiveTime: " << DebugPrint(message.m_receiveTime) << ", ";
|
||||
os << "updateTime: " << DebugPrint(message.m_updateTime) << ", ";
|
||||
os << "expirationTime: " << DebugPrint(message.m_expirationTime) << ", ";
|
||||
os << "startTime: " << (message.m_startTime ? DebugPrint(message.m_startTime.value()) : "nullopt") << ", ";
|
||||
os << "endTime: " << (message.m_endTime ? DebugPrint(message.m_endTime.value()) : "nullopt") << ", ";
|
||||
os << "cancellation: " << message.m_cancellation << ", ";
|
||||
os << "forecast: " << message.m_forecast << ", ";
|
||||
// TODO std::optional<Urgency> m_urgency; (not in struct yet)
|
||||
os << "location: " << (message.m_location ? DebugPrint(message.m_location.value()) : "nullopt") << ", ";
|
||||
|
||||
os << "events: [";
|
||||
sep = " ";
|
||||
for (auto const & event : message.m_events)
|
||||
{
|
||||
os << sep << DebugPrint(event);
|
||||
sep = ", ";
|
||||
}
|
||||
os << " ]";
|
||||
|
||||
os << " }";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string DebugPrint(TraffFeed feed)
|
||||
{
|
||||
std::string sep;
|
||||
std::ostringstream os;
|
||||
os << "[ ";
|
||||
sep = "";
|
||||
for (auto const & message : feed)
|
||||
{
|
||||
os << sep << DebugPrint(message);
|
||||
sep = ", ";
|
||||
}
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
} // namespace traffxml
|
||||
580
libs/traffxml/traff_model.hpp
Normal file
580
libs/traffxml/traff_model.hpp
Normal file
@@ -0,0 +1,580 @@
|
||||
#pragma once
|
||||
|
||||
//#include "traffxml/traff_foo.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
#include "traffic/speed_groups.hpp"
|
||||
#include "traffic/traffic_info.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
constexpr uint8_t kMaxspeedNone = 255;
|
||||
|
||||
/**
|
||||
* @brief Date and time decoded from ISO 8601.
|
||||
*
|
||||
* `IsoTime` is an opaque type. It is only guaranteed to be capable of holding a timestamp
|
||||
* converted from ISO 8601 which refers to the same UTC time as its ISO 8601 representation.
|
||||
* Time zone information is not guaranteed to be preserved: `13:37+01:00` may be returned e.g. as
|
||||
* `12:37Z` or `06:37-06:00`.
|
||||
*/
|
||||
class IsoTime
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Parses time in ISO 8601 format from a string and stores it in an `IsoTime`.
|
||||
*
|
||||
* ISO 8601 timestamps have the format `yyyy-mm-ddThh:mm:ss[.sss]`, optionally followed by a UTC
|
||||
* offset. For example, `2019-11-01T11:45:42+01:00` refers to 11:45:42 in the UTC+1 timezone, which
|
||||
* is 10:45:42 UTC.
|
||||
*
|
||||
* A UTC offset of `Z` denotes UTC and is equivalent to `+00:00` or `-00:00`. UTC is also assumed
|
||||
* if no UTC offset is specified.
|
||||
*
|
||||
* The UTC offset can be specified as `hh:mm`, `hhmm` or `hh`.
|
||||
*
|
||||
* Seconds can be specified as integer or float, but will be rounded to the nearest integer. For
|
||||
* example, 42.645 seconds will be rounded to 43 seconds.
|
||||
*
|
||||
* @param timeString Time in ISO8601 format
|
||||
* @return An `IsoTime` instance corresponding to `timeString`, or `std::nullopt` if `timeString` is not a valid ISO8601 time string.
|
||||
*/
|
||||
static std::optional<IsoTime> ParseIsoTime(std::string timeString);
|
||||
|
||||
/**
|
||||
* @brief Returns an `IsoTime` corresponding to current wall clock time.
|
||||
*
|
||||
* @return An `IsoTime` corresponding to current wall clock time.
|
||||
*/
|
||||
static IsoTime Now();
|
||||
|
||||
/**
|
||||
* @brief Whether the instance refers to a point of time in the past.
|
||||
*
|
||||
* Comparison is against system time.
|
||||
*
|
||||
* @return true if in the past, false of not.
|
||||
*/
|
||||
bool IsPast();
|
||||
|
||||
/**
|
||||
* @brief Shifts time to the present.
|
||||
*
|
||||
* This method is intended for testing. It shifts the timestamp by a fixed amount, so that
|
||||
* `nowRef` corresponds to current time. After this method returns, the timestamp will have the
|
||||
* same offset from current time that it had from `nowRef` at the time the call was made.
|
||||
*
|
||||
* @param nowRef
|
||||
*/
|
||||
void Shift(IsoTime nowRef);
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the instance.
|
||||
* @return The timestamp in ISO 8601 format.
|
||||
*/
|
||||
std::string ToString() const;
|
||||
|
||||
bool operator< (IsoTime & rhs);
|
||||
bool operator> (IsoTime & rhs);
|
||||
private:
|
||||
friend std::string DebugPrint(IsoTime time);
|
||||
|
||||
IsoTime(std::chrono::time_point<std::chrono::system_clock> tp);
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> m_tp;
|
||||
};
|
||||
|
||||
// TODO enum urgency
|
||||
|
||||
enum class Directionality
|
||||
{
|
||||
OneDirection,
|
||||
BothDirections
|
||||
};
|
||||
|
||||
enum class Fuzziness
|
||||
{
|
||||
LowRes,
|
||||
MediumRes,
|
||||
EndUnknown,
|
||||
StartUnknown,
|
||||
ExtentUnknown
|
||||
};
|
||||
|
||||
enum class Ramps
|
||||
{
|
||||
None,
|
||||
All,
|
||||
Entry,
|
||||
Exit
|
||||
};
|
||||
|
||||
enum class RoadClass
|
||||
{
|
||||
Motorway,
|
||||
Trunk,
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Other
|
||||
};
|
||||
|
||||
enum class QuantifierType
|
||||
{
|
||||
Dimension,
|
||||
Duration,
|
||||
Int,
|
||||
Ints,
|
||||
Speed,
|
||||
Temperature,
|
||||
Time,
|
||||
Weight,
|
||||
Invalid
|
||||
};
|
||||
|
||||
/*
|
||||
* When adding a new event class to this enum, be sure to do the following:
|
||||
*
|
||||
* * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventClassMap`
|
||||
* * in `traff_model.cpp`, extend `DebugPrint(EventClass)` to correctly process the new event classes
|
||||
* * in this file, add event types for this class to `EventType`
|
||||
*/
|
||||
enum class EventClass
|
||||
{
|
||||
Invalid,
|
||||
Activity,
|
||||
Authority,
|
||||
Carpool,
|
||||
Congestion,
|
||||
Construction,
|
||||
Delay,
|
||||
Environment,
|
||||
EquipmentStatus,
|
||||
Hazard,
|
||||
Incident,
|
||||
Restriction,
|
||||
Security,
|
||||
Transport,
|
||||
Weather
|
||||
};
|
||||
|
||||
/*
|
||||
* When adding a new event type to this enum, be sure to do the following:
|
||||
*
|
||||
* * in `traff_model_xml.cpp`, add the corresponding mapping to `kEventTypeMap`
|
||||
* * in `traff_model.cpp`:
|
||||
* * add speed group mappings in `kEventSpeedGroupMap`, if any
|
||||
* * add maxspeed mappings in `kEventMaxspeedMap`, if any (uncomment if needed)
|
||||
* * add delay mappings in `kEventDelayMap`, if any
|
||||
* * extend `DebugPrint(TraffEvent)` to correctly process the new events
|
||||
*/
|
||||
enum class EventType
|
||||
{
|
||||
Invalid,
|
||||
// TODO Activity*, Authority*, Carpool*
|
||||
CongestionCleared,
|
||||
CongestionForecastWithdrawn,
|
||||
CongestionHeavyTraffic,
|
||||
CongestionLongQueue,
|
||||
CongestionNone,
|
||||
CongestionNormalTraffic,
|
||||
CongestionQueue,
|
||||
CongestionQueueLikely,
|
||||
CongestionSlowTraffic,
|
||||
CongestionStationaryTraffic,
|
||||
CongestionStationaryTrafficLikely,
|
||||
CongestionTrafficBuildingUp,
|
||||
CongestionTrafficCongestion,
|
||||
CongestionTrafficEasing,
|
||||
CongestionTrafficFlowingFreely,
|
||||
CongestionTrafficHeavierThanNormal,
|
||||
CongestionTrafficLighterThanNormal,
|
||||
CongestionTrafficMuchHeavierThanNormal,
|
||||
CongestionTrafficProblem,
|
||||
// TODO Construction*
|
||||
DelayClearance,
|
||||
DelayDelay,
|
||||
DelayDelayPossible,
|
||||
DelayForecastWithdrawn,
|
||||
DelayLongDelay,
|
||||
DelaySeveralHours,
|
||||
DelayUncertainDuration,
|
||||
DelayVeryLongDelay,
|
||||
// TODO Environment*, EquipmentStatus*, Hazard*, Incident*
|
||||
// TODO complete Restriction*
|
||||
RestrictionBlocked,
|
||||
RestrictionBlockedAhead,
|
||||
RestrictionCarriagewayBlocked,
|
||||
RestrictionCarriagewayClosed,
|
||||
RestrictionClosed,
|
||||
RestrictionClosedAhead,
|
||||
RestrictionEntryBlocked,
|
||||
RestrictionEntryReopened,
|
||||
RestrictionExitBlocked,
|
||||
RestrictionExitReopened,
|
||||
RestrictionOpen,
|
||||
RestrictionRampBlocked,
|
||||
RestrictionRampClosed,
|
||||
RestrictionRampReopened,
|
||||
RestrictionReopened,
|
||||
RestrictionSpeedLimit,
|
||||
RestrictionSpeedLimitLifted,
|
||||
// TODO Security*, Transport*, Weather*
|
||||
};
|
||||
|
||||
enum class ResponseStatus
|
||||
{
|
||||
/**
|
||||
* The operation was successful.
|
||||
*/
|
||||
Ok,
|
||||
|
||||
/**
|
||||
* The source rejected the operation as invalid
|
||||
*
|
||||
* This may happen when a nonexistent operation is attempted, or an operation is attempted with
|
||||
* incomplete or otherwise invalid data.
|
||||
*
|
||||
* @note This corresponds to TraFF status `INVALID` but was renamed here.
|
||||
* `ResponseStatus::Invalid` refers to a different kind of error.
|
||||
*/
|
||||
InvalidOperation,
|
||||
|
||||
/**
|
||||
* The source rejected the subscription, e.g. because the filtered region is too large.
|
||||
*/
|
||||
SubscriptionRejected,
|
||||
|
||||
/**
|
||||
* The source does not supply data for the requested area; the request has failed.
|
||||
*/
|
||||
NotCovered,
|
||||
|
||||
/**
|
||||
* The source supplies data only for a subset of the requested area; the request was successful
|
||||
* (i.e. the subscription was created or changed as requested) but the consumer should be prepared
|
||||
* to receive incomplete data.
|
||||
*/
|
||||
PartiallyCovered,
|
||||
|
||||
/**
|
||||
* An operation (change, push, pull) was attempted on a subscription which the recipient did not
|
||||
* recognize. On transport channels which support stable identifiers for both communication
|
||||
* parties, this is also used if a consumer attempts an operation on a subscription created by
|
||||
* another consumer.
|
||||
*/
|
||||
SubscriptionUnknown,
|
||||
|
||||
/**
|
||||
* The aggregator does not accept unsolicited push requests from the sensor. Reserved for future
|
||||
* versions and not used as of TraFF 0.8.
|
||||
*/
|
||||
PushRejected,
|
||||
|
||||
/**
|
||||
* An internal error prevented the recipient of the request from fulfilling it.
|
||||
*
|
||||
* This is either translated directly from `INTERNAL_ERROR` returned from the source, or may be
|
||||
* inferred from errors on the transport channel (e.g. HTTP errors).
|
||||
*/
|
||||
InternalError,
|
||||
|
||||
/**
|
||||
* An unrecognized status code.
|
||||
*
|
||||
* This is used for all situations where we got a response from the source, with no indication of
|
||||
* an error, but could not obtain a known status code from it (e.g. XML failed to parse, did not
|
||||
* contain a status code, or contained an unknown status code).
|
||||
*
|
||||
* @note Not to be confused with TraFF status `INVALID`, which maps to
|
||||
* `ResponseStatus::InvalidOperation`.
|
||||
*/
|
||||
Invalid
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents the impact of one or more traffic events.
|
||||
*
|
||||
* Impact can be expressed in three ways:
|
||||
*
|
||||
* Traffic may flow at a certain percentage of the posted limit, often divided in bins. This is
|
||||
* used by some traffic services which report e.g. “slow traffic”, “stationary traffic” or
|
||||
* “queues”, and maps to speed groups in a straightforward way.
|
||||
*
|
||||
* Traffic may flow at, or be restricted to, a given speed. This is common with traffic flow
|
||||
* measurement data, or with temporary speed limits. Converting this to a speed group requires
|
||||
* knowledge of the regular speed limit.
|
||||
*
|
||||
* There may be a fixed delay, expressed as a duration in time. This may happen at checkpoints,
|
||||
* at sections where traffic flow is limited or where there is single alternate-lane traffic.
|
||||
* As the routing data model does not provide for explicit delays, they have to be converted into
|
||||
* speed groups. Again, this requires knowledge of the regular travel time along the route, as well
|
||||
* as its length.
|
||||
*
|
||||
* Closures can be expressed by setting `m_speedGroup` to `traffic::SpeedGroup::TempBlock`. If that
|
||||
* is the case, the other struct members are to be ignored.
|
||||
*/
|
||||
struct TrafficImpact
|
||||
{
|
||||
/**
|
||||
* @brief Whether two `TrafficImpact` instances are equal.
|
||||
*
|
||||
* Instances are considered equal if both have a speed group of `TempBlock`, in which case other
|
||||
* members are not compared. Otherwise, they are equal if, and only if, all three members hold
|
||||
* identical values between both instances.
|
||||
*/
|
||||
// Non-member friend as member operators do not work with std::optional
|
||||
friend bool operator==(TrafficImpact const & lhs, TrafficImpact const & rhs);
|
||||
friend bool operator!=(TrafficImpact const & lhs, TrafficImpact const & rhs) { return !(lhs == rhs); }
|
||||
|
||||
/**
|
||||
* @brief The speed group for the affected segments, or `traffic::SpeedGroup::Unknown` if unknown.
|
||||
*/
|
||||
traffic::SpeedGroup m_speedGroup = traffic::SpeedGroup::Unknown;
|
||||
|
||||
/**
|
||||
* @brief The speed limit, or speed of flowing traffic; `kMaxspeedNone` if none or unknown.
|
||||
*/
|
||||
uint8_t m_maxspeed = kMaxspeedNone;
|
||||
|
||||
/**
|
||||
* @brief The delay in minutes; 0 if none or unknown.
|
||||
*/
|
||||
uint16_t m_delayMins = 0;
|
||||
};
|
||||
|
||||
struct Point
|
||||
{
|
||||
/**
|
||||
* @brief Whether two points are equal.
|
||||
*
|
||||
* Two points are equal if, and only if, their coordinates are. Other attributes are not compared.
|
||||
*/
|
||||
// Non-member friend as member operators do not work with std::optional
|
||||
friend bool operator==(Point const & lhs, Point const & rhs);
|
||||
friend bool operator!=(Point const & lhs, Point const & rhs) { return !(lhs == rhs); }
|
||||
|
||||
// TODO role?
|
||||
ms::LatLon m_coordinates = ms::LatLon::Zero();
|
||||
std::optional<float> m_distance;
|
||||
std::optional<std::string> m_junctionName;
|
||||
std::optional<std::string> m_junctionRef;
|
||||
};
|
||||
|
||||
struct TraffLocation
|
||||
{
|
||||
/**
|
||||
* @brief Whether two locations are equal.
|
||||
*
|
||||
* Two locations are equal if, and only if, they contain the same points in the same roles.
|
||||
*
|
||||
* @todo Road class and ramps are not compared, though these values are used by the decoder. Not
|
||||
* comparing these values could lead to two seemingly equal locations resolving to a different
|
||||
* path. However, given that comparison only takes place between messages with identical IDs
|
||||
* (indicating both refer to the same event at the same location), such a situation is highly
|
||||
* unlikely to occur in practice.
|
||||
*/
|
||||
// Non-member friend as member operators do not work with std::optional
|
||||
friend bool operator==(TraffLocation const & lhs, TraffLocation const & rhs);
|
||||
friend bool operator!=(TraffLocation const & lhs, TraffLocation const & rhs) { return !(lhs == rhs); }
|
||||
|
||||
std::optional<std::string> m_country;
|
||||
std::optional<std::string> m_destination;
|
||||
std::optional<std::string> m_direction;
|
||||
Directionality m_directionality = Directionality::BothDirections;
|
||||
std::optional<Fuzziness> m_fuzziness;
|
||||
std::optional<std::string> m_origin;
|
||||
Ramps m_ramps = Ramps::None;
|
||||
std::optional<RoadClass> m_roadClass;
|
||||
// disabled for now, optional<bool> behaves weird and we don't really need it
|
||||
//std::optional<bool> m_roadIsUrban;
|
||||
std::optional<std::string> m_roadRef;
|
||||
std::optional<std::string> m_roadName;
|
||||
std::optional<std::string> m_territory;
|
||||
std::optional<std::string> m_town;
|
||||
std::optional<Point> m_from;
|
||||
std::optional<Point> m_to;
|
||||
std::optional<Point> m_at;
|
||||
std::optional<Point> m_via;
|
||||
std::optional<Point> m_notVia;
|
||||
};
|
||||
|
||||
struct TraffEvent
|
||||
{
|
||||
EventClass m_class = EventClass::Invalid;
|
||||
EventType m_type = EventType::Invalid;
|
||||
std::optional<uint16_t> m_length;
|
||||
std::optional<uint8_t> m_probability;
|
||||
std::optional<uint16_t> m_qDurationMins;
|
||||
/*
|
||||
* TODO remaining quantifiers
|
||||
* q_dimension
|
||||
* q_int
|
||||
* q_ints
|
||||
* q_speed
|
||||
* q_temperature
|
||||
* q_time
|
||||
* q_weight
|
||||
*/
|
||||
std::optional<uint8_t> m_speed;
|
||||
// TODO supplementary information
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global mapping from feature segments to speed groups, across all MWMs.
|
||||
*/
|
||||
using MultiMwmColoring = std::map<MwmSet::MwmId, std::map<traffic::TrafficInfo::RoadSegmentId, traffic::SpeedGroup>>;
|
||||
|
||||
struct TraffMessage
|
||||
{
|
||||
/**
|
||||
* @brief Gets the time after which this message effectively expires.
|
||||
*
|
||||
* The effective expiration time is the latest of `m_expirationTime`, `m_startTime` and
|
||||
* `m_endTime`. `nullopt` values are ignored.
|
||||
*
|
||||
* @return The effective expiration time for the message.
|
||||
*/
|
||||
IsoTime GetEffectiveExpirationTime();
|
||||
|
||||
/**
|
||||
* @brief Whether the message has expired.
|
||||
*
|
||||
* A message is considered to have expired if its effective expiration time (as returned by
|
||||
* `GetEffectiveExpirationTime()` refers to a point in time before `now`.
|
||||
*
|
||||
* @param now The reference time to compare to (usually current time)
|
||||
* @return True if the message has expired, false if not.
|
||||
*/
|
||||
bool IsExpired(IsoTime now);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the traffic impact of all events.
|
||||
*
|
||||
* If the message has multiple events, the traffic impact is determined separately for each
|
||||
* event and then aggregated. Aggregation takes the most restrictive value in each category
|
||||
* (speed group, maxspeed, delay).
|
||||
*
|
||||
* If the aggregated traffic impact includes `SpeedGroup::TempBlock`, its other members are to
|
||||
* be considered invalid.
|
||||
*
|
||||
* @return The aggregated traffic impact, or `std::nullopt` if the message has no events with traffic impact.
|
||||
*/
|
||||
std::optional<TrafficImpact> GetTrafficImpact();
|
||||
|
||||
/**
|
||||
* @brief Shifts timestamps to the present.
|
||||
*
|
||||
* This method is intended for testing. It shifts the timestamps of the message by a fixed amount,
|
||||
* so that `m_updateTime` corresponds to current time, and all other timestamps maintain their
|
||||
* offset to `m_updateTime`. If `m_startTime` and/or `m_endTime` are set, they may be adjusted
|
||||
* further to maintain their offset from midnight or the full hour (currently not implemented).
|
||||
*/
|
||||
void ShiftTimestamps();
|
||||
|
||||
std::string m_id;
|
||||
IsoTime m_receiveTime = IsoTime::Now();
|
||||
IsoTime m_updateTime = IsoTime::Now();
|
||||
IsoTime m_expirationTime = IsoTime::Now();
|
||||
std::optional<IsoTime> m_startTime = {};
|
||||
std::optional<IsoTime> m_endTime = {};
|
||||
bool m_cancellation = false;
|
||||
bool m_forecast = false;
|
||||
// TODO std::optional<Urgency> m_urgency;
|
||||
std::optional<TraffLocation> m_location;
|
||||
std::vector<TraffEvent> m_events;
|
||||
std::vector<std::string> m_replaces;
|
||||
MultiMwmColoring m_decoded;
|
||||
};
|
||||
|
||||
using TraffFeed = std::vector<TraffMessage>;
|
||||
|
||||
// TODO Capabilities
|
||||
|
||||
/*
|
||||
* Filter: currently not implemented.
|
||||
* We only use bbox, for which we have a suitable data type.
|
||||
* min_road_class is not needed as we do not filter by road class.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TraffSubscription: currently not implemented.
|
||||
* We just store the ID as a string.
|
||||
* Filters are only by bbox, not by min_road_class. The list is auto-generated from the list of
|
||||
* active MWMs and changes exactly when the active MWM set changes, eliminating the need to store
|
||||
* the full filter list.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Encapsulates the response to a TraFF request.
|
||||
*/
|
||||
struct TraffResponse
|
||||
{
|
||||
/**
|
||||
* @brief The response status for the request which triggered the response.
|
||||
*/
|
||||
ResponseStatus m_status = ResponseStatus::Invalid;
|
||||
|
||||
/**
|
||||
* @brief The subscription ID which the source has assigned to the subscriber.
|
||||
*
|
||||
* This attribute is how the source communicates the subscription ID to a subscriber. Required for
|
||||
* responses to a subscription request; some transport channels may require it for every
|
||||
* subscription-related operation; forbidden otherwise.
|
||||
*/
|
||||
std::string m_subscriptionId;
|
||||
|
||||
/**
|
||||
* @brief The time in seconds after which the source will consider the subscription invalid if no
|
||||
* activity occurs.
|
||||
*
|
||||
* Required for responses to a subscription request on some transport channels, optional on other
|
||||
* channels, forbidden for other requests.
|
||||
*
|
||||
* If not used, the value is zero.
|
||||
*/
|
||||
uint32_t m_timeout = 0;
|
||||
|
||||
/**
|
||||
* @brief A feed of traffic messages sent as part of the response.
|
||||
*/
|
||||
std::optional<TraffFeed> m_feed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Merges the contents of one `MultiMwmColoring` into another.
|
||||
*
|
||||
* After this function returns, `target` will hold the union of the entries it had prior to the
|
||||
* function call and the entries from `delta`.
|
||||
*
|
||||
* In case of conflict, the more restrictive speed group wins. That is, `TempBlock` overrides
|
||||
* everything else, `Unknown` never overrides anything else, and among `G0` to `G5`, the lowest
|
||||
* group wins.
|
||||
*
|
||||
* @param delta Contains the entries to be added.
|
||||
* @param target Receives the added entries.
|
||||
*/
|
||||
void MergeMultiMwmColoring(const MultiMwmColoring & delta, MultiMwmColoring & target);
|
||||
|
||||
std::string DebugPrint(IsoTime time);
|
||||
std::string DebugPrint(Directionality directionality);
|
||||
std::string DebugPrint(Ramps ramps);
|
||||
std::string DebugPrint(RoadClass roadClass);
|
||||
std::string DebugPrint(EventClass eventClass);
|
||||
std::string DebugPrint(EventType eventType);
|
||||
std::string DebugPrint(ResponseStatus status);
|
||||
std::string DebugPrint(TrafficImpact impact);
|
||||
std::string DebugPrint(Point point);
|
||||
std::string DebugPrint(TraffLocation location);
|
||||
std::string DebugPrint(TraffEvent event);
|
||||
std::string DebugPrint(TraffMessage message);
|
||||
std::string DebugPrint(TraffFeed feed);
|
||||
} // namespace traffxml
|
||||
1221
libs/traffxml/traff_model_xml.cpp
Normal file
1221
libs/traffxml/traff_model_xml.cpp
Normal file
File diff suppressed because it is too large
Load Diff
123
libs/traffxml/traff_model_xml.hpp
Normal file
123
libs/traffxml/traff_model_xml.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include "traffxml/traff_model.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace pugi
|
||||
{
|
||||
class xml_document;
|
||||
class xml_node;
|
||||
} // namespace pugi
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
/**
|
||||
* @brief Retrieves a TraFF feed from an XML document.
|
||||
*
|
||||
* The document must conform loosely to the TraFF specification (currently version 0.8).
|
||||
*
|
||||
* The name of the root element is not verified, but the `message` elements must be its immediate
|
||||
* children.
|
||||
*
|
||||
* Values which cannot be parsed correctly are skipped.
|
||||
*
|
||||
* Events whose event type does not match their event class are skipped.
|
||||
*
|
||||
* Messages, events, locations or points which lack mandatory information are skipped.
|
||||
*
|
||||
* If children are skipped but the parent remains valid, parsing it will report success.
|
||||
*
|
||||
* Parsing the feed will report failure if all its messages fail to parse, but not if it has no
|
||||
* messages.
|
||||
*
|
||||
* In addition to the TraFF specification, we also use a custom extension, `mwm_coloring`, which is
|
||||
* a child of `message` and holds decoded traffic coloring. In order to parse it, `dataSource` must
|
||||
* be specified. If `dataSource` is `nullopt`, coloring will be ignored. It is recommended to pass
|
||||
* `dataSource` if, and only if, parsing an XML stream that is expected to contain traffic coloring.
|
||||
* This is only expected to occur in cached data. TraFF from external sources is not expected to
|
||||
* contain `mwm_coloring` elements and souch information should be ignored in feeds from outside.
|
||||
*
|
||||
* @note To pass a reference to the framework data source (assuming the `framework` is the framework
|
||||
* instance), use `std::cref(framework.GetDataSource())`.
|
||||
*
|
||||
* @note Custom elements and attributes which are not part of the TraFF specification, other than
|
||||
* `mwm_coloring`, are ignored.
|
||||
*
|
||||
* @param document The XML document from which to retrieve the messages.
|
||||
* @param dataSource The data source for coloring, see description.
|
||||
* @param feed Receives the TraFF feed.
|
||||
* @return `true` on success, `false` on failure.
|
||||
*/
|
||||
bool ParseTraff(pugi::xml_document const & document,
|
||||
std::optional<std::reference_wrapper<const DataSource>> dataSource,
|
||||
TraffFeed & feed);
|
||||
|
||||
/**
|
||||
* @brief Generates XML from a TraFF feed.
|
||||
*
|
||||
* The resulting document largely conforms to the TraFF specification (currently version 0.8), but
|
||||
* may contain custom elements.
|
||||
*
|
||||
* The root element of the generated XML document is `feed`.
|
||||
*
|
||||
* @note Currently no custom elements are generated. Future versions may add the location decoded
|
||||
* into MWM IDs, feature IDs, directions and segments, along with their speed groups.
|
||||
*
|
||||
* @param feed The TraFF feed to encode.
|
||||
* @param document The XML document in which to store the messages.
|
||||
*/
|
||||
void GenerateTraff(TraffFeed const & feed, pugi::xml_document & document);
|
||||
|
||||
/**
|
||||
* @brief Generates XML from a map of TraFF messages.
|
||||
*
|
||||
* The resulting document largely conforms to the TraFF specification (currently version 0.8), but
|
||||
* may contain custom elements.
|
||||
*
|
||||
* The root element of the generated XML document is `feed`.
|
||||
*
|
||||
* @note Currently no custom elements are generated. Future versions may add the location decoded
|
||||
* into MWM IDs, feature IDs, directions and segments, along with their speed groups.
|
||||
*
|
||||
* @param messages A map whose values contain the TraFF messages to encode.
|
||||
* @param document The XML document in which to store the messages.
|
||||
*/
|
||||
void GenerateTraff(std::map<std::string, traffxml::TraffMessage> const & messages,
|
||||
pugi::xml_document & document);
|
||||
|
||||
/**
|
||||
* @brief Generates a list of XML `filter` elements from a vector of rects representing bboxes.
|
||||
*
|
||||
* The resulting string can be placed inside a TraFF XML `filter_list` element or can be passed as
|
||||
* an extra to an Android intent.
|
||||
*
|
||||
* It will have one `filter` element for each element in `bboxRects`, although simplification may
|
||||
* be applied to reduce the number of rects (currently not implemented).
|
||||
*
|
||||
* The `min_road_class` attribute is not used.
|
||||
*
|
||||
* @param bboxRects The rectangles, each of which represents a bounding box.
|
||||
* @return A string of XML `filter` elements.
|
||||
*/
|
||||
std::string FiltersToXml(std::vector<m2::RectD> & bboxRects);
|
||||
|
||||
/**
|
||||
* @brief Parses the response to a TraFF request.
|
||||
*
|
||||
* The response must comply with TraFF 0.8. The root element must be `response`.
|
||||
*
|
||||
* If a parsing error occurs, the response returned will have its `m_status` member set to
|
||||
* `ResponseStatus::Invalid`.
|
||||
*
|
||||
* @param responseXml The response, as a string in XML format.
|
||||
* @return The parsed response.
|
||||
*/
|
||||
TraffResponse ParseResponse(std::string const & responseXml);
|
||||
} // namespace traffxml
|
||||
328
libs/traffxml/traff_source.cpp
Normal file
328
libs/traffxml/traff_source.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
#include "traffxml/traff_source.hpp"
|
||||
|
||||
#include "traffxml/traff_model_xml.hpp"
|
||||
#include "traffxml/traff_storage.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace traffxml {
|
||||
TraffSource::TraffSource(TraffSourceManager & manager)
|
||||
: m_manager(manager)
|
||||
{}
|
||||
|
||||
void TraffSource::SubscribeOrChangeSubscription(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!IsSubscribed())
|
||||
Subscribe(mwms);
|
||||
else
|
||||
ChangeSubscription(mwms);
|
||||
}
|
||||
|
||||
std::string TraffSource::GetMwmFilters(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
std::vector<m2::RectD> rects;
|
||||
for (auto mwmId : mwms)
|
||||
rects.push_back(mwmId.GetInfo()->m_bordersRect);
|
||||
return traffxml::FiltersToXml(rects);
|
||||
}
|
||||
|
||||
void MockTraffSource::Create(TraffSourceManager & manager)
|
||||
{
|
||||
std::unique_ptr<MockTraffSource> source = std::unique_ptr<MockTraffSource>(new MockTraffSource(manager));
|
||||
manager.RegisterSource(std::move(source));
|
||||
}
|
||||
|
||||
MockTraffSource::MockTraffSource(TraffSourceManager & manager)
|
||||
: TraffSource(manager)
|
||||
{}
|
||||
|
||||
void MockTraffSource::Subscribe(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
std::string filterList = GetMwmFilters(mwms);
|
||||
LOG(LINFO, ("Would subscribe to:\n", filterList));
|
||||
m_subscriptionId = "placeholder_subscription_id";
|
||||
m_nextRequestTime = std::chrono::steady_clock::now(); // would be in the future if we got a feed here
|
||||
}
|
||||
|
||||
void MockTraffSource::ChangeSubscription(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
if (!IsSubscribed())
|
||||
return;
|
||||
std::string filterList = GetMwmFilters(mwms);
|
||||
LOG(LINFO, ("Would change subscription", m_subscriptionId, "to:\n", filterList));
|
||||
m_nextRequestTime = std::chrono::steady_clock::now(); // would be in the future if we got a feed here
|
||||
}
|
||||
|
||||
void MockTraffSource::Unsubscribe()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!IsSubscribed())
|
||||
return;
|
||||
LOG(LINFO, ("Would unsubscribe from", m_subscriptionId));
|
||||
m_subscriptionId.clear();
|
||||
}
|
||||
|
||||
bool MockTraffSource::IsPollNeeded()
|
||||
{
|
||||
return m_nextRequestTime.load() <= std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void MockTraffSource::Poll()
|
||||
{
|
||||
//std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany.xml");
|
||||
std::string fileName("test_data/traff/PL-A18-Krzyzowa-Lipiany-bidir.xml");
|
||||
//std::string fileName("test_data/traff/LT-A1-Vezaiciai-Endriejavas.xml");
|
||||
traffxml::LocalStorage storage(fileName);
|
||||
pugi::xml_document document;
|
||||
auto const load_result = storage.Load(document);
|
||||
if (!load_result)
|
||||
return;
|
||||
|
||||
m_lastRequestTime = std::chrono::steady_clock::now();
|
||||
std::setlocale(LC_ALL, "en_US.UTF-8");
|
||||
traffxml::TraffFeed feed;
|
||||
if (traffxml::ParseTraff(document, std::nullopt /* dataSource */, feed))
|
||||
{
|
||||
m_lastResponseTime = std::chrono::steady_clock::now();
|
||||
m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval;
|
||||
m_lastAvailability = Availability::IsAvailable;
|
||||
m_manager.ReceiveFeed(feed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("An error occurred parsing the TraFF feed"));
|
||||
m_lastAvailability = Availability::Error;
|
||||
/*
|
||||
* TODO how should we deal with future requests?
|
||||
* Static files usually don’t change.
|
||||
*/
|
||||
m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
TraffResponse HttpPost(std::string const & url, std::string data)
|
||||
{
|
||||
platform::HttpClient request(url);
|
||||
request.SetBodyData(data, "application/xml");
|
||||
|
||||
if (!request.RunHttpRequest() || request.ErrorCode() != 200)
|
||||
{
|
||||
TraffResponse result;
|
||||
result.m_status = ResponseStatus::InternalError;
|
||||
return result;
|
||||
}
|
||||
|
||||
LOG(LDEBUG, ("Got response, status", request.ErrorCode()));
|
||||
|
||||
TraffResponse result = ParseResponse(request.ServerResponse());
|
||||
return result;
|
||||
}
|
||||
|
||||
void HttpTraffSource::Create(TraffSourceManager & manager, std::string const & url)
|
||||
{
|
||||
std::unique_ptr<HttpTraffSource> source = std::unique_ptr<HttpTraffSource>(new HttpTraffSource(manager, url));
|
||||
manager.RegisterSource(std::move(source));
|
||||
}
|
||||
|
||||
HttpTraffSource::HttpTraffSource(TraffSourceManager & manager, std::string const & url)
|
||||
: TraffSource(manager)
|
||||
, m_url(url)
|
||||
{}
|
||||
|
||||
void HttpTraffSource::Close()
|
||||
{
|
||||
std::string data;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_subscriptionId.empty())
|
||||
return;
|
||||
data = "<request operation=\"UNSUBSCRIBE\" subscription_id=\"" + m_subscriptionId + "\"/>";
|
||||
m_subscriptionId.clear();
|
||||
}
|
||||
|
||||
LOG(LDEBUG, ("Sending request:\n", data));
|
||||
|
||||
threads::SimpleThread thread([this, data]() {
|
||||
TraffResponse response = HttpPost(m_url, data);
|
||||
return;
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void HttpTraffSource::Subscribe(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
std::string data = "<request operation=\"SUBSCRIBE\">\n<filter_list>\n"
|
||||
+ GetMwmFilters(mwms)
|
||||
+ "</filter_list>\n"
|
||||
+ "</request>";
|
||||
LOG(LDEBUG, ("Sending request:\n", data));
|
||||
|
||||
threads::SimpleThread thread([this, data]() {
|
||||
// TODO sometimes the request gets sent (and processed) twice
|
||||
TraffResponse response = HttpPost(m_url, data);
|
||||
OnSubscribeResponse(response);
|
||||
return;
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void HttpTraffSource::OnFeedReceived(TraffFeed & feed)
|
||||
{
|
||||
m_lastResponseTime = std::chrono::steady_clock::now();
|
||||
m_nextRequestTime = std::chrono::steady_clock::now() + m_updateInterval;
|
||||
m_lastAvailability = Availability::IsAvailable;
|
||||
m_manager.ReceiveFeed(feed);
|
||||
}
|
||||
|
||||
void HttpTraffSource::OnSubscribeResponse(TraffResponse & response)
|
||||
{
|
||||
if (response.m_status == ResponseStatus::Ok
|
||||
|| response.m_status == ResponseStatus::PartiallyCovered)
|
||||
{
|
||||
if (response.m_subscriptionId.empty())
|
||||
LOG(LWARNING, ("Server replied with", response.m_status, "but subscription ID is empty; ignoring"));
|
||||
else
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_subscriptionId = response.m_subscriptionId;
|
||||
// TODO timeout
|
||||
}
|
||||
if (response.m_feed && !response.m_feed.value().empty())
|
||||
OnFeedReceived(response.m_feed.value());
|
||||
else
|
||||
Poll();
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("Subscribe request failed:", response.m_status));
|
||||
}
|
||||
|
||||
void HttpTraffSource::ChangeSubscription(std::set<MwmSet::MwmId> & mwms)
|
||||
{
|
||||
std::string data = "<request operation=\"SUBSCRIPTION_CHANGE\" subscription_id=\"" + m_subscriptionId + "\">\n"
|
||||
+ "<filter_list>\n"
|
||||
+ GetMwmFilters(mwms)
|
||||
+ "</filter_list>\n"
|
||||
+ "</request>";
|
||||
LOG(LDEBUG, ("Sending request:\n", data));
|
||||
|
||||
threads::SimpleThread thread([this, data]() {
|
||||
TraffResponse response = HttpPost(m_url, data);
|
||||
OnChangeSubscriptionResponse(response);
|
||||
return;
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void HttpTraffSource::OnChangeSubscriptionResponse(TraffResponse & response)
|
||||
{
|
||||
if (response.m_status == ResponseStatus::Ok
|
||||
|| response.m_status == ResponseStatus::PartiallyCovered)
|
||||
{
|
||||
if (response.m_feed && !response.m_feed.value().empty())
|
||||
OnFeedReceived(response.m_feed.value());
|
||||
else
|
||||
Poll();
|
||||
}
|
||||
else if (response.m_status == ResponseStatus::SubscriptionUnknown)
|
||||
{
|
||||
LOG(LWARNING, ("Change Subscription returned", response.m_status, " – removing subscription", m_subscriptionId));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_subscriptionId.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("Change Subscription request failed:", response.m_status));
|
||||
}
|
||||
|
||||
void HttpTraffSource::Unsubscribe()
|
||||
{
|
||||
std::string data;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_subscriptionId.empty())
|
||||
return;
|
||||
data = "<request operation=\"UNSUBSCRIBE\" subscription_id=\"" + m_subscriptionId + "\"/>";
|
||||
}
|
||||
|
||||
LOG(LDEBUG, ("Sending request:\n", data));
|
||||
|
||||
threads::SimpleThread thread([this, data]() {
|
||||
TraffResponse response = HttpPost(m_url, data);
|
||||
OnUnsubscribeResponse(response);
|
||||
return;
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void HttpTraffSource::OnUnsubscribeResponse(TraffResponse & response)
|
||||
{
|
||||
if (response.m_status != ResponseStatus::Ok
|
||||
&& response.m_status != ResponseStatus::SubscriptionUnknown)
|
||||
{
|
||||
LOG(LWARNING, ("Unsubscribe returned", response.m_status, " – removing subscription"));
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_subscriptionId.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpTraffSource::IsPollNeeded()
|
||||
{
|
||||
// TODO revisit logic
|
||||
return m_nextRequestTime.load() <= std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void HttpTraffSource::Poll()
|
||||
{
|
||||
std::string data;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_subscriptionId.empty())
|
||||
return;
|
||||
data = "<request operation=\"POLL\" subscription_id=\"" + m_subscriptionId + "\"/>";
|
||||
}
|
||||
|
||||
LOG(LDEBUG, ("Sending request:\n", data));
|
||||
|
||||
threads::SimpleThread thread([this, data]() {
|
||||
// TODO sometimes the request gets sent (and processed) twice
|
||||
TraffResponse response = HttpPost(m_url, data);
|
||||
OnPollResponse(response);
|
||||
return;
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void HttpTraffSource::OnPollResponse(TraffResponse & response)
|
||||
{
|
||||
if (response.m_status == ResponseStatus::Ok)
|
||||
{
|
||||
if (response.m_feed && !response.m_feed.value().empty())
|
||||
OnFeedReceived(response.m_feed.value());
|
||||
}
|
||||
else if (response.m_status == ResponseStatus::SubscriptionUnknown)
|
||||
{
|
||||
LOG(LWARNING, ("Poll returned", response.m_status, " – removing subscription", m_subscriptionId));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_subscriptionId.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
LOG(LWARNING, ("Poll returned", response.m_status));
|
||||
}
|
||||
} // namespace traffxml
|
||||
546
libs/traffxml/traff_source.hpp
Normal file
546
libs/traffxml/traff_source.hpp
Normal file
@@ -0,0 +1,546 @@
|
||||
#pragma once
|
||||
|
||||
#include "traffxml/traff_model.hpp"
|
||||
|
||||
#include "base/thread.hpp"
|
||||
|
||||
#include "indexer/mwm_set.hpp"
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
class TraffSource;
|
||||
|
||||
/**
|
||||
* @brief Abstract class which manages TraFF sources.
|
||||
*
|
||||
* `TraffSource` and its subclasses register with `TraffSourceManager` upon creation. The
|
||||
* `TraffSourceManager` calls `TraffSource` methods to manage its subscription and poll for
|
||||
* messages, and exposes a method to deliver message feeds.
|
||||
*/
|
||||
class TraffSourceManager
|
||||
{
|
||||
public:
|
||||
virtual ~TraffSourceManager() {}
|
||||
|
||||
/**
|
||||
* @brief Retrieves all currently active MWMs.
|
||||
*
|
||||
* This method retrieves all MWMs for which traffic data is needed (viewport, current position
|
||||
* and route) and stores them in `activeMwms`.
|
||||
*
|
||||
* Implementations must ensure thread safety, so that this method can be called from any thread.
|
||||
*
|
||||
* @param activeMwms Retrieves the list of active MWMs.
|
||||
*/
|
||||
virtual void GetActiveMwms(std::set<MwmSet::MwmId> & activeMwms) = 0;
|
||||
|
||||
/**
|
||||
* @brief Processes a traffic feed.
|
||||
*
|
||||
* The feed may be a result of a pull operation, or received through a push operation.
|
||||
* (Push operations are not supported by all sources.)
|
||||
*
|
||||
* This method is safe to call from any thread.
|
||||
*
|
||||
* @param feed The traffic feed.
|
||||
*/
|
||||
virtual void ReceiveFeed(traffxml::TraffFeed feed) = 0;
|
||||
|
||||
/**
|
||||
* @brief Registers a `TraffSource`.
|
||||
* @param source The source.
|
||||
*/
|
||||
virtual void RegisterSource(std::unique_ptr<TraffSource> source) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract base class for TraFF sources.
|
||||
*
|
||||
* Subclasses encapsulate various forms of TraFF sources. The base class provides methods for
|
||||
* subscription management, message retrieval and service status.
|
||||
*
|
||||
* Any `TraffSource` method may call `TrafficManager` methods exposed through the
|
||||
* `TraffSourceManager` interface. The traffic manager must therefore ensure there is no conflict
|
||||
* between thread-synchronization mechanisms held when calling a `TraffSource` method and those
|
||||
* which may get requested when that method calls a `TraffSourceManager` method.
|
||||
*
|
||||
* Each subclass should implement a non-public constructor (private if the subclass is final,
|
||||
* protected otherwise) and a public factory method. The factory method takes the same arguments
|
||||
* as the constructor, creates an instance wrapped in a `std::unique_ptr` and registers it with
|
||||
* the `TraffSourceManager`. It can be implemented as follows:
|
||||
* ```
|
||||
* void SomeTraffSource::Create(TraffSourceManager & manager, SomeOtherArg & otherArg)
|
||||
* {
|
||||
* std::unique_ptr<SomeTraffSource> source = std::unique_ptr<SomeTraffSource>(new SomeTraffSource(manager, otherArg));
|
||||
* manager.RegisterSource(std::move(source));
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Each subclass must provide implementations for `Subscribe()`, `ChangeSubscription()`,
|
||||
* `Unsubscribe()`, `IsPollNeeded()` and `Poll()`.
|
||||
*
|
||||
* Most of these methods can be called from any thread, including the UI thread (see documentation
|
||||
* of individual methods for details). This has two implications:
|
||||
*
|
||||
* Subclasses must ensure thread safety for methods they implement, in particular regarding access
|
||||
* to shared members. This can be done by locking `m_mutex`.
|
||||
*
|
||||
* Also, methods should not block or perform lengthy operations. Network operations must be
|
||||
* delegated to a separate thread (attempting a network operation on the UI thread will cause the
|
||||
* application to be killed on Android).
|
||||
*
|
||||
* This class provides various protected members which subclasses can build upon. These include a
|
||||
* reference to the `TraffSourceManager`, a mutex for thread-safe access, a subscription ID,
|
||||
* timestamps for the last request and response, as well as for the next request, a retry count
|
||||
* for failed operations, and an indication of a pending request.
|
||||
*/
|
||||
class TraffSource
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Whether traffic data is available.
|
||||
*
|
||||
* The default value upon creating a new instance should be `Unknown`. After that, the value
|
||||
* should be changed based on the result of the last TraFF operation, as detailed below:
|
||||
*
|
||||
* `OK` changes the status from `Unknown`, or any error which would be resolved by the last
|
||||
* operation, to `IsAvailable`.
|
||||
*
|
||||
* `INVALID` indicates a condition which should be treated as a bug, either in the source or its
|
||||
* backend. It should generate a log entry, and changes the status to `Error`.
|
||||
*
|
||||
* `SUBSCRIPTION_REJECTED` changes the status to `SubscriptionRejected`.
|
||||
*
|
||||
* `NOT_COVERED` changes the status to `NotCovered`.
|
||||
*
|
||||
* `PARTIALLY_COVERED` has the same effect as `OK`.
|
||||
*
|
||||
* `SUBSCRIPTION_UNKNOWN` should be handled by clearing the subscription ID and resubscribing,
|
||||
* then setting the status based on the result of the new subscription.
|
||||
*
|
||||
* `INTERNAL_ERROR` changes the status to `Error`.
|
||||
*
|
||||
* If the source does not seem to be connected to a valid backend (e.g. if a HTTP source responds
|
||||
* with an HTTP error), the status should be changed to `Error`.
|
||||
*
|
||||
* If a TraFF `GET_CAPABILITIES` request returns a minimum version higher than supported by this
|
||||
* application, the status should be changed to `ExpiredApp` and no further requests to the source
|
||||
* should be attempted.
|
||||
*
|
||||
* @todo Should `PARTIALLY_COVERED`, or `GET_CAPABILITIES` reporting a target version higher than
|
||||
* supported, be stored in the class instance?
|
||||
*/
|
||||
enum class Availability
|
||||
{
|
||||
/**
|
||||
* The source is working normally.
|
||||
* This status is reached after the first request was made, if it is successful.
|
||||
*/
|
||||
IsAvailable,
|
||||
/**
|
||||
* The source, or its backend, rejected the subscription.
|
||||
* This may happen for various reasons, possibly because the requested area was too large.
|
||||
* An existing subscription ID (if any) remains valid, but no poll operations should be
|
||||
* attempted until the subscription is changed successfully.
|
||||
*/
|
||||
SubscriptionRejected,
|
||||
/**
|
||||
* The requested area is not covered by the source.
|
||||
* An existing subscription ID (if any) remains valid, but poll operations will not return any
|
||||
* messages until the subscription is changed successfully.
|
||||
*/
|
||||
NotCovered,
|
||||
/**
|
||||
* The source has reported an internal error, has reported an invalid request or returned
|
||||
* invalid data.
|
||||
* The failed operation should be retried at a resonably chosen interval. After the source
|
||||
* resumes normal operation, previously issued subscription IDs may no longer be valid (in which
|
||||
* case the caller should attempt to resubscribe) and/or messages may be repeated.
|
||||
*/
|
||||
Error,
|
||||
/** The app does not support the minimum TraFF version required by the source. */
|
||||
ExpiredApp,
|
||||
/** No request was made yet. */
|
||||
Unknown
|
||||
};
|
||||
|
||||
virtual ~TraffSource() {}
|
||||
|
||||
/**
|
||||
* @brief Ensures we have a subscription covering the MWMs indicated.
|
||||
*
|
||||
* This method subscribes to a traffic service if not already subscribed, or changes the existing
|
||||
* subscription otherwise.
|
||||
*
|
||||
* The default implementation acquires the mutex before running the following code:
|
||||
*
|
||||
* ```
|
||||
* if (!IsSubscribed())
|
||||
* Subscribe(mwms);
|
||||
* else
|
||||
* ChangeSubscription(mwms);
|
||||
* ```
|
||||
*
|
||||
* Therefore, `IsSubscribed()`, `Subscribe()` and `ChangeSubscription()` need not (and should not)
|
||||
* acquire the mutex on their own.
|
||||
*
|
||||
* @param mwms The new set of MWMs for which data is needed.
|
||||
*/
|
||||
virtual void SubscribeOrChangeSubscription(std::set<MwmSet::MwmId> & mwms);
|
||||
|
||||
/**
|
||||
* @brief Whether this source should be polled.
|
||||
*
|
||||
* Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and
|
||||
* poll the source only if the result is true.
|
||||
*
|
||||
* It is up to the source to decide when to return true or false. Typically a source would return
|
||||
* false if another request is still pending, a predefined poll interval has not yet elapsed since
|
||||
* the previous successful response, during the retry interval following an error, or if an error
|
||||
* is not recoverable (such as `ExpiredApp`). In all other case it would return true.
|
||||
*
|
||||
* This method is only called from the `TrafficManager` worker thread.
|
||||
*
|
||||
* @return true if the source should be polled, false if not.
|
||||
*/
|
||||
virtual bool IsPollNeeded() = 0;
|
||||
|
||||
/**
|
||||
* @brief Polls the traffic service for updates.
|
||||
*
|
||||
* For sources which reliably push data, this implementation may do nothing.
|
||||
*
|
||||
* It is up to the caller to call `IsPollNeeded()` prior to calling this function, and use its
|
||||
* result to decide whether or not to poll, or to force a poll operation.
|
||||
*
|
||||
* Sources should handle cases in which the backend responds with `SUBSCRIPTION_UNKNOWN`, usually
|
||||
* by deleting the subscription ID and resubscribing to the set of active MWMs. The set of active
|
||||
* MWMs can be retrieved by calling `m_manager.GetActiveMwms()`.
|
||||
*
|
||||
* This method is only called from the `TrafficManager` worker thread.
|
||||
*/
|
||||
virtual void Poll() = 0;
|
||||
|
||||
/**
|
||||
* @brief Unsubscribes from a traffic service we are subscribed to.
|
||||
*
|
||||
* Unsubscribing without being subscribed is a no-op.
|
||||
*/
|
||||
virtual void Unsubscribe() = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructs a new `TraffSource`.
|
||||
* @param manager The `TrafficSourceManager` instance to register the source with.
|
||||
*/
|
||||
TraffSource(TraffSourceManager & manager);
|
||||
|
||||
/**
|
||||
* @brief Returns a TraFF filter list for a set of MWMs.
|
||||
*
|
||||
* @param mwms The MWMs for which a filter list is to be created.
|
||||
* @return A `filter_list` in XML format.
|
||||
*/
|
||||
static std::string GetMwmFilters(std::set<MwmSet::MwmId> & mwms);
|
||||
|
||||
/**
|
||||
* @brief Subscribes to a traffic service.
|
||||
*
|
||||
* If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is
|
||||
* acquired before this method is called, and implementations do not need to (and should not)
|
||||
* acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`.
|
||||
*
|
||||
* @param mwms The MWMs for which data is needed.
|
||||
*/
|
||||
virtual void Subscribe(std::set<MwmSet::MwmId> & mwms) = 0;
|
||||
|
||||
/**
|
||||
* @brief Changes an existing traffic subscription.
|
||||
*
|
||||
* If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is
|
||||
* acquired before this method is called, and implementations do not need to (and should not)
|
||||
* acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`.
|
||||
*
|
||||
* Sources should handle cases in which the backend responds with `SUBSCRIPTION_UNKNOWN`, usually
|
||||
* by deleting the subscription ID and resubscribing to `mwms`. Asynchronous implementations, in
|
||||
* which `mwms` may no longer be available when the operation completes, can retrieve the set of
|
||||
* active MWMs can be retrieved by calling `m_manager.GetActiveMwms()`.
|
||||
*
|
||||
* @param mwms The new set of MWMs for which data is needed.
|
||||
*/
|
||||
virtual void ChangeSubscription(std::set<MwmSet::MwmId> & mwms) = 0;
|
||||
|
||||
/**
|
||||
* @brief Whether we are currently subscribed to a traffic service.
|
||||
*
|
||||
* If the default implementation of `SubscribeOrChangeSubscription()` is used, `m_mutex` is
|
||||
* acquired before this method is called, and implementations do not need to (and should not)
|
||||
* acquire it again. Any other calls to this method must be protected by acquiring `m_mutex`.
|
||||
*
|
||||
* @return true if subscribed, false if not.
|
||||
*/
|
||||
virtual bool IsSubscribed() { return !m_subscriptionId.empty(); }
|
||||
|
||||
TraffSourceManager & m_manager;
|
||||
|
||||
/**
|
||||
* @brief Mutex for access to shared members.
|
||||
*
|
||||
* Any access to members shared between threads must be protected by obtaining this mutex first.
|
||||
*/
|
||||
std::mutex m_mutex;
|
||||
|
||||
/**
|
||||
* @brief The subscription ID received from the backend.
|
||||
*
|
||||
* An empty subscription ID means no subscription.
|
||||
*/
|
||||
std::string m_subscriptionId;
|
||||
|
||||
/**
|
||||
* @brief When the last update request occurred.
|
||||
*
|
||||
* This timestamp is the basis for determining whether an update is needed.
|
||||
*
|
||||
* It is initially in the past. Subclasses that use it should update it whenever a request is made.
|
||||
*/
|
||||
std::atomic<std::chrono::time_point<std::chrono::steady_clock>> m_lastRequestTime;
|
||||
|
||||
/**
|
||||
* @brief When the last response was received.
|
||||
*
|
||||
* This timestamp is the basis for determining whether a network request timed out, or if data is
|
||||
* outdated.
|
||||
*
|
||||
* It is initially in the past. Subclasses that use it should update it whenever a response to a
|
||||
* request is received.
|
||||
*/
|
||||
std::atomic<std::chrono::time_point<std::chrono::steady_clock>> m_lastResponseTime;
|
||||
|
||||
/**
|
||||
* @brief When the next request should be made.
|
||||
*
|
||||
* This timestamp is initiated to current time and updated when a request is made, or a response
|
||||
* is received.
|
||||
*
|
||||
* It is initially in the present. Subclasses that use it should update it on every request or
|
||||
* response, setting it a defined timespan into the future.
|
||||
*/
|
||||
std::atomic<std::chrono::time_point<std::chrono::steady_clock>> m_nextRequestTime = std::chrono::steady_clock::now();
|
||||
|
||||
/**
|
||||
* @brief The number of failed traffic requests for this source.
|
||||
*
|
||||
* Reset when a request is successful.
|
||||
*/
|
||||
std::atomic<int> m_retriesCount = 0;
|
||||
|
||||
/**
|
||||
* @brief Whether a request is currently pending for this source.
|
||||
*
|
||||
* Set to `true` when a request is scheduled, reverted to `false` when a response is received or
|
||||
* the request fails.
|
||||
*/
|
||||
std::atomic<bool> m_isWaitingForResponse = false;
|
||||
|
||||
/**
|
||||
* @brief The last reported availability of the traffic source.
|
||||
*
|
||||
* See the documentation of `Availability` for possible values and their meanings.
|
||||
*
|
||||
* Availability is `Unknown` until a result for the first request (positive or negative) has been
|
||||
* received. Subclasses must update this value, ensuring it always correctly reflects the status
|
||||
* of the source.
|
||||
*/
|
||||
std::atomic<Availability> m_lastAvailability = Availability::Unknown;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY(TraffSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mock TraFF source.
|
||||
*
|
||||
* This source will accept any and all subscription requests and return a static subscription ID.
|
||||
* Polling will return a static set of messages.
|
||||
*/
|
||||
class MockTraffSource : public TraffSource
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a new `MockTraffSource` instance and registers it with the traffic manager.
|
||||
*
|
||||
* @param manager The traffic manager to register the new instance with
|
||||
*/
|
||||
static void Create(TraffSourceManager & manager);
|
||||
|
||||
/**
|
||||
* @brief Subscribes to a traffic service.
|
||||
*
|
||||
* @param mwms The MWMs for which data is needed.
|
||||
*/
|
||||
virtual void Subscribe(std::set<MwmSet::MwmId> & mwms) override;
|
||||
|
||||
/**
|
||||
* @brief Changes an existing traffic subscription.
|
||||
*
|
||||
* @param mwms The new set of MWMs for which data is needed.
|
||||
*/
|
||||
virtual void ChangeSubscription(std::set<MwmSet::MwmId> & mwms) override;
|
||||
|
||||
/**
|
||||
* @brief Unsubscribes from a traffic service we are subscribed to.
|
||||
*/
|
||||
virtual void Unsubscribe() override;
|
||||
|
||||
/**
|
||||
* @brief Whether this source should be polled.
|
||||
*
|
||||
* Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and
|
||||
* poll the source only if the result is true.
|
||||
*
|
||||
* This implementation uses `m_nextRequestTime` to determine when the next poll is due. When a
|
||||
* feed is received, `m_nextRequestTime` is set to a point in time 5 minutes in the future. As
|
||||
* long as `m_nextRequestTime` is in the future, this method returns false.
|
||||
*
|
||||
* @return true if the source should be polled, false if not.
|
||||
*/
|
||||
virtual bool IsPollNeeded() override;
|
||||
|
||||
/**
|
||||
* @brief Polls the traffic service for updates.
|
||||
*/
|
||||
virtual void Poll() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructs a new `MockTraffSource`.
|
||||
* @param manager The `TrafficSourceManager` instance to register the source with.
|
||||
*/
|
||||
MockTraffSource(TraffSourceManager & manager);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The update interval, 5 minutes.
|
||||
*/
|
||||
static auto constexpr m_updateInterval = std::chrono::minutes(5);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A TraFF source backed by a HTTP[S] server.
|
||||
*/
|
||||
class HttpTraffSource : public TraffSource
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Creates a new `HttpTraffSource` instance and registers it with the traffic manager.
|
||||
*
|
||||
* @param manager The traffic manager to register the new instance with
|
||||
* @param url The URL for the TraFF service API.
|
||||
*/
|
||||
static void Create(TraffSourceManager & manager, std::string const & url);
|
||||
|
||||
/**
|
||||
* @brief Prepares the HTTP traffic source for unloading.
|
||||
*
|
||||
* If there is still an active subscription, it unsubscribes, but without processing the result
|
||||
* received from the service. Otherwise, teardown is a no-op.
|
||||
*/
|
||||
// TODO move this to the parent class and override it here?
|
||||
void Close();
|
||||
|
||||
/**
|
||||
* @brief Subscribes to a traffic service.
|
||||
*
|
||||
* @param mwms The MWMs for which data is needed.
|
||||
*/
|
||||
virtual void Subscribe(std::set<MwmSet::MwmId> & mwms) override;
|
||||
|
||||
/**
|
||||
* @brief Changes an existing traffic subscription.
|
||||
*
|
||||
* @param mwms The new set of MWMs for which data is needed.
|
||||
*/
|
||||
virtual void ChangeSubscription(std::set<MwmSet::MwmId> & mwms) override;
|
||||
|
||||
/**
|
||||
* @brief Unsubscribes from a traffic service we are subscribed to.
|
||||
*/
|
||||
virtual void Unsubscribe() override;
|
||||
|
||||
/**
|
||||
* @brief Whether this source should be polled.
|
||||
*
|
||||
* Prior to calling `Poll()` on a source, the caller should always first call `IsPollNeeded()` and
|
||||
* poll the source only if the result is true.
|
||||
*
|
||||
* @todo Document how the result is calculated. For example:
|
||||
* This implementation uses `m_nextRequestTime` to determine when the next poll is due. When a
|
||||
* feed is received, `m_nextRequestTime` is set to a point in time 5 minutes in the future. As
|
||||
* long as `m_nextRequestTime` is in the future, this method returns false.
|
||||
*
|
||||
* @return true if the source should be polled, false if not.
|
||||
*/
|
||||
virtual bool IsPollNeeded() override;
|
||||
|
||||
/**
|
||||
* @brief Polls the traffic service for updates.
|
||||
*/
|
||||
virtual void Poll() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructs a new `HttpTraffSource`.
|
||||
* @param manager The `TrafficSourceManager` instance to register the source with.
|
||||
* @param url The URL for the TraFF service API.
|
||||
*/
|
||||
HttpTraffSource(TraffSourceManager & manager, std::string const & url);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Processes a TraFF feed.
|
||||
* @param feed The feed.
|
||||
*/
|
||||
void OnFeedReceived(TraffFeed & feed);
|
||||
|
||||
/**
|
||||
* @brief Processes the response to a subscribe request.
|
||||
* @param response The response to the subscribe operation.
|
||||
*/
|
||||
void OnSubscribeResponse(TraffResponse & response);
|
||||
|
||||
/**
|
||||
* @brief Processes the response to a change subscription request.
|
||||
* @param response The response to the change subscription operation.
|
||||
*/
|
||||
void OnChangeSubscriptionResponse(TraffResponse & response);
|
||||
|
||||
/**
|
||||
* @brief Processes the response to an unsubscribe request.
|
||||
* @param response The response to the unsubscribe operation.
|
||||
*/
|
||||
void OnUnsubscribeResponse(TraffResponse & response);
|
||||
|
||||
/**
|
||||
* @brief Processes the response to a poll request.
|
||||
* @param response The response to the poll operation.
|
||||
*/
|
||||
void OnPollResponse(TraffResponse & response);
|
||||
|
||||
/**
|
||||
* @brief The update interval, 5 minutes.
|
||||
*/
|
||||
static auto constexpr m_updateInterval = std::chrono::minutes(5);
|
||||
|
||||
/**
|
||||
* @brief The URL for the TraFF service.
|
||||
*/
|
||||
const std::string m_url;
|
||||
};
|
||||
} // namespace traffxml
|
||||
76
libs/traffxml/traff_storage.cpp
Normal file
76
libs/traffxml/traff_storage.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "traffxml/traff_storage.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/internal/file_data.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string GetFilePath(std::string const & fileName) { return GetPlatform().WritablePathForFile(fileName); }
|
||||
} // namespace
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
// StorageLocal ------------------------------------------------------------------------------------
|
||||
bool LocalStorage::Save(pugi::xml_document const & doc)
|
||||
{
|
||||
auto const filePath = GetFilePath(m_fileName);
|
||||
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
|
||||
return base::WriteToTempAndRenameToFile(filePath, [&doc](std::string const & fileName) {
|
||||
return doc.save_file(fileName.data(), " " /* indent */);
|
||||
});
|
||||
}
|
||||
|
||||
bool LocalStorage::Load(pugi::xml_document & doc)
|
||||
{
|
||||
auto const filePath = GetFilePath(m_fileName);
|
||||
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
|
||||
auto const result = doc.load_file(filePath.c_str());
|
||||
/*
|
||||
* Note: status_file_not_found is ok for our use cases:
|
||||
* - editor: if a user has never made any edits.
|
||||
* - traffic: if no traffic information has ever been retrieved (first run)
|
||||
*/
|
||||
if (result != pugi::status_ok && result != pugi::status_file_not_found)
|
||||
{
|
||||
LOG(LERROR, ("Can't load file from disk:", filePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalStorage::Reset()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
|
||||
return base::DeleteFileX(GetFilePath(m_fileName));
|
||||
}
|
||||
|
||||
// StorageMemory -----------------------------------------------------------------------------------
|
||||
bool InMemoryStorage::Save(pugi::xml_document const & doc)
|
||||
{
|
||||
m_doc.reset(doc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InMemoryStorage::Load(pugi::xml_document & doc)
|
||||
{
|
||||
doc.reset(m_doc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InMemoryStorage::Reset()
|
||||
{
|
||||
m_doc.reset();
|
||||
return true;
|
||||
}
|
||||
} // namespace traffxml
|
||||
77
libs/traffxml/traff_storage.hpp
Normal file
77
libs/traffxml/traff_storage.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
/*
|
||||
* TODO combine this header and its CPP file with editor/editor_storage.hpp and its CPP counterpart,
|
||||
* give it an appropriate namespace and place it where both editor and traffic can use it.
|
||||
* `traff_storage` is essentially copied from `editor_storage` with just a few modifications:
|
||||
* - namespace
|
||||
* - instead of relying on a hardcoded file name, `LocalStorage` is initialized with a file name
|
||||
* - log output, variable names and comments changed to be use case neutral (no API changes)
|
||||
* - refactoring: no global `using namespace` (no API changes)
|
||||
* Apart from the first two points, this file can serve as a drop-in replacement for `editor_storage`.
|
||||
* Traffic uses only `LocalStorage`, the rest has been kept to ease migration to a unified storage
|
||||
* component.
|
||||
*/
|
||||
|
||||
namespace traffxml
|
||||
{
|
||||
/**
|
||||
* @brief Storage interface for XML data.
|
||||
*/
|
||||
class StorageBase
|
||||
{
|
||||
public:
|
||||
virtual ~StorageBase() = default;
|
||||
|
||||
virtual bool Save(pugi::xml_document const & doc) = 0;
|
||||
virtual bool Load(pugi::xml_document & doc) = 0;
|
||||
virtual bool Reset() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class which saves/loads XML data to/from local file.
|
||||
* @note this class IS thread-safe.
|
||||
*/
|
||||
class LocalStorage : public StorageBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a `LocalStorage` instance.
|
||||
* @param fileName The file name and path where the file in question will be persisted. It is
|
||||
* interpreted relative to the platform-specific path; absolute paths are not supported as some
|
||||
* platforms restrict applications’ access to files outside their designated path.
|
||||
*/
|
||||
LocalStorage(std::string const & fileName)
|
||||
: m_fileName(fileName)
|
||||
{}
|
||||
|
||||
// StorageBase overrides:
|
||||
bool Save(pugi::xml_document const & doc) override;
|
||||
bool Load(pugi::xml_document & doc) override;
|
||||
bool Reset() override;
|
||||
|
||||
private:
|
||||
std::string m_fileName;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class which saves/loads data to/from xml_document class instance.
|
||||
* @note this class is NOT thread-safe.
|
||||
*/
|
||||
class InMemoryStorage : public StorageBase
|
||||
{
|
||||
public:
|
||||
// StorageBase overrides:
|
||||
bool Save(pugi::xml_document const & doc) override;
|
||||
bool Load(pugi::xml_document & doc) override;
|
||||
bool Reset() override;
|
||||
|
||||
private:
|
||||
pugi::xml_document m_doc;
|
||||
};
|
||||
} // namespace traffxml
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user