mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-06 12:34:24 +00:00
Compare commits
235 Commits
3bad6d25f0
...
traffic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
426a9661ab | ||
|
|
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/bookmarks
|
||||||
data/edits.xml
|
data/edits.xml
|
||||||
|
data/traffic.xml
|
||||||
data/World.mwm
|
data/World.mwm
|
||||||
data/WorldCoasts.mwm
|
data/WorldCoasts.mwm
|
||||||
data/world_mwm/*
|
data/world_mwm/*
|
||||||
|
|||||||
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.
|
# R8 crypts the source line numbers in all log messages.
|
||||||
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
|
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
|
||||||
-dontoptimize
|
-dontoptimize
|
||||||
|
|
||||||
|
# Keep classes for Android TraFF support
|
||||||
|
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_7 { *; }
|
||||||
|
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_8 { *; }
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent>
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="org.traffxml.traff.GET_CAPABILITIES"/>
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="org.traffxml.traff.POLL"/>
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="org.traffxml.traff.SUBSCRIBE"/>
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="org.traffxml.traff.SUBSCRIPTION_CHANGE"/>
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="org.traffxml.traff.UNSUBSCRIBE"/>
|
||||||
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<supports-screens
|
<supports-screens
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class LayersUtils
|
|||||||
availableLayers.add(Mode.OUTDOORS);
|
availableLayers.add(Mode.OUTDOORS);
|
||||||
availableLayers.add(Mode.ISOLINES);
|
availableLayers.add(Mode.ISOLINES);
|
||||||
availableLayers.add(Mode.SUBWAY);
|
availableLayers.add(Mode.SUBWAY);
|
||||||
|
availableLayers.add(Mode.TRAFFIC);
|
||||||
return availableLayers;
|
return availableLayers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,19 @@ package app.organicmaps.settings;
|
|||||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.MultiSelectListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceCategory;
|
import androidx.preference.PreferenceCategory;
|
||||||
import androidx.preference.TwoStatePreference;
|
import androidx.preference.TwoStatePreference;
|
||||||
@@ -28,6 +35,7 @@ import app.organicmaps.sdk.routing.RoutingOptions;
|
|||||||
import app.organicmaps.sdk.search.SearchRecents;
|
import app.organicmaps.sdk.search.SearchRecents;
|
||||||
import app.organicmaps.sdk.settings.MapLanguageCode;
|
import app.organicmaps.sdk.settings.MapLanguageCode;
|
||||||
import app.organicmaps.sdk.settings.UnitLocale;
|
import app.organicmaps.sdk.settings.UnitLocale;
|
||||||
|
import app.organicmaps.sdk.traffxml.AndroidTransport;
|
||||||
import app.organicmaps.sdk.util.Config;
|
import app.organicmaps.sdk.util.Config;
|
||||||
import app.organicmaps.sdk.util.NetworkPolicy;
|
import app.organicmaps.sdk.util.NetworkPolicy;
|
||||||
import app.organicmaps.sdk.util.PowerManagment;
|
import app.organicmaps.sdk.util.PowerManagment;
|
||||||
@@ -35,11 +43,13 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
|
|||||||
import app.organicmaps.sdk.util.log.LogsManager;
|
import app.organicmaps.sdk.util.log.LogsManager;
|
||||||
import app.organicmaps.util.ThemeSwitcher;
|
import app.organicmaps.util.ThemeSwitcher;
|
||||||
import app.organicmaps.util.Utils;
|
import app.organicmaps.util.Utils;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
|
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
|
||||||
{
|
{
|
||||||
@@ -61,6 +71,10 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
initAutoDownloadPrefsCallbacks();
|
initAutoDownloadPrefsCallbacks();
|
||||||
initLargeFontSizePrefsCallbacks();
|
initLargeFontSizePrefsCallbacks();
|
||||||
initTransliterationPrefsCallbacks();
|
initTransliterationPrefsCallbacks();
|
||||||
|
initTrafficHttpEnabledPrefsCallbacks();
|
||||||
|
initTrafficHttpUrlPrefsCallbacks();
|
||||||
|
initTrafficAppsPrefs();
|
||||||
|
initTrafficLegacyEnabledPrefsCallbacks();
|
||||||
init3dModePrefsCallbacks();
|
init3dModePrefsCallbacks();
|
||||||
initPerspectivePrefsCallbacks();
|
initPerspectivePrefsCallbacks();
|
||||||
initAutoZoomPrefsCallbacks();
|
initAutoZoomPrefsCallbacks();
|
||||||
@@ -136,6 +150,46 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
pref.setSummary(locale.getDisplayLanguage());
|
pref.setSummary(locale.getDisplayLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTrafficHttpUrlSummary()
|
||||||
|
{
|
||||||
|
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||||
|
String summary = Config.getTrafficHttpUrl();
|
||||||
|
if (summary.length() == 0)
|
||||||
|
pref.setSummary(R.string.traffic_http_url_not_set);
|
||||||
|
else
|
||||||
|
pref.setSummary(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTrafficAppsSummary()
|
||||||
|
{
|
||||||
|
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||||
|
/*
|
||||||
|
* If the preference is disabled, it has not been initialized. This is the case if no TraFF
|
||||||
|
* apps were found. The code below would crash when trying to access the entries, and there
|
||||||
|
* is no need to update the summary if the setting cannot be changed.
|
||||||
|
*/
|
||||||
|
if (!pref.isEnabled())
|
||||||
|
return;
|
||||||
|
String[] apps = Config.getTrafficApps();
|
||||||
|
if (apps.length == 0)
|
||||||
|
pref.setSummary(R.string.traffic_apps_none_selected);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String summary = "";
|
||||||
|
for (int i = 0; i < apps.length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
summary = summary + ", ";
|
||||||
|
int index = pref.findIndexOfValue(apps[i]);
|
||||||
|
if (i >= 0)
|
||||||
|
summary = summary + pref.getEntries()[index];
|
||||||
|
else
|
||||||
|
summary = summary + apps[i];
|
||||||
|
}
|
||||||
|
pref.setSummary(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRoutingSettingsPrefsSummary()
|
private void updateRoutingSettingsPrefsSummary()
|
||||||
{
|
{
|
||||||
final Preference pref = getPreference(getString(R.string.prefs_routing));
|
final Preference pref = getPreference(getString(R.string.prefs_routing));
|
||||||
@@ -163,6 +217,8 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
updateVoiceInstructionsPrefsSummary();
|
updateVoiceInstructionsPrefsSummary();
|
||||||
updateRoutingSettingsPrefsSummary();
|
updateRoutingSettingsPrefsSummary();
|
||||||
updateMapLanguageCodeSummary();
|
updateMapLanguageCodeSummary();
|
||||||
|
updateTrafficHttpUrlSummary();
|
||||||
|
updateTrafficAppsSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -224,6 +280,91 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initTrafficHttpEnabledPrefsCallbacks()
|
||||||
|
{
|
||||||
|
final Preference pref = getPreference(getString(R.string.pref_traffic_http_enabled));
|
||||||
|
|
||||||
|
((TwoStatePreference)pref).setChecked(Config.getTrafficHttpEnabled());
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
final boolean oldVal = Config.getTrafficHttpEnabled();
|
||||||
|
final boolean newVal = (Boolean) newValue;
|
||||||
|
if (oldVal != newVal)
|
||||||
|
Config.setTrafficHttpEnabled(newVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTrafficHttpUrlPrefsCallbacks()
|
||||||
|
{
|
||||||
|
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||||
|
|
||||||
|
((EditTextPreference)pref).setText(Config.getTrafficHttpUrl());
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
final String oldVal = Config.getTrafficHttpUrl();
|
||||||
|
final String newVal = (String) newValue;
|
||||||
|
if (!oldVal.equals(newVal))
|
||||||
|
Config.setTrafficHttpUrl(newVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTrafficAppsPrefs()
|
||||||
|
{
|
||||||
|
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||||
|
|
||||||
|
PackageManager pm = getContext().getPackageManager();
|
||||||
|
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0);
|
||||||
|
|
||||||
|
if (receivers == null || receivers.isEmpty())
|
||||||
|
{
|
||||||
|
pref.setSummary(R.string.traffic_apps_not_available);
|
||||||
|
pref.setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pref.setEnabled(true);
|
||||||
|
|
||||||
|
List<String> entryList = new ArrayList<>(receivers.size());
|
||||||
|
List<String> valueList = new ArrayList<>(receivers.size());
|
||||||
|
|
||||||
|
for (ResolveInfo receiver : receivers)
|
||||||
|
{
|
||||||
|
// friendly name
|
||||||
|
entryList.add(receiver.loadLabel(pm).toString());
|
||||||
|
// actual value (we just need the package name, broadcasts are sent to any receiver in the package)
|
||||||
|
valueList.add(receiver.activityInfo.applicationInfo.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
pref.setEntries(entryList.toArray(new CharSequence[0]));
|
||||||
|
pref.setEntryValues(valueList.toArray(new CharSequence[0]));
|
||||||
|
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
// newValue is a Set<String>, each item is a package ID
|
||||||
|
String[] apps = ((Set<String>)newValue).toArray(new String[0]);
|
||||||
|
Config.setTrafficApps(apps);
|
||||||
|
updateTrafficAppsSummary();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTrafficLegacyEnabledPrefsCallbacks()
|
||||||
|
{
|
||||||
|
final Preference pref = getPreference(getString(R.string.pref_traffic_legacy_enabled));
|
||||||
|
|
||||||
|
((TwoStatePreference)pref).setChecked(Config.getTrafficLegacyEnabled());
|
||||||
|
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
final boolean oldVal = Config.getTrafficLegacyEnabled();
|
||||||
|
final boolean newVal = (Boolean) newValue;
|
||||||
|
if (oldVal != newVal)
|
||||||
|
Config.setTrafficLegacyEnabled(newVal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void initUseMobileDataPrefsCallbacks()
|
private void initUseMobileDataPrefsCallbacks()
|
||||||
{
|
{
|
||||||
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));
|
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ public enum ThemeSwitcher
|
|||||||
{
|
{
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
|
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
else
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||||
|
|
||||||
if (RoutingController.get().isVehicleNavigation())
|
if (RoutingController.get().isVehicleNavigation())
|
||||||
style = MapStyle.VehicleDark;
|
style = MapStyle.VehicleDark;
|
||||||
@@ -103,7 +104,8 @@ public enum ThemeSwitcher
|
|||||||
{
|
{
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
|
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
else
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||||
|
|
||||||
if (RoutingController.get().isVehicleNavigation())
|
if (RoutingController.get().isVehicleNavigation())
|
||||||
style = MapStyle.VehicleClear;
|
style = MapStyle.VehicleClear;
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ public class PlacePageView extends Fragment
|
|||||||
private MaterialTextView mTvLastChecked;
|
private MaterialTextView mTvLastChecked;
|
||||||
private View mEditPlace;
|
private View mEditPlace;
|
||||||
private View mAddPlace;
|
private View mAddPlace;
|
||||||
private View mMapTooOld;
|
|
||||||
private View mEditTopSpace;
|
private View mEditTopSpace;
|
||||||
private ShapeableImageView mColorIcon;
|
private ShapeableImageView mColorIcon;
|
||||||
private MaterialTextView mTvCategory;
|
private MaterialTextView mTvCategory;
|
||||||
@@ -319,7 +318,6 @@ public class PlacePageView extends Fragment
|
|||||||
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
|
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
|
||||||
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
|
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
|
||||||
mAddPlace = mFrame.findViewById(R.id.ll__place_add);
|
mAddPlace = mFrame.findViewById(R.id.ll__place_add);
|
||||||
mMapTooOld = mFrame.findViewById(R.id.cv__map_too_old);
|
|
||||||
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
|
mEditTopSpace = mFrame.findViewById(R.id.edit_top_space);
|
||||||
latlon.setOnLongClickListener(this);
|
latlon.setOnLongClickListener(this);
|
||||||
address.setOnLongClickListener(this);
|
address.setOnLongClickListener(this);
|
||||||
@@ -686,7 +684,7 @@ public class PlacePageView extends Fragment
|
|||||||
|
|
||||||
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
|
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
|
||||||
{
|
{
|
||||||
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld);
|
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -694,59 +692,31 @@ public class PlacePageView extends Fragment
|
|||||||
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
|
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
|
||||||
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
|
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
|
||||||
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
|
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
|
||||||
|
mTvEditPlace.setOnClickListener(this);
|
||||||
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace();
|
mTvAddPlace.setOnClickListener(this);
|
||||||
|
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
|
||||||
if (shouldEnableEditPlace)
|
mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
|
||||||
{
|
final int editTextButtonColor =
|
||||||
mTvEditPlace.setOnClickListener(this);
|
Editor.nativeShouldEnableEditPlace()
|
||||||
mTvAddPlace.setOnClickListener(this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTvEditPlace.setOnClickListener((v) -> {
|
|
||||||
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
|
|
||||||
});
|
|
||||||
mTvAddPlace.setOnClickListener((v) -> {
|
|
||||||
Utils.showSnackbar(v.getContext(), v.getRootView(), R.string.place_page_too_old_to_edit);
|
|
||||||
});
|
|
||||||
|
|
||||||
CountryItem map = CountryItem.fill(MapManager.nativeGetSelectedCountry());
|
|
||||||
|
|
||||||
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|
|
||||||
|| map.status == CountryItem.STATUS_FAILED)
|
|
||||||
{
|
|
||||||
mMapTooOld.setVisibility(VISIBLE);
|
|
||||||
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
|
|
||||||
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
|
|
||||||
|
|
||||||
if (canUpdateMap)
|
|
||||||
{
|
|
||||||
mTvUpdateTooOldMap.setOnClickListener((v) -> {
|
|
||||||
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
|
|
||||||
mMapTooOld.setVisibility(GONE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTvUpdateTooOldMap.setVisibility(GONE);
|
|
||||||
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
|
|
||||||
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final int editButtonColor =
|
|
||||||
shouldEnableEditPlace
|
|
||||||
? ContextCompat.getColor(
|
? ContextCompat.getColor(
|
||||||
getContext(),
|
getContext(),
|
||||||
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
|
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
|
||||||
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
|
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
|
||||||
|
final ColorStateList editStrokeButtonColor = new ColorStateList(
|
||||||
mTvEditPlace.setTextColor(editButtonColor);
|
new int[][]{
|
||||||
mTvAddPlace.setTextColor(editButtonColor);
|
new int[]{android.R.attr.state_enabled}, // enabled
|
||||||
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
new int[]{-android.R.attr.state_enabled} // disabled
|
||||||
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
},
|
||||||
|
new int[]{
|
||||||
|
ContextCompat.getColor(
|
||||||
|
getContext(),
|
||||||
|
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary)),
|
||||||
|
ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled)
|
||||||
|
});
|
||||||
|
mTvEditPlace.setTextColor(editTextButtonColor);
|
||||||
|
mTvAddPlace.setTextColor(editTextButtonColor);
|
||||||
|
mTvEditPlace.setStrokeColor(editStrokeButtonColor);
|
||||||
|
mTvAddPlace.setStrokeColor(editStrokeButtonColor);
|
||||||
UiUtils.showIf(
|
UiUtils.showIf(
|
||||||
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
|
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
|
||||||
mEditTopSpace);
|
mEditTopSpace);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="48dp"
|
|
||||||
android:height="48dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M453,680L513,680L513,440L453,440L453,680ZM479.98,366Q494,366 503.5,356.8Q513,347.6 513,334Q513,319.55 503.52,309.78Q494.04,300 480.02,300Q466,300 456.5,309.78Q447,319.55 447,334Q447,347.6 456.48,356.8Q465.96,366 479.98,366ZM480.27,880Q397.53,880 324.77,848.5Q252,817 197.5,762.5Q143,708 111.5,635.16Q80,562.32 80,479.5Q80,396.68 111.5,323.84Q143,251 197.5,197Q252,143 324.84,111.5Q397.68,80 480.5,80Q563.32,80 636.16,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,479.73Q880,562.47 848.5,635.23Q817,708 763,762.32Q709,816.63 636,848.32Q563,880 480.27,880ZM480.5,820Q622,820 721,720.5Q820,621 820,479.5Q820,338 721.19,239Q622.38,140 480,140Q339,140 239.5,238.81Q140,337.62 140,480Q140,621 239.5,720.5Q339,820 480.5,820ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
app:tint="?colorSecondary"/>
|
app:tint="?colorSecondary"/>
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/head_message"
|
android:id="@+id/head_message"
|
||||||
style="@style/TextAppearance.Headline6"
|
style="?fontHeadline6"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/margin_base"
|
android:layout_marginTop="@dimen/margin_base"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
android:text="@string/download_map_title" />
|
android:text="@string/download_map_title" />
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/download_message"
|
android:id="@+id/download_message"
|
||||||
style="@style/TextAppearance.Body2"
|
style="?fontBody2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/margin_base"
|
android:layout_marginStart="@dimen/margin_base"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:text="@string/aa_connected_title"
|
android:text="@string/aa_connected_title"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/TextAppearance.Headline4"
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/car_used_on_the_car_screen"
|
android:text="@string/car_used_on_the_car_screen"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/TextAppearance.Body1" />
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:text="@string/aa_request_permission_activity_text"
|
android:text="@string/aa_request_permission_activity_text"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/TextAppearance.Headline4"
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
android:layout_marginStart="@dimen/margin_base"
|
android:layout_marginStart="@dimen/margin_base"
|
||||||
android:maxLines="3"
|
android:maxLines="3"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textAppearance="@style/TextAppearance.Headline6"
|
android:textAppearance="?fontHeadline6"
|
||||||
tools:text="Title" />
|
tools:text="Title" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/margin_base"
|
android:layout_marginStart="@dimen/margin_base"
|
||||||
android:text="@string/download_country_ask"
|
android:text="@string/download_country_ask"
|
||||||
android:textAppearance="@style/TextAppearance.Body2"
|
android:textAppearance="?fontBody2"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
android:textAppearance="@style/MwmTextAppearance.Title" />
|
android:textAppearance="@style/MwmTextAppearance.Title" />
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/et__input_layout"
|
android:id="@+id/et__input_layout"
|
||||||
|
style="?fontBody1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:hintEnabled="false">
|
app:hintEnabled="false">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
android:layout_marginTop="@dimen/margin_base">
|
android:layout_marginTop="@dimen/margin_base">
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
style="@style/TextAppearance.Subtitle1"
|
style="?fontSubtitle1"
|
||||||
android:id="@+id/sort_by_default"
|
android:id="@+id/sort_by_default"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
app:buttonTint="@null"/>
|
app:buttonTint="@null"/>
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
style="@style/TextAppearance.Subtitle1"
|
style="?fontSubtitle1"
|
||||||
android:id="@+id/sort_by_name"
|
android:id="@+id/sort_by_name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
app:buttonTint="@null"/>
|
app:buttonTint="@null"/>
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
style="@style/TextAppearance.Subtitle1"
|
style="?fontSubtitle1"
|
||||||
android:id="@+id/sort_by_type"
|
android:id="@+id/sort_by_type"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
app:buttonTint="@null"/>
|
app:buttonTint="@null"/>
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
style="@style/TextAppearance.Subtitle1"
|
style="?fontSubtitle1"
|
||||||
android:id="@+id/sort_by_distance"
|
android:id="@+id/sort_by_distance"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
app:buttonTint="@null"/>
|
app:buttonTint="@null"/>
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
style="@style/TextAppearance.Subtitle1"
|
style="?fontSubtitle1"
|
||||||
android:id="@+id/sort_by_time"
|
android:id="@+id/sort_by_time"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/edit_bookmark_name_input"
|
android:id="@+id/edit_bookmark_name_input"
|
||||||
|
style="?fontBody1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/name"
|
android:hint="@string/name"
|
||||||
@@ -87,6 +88,7 @@
|
|||||||
tools:src="@drawable/ic_bookmark_none" />
|
tools:src="@drawable/ic_bookmark_none" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="?fontBody1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/margin_half"
|
android:layout_marginEnd="@dimen/margin_half"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
android:layout_marginEnd="@dimen/margin_base"
|
android:layout_marginEnd="@dimen/margin_base"
|
||||||
android:paddingTop="@dimen/margin_half"
|
android:paddingTop="@dimen/margin_half"
|
||||||
android:paddingBottom="@dimen/margin_base"
|
android:paddingBottom="@dimen/margin_base"
|
||||||
android:textAppearance="@style/TextAppearance.Headline6"
|
android:textAppearance="?fontHeadline6"
|
||||||
tools:text="Xindian Shitoushan Trail" />
|
tools:text="Xindian Shitoushan Trail" />
|
||||||
<include
|
<include
|
||||||
layout="@layout/item_divider"
|
layout="@layout/item_divider"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
android:layout_marginStart="@dimen/margin_base"
|
android:layout_marginStart="@dimen/margin_base"
|
||||||
android:layout_marginTop="@dimen/margin_base"
|
android:layout_marginTop="@dimen/margin_base"
|
||||||
android:text="@string/layers_title"
|
android:text="@string/layers_title"
|
||||||
android:textAppearance="@style/TextAppearance.Headline6"
|
android:textAppearance="?fontHeadline6"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
android:ellipsize="middle"
|
android:ellipsize="middle"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textAppearance="@style/TextAppearance.Body1"
|
android:textAppearance="?fontBody1"
|
||||||
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/bottom_line_container"
|
android:id="@+id/bottom_line_container"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
||||||
android:ellipsize="middle"
|
android:ellipsize="middle"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="@style/TextAppearance.Body1"
|
android:textAppearance="?fontBody1"
|
||||||
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/size"
|
android:id="@+id/size"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
style="@style/TextAppearance.Body2"
|
style="?fontBody2"
|
||||||
tools:text="42000000" />
|
tools:text="42000000" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
android:paddingBottom="@dimen/margin_half_plus">
|
android:paddingBottom="@dimen/margin_half_plus">
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/text_message"
|
android:id="@+id/text_message"
|
||||||
style="@style/TextAppearance.Headline6"
|
style="?fontHeadline6"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextAppearance.Headline6"
|
style="?fontHeadline6"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?windowBackgroundForced"
|
android:background="?windowBackgroundForced"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxWidth="500dp"
|
android:maxWidth="500dp"
|
||||||
android:text="@string/editor_category_unsuitable_title"
|
android:text="@string/editor_category_unsuitable_title"
|
||||||
android:textAppearance="@style/TextAppearance.Headline6"
|
android:textAppearance="@style/TextAppearance.MdcTypographyStyles.Headline6"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/editor_category_unsuitable_text"
|
android:id="@+id/editor_category_unsuitable_text"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/input_layout"
|
android:id="@+id/input_layout"
|
||||||
|
style="?fontBody1"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
|||||||
@@ -147,12 +147,12 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/allday"
|
android:id="@+id/allday"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/editor_height_allday"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="?clickableBackground"
|
android:background="?clickableBackground"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="@dimen/margin_half">
|
android:padding="@dimen/margin_half_plus">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -164,8 +164,7 @@
|
|||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
android:id="@+id/sw__allday"
|
android:id="@+id/sw__allday"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"/>
|
||||||
android:layout_marginEnd="@dimen/margin_quarter"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
android:layout_marginTop="@dimen/margin_base"
|
android:layout_marginTop="@dimen/margin_base"
|
||||||
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="@style/TextAppearance.Body1"
|
android:textAppearance="?fontBody1"
|
||||||
tools:text="@string/tracks_title" />
|
tools:text="@string/tracks_title" />
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/tv__bookmark_distance"
|
android:id="@+id/tv__bookmark_distance"
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textAppearance="@style/TextAppearance.Body2"
|
android:textAppearance="?fontBody2"
|
||||||
tools:text="@string/by_distance" />
|
tools:text="@string/by_distance" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
|||||||
@@ -82,15 +82,13 @@
|
|||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/place_page_last_checked"
|
android:id="@+id/place_page_last_checked"
|
||||||
style="@style/TextAppearance.Caption"
|
style="?fontCaption"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="@dimen/margin_half"
|
android:paddingBottom="@dimen/margin_half"
|
||||||
android:paddingHorizontal="@dimen/margin_base"
|
android:paddingHorizontal="@dimen/margin_base"
|
||||||
tools:text="Existence confirmed 1 month ago"/>
|
tools:text="Existence confirmed 1 month ago"/>
|
||||||
|
|
||||||
<include android:visibility="gone" layout="@layout/place_page_map_too_old"/>
|
|
||||||
|
|
||||||
<include android:visibility="gone" layout="@layout/place_page_editor"/>
|
<include android:visibility="gone" layout="@layout/place_page_editor"/>
|
||||||
|
|
||||||
<include android:visibility="gone" layout="@layout/place_page_add"/>
|
<include android:visibility="gone" layout="@layout/place_page_add"/>
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/cv__map_too_old"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/margin_base"
|
|
||||||
android:layout_marginTop="@dimen/margin_half"
|
|
||||||
app:strokeWidth="1dp"
|
|
||||||
app:strokeColor="@color/base_accent"
|
|
||||||
app:cardBackgroundColor="@color/bg_cards">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="@dimen/margin_base"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginEnd="@dimen/margin_base"
|
|
||||||
app:srcCompat="@drawable/info_icon"
|
|
||||||
app:tint="@color/base_accent" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/margin_quarter"
|
|
||||||
android:text="@string/place_page_map_too_old_title"
|
|
||||||
android:textAppearance="@style/MwmTextAppearance.Body2"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="?android:textColorPrimary" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/tv__map_too_old_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/margin_quarter"
|
|
||||||
android:text="@string/place_page_map_too_old_description"
|
|
||||||
android:fontFamily="@string/robotoRegular"
|
|
||||||
android:textAppearance="@style/MwmTextAppearance.Body3"
|
|
||||||
android:textColor="?android:textColorPrimary" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/mb__update_too_old_map"
|
|
||||||
style="@style/MwmWidget.M3.Button.Primary"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/place_page_update_too_old_map"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/oh_check_date"
|
android:id="@+id/oh_check_date"
|
||||||
style="@style/TextAppearance.Caption"
|
style="?fontCaption"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
|||||||
@@ -88,19 +88,19 @@
|
|||||||
<item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
|
<item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
|
||||||
<item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
|
<item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
|
||||||
<item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
|
<item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
|
||||||
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
|
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
|
||||||
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
|
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
|
||||||
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
|
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
|
||||||
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
|
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
|
||||||
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||||
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
|
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
|
||||||
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
|
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
|
||||||
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
|
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
|
||||||
<item name="fontBody1">@style/TextAppearance.Body1</item>
|
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
|
||||||
<item name="fontBody2">@style/TextAppearance.Body2</item>
|
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
|
||||||
<item name="fontButton">@style/TextAppearance.Button</item>
|
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
|
||||||
<item name="fontCaption">@style/TextAppearance.Caption</item>
|
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
|
||||||
<item name="fontOverline">@style/TextAppearance.Overline</item>
|
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
|
||||||
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
||||||
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
||||||
<item name="elevationProfilePropIconTint">@color/white_secondary</item>
|
<item name="elevationProfilePropIconTint">@color/white_secondary</item>
|
||||||
|
|||||||
@@ -130,6 +130,7 @@
|
|||||||
<!-- Editor -->
|
<!-- Editor -->
|
||||||
<dimen name="editor_height_days">72dp</dimen>
|
<dimen name="editor_height_days">72dp</dimen>
|
||||||
<dimen name="editor_height_closed">72dp</dimen>
|
<dimen name="editor_height_closed">72dp</dimen>
|
||||||
|
<dimen name="editor_height_allday">56dp</dimen>
|
||||||
<dimen name="editor_height_field">64dp</dimen>
|
<dimen name="editor_height_field">64dp</dimen>
|
||||||
<dimen name="editor_margin_left">56dp</dimen>
|
<dimen name="editor_margin_left">56dp</dimen>
|
||||||
<dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen>
|
<dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen>
|
||||||
|
|||||||
@@ -35,6 +35,11 @@
|
|||||||
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
|
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
|
||||||
<string name="pref_navigation" translatable="false">Navigation</string>
|
<string name="pref_navigation" translatable="false">Navigation</string>
|
||||||
<string name="pref_information" translatable="false">Information</string>
|
<string name="pref_information" translatable="false">Information</string>
|
||||||
|
<string name="pref_traffic" translatable="false">Traffic</string>
|
||||||
|
<string name="pref_traffic_http_enabled" translatable="false">TrafficHttpEnabled</string>
|
||||||
|
<string name="pref_traffic_http_url" translatable="false">TrafficHttpUrl</string>
|
||||||
|
<string name="pref_traffic_apps" translatable="false">TrafficApps</string>
|
||||||
|
<string name="pref_traffic_legacy_enabled" translatable="false">TrafficLegacyEnabled</string>
|
||||||
<string name="pref_transliteration" translatable="false">Transliteration</string>
|
<string name="pref_transliteration" translatable="false">Transliteration</string>
|
||||||
<string name="pref_power_management" translatable="false">PowerManagment</string>
|
<string name="pref_power_management" translatable="false">PowerManagment</string>
|
||||||
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>
|
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>
|
||||||
|
|||||||
@@ -215,6 +215,7 @@
|
|||||||
<!-- Settings information group in settings screen -->
|
<!-- Settings information group in settings screen -->
|
||||||
<string name="prefs_group_information">Information</string>
|
<string name="prefs_group_information">Information</string>
|
||||||
<string name="prefs_group_route">Navigation</string>
|
<string name="prefs_group_route">Navigation</string>
|
||||||
|
<string name="prefs_group_traffic">Traffic information</string>
|
||||||
<string name="pref_zoom_title">Zoom buttons</string>
|
<string name="pref_zoom_title">Zoom buttons</string>
|
||||||
<string name="pref_zoom_summary">Display on the map</string>
|
<string name="pref_zoom_summary">Display on the map</string>
|
||||||
<!-- Settings «Map» category: «Night style» title -->
|
<!-- Settings «Map» category: «Night style» title -->
|
||||||
@@ -573,16 +574,6 @@
|
|||||||
<string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string>
|
<string name="error_enter_correct_fediverse_page">Enter a valid Mastodon username or web address</string>
|
||||||
<string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string>
|
<string name="error_enter_correct_bluesky_page">Enter a valid Bluesky username or web address</string>
|
||||||
<string name="placepage_add_place_button">Add Place to OpenStreetMap</string>
|
<string name="placepage_add_place_button">Add Place to OpenStreetMap</string>
|
||||||
<!-- Title of info shown when the map is older than 3 to 6 months -->
|
|
||||||
<string name="place_page_map_too_old_title">Map data outdated</string>
|
|
||||||
<!-- Description of info shown when the map is older than 3 months -->
|
|
||||||
<string name="place_page_map_too_old_description"> Your current map data is very old, please update the map.</string>
|
|
||||||
<!-- Description of info shown when the app and the map are older than 6 months -->
|
|
||||||
<string name="place_page_app_too_old_description"> Your current map data is very old, please update the CoMaps app.</string>
|
|
||||||
<!-- Button to update map region, part of the map too old info -->
|
|
||||||
<string name="place_page_update_too_old_map">Update map region</string>
|
|
||||||
<!-- Toast shown after pressing add place / edit OpenStreetMap when editing is disabled -->
|
|
||||||
<string name="place_page_too_old_to_edit">OpenStreetMap editing is disabled because the map data is too old.</string>
|
|
||||||
<string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string>
|
<string name="osm_note_hint">Or, alternatively, leave a note to OpenStreetMap community so that someone else can add or fix a place here.</string>
|
||||||
<string name="osm_note_toast">Note will be sent to OpenStreetMap</string>
|
<string name="osm_note_toast">Note will be sent to OpenStreetMap</string>
|
||||||
<!-- Displayed when saving some edits to the map to warn against publishing personal data -->
|
<!-- Displayed when saving some edits to the map to warn against publishing personal data -->
|
||||||
@@ -799,6 +790,24 @@
|
|||||||
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
|
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
|
||||||
<!-- Current language of the map! -->
|
<!-- Current language of the map! -->
|
||||||
<string name="change_map_locale">Map language</string>
|
<string name="change_map_locale">Map language</string>
|
||||||
|
<!-- Enable live traffic data via HTTP (title) -->
|
||||||
|
<string name="traffic_http_enabled">Enable live traffic data</string>
|
||||||
|
<!-- Enable live traffic data via HTTP (description) -->
|
||||||
|
<string name="traffic_http_enabled_description">When enabled, the app will periodically retrieve traffic information from the configured URL.</string>
|
||||||
|
<!-- URL for live traffic data -->
|
||||||
|
<string name="traffic_http_url">Traffic service URL</string>
|
||||||
|
<!-- Status message indicating that user did not set a traffic URL yet. -->
|
||||||
|
<string name="traffic_http_url_not_set">Not set</string>
|
||||||
|
<!-- TraFF 0.8 apps from which to receive data (title) -->
|
||||||
|
<string name="traffic_apps">Use data from TraFF applications</string>
|
||||||
|
<!-- Status message indicating that no TraFF 0.8 apps are installed -->
|
||||||
|
<string name="traffic_apps_not_available">No apps installed</string>
|
||||||
|
<!-- Status message indicating that no TraFF 0.8 apps are currently selected -->
|
||||||
|
<string name="traffic_apps_none_selected">No apps salected</string>
|
||||||
|
<!-- Enable traffic data from TraFF 0.7 apps (title) -->
|
||||||
|
<string name="traffic_legacy_enabled">Use data from legacy TraFF applications</string>
|
||||||
|
<!-- Enable traffic data from TraFF 0.7 apps (description) -->
|
||||||
|
<string name="traffic_legacy_enabled_description">When enabled, the app will receive and process traffic data from legacy TraFF applications.</string>
|
||||||
<!-- OpenStreetMap text on splash screen -->
|
<!-- OpenStreetMap text on splash screen -->
|
||||||
<string name="splash_subtitle">Map data from OpenStreetMap</string>
|
<string name="splash_subtitle">Map data from OpenStreetMap</string>
|
||||||
<!-- Telegram group url for the "?" About page -->
|
<!-- Telegram group url for the "?" About page -->
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
<item name="android:textStyle">normal</item>
|
<item name="android:textStyle">normal</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="MwmTextAppearance.Display1">
|
||||||
|
<item name="android:textSize">@dimen/text_size_display_1</item>
|
||||||
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="MwmTextAppearance.Title">
|
<style name="MwmTextAppearance.Title">
|
||||||
<item name="android:textSize">@dimen/text_size_title</item>
|
<item name="android:textSize">@dimen/text_size_title</item>
|
||||||
<item name="android:textColor">?android:textColorPrimary</item>
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
@@ -185,91 +190,91 @@
|
|||||||
<item name="android:textColor">@color/white_secondary</item>
|
<item name="android:textColor">@color/white_secondary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline1"
|
<style name="TextAppearance.MdcTypographyStyles.Headline1"
|
||||||
parent="TextAppearance.Material3.DisplayLarge">
|
parent="TextAppearance.MaterialComponents.Headline1">
|
||||||
<item name="fontFamily">@string/robotoLight</item>
|
<item name="fontFamily">@string/robotoLight</item>
|
||||||
<item name="android:fontFamily">@string/robotoLight</item>
|
<item name="android:fontFamily">@string/robotoLight</item>
|
||||||
<item name="android:textSize">96sp</item>
|
<item name="android:textSize">96sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline2"
|
<style name="TextAppearance.MdcTypographyStyles.Headline2"
|
||||||
parent="TextAppearance.Material3.DisplayLarge">
|
parent="TextAppearance.MaterialComponents.Headline2">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">60sp</item>
|
<item name="android:textSize">60sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline3"
|
<style name="TextAppearance.MdcTypographyStyles.Headline3"
|
||||||
parent="TextAppearance.Material3.DisplayMedium">
|
parent="TextAppearance.MaterialComponents.Headline3">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">48sp</item>
|
<item name="android:textSize">48sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline4"
|
<style name="TextAppearance.MdcTypographyStyles.Headline4"
|
||||||
parent="TextAppearance.Material3.HeadlineLarge">
|
parent="TextAppearance.MaterialComponents.Headline4">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">34sp</item>
|
<item name="android:textSize">34sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline5"
|
<style name="TextAppearance.MdcTypographyStyles.Headline5"
|
||||||
parent="TextAppearance.Material3.HeadlineMedium">
|
parent="TextAppearance.MaterialComponents.Headline5">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">24sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Headline6"
|
<style name="TextAppearance.MdcTypographyStyles.Headline6"
|
||||||
parent="TextAppearance.Material3.HeadlineSmall">
|
parent="TextAppearance.MaterialComponents.Headline6">
|
||||||
<item name="fontFamily">@string/robotoMedium</item>
|
<item name="fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:textSize">20sp</item>
|
<item name="android:textSize">20sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Subtitle1"
|
<style name="TextAppearance.MdcTypographyStyles.Subtitle1"
|
||||||
parent="TextAppearance.Material3.TitleMedium">
|
parent="TextAppearance.MaterialComponents.Subtitle1">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Subtitle2"
|
<style name="TextAppearance.MdcTypographyStyles.Subtitle2"
|
||||||
parent="TextAppearance.Material3.TitleSmall">
|
parent="TextAppearance.MaterialComponents.Subtitle2">
|
||||||
<item name="fontFamily">@string/robotoMedium</item>
|
<item name="fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:textSize">14sp</item>
|
<item name="android:textSize">14sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Body1"
|
<style name="TextAppearance.MdcTypographyStyles.Body1"
|
||||||
parent="TextAppearance.Material3.BodyLarge">
|
parent="TextAppearance.MaterialComponents.Body1">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Body2"
|
<style name="TextAppearance.MdcTypographyStyles.Body2"
|
||||||
parent="TextAppearance.Material3.BodyMedium">
|
parent="TextAppearance.MaterialComponents.Body2">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">14sp</item>
|
<item name="android:textSize">14sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Button"
|
<style name="TextAppearance.MdcTypographyStyles.Button"
|
||||||
parent="TextAppearance.MaterialComponents.Button">
|
parent="TextAppearance.MaterialComponents.Button">
|
||||||
<item name="fontFamily">@string/robotoMedium</item>
|
<item name="fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||||
<item name="android:textSize">14sp</item>
|
<item name="android:textSize">14sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Caption"
|
<style name="TextAppearance.MdcTypographyStyles.Caption"
|
||||||
parent="TextAppearance.Material3.LabelMedium">
|
parent="TextAppearance.MaterialComponents.Caption">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Overline"
|
<style name="TextAppearance.MdcTypographyStyles.Overline"
|
||||||
parent="TextAppearance.MaterialComponents.Overline">
|
parent="TextAppearance.MaterialComponents.Overline">
|
||||||
<item name="fontFamily">@string/robotoRegular</item>
|
<item name="fontFamily">@string/robotoRegular</item>
|
||||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||||
|
|||||||
@@ -139,13 +139,12 @@
|
|||||||
<item name="cornerSize">50%</item>
|
<item name="cornerSize">50%</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="MwmWidget.ToolbarStyle" parent="Widget.Material3.Toolbar">
|
<style name="MwmWidget.ToolbarStyle" parent="ThemeOverlay.Material3.Dark.ActionBar">
|
||||||
<item name="android:background">?colorPrimary</item>
|
<item name="android:background">?colorPrimary</item>
|
||||||
<item name="android:displayOptions">homeAsUp|showTitle</item>
|
<item name="android:displayOptions">homeAsUp|showTitle</item>
|
||||||
<item name="contentInsetStart">0dp</item>
|
<item name="contentInsetStart">0dp</item>
|
||||||
<item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
<item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
||||||
<item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
<item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
||||||
<item name="buttonGravity">center_vertical</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="MwmWidget.ToolbarStyle.Light">
|
<style name="MwmWidget.ToolbarStyle.Light">
|
||||||
|
|||||||
@@ -93,19 +93,19 @@
|
|||||||
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
|
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
|
||||||
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
|
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
|
||||||
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
|
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
|
||||||
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
|
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
|
||||||
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
|
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
|
||||||
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
|
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
|
||||||
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
|
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
|
||||||
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||||
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
|
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
|
||||||
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
|
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
|
||||||
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
|
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
|
||||||
<item name="fontBody1">@style/TextAppearance.Body1</item>
|
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
|
||||||
<item name="fontBody2">@style/TextAppearance.Body2</item>
|
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
|
||||||
<item name="fontButton">@style/TextAppearance.Button</item>
|
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
|
||||||
<item name="fontCaption">@style/TextAppearance.Caption</item>
|
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
|
||||||
<item name="fontOverline">@style/TextAppearance.Overline</item>
|
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
|
||||||
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
||||||
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
||||||
<item name="elevationProfilePropIconTint">@color/black_secondary</item>
|
<item name="elevationProfilePropIconTint">@color/black_secondary</item>
|
||||||
|
|||||||
@@ -191,6 +191,36 @@
|
|||||||
android:widgetLayout="@layout/preference_switch"
|
android:widgetLayout="@layout/preference_switch"
|
||||||
android:order="5"/>
|
android:order="5"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
<androidx.preference.PreferenceCategory
|
||||||
|
android:key="@string/pref_traffic"
|
||||||
|
android:title="@string/prefs_group_traffic"
|
||||||
|
android:order="4">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="@string/pref_traffic_http_enabled"
|
||||||
|
android:title="@string/traffic_http_enabled"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/traffic_http_enabled_description"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:order="1"/>
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="@string/pref_traffic_http_url"
|
||||||
|
android:title="@string/traffic_http_url"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:order="2"/>
|
||||||
|
<MultiSelectListPreference
|
||||||
|
android:key="@string/pref_traffic_apps"
|
||||||
|
android:title="@string/traffic_apps"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:order="3"/>
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:key="@string/pref_traffic_legacy_enabled"
|
||||||
|
android:title="@string/traffic_legacy_enabled"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/traffic_legacy_enabled_description"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:order="4"/>
|
||||||
|
</androidx.preference.PreferenceCategory>
|
||||||
|
|
||||||
<androidx.preference.PreferenceCategory
|
<androidx.preference.PreferenceCategory
|
||||||
android:key="@string/pref_privacy"
|
android:key="@string/pref_privacy"
|
||||||
android:title="@string/privacy"
|
android:title="@string/privacy"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ set(SRC
|
|||||||
app/organicmaps/sdk/opengl/gl3stub.h
|
app/organicmaps/sdk/opengl/gl3stub.h
|
||||||
app/organicmaps/sdk/platform/GuiThread.hpp
|
app/organicmaps/sdk/platform/GuiThread.hpp
|
||||||
app/organicmaps/sdk/platform/AndroidPlatform.hpp
|
app/organicmaps/sdk/platform/AndroidPlatform.hpp
|
||||||
|
app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp
|
||||||
app/organicmaps/sdk/util/Distance.hpp
|
app/organicmaps/sdk/util/Distance.hpp
|
||||||
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
|
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
|
||||||
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
|
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
|
||||||
@@ -76,6 +77,8 @@ set(SRC
|
|||||||
app/organicmaps/sdk/platform/PThreadImpl.cpp
|
app/organicmaps/sdk/platform/PThreadImpl.cpp
|
||||||
app/organicmaps/sdk/platform/SecureStorage.cpp
|
app/organicmaps/sdk/platform/SecureStorage.cpp
|
||||||
app/organicmaps/sdk/platform/SocketImpl.cpp
|
app/organicmaps/sdk/platform/SocketImpl.cpp
|
||||||
|
app/organicmaps/sdk/traffxml/AndroidTraffSource.cpp
|
||||||
|
app/organicmaps/sdk/traffxml/SourceImpl.cpp
|
||||||
app/organicmaps/sdk/util/Config.cpp
|
app/organicmaps/sdk/util/Config.cpp
|
||||||
app/organicmaps/sdk/util/GeoUtils.cpp
|
app/organicmaps/sdk/util/GeoUtils.cpp
|
||||||
app/organicmaps/sdk/util/HttpClient.cpp
|
app/organicmaps/sdk/util/HttpClient.cpp
|
||||||
@@ -127,6 +130,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
# icu
|
# icu
|
||||||
# agg
|
# agg
|
||||||
# vulkan_wrapper
|
# vulkan_wrapper
|
||||||
|
traffxml
|
||||||
|
|
||||||
# Android libs
|
# Android libs
|
||||||
log
|
log
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ public:
|
|||||||
void Set3dMode(bool allow3d, bool allow3dBuildings);
|
void Set3dMode(bool allow3d, bool allow3dBuildings);
|
||||||
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
|
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
|
||||||
|
|
||||||
|
TrafficManager & GetTrafficManager() { return m_work.GetTrafficManager(); }
|
||||||
|
|
||||||
void SetMapLanguageCode(std::string const & languageCode);
|
void SetMapLanguageCode(std::string const & languageCode);
|
||||||
std::string GetMapLanguageCode();
|
std::string GetMapLanguageCode();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "app/organicmaps/sdk/Framework.hpp"
|
#include "app/organicmaps/sdk/Framework.hpp"
|
||||||
|
|
||||||
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
|
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
|
||||||
|
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
|
||||||
|
|
||||||
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
||||||
|
|
||||||
@@ -34,6 +35,26 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_OrganicMaps_nativeInitFramework(
|
|||||||
JNIEnv * env = jni::GetEnv();
|
JNIEnv * env = jni::GetEnv();
|
||||||
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
|
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
|
||||||
env->CallVoidMethod(*onComplete, methodId);
|
env->CallVoidMethod(*onComplete, methodId);
|
||||||
|
|
||||||
|
ASSERT(g_framework, ("g_framework must be non-null"));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add traffic sources for Android.
|
||||||
|
*/
|
||||||
|
jclass configClass = env->FindClass("app/organicmaps/sdk/util/Config");
|
||||||
|
jmethodID const getTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||||
|
"getTrafficLegacyEnabled", "()Z");
|
||||||
|
jmethodID const applyTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||||
|
"applyTrafficLegacyEnabled", "(Z)V");
|
||||||
|
jmethodID const getTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||||
|
"getTrafficApps", "()[Ljava/lang/String;");
|
||||||
|
jmethodID const applyTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||||
|
"applyTrafficApps", "([Ljava/lang/String;)V");
|
||||||
|
|
||||||
|
env->CallStaticVoidMethod(configClass, applyTrafficLegacyEnabledId,
|
||||||
|
env->CallStaticBooleanMethod(configClass, getTrafficLegacyEnabledId));
|
||||||
|
env->CallStaticVoidMethod(configClass, applyTrafficAppsId,
|
||||||
|
(jobjectArray)env->CallStaticObjectMethod(configClass, getTrafficAppsId));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()->SaveTransliteration(value);
|
||||||
frm()->AllowTransliteration(value);
|
frm()->AllowTransliteration(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz)
|
||||||
|
{
|
||||||
|
return frm()->LoadTrafficHttpEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz,
|
||||||
|
jboolean value)
|
||||||
|
{
|
||||||
|
frm()->SaveTrafficHttpEnabled(value);
|
||||||
|
frm()->SetTrafficHttpEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz)
|
||||||
|
{
|
||||||
|
std::string value = frm()->LoadTrafficHttpUrl();
|
||||||
|
return jni::ToJavaString(env, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz,
|
||||||
|
jstring value)
|
||||||
|
{
|
||||||
|
frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||||
|
frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_applyTrafficLegacyEnabled(JNIEnv * env, jclass thiz,
|
||||||
|
jboolean value)
|
||||||
|
{
|
||||||
|
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||||
|
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||||
|
if (traffxml::AndroidTraffSourceV0_7* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_7*>(source))
|
||||||
|
{
|
||||||
|
traffSource->Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (value)
|
||||||
|
traffxml::AndroidTraffSourceV0_7::Create(tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_app_organicmaps_sdk_util_Config_applyTrafficApps(JNIEnv * env, jclass thiz, jobjectArray value)
|
||||||
|
{
|
||||||
|
jsize valueLen = env->GetArrayLength(value);
|
||||||
|
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||||
|
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||||
|
if (traffxml::AndroidTraffSourceV0_8* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_8*>(source))
|
||||||
|
{
|
||||||
|
traffSource->Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
for (jsize i = 0; i < valueLen; i++)
|
||||||
|
{
|
||||||
|
jstring jAppId = (jstring)env->GetObjectArrayElement(value, i);
|
||||||
|
std::string appId = jni::ToNativeString(env, jAppId);
|
||||||
|
traffxml::AndroidTraffSourceV0_8::Create(tm, appId);
|
||||||
|
env->DeleteLocalRef(jAppId);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -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.
|
* True if the first start animation has been seen.
|
||||||
*/
|
*/
|
||||||
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen";
|
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether feeds from legacy TraFF applications (TraFF 0.7, Android transport) are enabled.
|
||||||
|
*/
|
||||||
|
private static final String KEY_TRAFFIC_LEGACY_ENABLED = "TrafficLegacyEnabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TraFF (0.8+) applications from which to request traffic data.
|
||||||
|
*/
|
||||||
|
private static final String KEY_TRAFFIC_APPS = "TrafficApps";
|
||||||
|
|
||||||
private Config() {}
|
private Config() {}
|
||||||
|
|
||||||
@@ -393,6 +403,63 @@ public final class Config
|
|||||||
nativeSetTransliteration(value);
|
nativeSetTransliteration(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getTrafficHttpEnabled()
|
||||||
|
{
|
||||||
|
return nativeGetTrafficHttpEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrafficHttpEnabled(boolean value)
|
||||||
|
{
|
||||||
|
nativeSetTrafficHttpEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTrafficHttpUrl()
|
||||||
|
{
|
||||||
|
return nativeGetTrafficHttpUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrafficHttpUrl(String value)
|
||||||
|
{
|
||||||
|
nativeSetTrafficHttpUrl(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getTrafficApps()
|
||||||
|
{
|
||||||
|
String appString = getString(KEY_TRAFFIC_APPS, "");
|
||||||
|
if (appString.length() == 0)
|
||||||
|
return new String[0];
|
||||||
|
return appString.split(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrafficApps(String[] value)
|
||||||
|
{
|
||||||
|
String valueString = "";
|
||||||
|
for (int i = 0; i < value.length; i++)
|
||||||
|
{
|
||||||
|
valueString = valueString + value[i];
|
||||||
|
if ((i + 1) < value.length)
|
||||||
|
valueString = valueString + ",";
|
||||||
|
}
|
||||||
|
setString(KEY_TRAFFIC_APPS, valueString);
|
||||||
|
applyTrafficApps(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getTrafficLegacyEnabled()
|
||||||
|
{
|
||||||
|
return getBool(KEY_TRAFFIC_LEGACY_ENABLED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrafficLegacyEnabled(boolean value)
|
||||||
|
{
|
||||||
|
setBool(KEY_TRAFFIC_LEGACY_ENABLED, value);
|
||||||
|
applyTrafficLegacyEnabled(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNY()
|
||||||
|
{
|
||||||
|
return getBool("NY");
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getDonateUrl()
|
public static String getDonateUrl()
|
||||||
{
|
{
|
||||||
@@ -536,4 +603,10 @@ public final class Config
|
|||||||
private static native void nativeSetLargeFontsSize(boolean value);
|
private static native void nativeSetLargeFontsSize(boolean value);
|
||||||
private static native boolean nativeGetTransliteration();
|
private static native boolean nativeGetTransliteration();
|
||||||
private static native void nativeSetTransliteration(boolean value);
|
private static native void nativeSetTransliteration(boolean value);
|
||||||
|
private static native boolean nativeGetTrafficHttpEnabled();
|
||||||
|
private static native void nativeSetTrafficHttpEnabled(boolean value);
|
||||||
|
private static native String nativeGetTrafficHttpUrl();
|
||||||
|
private static native void nativeSetTrafficHttpUrl(String value);
|
||||||
|
private static native void applyTrafficApps(String[] value);
|
||||||
|
private static native void applyTrafficLegacyEnabled(boolean value);
|
||||||
}
|
}
|
||||||
|
|||||||
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 CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways"
|
||||||
#define TEMP_ADDR_EXTENSION ".tempaddr"
|
#define TEMP_ADDR_EXTENSION ".tempaddr"
|
||||||
|
|
||||||
#define TRAFFIC_FILE_EXTENSION ".traffic"
|
|
||||||
|
|
||||||
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
|
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
|
||||||
|
|
||||||
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"
|
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"
|
||||||
|
|||||||
@@ -207,8 +207,6 @@ set(SRC
|
|||||||
tesselator.hpp
|
tesselator.hpp
|
||||||
towns_dumper.cpp
|
towns_dumper.cpp
|
||||||
towns_dumper.hpp
|
towns_dumper.hpp
|
||||||
traffic_generator.cpp
|
|
||||||
traffic_generator.hpp
|
|
||||||
transit_generator.cpp
|
transit_generator.cpp
|
||||||
transit_generator.hpp
|
transit_generator.hpp
|
||||||
transit_generator_experimental.cpp
|
transit_generator_experimental.cpp
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#include "generator/routing_world_roads_generator.hpp"
|
#include "generator/routing_world_roads_generator.hpp"
|
||||||
#include "generator/search_index_builder.hpp"
|
#include "generator/search_index_builder.hpp"
|
||||||
#include "generator/statistics.hpp"
|
#include "generator/statistics.hpp"
|
||||||
#include "generator/traffic_generator.hpp"
|
|
||||||
#include "generator/transit_generator.hpp"
|
#include "generator/transit_generator.hpp"
|
||||||
#include "generator/transit_generator_experimental.hpp"
|
#include "generator/transit_generator_experimental.hpp"
|
||||||
#include "generator/unpack_mwm.hpp"
|
#include "generator/unpack_mwm.hpp"
|
||||||
@@ -171,7 +170,6 @@ DEFINE_string(unpack_borders, "", "Convert packed_polygons to a directory of pol
|
|||||||
DEFINE_bool(unpack_mwm, false, "Unpack each section of mwm into a separate file with name filePath.sectionName.");
|
DEFINE_bool(unpack_mwm, false, "Unpack each section of mwm into a separate file with name filePath.sectionName.");
|
||||||
DEFINE_bool(check_mwm, false, "Check map file to be correct.");
|
DEFINE_bool(check_mwm, false, "Check map file to be correct.");
|
||||||
DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container.");
|
DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container.");
|
||||||
DEFINE_bool(generate_traffic_keys, false, "Generate keys for the traffic map (road segment -> speed group).");
|
|
||||||
|
|
||||||
DEFINE_bool(dump_mwm_tmp, false, "Prints feature builder objects from .mwm.tmp");
|
DEFINE_bool(dump_mwm_tmp, false, "Prints feature builder objects from .mwm.tmp");
|
||||||
|
|
||||||
@@ -546,12 +544,6 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
|
|||||||
BuildPopularPlacesFromDescriptions(dataFile);
|
BuildPopularPlacesFromDescriptions(dataFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FLAGS_generate_traffic_keys)
|
|
||||||
{
|
|
||||||
if (!traffic::GenerateTrafficKeysFromDataFile(dataFile))
|
|
||||||
LOG(LCRITICAL, ("Error generating traffic keys."));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION);
|
string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION);
|
||||||
|
|||||||
@@ -134,8 +134,6 @@ TagMapping const kDefaultTagMapping = {
|
|||||||
{OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private},
|
{OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private},
|
||||||
{OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private},
|
{OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private},
|
||||||
{OsmElement::Tag("locked", "yes"), RoadAccess::Type::Locked},
|
{OsmElement::Tag("locked", "yes"), RoadAccess::Type::Locked},
|
||||||
{OsmElement::Tag("service", "parking_aisle"), RoadAccess::Type::Private},
|
|
||||||
{OsmElement::Tag("amenity", "parking_entrance"), RoadAccess::Type::Private},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Removed secondary, tertiary from car list. Example https://www.openstreetmap.org/node/8169922700
|
// Removed secondary, tertiary from car list. Example https://www.openstreetmap.org/node/8169922700
|
||||||
|
|||||||
@@ -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)
|
NS_SWIFT_NAME(SettingsBridge)
|
||||||
@interface MWMSettings : NSObject
|
@interface MWMSettings : NSObject
|
||||||
|
|
||||||
|
+ (BOOL)liveTrafficEnabled;
|
||||||
|
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||||
|
|
||||||
|
+ (NSURL *)liveTrafficUrl;
|
||||||
|
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||||
|
|
||||||
+ (BOOL)buildings3dViewEnabled;
|
+ (BOOL)buildings3dViewEnabled;
|
||||||
+ (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled;
|
+ (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled;
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,40 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
|
|||||||
|
|
||||||
@implementation MWMSettings
|
@implementation MWMSettings
|
||||||
|
|
||||||
|
+ (BOOL)liveTrafficEnabled;
|
||||||
|
{
|
||||||
|
return GetFramework().LoadTrafficHttpEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||||
|
{
|
||||||
|
auto &f = GetFramework();
|
||||||
|
f.SaveTrafficHttpEnabled(liveTrafficEnabled);
|
||||||
|
f.SetTrafficHttpEnabled(liveTrafficEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSURL *)liveTrafficUrl;
|
||||||
|
{
|
||||||
|
NSString * link = @(GetFramework().LoadTrafficHttpUrl().c_str());
|
||||||
|
if ([link length] == 0) {
|
||||||
|
return nil;
|
||||||
|
} else {
|
||||||
|
return [NSURL URLWithString:link];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||||
|
{
|
||||||
|
auto &f = GetFramework();
|
||||||
|
if (liveTrafficUrl == nil) {
|
||||||
|
f.SaveTrafficHttpUrl(@"".UTF8String);
|
||||||
|
f.SetTrafficHttpUrl(@"".UTF8String);
|
||||||
|
} else {
|
||||||
|
f.SaveTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||||
|
f.SetTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+ (BOOL)buildings3dViewEnabled;
|
+ (BOOL)buildings3dViewEnabled;
|
||||||
{
|
{
|
||||||
bool _ = true, on = true;
|
bool _ = true, on = true;
|
||||||
|
|||||||
@@ -717,6 +717,13 @@
|
|||||||
"editor_place_doesnt_exist" = "Place does not exist";
|
"editor_place_doesnt_exist" = "Place does not exist";
|
||||||
"text_more_button" = "…more";
|
"text_more_button" = "…more";
|
||||||
|
|
||||||
|
/* Live traffic data */
|
||||||
|
"traffic_http" = "Live Traffic";
|
||||||
|
"traffic_http_enabled" = "Enable live traffic data";
|
||||||
|
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||||
|
"traffic_http_url" = "Traffic service URL";
|
||||||
|
"traffic_http_url_not_set" = "Not set";
|
||||||
|
|
||||||
/* Phone number error message */
|
/* Phone number error message */
|
||||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||||
"error_enter_correct_web" = "Enter a valid web address";
|
"error_enter_correct_web" = "Enter a valid web address";
|
||||||
@@ -816,6 +823,7 @@
|
|||||||
"privacy_policy" = "Privacy policy";
|
"privacy_policy" = "Privacy policy";
|
||||||
"terms_of_use" = "Terms of use";
|
"terms_of_use" = "Terms of use";
|
||||||
"button_layer_subway" = "Metro";
|
"button_layer_subway" = "Metro";
|
||||||
|
"button_layer_traffic" = "Traffic";
|
||||||
"layers_title" = "Map Styles and Layers";
|
"layers_title" = "Map Styles and Layers";
|
||||||
"subway_data_unavailable" = "Metro map is unavailable";
|
"subway_data_unavailable" = "Metro map is unavailable";
|
||||||
"title_error_downloading_bookmarks" = "An error occurred";
|
"title_error_downloading_bookmarks" = "An error occurred";
|
||||||
|
|||||||
@@ -737,6 +737,13 @@
|
|||||||
"editor_place_doesnt_exist_description" = "Describe what the place looks like now to send an error note to the OpenStreetMap community";
|
"editor_place_doesnt_exist_description" = "Describe what the place looks like now to send an error note to the OpenStreetMap community";
|
||||||
"text_more_button" = "…more";
|
"text_more_button" = "…more";
|
||||||
|
|
||||||
|
/* Live traffic data */
|
||||||
|
"traffic_http" = "Live Traffic";
|
||||||
|
"traffic_http_enabled" = "Enable live traffic data";
|
||||||
|
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||||
|
"traffic_http_url" = "Traffic service URL";
|
||||||
|
"traffic_http_url_not_set" = "Not set";
|
||||||
|
|
||||||
/* Phone number error message */
|
/* Phone number error message */
|
||||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||||
"error_enter_correct_web" = "Enter a valid web address";
|
"error_enter_correct_web" = "Enter a valid web address";
|
||||||
@@ -837,6 +844,7 @@
|
|||||||
"privacy_policy" = "Privacy policy";
|
"privacy_policy" = "Privacy policy";
|
||||||
"terms_of_use" = "Terms of use";
|
"terms_of_use" = "Terms of use";
|
||||||
"button_layer_subway" = "Subway";
|
"button_layer_subway" = "Subway";
|
||||||
|
"button_layer_traffic" = "Traffic";
|
||||||
"layers_title" = "Map Styles and Layers";
|
"layers_title" = "Map Styles and Layers";
|
||||||
"subway_data_unavailable" = "Subway map is unavailable";
|
"subway_data_unavailable" = "Subway map is unavailable";
|
||||||
"title_error_downloading_bookmarks" = "An error occurred";
|
"title_error_downloading_bookmarks" = "An error occurred";
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; };
|
272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; };
|
||||||
272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; };
|
272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; };
|
||||||
272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.swift */; };
|
272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.swift */; };
|
||||||
|
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 274720592E439FBA00C516DF /* libtraffxml.a */; };
|
||||||
2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
|
2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
|
||||||
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
|
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
|
||||||
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; };
|
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; };
|
||||||
@@ -767,6 +768,7 @@
|
|||||||
272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = "<group>"; };
|
272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = "<group>"; };
|
||||||
272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||||
|
274720592E439FBA00C516DF /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
2752B6C92E31197000887CC4 /* MapLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLanguage.swift; sourceTree = "<group>"; };
|
2752B6C92E31197000887CC4 /* MapLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLanguage.swift; sourceTree = "<group>"; };
|
||||||
2752B6CD2E3121D800887CC4 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = "<group>"; };
|
2752B6CD2E3121D800887CC4 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = "<group>"; };
|
||||||
2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
|
2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
|
||||||
@@ -1792,6 +1794,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */,
|
||||||
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
|
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
|
||||||
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
|
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
|
||||||
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
|
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
|
||||||
@@ -2029,6 +2032,7 @@
|
|||||||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
274720592E439FBA00C516DF /* libtraffxml.a */,
|
||||||
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
|
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
|
||||||
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
|
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
|
||||||
FA853BF226BC5DE50026D455 /* libshaders.a */,
|
FA853BF226BC5DE50026D455 /* libshaders.a */,
|
||||||
|
|||||||
@@ -448,6 +448,28 @@ import AVFoundation
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// If live traffic data should be used
|
||||||
|
@objc static var hasLiveTraffic: Bool {
|
||||||
|
get {
|
||||||
|
return SettingsBridge.liveTrafficEnabled()
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
SettingsBridge.setLiveTrafficEnabled(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The url of the live traffic data server
|
||||||
|
@objc static var liveTrafficServerUrl: URL? {
|
||||||
|
get {
|
||||||
|
return SettingsBridge.liveTrafficUrl()
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
SettingsBridge.setLiveTrafficUrl(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
|||||||
updateOutdoorButton()
|
updateOutdoorButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@IBOutlet private var trafficButton: BottomMenuLayerButton! {
|
||||||
|
didSet {
|
||||||
|
updateTrafficButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var onClose: (()->())?
|
var onClose: (()->())?
|
||||||
|
|
||||||
@@ -32,6 +37,7 @@ class BottomMenuLayersCell: UITableViewCell {
|
|||||||
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
|
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
|
||||||
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
|
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
|
||||||
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
|
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
|
||||||
|
trafficButton.setupWith(image: UIImage(resource: .btnMenuTraffic), text: L("button_layer_traffic"))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@@ -56,6 +62,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
|||||||
let enabled = MapOverlayManager.outdoorEnabled()
|
let enabled = MapOverlayManager.outdoorEnabled()
|
||||||
outdoorButton.setStyleAndApply(styleFor(enabled))
|
outdoorButton.setStyleAndApply(styleFor(enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateTrafficButton() {
|
||||||
|
let enabled = MapOverlayManager.trafficEnabled()
|
||||||
|
trafficButton.setStyleAndApply(styleFor(enabled))
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func onCloseButtonPressed(_ sender: Any) {
|
@IBAction func onCloseButtonPressed(_ sender: Any) {
|
||||||
onClose?()
|
onClose?()
|
||||||
@@ -75,6 +86,11 @@ class BottomMenuLayersCell: UITableViewCell {
|
|||||||
let enable = !MapOverlayManager.outdoorEnabled()
|
let enable = !MapOverlayManager.outdoorEnabled()
|
||||||
MapOverlayManager.setOutdoorEnabled(enable)
|
MapOverlayManager.setOutdoorEnabled(enable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func onTrafficButton(_ sender: Any) {
|
||||||
|
let enable = !MapOverlayManager.trafficEnabled()
|
||||||
|
MapOverlayManager.setTrafficEnabled(enable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
||||||
@@ -89,6 +105,10 @@ extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
|||||||
func onOutdoorStateUpdated() {
|
func onOutdoorStateUpdated() {
|
||||||
updateOutdoorButton()
|
updateOutdoorButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onTrafficStateUpdated() {
|
||||||
|
updateTrafficButton()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BottomMenuLayersCell {
|
private extension BottomMenuLayersCell {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
|
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -81,26 +81,35 @@
|
|||||||
<rect key="frame" x="16" y="58" width="308" height="64"/>
|
<rect key="frame" x="16" y="58" width="308" height="64"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
|
<rect key="frame" x="0.0" y="0.0" width="1024" height="64"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
|
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
|
||||||
</connections>
|
</connections>
|
||||||
</view>
|
</view>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||||
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
|
<rect key="frame" x="1024" y="0.0" width="1024" height="64"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
|
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
|
||||||
</connections>
|
</connections>
|
||||||
</view>
|
</view>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||||
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
|
<rect key="frame" x="2048" y="0.0" width="1024" height="64"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
|
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
|
||||||
</connections>
|
</connections>
|
||||||
</view>
|
</view>
|
||||||
|
<view contentMode="scaleToFill" id="95L-lU-yCQ" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="-358" y="-352" width="1024" height="768"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="ZQs-ua-gDZ"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="onTrafficButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="tie-i7-JaB"/>
|
||||||
|
</connections>
|
||||||
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
|
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
|
||||||
@@ -144,6 +153,7 @@
|
|||||||
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
|
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
|
||||||
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
|
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
|
||||||
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
|
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
|
||||||
|
<outlet property="trafficButton" destination="95L-lU-yCQ" id="O9W-En-8Rc"/>
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
|
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ struct SettingsNavigationView: View {
|
|||||||
@State var forceRefreshDate: Date = Date.now
|
@State var forceRefreshDate: Date = Date.now
|
||||||
|
|
||||||
|
|
||||||
|
/// If live traffic data should be used
|
||||||
|
@State var hasLiveTraffic: Bool = false
|
||||||
|
|
||||||
|
|
||||||
|
/// The url of the live traffic data server
|
||||||
|
@State var liveTrafficServerUrlString: String = ""
|
||||||
|
|
||||||
|
|
||||||
/// The actual view
|
/// The actual view
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
@@ -212,6 +220,24 @@ struct SettingsNavigationView: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text("driving_options_title")
|
Text("driving_options_title")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $hasLiveTraffic) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("traffic_http_enabled")
|
||||||
|
|
||||||
|
Text("traffic_http_enabled_description")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tint(.accent)
|
||||||
|
|
||||||
|
TextField("traffic_http_url", text: $liveTrafficServerUrlString, prompt: Text("traffic_http_url_not_set"))
|
||||||
|
.tint(.accent)
|
||||||
|
} header: {
|
||||||
|
Text("traffic_http")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.accentColor(.accent)
|
.accentColor(.accent)
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
@@ -229,6 +255,8 @@ struct SettingsNavigationView: View {
|
|||||||
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
|
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
|
||||||
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
|
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
|
||||||
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
|
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
|
||||||
|
hasLiveTraffic = Settings.hasLiveTraffic
|
||||||
|
liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? ""
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase) { _ in
|
.onChange(of: scenePhase) { _ in
|
||||||
forceRefreshDate = Date.now
|
forceRefreshDate = Date.now
|
||||||
@@ -276,6 +304,15 @@ struct SettingsNavigationView: View {
|
|||||||
}
|
}
|
||||||
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
|
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
|
||||||
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
|
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
|
||||||
|
.onChange(of: hasLiveTraffic) { changedHasLiveTraffic in
|
||||||
|
Settings.hasLiveTraffic = changedHasLiveTraffic
|
||||||
|
}
|
||||||
|
.onChange(of: liveTrafficServerUrlString) { changedLiveTrafficServerUrlString in
|
||||||
|
if !changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, let changedLiveTrafficServerUrl = URL(string: changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||||
|
Settings.liveTrafficServerUrl = changedLiveTrafficServerUrl
|
||||||
|
} else {
|
||||||
|
Settings.liveTrafficServerUrl = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ add_subdirectory(shaders)
|
|||||||
add_subdirectory(storage)
|
add_subdirectory(storage)
|
||||||
add_subdirectory(tracking)
|
add_subdirectory(tracking)
|
||||||
add_subdirectory(traffic)
|
add_subdirectory(traffic)
|
||||||
|
add_subdirectory(traffxml)
|
||||||
add_subdirectory(transit)
|
add_subdirectory(transit)
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ namespace
|
|||||||
{
|
{
|
||||||
// The first zoom level in kAverageSegmentsCount.
|
// The first zoom level in kAverageSegmentsCount.
|
||||||
int constexpr kFirstZoomInAverageSegments = 10;
|
int constexpr kFirstZoomInAverageSegments = 10;
|
||||||
std::array<size_t, 10> constexpr kAverageSegmentsCount = {
|
std::array<size_t, 11> constexpr kAverageSegmentsCount = {
|
||||||
// 10 11 12 13 14 15 16 17 18 19
|
// 10 11 12 13 14 15 16 17 18 19 20
|
||||||
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500};
|
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500, 500};
|
||||||
|
|
||||||
double constexpr kMetersPerLevel = 3.0;
|
double constexpr kMetersPerLevel = 3.0;
|
||||||
|
|
||||||
|
|||||||
@@ -243,12 +243,15 @@ void ChangesetWrapper::Modify(editor::XMLFeature node)
|
|||||||
|
|
||||||
void ChangesetWrapper::AddChangesetTag(std::string key, std::string value)
|
void ChangesetWrapper::AddChangesetTag(std::string key, std::string value)
|
||||||
{
|
{
|
||||||
// Truncate to 254 characters as OSM has a length limit of 255
|
|
||||||
if (strings::Truncate(value, kMaximumOsmChars))
|
|
||||||
value += "…";
|
|
||||||
|
|
||||||
value = strings::EscapeForXML(value);
|
value = strings::EscapeForXML(value);
|
||||||
|
|
||||||
|
//OSM has a length limit of 255 characters
|
||||||
|
if (value.length() > kMaximumOsmChars)
|
||||||
|
{
|
||||||
|
LOG(LWARNING, ("value is too long for OSM 255 char limit: ", value));
|
||||||
|
value = value.substr(0, kMaximumOsmChars - 3).append("...");
|
||||||
|
}
|
||||||
|
|
||||||
m_changesetComments.insert_or_assign(std::move(key), std::move(value));
|
m_changesetComments.insert_or_assign(std::move(key), std::move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ void CreateCafeAtPoint(m2::PointD const & point, MwmSet::MwmId const & mwmId, os
|
|||||||
|
|
||||||
editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo);
|
editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo);
|
||||||
emo.SetHouseNumber("12");
|
emo.SetHouseNumber("12");
|
||||||
emo.LogDiffInJournal({});
|
|
||||||
TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ());
|
TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +157,6 @@ void GenerateUploadedFeature(MwmSet::MwmId const & mwmId, osm::EditableMapObject
|
|||||||
pugi::xml_node created = mwmNode.append_child("create");
|
pugi::xml_node created = mwmNode.append_child("create");
|
||||||
|
|
||||||
editor::XMLFeature xf = editor::ToXML(emo, true);
|
editor::XMLFeature xf = editor::ToXML(emo, true);
|
||||||
xf.SetEditJournal(emo.GetJournal());
|
|
||||||
xf.SetMWMFeatureIndex(emo.GetID().m_index);
|
xf.SetMWMFeatureIndex(emo.GetID().m_index);
|
||||||
xf.SetModificationTime(time(nullptr));
|
xf.SetModificationTime(time(nullptr));
|
||||||
xf.SetUploadTime(time(nullptr));
|
xf.SetUploadTime(time(nullptr));
|
||||||
@@ -872,7 +870,7 @@ void EditorTest::CreateNoteTest()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Should match a piece of text in the editor note.
|
// Should match a piece of text in the editor note.
|
||||||
constexpr char const * kPlaceDoesNotExistMessage = "This place does not exist:";
|
constexpr char const * kPlaceDoesNotExistMessage = "The place has gone or never existed";
|
||||||
|
|
||||||
ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft)
|
ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,42 +115,6 @@ UNIT_TEST(XMLFeature_UintLang)
|
|||||||
TEST_EQUAL(f2.GetName("int_name"), "Gorky Park", ());
|
TEST_EQUAL(f2.GetName("int_name"), "Gorky Park", ());
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_TEST(XMLFeature_SetOSMTagsForType)
|
|
||||||
{
|
|
||||||
XMLFeature restaurantFeature(XMLFeature::Type::Node);
|
|
||||||
restaurantFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-restaurant"));
|
|
||||||
ASSERT(restaurantFeature.HasTag("amenity"), ());
|
|
||||||
TEST_EQUAL(restaurantFeature.GetTagValue("amenity"), "restaurant", ());
|
|
||||||
|
|
||||||
XMLFeature officeFeature(XMLFeature::Type::Node);
|
|
||||||
officeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("office"));
|
|
||||||
ASSERT(officeFeature.HasTag("office"), ());
|
|
||||||
TEST_EQUAL(officeFeature.GetTagValue("office"), "yes", ());
|
|
||||||
|
|
||||||
XMLFeature touristOfficeFeature(XMLFeature::Type::Node);
|
|
||||||
touristOfficeFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("tourism-information-office"));
|
|
||||||
ASSERT(touristOfficeFeature.HasTag("tourism"), ());
|
|
||||||
TEST_EQUAL(touristOfficeFeature.GetTagValue("tourism"), "information", ());
|
|
||||||
|
|
||||||
XMLFeature addressFeature(XMLFeature::Type::Node);
|
|
||||||
addressFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("building-address"));
|
|
||||||
ASSERT(!addressFeature.HasAnyTags(), ("Addresses should not have a category tag"));
|
|
||||||
|
|
||||||
XMLFeature recyclingCenterFeature(XMLFeature::Type::Node);
|
|
||||||
recyclingCenterFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-centre"));
|
|
||||||
ASSERT(recyclingCenterFeature.HasTag("amenity"), ());
|
|
||||||
TEST_EQUAL(recyclingCenterFeature.GetTagValue("amenity"), "recycling", ());
|
|
||||||
ASSERT(recyclingCenterFeature.HasTag("recycling_type"), ());
|
|
||||||
TEST_EQUAL(recyclingCenterFeature.GetTagValue("recycling_type"), "centre", ());
|
|
||||||
|
|
||||||
XMLFeature recyclingContainerFeature(XMLFeature::Type::Node);
|
|
||||||
recyclingContainerFeature.SetOSMTagsForType(classif().GetTypeByReadableObjectName("amenity-recycling-container"));
|
|
||||||
ASSERT(recyclingContainerFeature.HasTag("amenity"), ());
|
|
||||||
TEST_EQUAL(recyclingContainerFeature.GetTagValue("amenity"), "recycling", ());
|
|
||||||
ASSERT(recyclingContainerFeature.HasTag("recycling_type"), ());
|
|
||||||
TEST_EQUAL(recyclingContainerFeature.GetTagValue("recycling_type"), "container", ());
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(XMLFeature_ToOSMString)
|
UNIT_TEST(XMLFeature_ToOSMString)
|
||||||
{
|
{
|
||||||
XMLFeature feature(XMLFeature::Type::Node);
|
XMLFeature feature(XMLFeature::Type::Node);
|
||||||
@@ -303,52 +267,104 @@ UNIT_TEST(XMLFeature_Geometry)
|
|||||||
TEST_EQUAL(feature.GetGeometry(), geometry, ());
|
TEST_EQUAL(feature.GetGeometry(), geometry, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UNIT_TEST(XMLFeature_ApplyPatch)
|
||||||
|
{
|
||||||
|
auto const kOsmFeature = R"(<?xml version="1.0"?>
|
||||||
|
<osm>
|
||||||
|
<node id="1" lat="1" lon="2" timestamp="2015-11-27T21:13:32Z" version="1">
|
||||||
|
<tag k="amenity" v="cafe"/>
|
||||||
|
</node>
|
||||||
|
</osm>
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto const kPatch = R"(<?xml version="1.0"?>
|
||||||
|
<node lat="1" lon="2" timestamp="2015-11-27T21:13:32Z">
|
||||||
|
<tag k="website" v="maps.me"/>
|
||||||
|
</node>
|
||||||
|
)";
|
||||||
|
|
||||||
|
XMLFeature const baseOsmFeature = XMLFeature::FromOSM(kOsmFeature).front();
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature noAnyTags = baseOsmFeature;
|
||||||
|
noAnyTags.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST(noAnyTags.HasKey("website"), ());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature hasMainTag = baseOsmFeature;
|
||||||
|
hasMainTag.SetTagValue("website", "mapswith.me");
|
||||||
|
hasMainTag.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST_EQUAL(hasMainTag.GetTagValue("website"), "maps.me", ());
|
||||||
|
size_t tagsCount = 0;
|
||||||
|
hasMainTag.ForEachTag([&tagsCount](std::string const &, std::string const &) { ++tagsCount; });
|
||||||
|
TEST_EQUAL(2, tagsCount, ("website should be replaced, not duplicated."));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature hasAltTag = baseOsmFeature;
|
||||||
|
hasAltTag.SetTagValue("contact:website", "mapswith.me");
|
||||||
|
hasAltTag.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
|
||||||
|
TEST_EQUAL(hasAltTag.GetTagValue("contact:website"), "maps.me", ());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature hasAltTag = baseOsmFeature;
|
||||||
|
hasAltTag.SetTagValue("url", "mapswithme.com");
|
||||||
|
hasAltTag.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST(!hasAltTag.HasTag("website"), ("Existing alt tag should be used."));
|
||||||
|
TEST_EQUAL(hasAltTag.GetTagValue("url"), "maps.me", ());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature hasTwoAltTags = baseOsmFeature;
|
||||||
|
hasTwoAltTags.SetTagValue("contact:website", "mapswith.me");
|
||||||
|
hasTwoAltTags.SetTagValue("url", "mapswithme.com");
|
||||||
|
hasTwoAltTags.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST(!hasTwoAltTags.HasTag("website"), ("Existing alt tag should be used."));
|
||||||
|
TEST_EQUAL(hasTwoAltTags.GetTagValue("contact:website"), "maps.me", ());
|
||||||
|
TEST_EQUAL(hasTwoAltTags.GetTagValue("url"), "mapswithme.com", ());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
XMLFeature hasMainAndAltTag = baseOsmFeature;
|
||||||
|
hasMainAndAltTag.SetTagValue("website", "osmrulezz.com");
|
||||||
|
hasMainAndAltTag.SetTagValue("url", "mapswithme.com");
|
||||||
|
hasMainAndAltTag.ApplyPatch(XMLFeature(kPatch));
|
||||||
|
TEST_EQUAL(hasMainAndAltTag.GetTagValue("website"), "maps.me", ());
|
||||||
|
TEST_EQUAL(hasMainAndAltTag.GetTagValue("url"), "mapswithme.com", ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UNIT_TEST(XMLFeature_FromXMLAndBackToXML)
|
UNIT_TEST(XMLFeature_FromXMLAndBackToXML)
|
||||||
{
|
{
|
||||||
classificator::Load();
|
classificator::Load();
|
||||||
|
|
||||||
std::string const xmlNoTypeStr = R"(<?xml version="1.0"?>
|
std::string const xmlNoTypeStr = R"(<?xml version="1.0"?>
|
||||||
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
|
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
|
||||||
<tag k="leisure" v="park" />
|
<tag k="name" v="Gorki Park" />
|
||||||
<tag k="name" v="Gorki Park" />
|
<tag k="name:en" v="Gorki Park" />
|
||||||
<tag k="name:en" v="Gorki Park" />
|
<tag k="name:ru" v="Парк Горького" />
|
||||||
<tag k="name:ru" v="Парк Горького" />
|
<tag k="addr:housenumber" v="10" />
|
||||||
<tag k="addr:housenumber" v="10" />
|
|
||||||
<journal version="1.0">
|
|
||||||
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
|
|
||||||
<data key="name:en" old_value="" new_value="Gorki Park" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2015-10-05T12:33:02Z">
|
|
||||||
<data key="name:ru" old_value="xxx" new_value="Парк Горького" />
|
|
||||||
</entry>
|
|
||||||
</journal>
|
|
||||||
<journalHistory version="1.0">
|
|
||||||
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
|
|
||||||
<data type="leisure-park" geomType="Point" lat="55.7978998" lon="37.474528" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
|
|
||||||
<data key="addr:housenumber" old_value="43" new_value="10" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2015-10-03T12:33:02Z">
|
|
||||||
<data key="name" old_value="" new_value="Gorki Park" />
|
|
||||||
</entry>
|
|
||||||
</journalHistory>
|
|
||||||
</node>
|
</node>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
char const kTimestamp[] = "2015-11-27T21:13:32Z";
|
char const kTimestamp[] = "2015-11-27T21:13:32Z";
|
||||||
|
|
||||||
editor::XMLFeature xmlFeature(xmlNoTypeStr);
|
editor::XMLFeature xmlNoType(xmlNoTypeStr);
|
||||||
|
editor::XMLFeature xmlWithType = xmlNoType;
|
||||||
|
xmlWithType.SetTagValue("amenity", "atm");
|
||||||
|
|
||||||
osm::EditableMapObject emo;
|
osm::EditableMapObject emo;
|
||||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
editor::FromXML(xmlWithType, emo);
|
||||||
emo.ApplyEditsFromJournal(journal);
|
auto fromFtWithType = editor::ToXML(emo, true);
|
||||||
emo.SetJournal(std::move(journal));
|
fromFtWithType.SetAttribute("timestamp", kTimestamp);
|
||||||
|
TEST_EQUAL(fromFtWithType, xmlWithType, ());
|
||||||
|
|
||||||
auto xmlFromMapObject = editor::ToXML(emo, true);
|
auto fromFtWithoutType = editor::ToXML(emo, false);
|
||||||
xmlFromMapObject.SetEditJournal(emo.GetJournal());
|
fromFtWithoutType.SetAttribute("timestamp", kTimestamp);
|
||||||
xmlFromMapObject.SetAttribute("timestamp", kTimestamp);
|
TEST_EQUAL(fromFtWithoutType, xmlNoType, ());
|
||||||
TEST_EQUAL(xmlFromMapObject, xmlFeature, ());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
||||||
@@ -357,14 +373,8 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
|||||||
{
|
{
|
||||||
std::string const recyclingCentreStr = R"(<?xml version="1.0"?>
|
std::string const recyclingCentreStr = R"(<?xml version="1.0"?>
|
||||||
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
||||||
<tag k="amenity" v="recycling" />
|
<tag k="amenity" v="recycling" />
|
||||||
<tag k="recycling_type" v="centre" />
|
<tag k="recycling_type" v="centre" />
|
||||||
<journal version="1.0">
|
|
||||||
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
|
|
||||||
<data type="amenity-recycling-centre" geomType="Point" lat="55.8047445" lon="37.5865532" />
|
|
||||||
</entry>
|
|
||||||
</journal>
|
|
||||||
<journalHistory version="1.0" />
|
|
||||||
</node>
|
</node>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
@@ -373,30 +383,21 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
|||||||
editor::XMLFeature xmlFeature(recyclingCentreStr);
|
editor::XMLFeature xmlFeature(recyclingCentreStr);
|
||||||
|
|
||||||
osm::EditableMapObject emo;
|
osm::EditableMapObject emo;
|
||||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
editor::FromXML(xmlFeature, emo);
|
||||||
emo.ApplyEditsFromJournal(journal);
|
|
||||||
emo.SetJournal(std::move(journal));
|
|
||||||
|
|
||||||
auto const th = emo.GetTypes();
|
auto const th = emo.GetTypes();
|
||||||
TEST_EQUAL(th.Size(), 1, ());
|
TEST_EQUAL(th.Size(), 1, ());
|
||||||
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ());
|
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ());
|
||||||
|
|
||||||
auto convertedFt = editor::ToXML(emo, true);
|
auto convertedFt = editor::ToXML(emo, true);
|
||||||
convertedFt.SetEditJournal(emo.GetJournal());
|
|
||||||
convertedFt.SetAttribute("timestamp", kTimestamp);
|
convertedFt.SetAttribute("timestamp", kTimestamp);
|
||||||
TEST_EQUAL(xmlFeature, convertedFt, ());
|
TEST_EQUAL(xmlFeature, convertedFt, ());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::string const recyclingContainerStr = R"(<?xml version="1.0"?>
|
std::string const recyclingContainerStr = R"(<?xml version="1.0"?>
|
||||||
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
||||||
<tag k="amenity" v="recycling" />
|
<tag k="amenity" v="recycling" />
|
||||||
<tag k="recycling_type" v="container" />
|
<tag k="recycling_type" v="container" />
|
||||||
<journal version="1.0">
|
|
||||||
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
|
|
||||||
<data type="amenity-recycling-container" geomType="Point" lat="55.8047445" lon="37.5865532" />
|
|
||||||
</entry>
|
|
||||||
</journal>
|
|
||||||
<journalHistory version="1.0" />
|
|
||||||
</node>
|
</node>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
@@ -405,16 +406,13 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
|||||||
editor::XMLFeature xmlFeature(recyclingContainerStr);
|
editor::XMLFeature xmlFeature(recyclingContainerStr);
|
||||||
|
|
||||||
osm::EditableMapObject emo;
|
osm::EditableMapObject emo;
|
||||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
editor::FromXML(xmlFeature, emo);
|
||||||
emo.ApplyEditsFromJournal(journal);
|
|
||||||
emo.SetJournal(std::move(journal));
|
|
||||||
|
|
||||||
auto const th = emo.GetTypes();
|
auto const th = emo.GetTypes();
|
||||||
TEST_EQUAL(th.Size(), 1, ());
|
TEST_EQUAL(th.Size(), 1, ());
|
||||||
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ());
|
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ());
|
||||||
|
|
||||||
auto convertedFt = editor::ToXML(emo, true);
|
auto convertedFt = editor::ToXML(emo, true);
|
||||||
convertedFt.SetEditJournal(emo.GetJournal());
|
|
||||||
convertedFt.SetAttribute("timestamp", kTimestamp);
|
convertedFt.SetAttribute("timestamp", kTimestamp);
|
||||||
TEST_EQUAL(xmlFeature, convertedFt, ());
|
TEST_EQUAL(xmlFeature, convertedFt, ());
|
||||||
}
|
}
|
||||||
@@ -468,43 +466,58 @@ UNIT_TEST(XMLFeature_Diet)
|
|||||||
TEST_EQUAL(ft.GetCuisine(), "", ());
|
TEST_EQUAL(ft.GetCuisine(), "", ());
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_TEST(XMLFeature_SocialContactsProcessing_clean)
|
UNIT_TEST(XMLFeature_SocialContactsProcessing)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::string const nightclubStr = R"(<?xml version="1.0"?>
|
std::string const nightclubStr = R"(<?xml version="1.0"?>
|
||||||
<node lat="40.82862" lon="20.30017" timestamp="2022-02-24T05:07:00Z">
|
<node lat="50.4082862" lon="30.5130017" timestamp="2022-02-24T05:07:00Z">
|
||||||
<tag k="amenity" v="bar" />
|
<tag k="amenity" v="nightclub" />
|
||||||
<tag k="name" v="Irish Pub" />
|
<tag k="name" v="Stereo Plaza" />
|
||||||
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" />
|
<tag k="contact:facebook" v="http://www.facebook.com/pages/Stereo-Plaza/118100041593935" />
|
||||||
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" />
|
<tag k="contact:instagram" v="https://www.instagram.com/p/CSy87IhMhfm/" />
|
||||||
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" />
|
<tag k="contact:line" v="liff.line.me/1645278921-kWRPP32q/?accountId=673watcr" />
|
||||||
<journal version="1.0">
|
|
||||||
<entry type="ObjectCreated" timestamp="2022-12-05T12:32:21Z">
|
|
||||||
<data type="amenity-nightclub" geomType="Point" lat="50.4082862" lon="30.5130017" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
|
|
||||||
<data key="contact:facebook" old_value="" new_value="PierreCardinPeru.oficial" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
|
|
||||||
<data key="contact:instagram" old_value="" new_value="fraback.genusswelt" />
|
|
||||||
</entry>
|
|
||||||
<entry type="TagModification" timestamp="2022-12-05T12:33:02Z">
|
|
||||||
<data key="contact:line" old_value="" new_value="015qevdv" />
|
|
||||||
</entry>
|
|
||||||
</journal>
|
|
||||||
<journalHistory version="1.0" />
|
|
||||||
</node>
|
</node>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
editor::XMLFeature xmlFeature(nightclubStr);
|
editor::XMLFeature xmlFeature(nightclubStr);
|
||||||
|
|
||||||
osm::EditableMapObject emo;
|
osm::EditableMapObject emo;
|
||||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
editor::FromXML(xmlFeature, emo);
|
||||||
emo.ApplyEditsFromJournal(journal);
|
|
||||||
emo.SetJournal(std::move(journal));
|
auto convertedFt = editor::ToXML(emo, true);
|
||||||
|
|
||||||
|
TEST(convertedFt.HasTag("contact:facebook"), ());
|
||||||
|
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "https://facebook.com/pages/Stereo-Plaza/118100041593935",
|
||||||
|
());
|
||||||
|
|
||||||
|
TEST(convertedFt.HasTag("contact:instagram"), ());
|
||||||
|
TEST_EQUAL(convertedFt.GetTagValue("contact:instagram"), "https://instagram.com/p/CSy87IhMhfm", ());
|
||||||
|
|
||||||
|
TEST(convertedFt.HasTag("contact:line"), ());
|
||||||
|
TEST_EQUAL(convertedFt.GetTagValue("contact:line"), "https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr",
|
||||||
|
());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNIT_TEST(XMLFeature_SocialContactsProcessing_clean)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::string const nightclubStr = R"(<?xml version="1.0"?>
|
||||||
|
<node lat="40.82862" lon="20.30017" timestamp="2022-02-24T05:07:00Z">
|
||||||
|
<tag k="amenity" v="bar" />
|
||||||
|
<tag k="name" v="Irish Pub" />
|
||||||
|
<tag k="contact:facebook" v="https://www.facebook.com/PierreCardinPeru.oficial/" />
|
||||||
|
<tag k="contact:instagram" v="https://www.instagram.com/fraback.genusswelt/" />
|
||||||
|
<tag k="contact:line" v="https://line.me/R/ti/p/%40015qevdv" />
|
||||||
|
</node>
|
||||||
|
)";
|
||||||
|
|
||||||
|
editor::XMLFeature xmlFeature(nightclubStr);
|
||||||
|
|
||||||
|
osm::EditableMapObject emo;
|
||||||
|
editor::FromXML(xmlFeature, emo);
|
||||||
|
|
||||||
auto convertedFt = editor::ToXML(emo, true);
|
auto convertedFt = editor::ToXML(emo, true);
|
||||||
convertedFt.SetEditJournal(emo.GetJournal());
|
|
||||||
|
|
||||||
TEST(convertedFt.HasTag("contact:facebook"), ());
|
TEST(convertedFt.HasTag("contact:facebook"), ());
|
||||||
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ());
|
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ());
|
||||||
|
|||||||
@@ -610,101 +610,221 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
|
|||||||
|
|
||||||
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
|
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
|
||||||
|
|
||||||
|
// Don't use new editor for Legacy Objects
|
||||||
|
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
|
||||||
|
bool useNewEditor =
|
||||||
|
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (fti.m_status)
|
if (useNewEditor)
|
||||||
{
|
{
|
||||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
LOG(LDEBUG, ("New Editor used\n"));
|
||||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
|
||||||
case FeatureStatus::Created: // fallthrough
|
switch (fti.m_status)
|
||||||
case FeatureStatus::Modified:
|
{
|
||||||
|
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||||
|
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||||
|
case FeatureStatus::Created: // fallthrough
|
||||||
|
case FeatureStatus::Modified:
|
||||||
|
{
|
||||||
|
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
||||||
|
|
||||||
|
switch (fti.m_object.GetEditingLifecycle())
|
||||||
|
{
|
||||||
|
case EditingLifecycle::CREATED:
|
||||||
|
{
|
||||||
|
// Generate XMLFeature for new object
|
||||||
|
JournalEntry const & createEntry = journal.front();
|
||||||
|
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
|
||||||
|
("First item should have type ObjectCreated"));
|
||||||
|
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
|
||||||
|
XMLFeature feature =
|
||||||
|
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
|
||||||
|
|
||||||
|
// Check if place already exists
|
||||||
|
bool mergeSameLocation = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
||||||
|
|
||||||
|
// precision of OSM coordinates (WGS 84), ~= 1 cm
|
||||||
|
constexpr double tolerance = 0.0000001;
|
||||||
|
|
||||||
|
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
|
||||||
|
{
|
||||||
|
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
||||||
|
feature = osmFeature;
|
||||||
|
mergeSameLocation = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||||
|
{}
|
||||||
|
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Add tags to XMLFeature
|
||||||
|
UpdateXMLFeatureTags(feature, journal, changeset);
|
||||||
|
|
||||||
|
// Upload XMLFeature to OSM
|
||||||
|
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
|
||||||
|
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||||
|
if (!mergeSameLocation)
|
||||||
|
changeset.Create(feature);
|
||||||
|
else
|
||||||
|
changeset.Modify(feature);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EditingLifecycle::MODIFIED:
|
||||||
|
{
|
||||||
|
// Load existing OSM object (Throws, see catch below)
|
||||||
|
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
|
||||||
|
|
||||||
|
// Update tags of XMLFeature
|
||||||
|
UpdateXMLFeatureTags(feature, journal, changeset);
|
||||||
|
|
||||||
|
// Upload XMLFeature to OSM
|
||||||
|
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
|
||||||
|
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||||
|
changeset.Modify(feature);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EditingLifecycle::IN_SYNC:
|
||||||
|
{
|
||||||
|
CHECK(false, ("Object already IN_SYNC should not be here"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FeatureStatus::Deleted:
|
||||||
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
|
if (!originalObjectPtr)
|
||||||
|
{
|
||||||
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Use old editor
|
||||||
{
|
{
|
||||||
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
// Todo: Remove old editor after transition period
|
||||||
|
switch (fti.m_status)
|
||||||
switch (fti.m_object.GetEditingLifecycle())
|
|
||||||
{
|
{
|
||||||
case EditingLifecycle::CREATED:
|
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||||
|
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||||
|
case FeatureStatus::Created:
|
||||||
{
|
{
|
||||||
// Generate XMLFeature for new object
|
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
||||||
JournalEntry const & createEntry = journal.front();
|
if (!fti.m_street.empty())
|
||||||
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
|
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||||
("First item should have type ObjectCreated"));
|
|
||||||
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
|
|
||||||
XMLFeature feature =
|
|
||||||
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
|
|
||||||
|
|
||||||
// Check if place already exists
|
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
||||||
bool mergeSameLocation = false;
|
("Linear and area features creation is not supported yet."));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
auto const center = fti.m_object.GetMercator();
|
||||||
|
// Throws, see catch below.
|
||||||
|
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
||||||
|
|
||||||
// precision of OSM coordinates (WGS 84), ~= 1 cm
|
// If we are here, it means that object already exists at the given point.
|
||||||
constexpr double tolerance = 0.0000001;
|
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
||||||
|
XMLFeature const osmFeatureCopy = osmFeature;
|
||||||
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
|
osmFeature.ApplyPatch(feature);
|
||||||
|
// Check to avoid uploading duplicates into OSM.
|
||||||
|
if (osmFeature == osmFeatureCopy)
|
||||||
{
|
{
|
||||||
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||||
feature = osmFeature;
|
// Don't delete this local change right now for user to see it in profile.
|
||||||
mergeSameLocation = true;
|
// It will be automatically deleted by migration code on the next maps update.
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.AddChangesetTag("info:features_merged", "yes");
|
||||||
|
changeset.Modify(osmFeature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||||
{}
|
{
|
||||||
catch (ChangesetWrapper::EmptyFeatureException const &)
|
// Object was never created by anyone else - it's safe to create it.
|
||||||
{}
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
|
||||||
// Add tags to XMLFeature
|
|
||||||
UpdateXMLFeatureTags(feature, journal, changeset);
|
|
||||||
|
|
||||||
// Upload XMLFeature to OSM
|
|
||||||
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
|
|
||||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
|
||||||
if (!mergeSameLocation)
|
|
||||||
changeset.Create(feature);
|
changeset.Create(feature);
|
||||||
|
}
|
||||||
|
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||||
|
{
|
||||||
|
// There is another node nearby, but it should be safe to create a new one.
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Create(feature);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Pass network or other errors to outside exception handler.
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FeatureStatus::Modified:
|
||||||
|
{
|
||||||
|
// Do not serialize feature's type to avoid breaking OSM data.
|
||||||
|
// TODO: Implement correct types matching when we support modifying existing feature types.
|
||||||
|
XMLFeature feature = editor::ToXML(fti.m_object, false);
|
||||||
|
if (!fti.m_street.empty())
|
||||||
|
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||||
|
|
||||||
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
|
if (!originalObjectPtr)
|
||||||
|
{
|
||||||
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
|
||||||
|
XMLFeature const osmFeatureCopy = osmFeature;
|
||||||
|
osmFeature.ApplyPatch(feature);
|
||||||
|
// Check to avoid uploading duplicates into OSM.
|
||||||
|
if (osmFeature == osmFeatureCopy)
|
||||||
|
{
|
||||||
|
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||||
|
// Don't delete this local change right now for user to see it in profile.
|
||||||
|
// It will be automatically deleted by migration code on the next maps update.
|
||||||
|
}
|
||||||
else
|
else
|
||||||
changeset.Modify(feature);
|
{
|
||||||
break;
|
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
||||||
}
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Modify(osmFeature);
|
||||||
case EditingLifecycle::MODIFIED:
|
}
|
||||||
{
|
|
||||||
// Load existing OSM object (Throws, see catch below)
|
|
||||||
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
|
|
||||||
|
|
||||||
// Update tags of XMLFeature
|
|
||||||
UpdateXMLFeatureTags(feature, journal, changeset);
|
|
||||||
|
|
||||||
// Upload XMLFeature to OSM
|
|
||||||
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
|
|
||||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
|
||||||
changeset.Modify(feature);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditingLifecycle::IN_SYNC:
|
|
||||||
{
|
|
||||||
CHECK(false, ("Object already IN_SYNC should not be here"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case FeatureStatus::Deleted:
|
case FeatureStatus::Deleted:
|
||||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
if (!originalObjectPtr)
|
if (!originalObjectPtr)
|
||||||
{
|
{
|
||||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
GetPlatform().RunTask(Platform::Thread::Gui,
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
uploadInfo.m_uploadStatus = kUploaded;
|
uploadInfo.m_uploadStatus = kUploaded;
|
||||||
uploadInfo.m_uploadError.clear();
|
uploadInfo.m_uploadError.clear();
|
||||||
@@ -787,7 +907,23 @@ void Editor::SaveUploadedInformation(FeatureID const & fid, UploadInfo const & u
|
|||||||
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
|
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
|
||||||
FeatureTypeInfo & fti) const
|
FeatureTypeInfo & fti) const
|
||||||
{
|
{
|
||||||
if (status != FeatureStatus::Created)
|
EditJournal journal = xml.GetEditJournal();
|
||||||
|
|
||||||
|
// Do not load Legacy Objects form Journal
|
||||||
|
auto const & journalHistory = journal.GetJournalHistory();
|
||||||
|
bool loadFromJournal =
|
||||||
|
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
||||||
|
|
||||||
|
LOG(LDEBUG, ("loadFromJournal: ", loadFromJournal));
|
||||||
|
|
||||||
|
if (status == FeatureStatus::Created)
|
||||||
|
{
|
||||||
|
if (loadFromJournal)
|
||||||
|
fti.m_object.ApplyEditsFromJournal(journal);
|
||||||
|
else
|
||||||
|
editor::FromXML(xml, fti.m_object);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
auto const originalObjectPtr = GetOriginalMapObject(fid);
|
auto const originalObjectPtr = GetOriginalMapObject(fid);
|
||||||
if (!originalObjectPtr)
|
if (!originalObjectPtr)
|
||||||
@@ -797,10 +933,12 @@ bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, Featu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fti.m_object = *originalObjectPtr;
|
fti.m_object = *originalObjectPtr;
|
||||||
}
|
|
||||||
|
|
||||||
EditJournal journal = xml.GetEditJournal();
|
if (loadFromJournal)
|
||||||
fti.m_object.ApplyEditsFromJournal(journal);
|
fti.m_object.ApplyEditsFromJournal(journal);
|
||||||
|
else
|
||||||
|
editor::ApplyPatch(xml, fti.m_object);
|
||||||
|
}
|
||||||
|
|
||||||
fti.m_object.SetJournal(std::move(journal));
|
fti.m_object.SetJournal(std::move(journal));
|
||||||
fti.m_object.SetID(fid);
|
fti.m_object.SetID(fid);
|
||||||
|
|||||||
@@ -178,6 +178,38 @@ string XMLFeature::ToOSMString() const
|
|||||||
return ost.str();
|
return ost.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XMLFeature::ApplyPatch(XMLFeature const & featureWithChanges)
|
||||||
|
{
|
||||||
|
// TODO(mgsergio): Get these alt tags from the config.
|
||||||
|
base::StringIL const alternativeTags[] = {{"phone", "contact:phone", "contact:mobile", "mobile"},
|
||||||
|
{"website", "contact:website", "url"},
|
||||||
|
{"fax", "contact:fax"},
|
||||||
|
{"email", "contact:email"}};
|
||||||
|
|
||||||
|
featureWithChanges.ForEachTag([&alternativeTags, this](string_view k, string_view v)
|
||||||
|
{
|
||||||
|
// Avoid duplication for similar alternative osm tags.
|
||||||
|
for (auto const & alt : alternativeTags)
|
||||||
|
{
|
||||||
|
auto it = alt.begin();
|
||||||
|
ASSERT(it != alt.end(), ());
|
||||||
|
if (k == *it)
|
||||||
|
{
|
||||||
|
for (auto const & tag : alt)
|
||||||
|
{
|
||||||
|
// Reuse already existing tag if it's present.
|
||||||
|
if (HasTag(tag))
|
||||||
|
{
|
||||||
|
SetTagValue(tag, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetTagValue(k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
m2::PointD XMLFeature::GetMercatorCenter() const
|
m2::PointD XMLFeature::GetMercatorCenter() const
|
||||||
{
|
{
|
||||||
return mercator::FromLatLon(GetLatLonFromNode(GetRootNode()));
|
return mercator::FromLatLon(GetLatLonFromNode(GetRootNode()));
|
||||||
@@ -638,40 +670,6 @@ void XMLFeature::RemoveTag(string_view key)
|
|||||||
GetRootNode().remove_child(tag);
|
GetRootNode().remove_child(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFeature::SetOSMTagsForType(uint32_t type)
|
|
||||||
{
|
|
||||||
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
|
|
||||||
{
|
|
||||||
SetTagValue("amenity", "recycling");
|
|
||||||
SetTagValue("recycling_type", "centre");
|
|
||||||
}
|
|
||||||
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
|
|
||||||
{
|
|
||||||
SetTagValue("amenity", "recycling");
|
|
||||||
SetTagValue("recycling_type", "container");
|
|
||||||
}
|
|
||||||
else if (ftypes::IsAddressChecker::Instance()(type))
|
|
||||||
{
|
|
||||||
// Addresses don't have a category tag
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string const strType = classif().GetReadableObjectName(type);
|
|
||||||
strings::SimpleTokenizer iter(strType, "-");
|
|
||||||
string_view const k = *iter;
|
|
||||||
|
|
||||||
if (++iter)
|
|
||||||
{
|
|
||||||
// Main type is stored as "k=amenity v=restaurant"
|
|
||||||
SetTagValue(k, *iter);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Main type is stored as "k=building v=yes"
|
|
||||||
SetTagValue(k, kYes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
|
void XMLFeature::UpdateOSMTag(std::string_view key, std::string_view value)
|
||||||
{
|
{
|
||||||
if (value.empty())
|
if (value.empty())
|
||||||
@@ -809,6 +807,26 @@ XMLFeature::Type XMLFeature::StringToType(string const & type)
|
|||||||
return Type::Unknown;
|
return Type::Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object)
|
||||||
|
{
|
||||||
|
xml.ForEachName([&object](string_view lang, string_view name)
|
||||||
|
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
|
||||||
|
|
||||||
|
string const house = xml.GetHouse();
|
||||||
|
if (!house.empty())
|
||||||
|
object.SetHouseNumber(house);
|
||||||
|
|
||||||
|
auto const cuisineStr = xml.GetCuisine();
|
||||||
|
if (!cuisineStr.empty())
|
||||||
|
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
|
||||||
|
|
||||||
|
xml.ForEachTag([&object](string_view k, string v)
|
||||||
|
{
|
||||||
|
// Skip result because we iterate via *all* tags here.
|
||||||
|
(void)object.UpdateMetadataValue(k, std::move(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
||||||
{
|
{
|
||||||
bool const isPoint = object.GetGeomType() == feature::GeomType::Point;
|
bool const isPoint = object.GetGeomType() == feature::GeomType::Point;
|
||||||
@@ -824,15 +842,6 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
|||||||
toFeature.SetGeometry(begin(triangles), end(triangles));
|
toFeature.SetGeometry(begin(triangles), end(triangles));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serializeType)
|
|
||||||
{
|
|
||||||
feature::TypesHolder types = object.GetTypes();
|
|
||||||
types.SortBySpec();
|
|
||||||
ASSERT(!types.Empty(), ("Feature does not have a type"));
|
|
||||||
uint32_t mainType = types.front();
|
|
||||||
toFeature.SetOSMTagsForType(mainType);
|
|
||||||
}
|
|
||||||
|
|
||||||
object.GetNameMultilang().ForEach([&toFeature](uint8_t const & lang, string_view name)
|
object.GetNameMultilang().ForEach([&toFeature](uint8_t const & lang, string_view name)
|
||||||
{ toFeature.SetName(lang, name); });
|
{ toFeature.SetName(lang, name); });
|
||||||
|
|
||||||
@@ -847,8 +856,61 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
|||||||
toFeature.SetCuisine(cuisineStr);
|
toFeature.SetCuisine(cuisineStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serializeType)
|
||||||
|
{
|
||||||
|
feature::TypesHolder th = object.GetTypes();
|
||||||
|
// TODO(mgsergio): Use correct sorting instead of SortBySpec based on the config.
|
||||||
|
th.SortBySpec();
|
||||||
|
// TODO(mgsergio): Either improve "OSM"-compatible serialization for more complex types,
|
||||||
|
// or save all our types directly, to restore and reuse them in migration of modified features.
|
||||||
|
for (uint32_t const type : th)
|
||||||
|
{
|
||||||
|
if (ftypes::IsCuisineChecker::Instance()(type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ftypes::IsRecyclingTypeChecker::Instance()(type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
|
||||||
|
{
|
||||||
|
toFeature.SetTagValue("amenity", "recycling");
|
||||||
|
toFeature.SetTagValue("recycling_type", "centre");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ftypes::IsRecyclingContainerChecker::Instance()(type))
|
||||||
|
{
|
||||||
|
toFeature.SetTagValue("amenity", "recycling");
|
||||||
|
toFeature.SetTagValue("recycling_type", "container");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string const strType = classif().GetReadableObjectName(type);
|
||||||
|
strings::SimpleTokenizer iter(strType, "-");
|
||||||
|
string_view const k = *iter;
|
||||||
|
if (++iter)
|
||||||
|
{
|
||||||
|
// First (main) type is always stored as "k=amenity v=restaurant".
|
||||||
|
// Any other "k=amenity v=atm" is replaced by "k=atm v=yes".
|
||||||
|
if (toFeature.GetTagValue(k).empty())
|
||||||
|
toFeature.SetTagValue(k, *iter);
|
||||||
|
else
|
||||||
|
toFeature.SetTagValue(*iter, kYes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We're editing building, generic craft, shop, office, amenity etc.
|
||||||
|
// Skip it's serialization.
|
||||||
|
// TODO(mgsergio): Correcly serialize all types back and forth.
|
||||||
|
LOG(LDEBUG, ("Skipping type serialization:", k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object.ForEachMetadataItem([&toFeature](string_view tag, string_view value)
|
object.ForEachMetadataItem([&toFeature](string_view tag, string_view value)
|
||||||
{
|
{
|
||||||
|
if (osm::isSocialContactTag(tag) && value.find('/') != std::string::npos)
|
||||||
|
toFeature.SetTagValue(tag, osm::socialContactToURL(tag, value));
|
||||||
|
else
|
||||||
toFeature.SetTagValue(tag, value);
|
toFeature.SetTagValue(tag, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -861,11 +923,105 @@ XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD merca
|
|||||||
XMLFeature toFeature(XMLFeature::Type::Node);
|
XMLFeature toFeature(XMLFeature::Type::Node);
|
||||||
toFeature.SetCenter(mercator);
|
toFeature.SetCenter(mercator);
|
||||||
|
|
||||||
toFeature.SetOSMTagsForType(type);
|
// Set Type
|
||||||
|
if (ftypes::IsRecyclingCentreChecker::Instance()(type))
|
||||||
|
{
|
||||||
|
toFeature.SetTagValue("amenity", "recycling");
|
||||||
|
toFeature.SetTagValue("recycling_type", "centre");
|
||||||
|
}
|
||||||
|
else if (ftypes::IsRecyclingContainerChecker::Instance()(type))
|
||||||
|
{
|
||||||
|
toFeature.SetTagValue("amenity", "recycling");
|
||||||
|
toFeature.SetTagValue("recycling_type", "container");
|
||||||
|
}
|
||||||
|
else if (ftypes::IsAddressChecker::Instance()(type))
|
||||||
|
{
|
||||||
|
// Addresses don't have a category tag
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string const strType = classif().GetReadableObjectName(type);
|
||||||
|
strings::SimpleTokenizer iter(strType, "-");
|
||||||
|
string_view const k = *iter;
|
||||||
|
|
||||||
|
CHECK(++iter, ("Processing Type failed: ", strType));
|
||||||
|
// Main type is always stored as "k=amenity v=restaurant".
|
||||||
|
toFeature.SetTagValue(k, *iter);
|
||||||
|
|
||||||
|
ASSERT(!(++iter), ("Can not process 3-arity/complex types: ", strType));
|
||||||
|
}
|
||||||
return toFeature;
|
return toFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object)
|
||||||
|
{
|
||||||
|
ASSERT_EQUAL(XMLFeature::Type::Node, xml.GetType(), ("At the moment only new nodes (points) can be created."));
|
||||||
|
object.SetPointType();
|
||||||
|
object.SetMercator(xml.GetMercatorCenter());
|
||||||
|
xml.ForEachName([&object](string_view lang, string_view name)
|
||||||
|
{ object.SetName(name, StringUtf8Multilang::GetLangIndex(lang)); });
|
||||||
|
|
||||||
|
string const house = xml.GetHouse();
|
||||||
|
if (!house.empty())
|
||||||
|
object.SetHouseNumber(house);
|
||||||
|
|
||||||
|
auto const cuisineStr = xml.GetCuisine();
|
||||||
|
if (!cuisineStr.empty())
|
||||||
|
object.SetCuisines(strings::Tokenize(cuisineStr, ";"));
|
||||||
|
|
||||||
|
feature::TypesHolder types = object.GetTypes();
|
||||||
|
|
||||||
|
Classificator const & cl = classif();
|
||||||
|
xml.ForEachTag([&](string_view k, string_view v)
|
||||||
|
{
|
||||||
|
if (object.UpdateMetadataValue(k, string(v)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Cuisines are already processed before this loop.
|
||||||
|
if (k == "cuisine")
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We process recycling_type tag together with "amenity"="recycling" later.
|
||||||
|
// We currently ignore recycling tag because it's our custom tag and we cannot
|
||||||
|
// import it to osm directly.
|
||||||
|
if (k == "recycling" || k == "recycling_type")
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t type = 0;
|
||||||
|
if (k == "amenity" && v == "recycling" && xml.HasTag("recycling_type"))
|
||||||
|
{
|
||||||
|
auto const typeValue = xml.GetTagValue("recycling_type");
|
||||||
|
if (typeValue == "centre")
|
||||||
|
type = ftypes::IsRecyclingCentreChecker::Instance().GetType();
|
||||||
|
else if (typeValue == "container")
|
||||||
|
type = ftypes::IsRecyclingContainerChecker::Instance().GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple heuristics. It works for types converted from osm with short mapcss rules
|
||||||
|
// where k=v from osm is converted to our k-v type (amenity=restaurant, shop=convenience etc.).
|
||||||
|
if (type == 0)
|
||||||
|
type = cl.GetTypeByPathSafe({k, v});
|
||||||
|
if (type == 0)
|
||||||
|
type = cl.GetTypeByPathSafe({k}); // building etc.
|
||||||
|
if (type == 0)
|
||||||
|
type = cl.GetTypeByPathSafe({"amenity", k}); // atm=yes, toilet=yes etc.
|
||||||
|
|
||||||
|
if (type && types.Size() >= feature::kMaxTypesCount)
|
||||||
|
LOG(LERROR, ("Can't add type:", k, v, ". Types limit exceeded."));
|
||||||
|
else if (type)
|
||||||
|
types.Add(type);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// LOG(LWARNING, ("Can't load/parse type:", k, v));
|
||||||
|
/// @todo Refactor to make one ForEachTag loop. Now we have separate ForEachName,
|
||||||
|
/// so we can't log any suspicious tag here ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
object.SetTypes(types);
|
||||||
|
return types.Size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
string DebugPrint(XMLFeature const & feature)
|
string DebugPrint(XMLFeature const & feature)
|
||||||
{
|
{
|
||||||
std::ostringstream ost;
|
std::ostringstream ost;
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ public:
|
|||||||
void Save(std::ostream & ost) const;
|
void Save(std::ostream & ost) const;
|
||||||
std::string ToOSMString() const;
|
std::string ToOSMString() const;
|
||||||
|
|
||||||
|
/// Tags from featureWithChanges are applied to this(osm) feature.
|
||||||
|
void ApplyPatch(XMLFeature const & featureWithChanges);
|
||||||
|
|
||||||
Type GetType() const;
|
Type GetType() const;
|
||||||
std::string GetTypeString() const;
|
std::string GetTypeString() const;
|
||||||
|
|
||||||
@@ -182,8 +185,6 @@ public:
|
|||||||
void SetTagValue(std::string_view key, std::string_view value);
|
void SetTagValue(std::string_view key, std::string_view value);
|
||||||
void RemoveTag(std::string_view key);
|
void RemoveTag(std::string_view key);
|
||||||
|
|
||||||
/// Add the OSM tags for a feature type
|
|
||||||
void SetOSMTagsForType(uint32_t type);
|
|
||||||
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
|
/// Wrapper for SetTagValue and RemoveTag, avoids duplication for similar alternative osm tags
|
||||||
void UpdateOSMTag(std::string_view key, std::string_view value);
|
void UpdateOSMTag(std::string_view key, std::string_view value);
|
||||||
/// Replace an old business with a new business
|
/// Replace an old business with a new business
|
||||||
@@ -204,15 +205,22 @@ private:
|
|||||||
pugi::xml_document m_document;
|
pugi::xml_document m_document;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @param serializeType if false, type is not serialized.
|
/// Rewrites all but geometry and types.
|
||||||
/// This function converts the current state of a MapObject to a format similar to OSM style XML.
|
/// Should be applied to existing features only (in mwm files).
|
||||||
/// Tags written in this function are used to see POI details when debugging. Only the data stored
|
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object);
|
||||||
/// in the EditJournal is used for OSM editing.
|
|
||||||
|
/// @param serializeType if false, types are not serialized.
|
||||||
|
/// Useful for applying modifications to existing OSM features, to avoid issues when someone
|
||||||
|
/// has changed a type in OSM, but our users uploaded invalid outdated type after modifying feature.
|
||||||
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
|
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
|
||||||
|
|
||||||
/// Used to generate XML for created objects in the new editor
|
/// Used to generate XML for created objects in the new editor
|
||||||
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
|
XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD mercator);
|
||||||
|
|
||||||
|
/// Creates new feature, including geometry and types.
|
||||||
|
/// @Note: only nodes (points) are supported at the moment.
|
||||||
|
bool FromXML(XMLFeature const & xml, osm::EditableMapObject & object);
|
||||||
|
|
||||||
std::string DebugPrint(XMLFeature const & feature);
|
std::string DebugPrint(XMLFeature const & feature);
|
||||||
std::string DebugPrint(XMLFeature::Type const type);
|
std::string DebugPrint(XMLFeature::Type const type);
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "geometry/mercator.hpp"
|
||||||
#include "geometry/point2d.hpp"
|
#include "geometry/point2d.hpp"
|
||||||
|
|
||||||
#include "base/math.hpp"
|
#include "base/math.hpp"
|
||||||
@@ -9,17 +10,20 @@
|
|||||||
|
|
||||||
namespace m2
|
namespace m2
|
||||||
{
|
{
|
||||||
// This class holds a parametrization of the
|
/**
|
||||||
// line segment between two points p0 and p1.
|
* @brief This class holds a parametrization of the line segment between two points `p0` and `p1`.
|
||||||
// The parametrization is of the form
|
*
|
||||||
// p(t) = p0 + t * dir.
|
* The parametrization is of the form
|
||||||
// Other conditions:
|
* `p(t) = p0 + t * dir`.
|
||||||
// dir is the normalized (p1 - p0) vector.
|
*
|
||||||
// length(dir) = 1.
|
* Other conditions:
|
||||||
// p(0) = p0.
|
* * `dir` is the normalized `(p1 - p0)` vector.
|
||||||
// p(T) = p1 with T = length(p1 - p0).
|
* * `length(dir) = 1`.
|
||||||
//
|
* * `p(0) = p0`.
|
||||||
// The points with t in [0, T] are the points of the segment.
|
* * `p(T) = p1` with `T = length(p1 - p0)`.
|
||||||
|
*
|
||||||
|
* The points with `t` in `[0, T]` are the points of the segment.
|
||||||
|
*/
|
||||||
template <typename Point>
|
template <typename Point>
|
||||||
class ParametrizedSegment
|
class ParametrizedSegment
|
||||||
{
|
{
|
||||||
@@ -36,7 +40,9 @@ public:
|
|||||||
m_d = m_d / m_length;
|
m_d = m_d / m_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the squared (euclidean) distance from the segment to |p|.
|
/**
|
||||||
|
* @brief Returns the squared (euclidean) distance from the segment to `p`.
|
||||||
|
*/
|
||||||
double SquaredDistanceToPoint(Point const & p) const
|
double SquaredDistanceToPoint(Point const & p) const
|
||||||
{
|
{
|
||||||
m2::PointD const diff(p - m_p0);
|
m2::PointD const diff(p - m_p0);
|
||||||
@@ -52,9 +58,22 @@ public:
|
|||||||
return math::Pow2(CrossProduct(diff, m_d));
|
return math::Pow2(CrossProduct(diff, m_d));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the point of the segment that is closest to |p|.
|
/**
|
||||||
m2::PointD ClosestPointTo(Point const & p) const
|
* @brief Returns the point of the segment that is closest to `p`.
|
||||||
|
*
|
||||||
|
* @param p The checkpoint
|
||||||
|
* @param snapToEnds If true, the result is the endpoint of the segment which is closest to `p`
|
||||||
|
*/
|
||||||
|
m2::PointD ClosestPointTo(Point const & p, bool snapToEnds = false) const
|
||||||
{
|
{
|
||||||
|
if (snapToEnds)
|
||||||
|
{
|
||||||
|
if (mercator::DistanceOnEarth(p, m_p0) < mercator::DistanceOnEarth(p, m_p1))
|
||||||
|
return m_p0;
|
||||||
|
else
|
||||||
|
return m_p1;
|
||||||
|
}
|
||||||
|
|
||||||
m2::PointD const diff(p - m_p0);
|
m2::PointD const diff(p - m_p0);
|
||||||
double const t = DotProduct(m_d, diff);
|
double const t = DotProduct(m_d, diff);
|
||||||
|
|
||||||
|
|||||||
@@ -19,20 +19,31 @@ public:
|
|||||||
/// Registers a new map.
|
/// Registers a new map.
|
||||||
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
||||||
|
|
||||||
/// Deregisters a map from internal records.
|
/**
|
||||||
///
|
* @brief Deregisters a map from internal records.
|
||||||
/// \param countryFile A countryFile denoting a map to be deregistered.
|
* @param countryFile A `CountryFile` denoting a map to be deregistered.
|
||||||
/// \return True if the map was successfully deregistered. If map is locked
|
* @return True if the map was successfully deregistered, false if the map is locked now.
|
||||||
/// now, returns false.
|
*/
|
||||||
bool DeregisterMap(platform::CountryFile const & countryFile);
|
bool DeregisterMap(platform::CountryFile const & countryFile);
|
||||||
|
|
||||||
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
|
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
|
||||||
covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
|
covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
|
||||||
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const;
|
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const;
|
||||||
// Calls |f| for features closest to |center| until |stopCallback| returns true or distance
|
|
||||||
// |sizeM| from has been reached. Then for EditableDataSource calls |f| for each edited feature
|
/**
|
||||||
// inside square with center |center| and side |2 * sizeM|. Edited features are not in the same
|
* @brief Iterates over features within a given distance of a center point.
|
||||||
// hierarchy and there is no fast way to merge frozen and edited features.
|
*
|
||||||
|
* Calls `f` for features closest to `center` until `stopCallback` returns true or distance
|
||||||
|
* `sizeM` from has been reached. Then for EditableDataSource calls `f` for each edited feature
|
||||||
|
* inside square with center `center` and side `2 * sizeM`. Edited features are not in the same
|
||||||
|
* hierarchy and there is no fast way to merge frozen and edited features.
|
||||||
|
*
|
||||||
|
* @brief f Callback function that is called on each feature.
|
||||||
|
* @brief stopCallback Callback function which decides whether to continue searching or stop.
|
||||||
|
* @brief center The center of the search area.
|
||||||
|
* @brief sizeM The size of the search area, as a distance from the center point.
|
||||||
|
* @brief scale
|
||||||
|
*/
|
||||||
void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
|
void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
|
||||||
double sizeM, int scale) const;
|
double sizeM, int scale) const;
|
||||||
void ForEachInScale(FeatureCallback const & f, int scale) const;
|
void ForEachInScale(FeatureCallback const & f, int scale) const;
|
||||||
@@ -66,18 +77,24 @@ private:
|
|||||||
std::unique_ptr<FeatureSourceFactory> m_factory;
|
std::unique_ptr<FeatureSourceFactory> m_factory;
|
||||||
};
|
};
|
||||||
|
|
||||||
// DataSource which operates with features from mwm file and does not support features creation
|
/**
|
||||||
// deletion or modification.
|
* @brief A `DataSource` which operates with features from an MWM file and does not support
|
||||||
|
* creation, deletion or modification of features.
|
||||||
|
*/
|
||||||
class FrozenDataSource : public DataSource
|
class FrozenDataSource : public DataSource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {}
|
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Guard for loading features from particular MWM by demand.
|
/**
|
||||||
/// @note If you need to work with FeatureType from different threads you need to use
|
* @brief Guard for loading features from particular MWM by demand.
|
||||||
/// a unique FeaturesLoaderGuard instance for every thread.
|
*
|
||||||
/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest.
|
* @note If you need to work with `FeatureType` from different threads, you need to use
|
||||||
|
* a unique `FeaturesLoaderGuard` instance for every thread.
|
||||||
|
* For an example of concurrent extracting feature details please see `ConcurrentFeatureParsingTest`
|
||||||
|
* in `routing/routing_integration_tests`.
|
||||||
|
*/
|
||||||
class FeaturesLoaderGuard
|
class FeaturesLoaderGuard
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -479,9 +479,6 @@ bool EditableMapObject::CheckHouseNumberWhenIsAddress() const
|
|||||||
// static
|
// static
|
||||||
bool EditableMapObject::ValidateFlats(string const & flats)
|
bool EditableMapObject::ValidateFlats(string const & flats)
|
||||||
{
|
{
|
||||||
if (strings::CountChar(flats) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
|
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
|
||||||
{
|
{
|
||||||
string_view token = *it;
|
string_view token = *it;
|
||||||
@@ -519,9 +516,6 @@ bool EditableMapObject::ValidatePhoneList(string const & phone)
|
|||||||
if (phone.empty())
|
if (phone.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(phone) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto constexpr kMaxNumberLen = 15;
|
auto constexpr kMaxNumberLen = 15;
|
||||||
auto constexpr kMinNumberLen = 5;
|
auto constexpr kMinNumberLen = 5;
|
||||||
|
|
||||||
@@ -562,9 +556,6 @@ bool EditableMapObject::ValidateEmail(string const & email)
|
|||||||
if (email.empty())
|
if (email.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(email) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (strings::IsASCIIString(email))
|
if (strings::IsASCIIString(email))
|
||||||
{
|
{
|
||||||
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
|
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
|
||||||
@@ -598,9 +589,6 @@ bool EditableMapObject::ValidateLevel(string const & level)
|
|||||||
if (level.empty())
|
if (level.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(level) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
|
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -645,9 +633,6 @@ bool EditableMapObject::ValidateName(string const & name)
|
|||||||
if (name.empty())
|
if (name.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(name) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×¶";
|
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×¶";
|
||||||
|
|
||||||
using Iter = utf8::unchecked::iterator<string::const_iterator>;
|
using Iter = utf8::unchecked::iterator<string::const_iterator>;
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ class EditableMapObject : public MapObject
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
|
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
|
||||||
static int constexpr kMaximumOsmChars = 255;
|
|
||||||
|
|
||||||
bool IsNameEditable() const;
|
bool IsNameEditable() const;
|
||||||
bool IsAddressEditable() const;
|
bool IsAddressEditable() const;
|
||||||
|
|||||||
@@ -76,6 +76,18 @@ public:
|
|||||||
// (number of points in inner triangle-strips).
|
// (number of points in inner triangle-strips).
|
||||||
using PointsBufferT = buffer_vector<m2::PointD, 32>;
|
using PointsBufferT = buffer_vector<m2::PointD, 32>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the points of the feature.
|
||||||
|
*
|
||||||
|
* Depending on `scale`, the geometry may be simplified by reducing groups of nearby points to
|
||||||
|
* one point. If `scale` equals `FeatureType::BEST_GEOMETRY`, no such simplification takes place.
|
||||||
|
*
|
||||||
|
* Points are cached between calls and `scale` may not be honored if cached points are returned.
|
||||||
|
* To reliably enforce `scale`, call `ResetGemoetry()` immediately prior to `GetPoints()`.
|
||||||
|
*
|
||||||
|
* @param scale The map scale
|
||||||
|
* @return The points of the feature, simplified according to `scale`.
|
||||||
|
*/
|
||||||
PointsBufferT const & GetPoints(int scale);
|
PointsBufferT const & GetPoints(int scale);
|
||||||
PointsBufferT const & GetTrianglesAsPoints(int scale);
|
PointsBufferT const & GetTrianglesAsPoints(int scale);
|
||||||
|
|
||||||
@@ -85,6 +97,13 @@ public:
|
|||||||
void ParseHeader2();
|
void ParseHeader2();
|
||||||
void ParseRelations();
|
void ParseRelations();
|
||||||
void ParseAllBeforeGeometry() { ParseRelations(); }
|
void ParseAllBeforeGeometry() { ParseRelations(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the geometry.
|
||||||
|
*
|
||||||
|
* This discards any cached points, resulting in points being re-fetched the next time
|
||||||
|
* `GetPoints()` or `GetTrianglesAsPoints()` is called.
|
||||||
|
*/
|
||||||
void ResetGeometry();
|
void ResetGeometry();
|
||||||
void ParseGeometry(int scale);
|
void ParseGeometry(int scale);
|
||||||
void ParseTriangles(int scale);
|
void ParseTriangles(int scale);
|
||||||
|
|||||||
@@ -701,24 +701,46 @@ double GetRadiusByPopulationForRouting(uint64_t p, LocalityType localityType);
|
|||||||
uint64_t GetPopulationByRadius(double r);
|
uint64_t GetPopulationByRadius(double r);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
// Highway class. The order is important.
|
/**
|
||||||
// The enum values follow from the biggest roads (Trunk) to the smallest ones (Service).
|
* @brief Highway class.
|
||||||
|
*
|
||||||
|
* The order is important. The enum values follow from the biggest roads (Trunk) to the smallest ones (Service).
|
||||||
|
*/
|
||||||
enum class HighwayClass
|
enum class HighwayClass
|
||||||
{
|
{
|
||||||
Undefined = 0, // There has not been any attempt of calculating HighwayClass.
|
/**
|
||||||
|
* Used when there has not been any attempt of calculating HighwayClass.
|
||||||
|
*/
|
||||||
|
Undefined = 0,
|
||||||
Motorway,
|
Motorway,
|
||||||
Trunk,
|
Trunk,
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
Tertiary,
|
Tertiary,
|
||||||
|
/**
|
||||||
|
* Unclassified, residential, living street and `highway=road`.
|
||||||
|
*/
|
||||||
LivingStreet,
|
LivingStreet,
|
||||||
|
/**
|
||||||
|
* Service, track, busway and `man_made=pier`.
|
||||||
|
*/
|
||||||
Service,
|
Service,
|
||||||
// OSM highway=service type is widely used even for _significant_ roads.
|
// OSM highway=service type is widely used even for _significant_ roads.
|
||||||
// Adding a new type to distinguish mapped driveway or parking_aisle.
|
// Adding a new type to distinguish mapped driveway or parking_aisle.
|
||||||
ServiceMinor,
|
ServiceMinor,
|
||||||
|
/**
|
||||||
|
* Anything not intended for motorized traffic: pedestrian, footway, bridleway, steps, cycleway,
|
||||||
|
* path and also `highway=construction`.
|
||||||
|
*/
|
||||||
Pedestrian,
|
Pedestrian,
|
||||||
Transported, // Vehicles are transported by train or ferry.
|
/**
|
||||||
Count // This value is used for internals only.
|
* Vehicles are transported by train or ferry.
|
||||||
|
*/
|
||||||
|
Transported,
|
||||||
|
/**
|
||||||
|
* This value is used for internals only.
|
||||||
|
*/
|
||||||
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string DebugPrint(HighwayClass const cls);
|
std::string DebugPrint(HighwayClass const cls);
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ UNIT_TEST(RoadShields_Smoke)
|
|||||||
{
|
{
|
||||||
using namespace ftypes;
|
using namespace ftypes;
|
||||||
|
|
||||||
// TODO: Fix broken tests to make code compile
|
|
||||||
/*
|
|
||||||
auto shields = GetRoadShields("France", "D 116A");
|
auto shields = GetRoadShields("France", "D 116A");
|
||||||
TEST_EQUAL(shields.size(), 1, ());
|
TEST_EQUAL(shields.size(), 1, ());
|
||||||
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
|
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
|
||||||
@@ -57,5 +55,4 @@ UNIT_TEST(RoadShields_Smoke)
|
|||||||
shields = GetRoadShields("Estonia", "ee:national/27;ee:local/7841171");
|
shields = GetRoadShields("Estonia", "ee:national/27;ee:local/7841171");
|
||||||
TEST_EQUAL(shields.size(), 1, ());
|
TEST_EQUAL(shields.size(), 1, ());
|
||||||
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
|
TEST_EQUAL(shields[0].m_type, RoadShieldType::Generic_Orange, ());
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,8 +157,10 @@ public:
|
|||||||
explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {}
|
explicit MwmSet(size_t cacheSize = 64) : m_cacheSize(cacheSize) {}
|
||||||
virtual ~MwmSet() = default;
|
virtual ~MwmSet() = default;
|
||||||
|
|
||||||
// Mwm handle, which is used to refer to mwm and prevent it from
|
/**
|
||||||
// deletion when its FileContainer is used.
|
* @brief Mwm handle, which is used to refer to mwm and prevent it from deletion when its
|
||||||
|
* FileContainer is used.
|
||||||
|
*/
|
||||||
class MwmHandle
|
class MwmHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -243,19 +245,26 @@ public:
|
|||||||
BadFile
|
BadFile
|
||||||
};
|
};
|
||||||
|
|
||||||
// An Observer interface to MwmSet. Note that these functions can
|
/**
|
||||||
// be called from *ANY* thread because most signals are sent when
|
* @brief An Observer interface to `MwmSet`.
|
||||||
// some thread releases its MwmHandle, so overrides must be as fast
|
*
|
||||||
// as possible and non-blocking when it's possible.
|
* Note that these functions can be called from *ANY* thread because most signals are sent when
|
||||||
|
* some thread releases its MwmHandle, so overrides must be as fast as possible and non-blocking
|
||||||
|
* when it's possible.
|
||||||
|
*/
|
||||||
class Observer
|
class Observer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~Observer() = default;
|
virtual ~Observer() = default;
|
||||||
|
|
||||||
// Called when a map is registered for the first time and can be used.
|
/**
|
||||||
|
* @brief Called when a map is registered for the first time and can be used.
|
||||||
|
*/
|
||||||
virtual void OnMapRegistered(platform::LocalCountryFile const & /* localFile */) {}
|
virtual void OnMapRegistered(platform::LocalCountryFile const & /* localFile */) {}
|
||||||
|
|
||||||
// Called when a map is deregistered and can no longer be used.
|
/**
|
||||||
|
* @brief Called when a map is deregistered and can no longer be used.
|
||||||
|
*/
|
||||||
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {}
|
virtual void OnMapDeregistered(platform::LocalCountryFile const & /* localFile */) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -304,7 +313,14 @@ public:
|
|||||||
/// @todo In fact, std::shared_ptr<MwmInfo> is a MwmId. Seems like better to make vector<MwmId> interface.
|
/// @todo In fact, std::shared_ptr<MwmInfo> is a MwmId. Seems like better to make vector<MwmId> interface.
|
||||||
void GetMwmsInfo(std::vector<std::shared_ptr<MwmInfo>> & info) const;
|
void GetMwmsInfo(std::vector<std::shared_ptr<MwmInfo>> & info) const;
|
||||||
|
|
||||||
// Clears caches and mwm's registry. All known mwms won't be marked as DEREGISTERED.
|
/**
|
||||||
|
* @brief Clears caches and mwm's registry.
|
||||||
|
*
|
||||||
|
* All known mwms won't be marked as DEREGISTERED.
|
||||||
|
*
|
||||||
|
* @todo what does “all won’t be marked” mean? Not all will be marked/some might not be marked?
|
||||||
|
* Or all will be unmarked?
|
||||||
|
*/
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
void ClearCache();
|
void ClearCache();
|
||||||
@@ -345,10 +361,18 @@ private:
|
|||||||
ProcessEventList(events);
|
ProcessEventList(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets |status| in |info|, adds corresponding event to |event|.
|
/**
|
||||||
|
* @brief Sets `status` in `info`, adds corresponding event to `event`.
|
||||||
|
* @param info
|
||||||
|
* @param status
|
||||||
|
* @param events
|
||||||
|
*/
|
||||||
void SetStatus(MwmInfo & info, MwmInfo::Status status, EventList & events);
|
void SetStatus(MwmInfo & info, MwmInfo::Status status, EventList & events);
|
||||||
|
|
||||||
// Triggers observers on each event in |events|.
|
/**
|
||||||
|
* @brief Triggers observers on each event in `events`.
|
||||||
|
* @param events
|
||||||
|
*/
|
||||||
void ProcessEventList(EventList & events);
|
void ProcessEventList(EventList & events);
|
||||||
|
|
||||||
/// @precondition This function is always called under mutex m_lock.
|
/// @precondition This function is always called under mutex m_lock.
|
||||||
|
|||||||
@@ -408,10 +408,6 @@ bool ValidateWebsite(string const & site)
|
|||||||
|
|
||||||
auto const startPos = GetProtocolNameLength(site);
|
auto const startPos = GetProtocolNameLength(site);
|
||||||
|
|
||||||
// check lengt and leave room for addition of 'http://'
|
|
||||||
if (strings::CountChar(site) > (IsProtocolSpecified(site) ? kMaximumOsmChars : kMaximumOsmChars - 7))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (startPos >= site.size())
|
if (startPos >= site.size())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -433,9 +429,6 @@ bool ValidateFacebookPage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check if 'page' contains valid Facebook username or page name.
|
// Check if 'page' contains valid Facebook username or page name.
|
||||||
// * length >= 5
|
// * length >= 5
|
||||||
// * no forbidden symbols in the string
|
// * no forbidden symbols in the string
|
||||||
@@ -459,9 +452,6 @@ bool ValidateInstagramPage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Rules are defined here: https://blog.jstassen.com/2016/03/code-regex-for-instagram-username-and-hashtags/
|
// Rules are defined here: https://blog.jstassen.com/2016/03/code-regex-for-instagram-username-and-hashtags/
|
||||||
if (regex_match(page, s_instaRegex))
|
if (regex_match(page, s_instaRegex))
|
||||||
return true;
|
return true;
|
||||||
@@ -478,9 +468,6 @@ bool ValidateTwitterPage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!ValidateWebsite(page))
|
if (!ValidateWebsite(page))
|
||||||
return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044
|
return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044
|
||||||
|
|
||||||
@@ -493,9 +480,6 @@ bool ValidateVkPage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check that page contains valid username. Rules took here: https://vk.com/faq18038
|
// Check that page contains valid username. Rules took here: https://vk.com/faq18038
|
||||||
// The page name must be between 5 and 32 characters.
|
// The page name must be between 5 and 32 characters.
|
||||||
@@ -529,9 +513,6 @@ bool ValidateLinePage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check that linePage contains valid page name.
|
// Check that linePage contains valid page name.
|
||||||
// Rules are defined here: https://help.line.me/line/?contentId=10009904
|
// Rules are defined here: https://help.line.me/line/?contentId=10009904
|
||||||
@@ -555,9 +536,6 @@ bool ValidateFediversePage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Match @username@instance.name format
|
// Match @username@instance.name format
|
||||||
if (regex_match(page, s_fediverseRegex))
|
if (regex_match(page, s_fediverseRegex))
|
||||||
return true;
|
return true;
|
||||||
@@ -597,9 +575,6 @@ bool ValidateBlueskyPage(string const & page)
|
|||||||
if (page.empty())
|
if (page.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (strings::CountChar(page) > kMaximumOsmChars)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Match {@?}{user/domain.name} format
|
// Match {@?}{user/domain.name} format
|
||||||
if (regex_match(page, s_blueskyRegex))
|
if (regex_match(page, s_blueskyRegex))
|
||||||
return true;
|
return true;
|
||||||
@@ -626,6 +601,12 @@ bool ValidateBlueskyPage(string const & page)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSocialContactTag(string_view tag)
|
||||||
|
{
|
||||||
|
return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine || tag == kFediverse ||
|
||||||
|
tag == kBluesky || tag == kPanoramax;
|
||||||
|
}
|
||||||
|
|
||||||
bool isSocialContactTag(MapObject::MetadataID const metaID)
|
bool isSocialContactTag(MapObject::MetadataID const metaID)
|
||||||
{
|
{
|
||||||
return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM ||
|
return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM ||
|
||||||
@@ -637,6 +618,35 @@ bool isSocialContactTag(MapObject::MetadataID const metaID)
|
|||||||
|
|
||||||
// Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name
|
// Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name
|
||||||
// from OSM data and user input. This function prepends domain name to generate full URL.
|
// from OSM data and user input. This function prepends domain name to generate full URL.
|
||||||
|
string socialContactToURL(string_view tag, string_view value)
|
||||||
|
{
|
||||||
|
ASSERT(!value.empty(), ());
|
||||||
|
|
||||||
|
if (tag == kInstagram)
|
||||||
|
return string{kUrlInstagram}.append(value);
|
||||||
|
if (tag == kFacebook)
|
||||||
|
return string{kUrlFacebook}.append(value);
|
||||||
|
if (tag == kTwitter)
|
||||||
|
return string{kUrlTwitter}.append(value);
|
||||||
|
if (tag == kVk)
|
||||||
|
return string{kUrlVk}.append(value);
|
||||||
|
if (tag == kFediverse)
|
||||||
|
return fediverseHandleToUrl(value);
|
||||||
|
if (tag == kBluesky) // In future
|
||||||
|
return string{kUrlBluesky}.append(value);
|
||||||
|
if (tag == kLine)
|
||||||
|
{
|
||||||
|
if (value.find('/') == string::npos) // 'value' is a username.
|
||||||
|
return string{kUrlLine}.append(value);
|
||||||
|
else // 'value' is an URL.
|
||||||
|
return string{kHttps}.append(value);
|
||||||
|
}
|
||||||
|
if (tag == kPanoramax)
|
||||||
|
return string{kUrlPanoramax}.append(value);
|
||||||
|
|
||||||
|
return string{value};
|
||||||
|
}
|
||||||
|
|
||||||
string socialContactToURL(MapObject::MetadataID metaID, string_view value)
|
string socialContactToURL(MapObject::MetadataID metaID, string_view value)
|
||||||
{
|
{
|
||||||
ASSERT(!value.empty(), ());
|
ASSERT(!value.empty(), ());
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
namespace osm
|
namespace osm
|
||||||
{
|
{
|
||||||
static int constexpr kMaximumOsmChars = 255;
|
|
||||||
|
|
||||||
std::string ValidateAndFormat_website(std::string const & v);
|
std::string ValidateAndFormat_website(std::string const & v);
|
||||||
std::string ValidateAndFormat_facebook(std::string const & v);
|
std::string ValidateAndFormat_facebook(std::string const & v);
|
||||||
std::string ValidateAndFormat_instagram(std::string const & v);
|
std::string ValidateAndFormat_instagram(std::string const & v);
|
||||||
@@ -26,6 +24,8 @@ bool ValidateLinePage(std::string const & v);
|
|||||||
bool ValidateFediversePage(std::string const & v);
|
bool ValidateFediversePage(std::string const & v);
|
||||||
bool ValidateBlueskyPage(std::string const & v);
|
bool ValidateBlueskyPage(std::string const & v);
|
||||||
|
|
||||||
|
bool isSocialContactTag(std::string_view tag);
|
||||||
bool isSocialContactTag(osm::MapObject::MetadataID const metaID);
|
bool isSocialContactTag(osm::MapObject::MetadataID const metaID);
|
||||||
|
std::string socialContactToURL(std::string_view tag, std::string_view value);
|
||||||
std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value);
|
std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value);
|
||||||
} // namespace osm
|
} // namespace osm
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
#include "ge0/url_generator.hpp"
|
#include "ge0/url_generator.hpp"
|
||||||
|
|
||||||
|
#include "routing/index_router.hpp"
|
||||||
#include "routing/route.hpp"
|
#include "routing/route.hpp"
|
||||||
|
#include "routing/routing_helpers.hpp"
|
||||||
#include "routing/speed_camera_prohibition.hpp"
|
#include "routing/speed_camera_prohibition.hpp"
|
||||||
|
|
||||||
#include "routing_common/num_mwm_id.hpp"
|
#include "routing_common/num_mwm_id.hpp"
|
||||||
@@ -17,13 +19,18 @@
|
|||||||
#include "search/locality_finder.hpp"
|
#include "search/locality_finder.hpp"
|
||||||
|
|
||||||
#include "storage/country_info_getter.hpp"
|
#include "storage/country_info_getter.hpp"
|
||||||
|
#include "storage/routing_helpers.hpp"
|
||||||
#include "storage/storage.hpp"
|
#include "storage/storage.hpp"
|
||||||
#include "storage/storage_helpers.hpp"
|
#include "storage/storage_helpers.hpp"
|
||||||
|
|
||||||
|
#include "traffxml/traff_source.hpp"
|
||||||
|
|
||||||
#include "drape_frontend/color_constants.hpp"
|
#include "drape_frontend/color_constants.hpp"
|
||||||
#include "drape_frontend/gps_track_point.hpp"
|
#include "drape_frontend/gps_track_point.hpp"
|
||||||
#include "drape_frontend/visual_params.hpp"
|
#include "drape_frontend/visual_params.hpp"
|
||||||
|
|
||||||
|
#include "editor/editable_data_source.hpp"
|
||||||
|
|
||||||
#include "descriptions/loader.hpp"
|
#include "descriptions/loader.hpp"
|
||||||
|
|
||||||
#include "indexer/categories_holder.hpp"
|
#include "indexer/categories_holder.hpp"
|
||||||
@@ -39,6 +46,7 @@
|
|||||||
#include "indexer/scales.hpp"
|
#include "indexer/scales.hpp"
|
||||||
#include "indexer/transliteration_loader.hpp"
|
#include "indexer/transliteration_loader.hpp"
|
||||||
|
|
||||||
|
#include "platform/local_country_file_utils.hpp"
|
||||||
#include "platform/localization.hpp"
|
#include "platform/localization.hpp"
|
||||||
#include "platform/measurement_utils.hpp"
|
#include "platform/measurement_utils.hpp"
|
||||||
#include "platform/mwm_version.hpp"
|
#include "platform/mwm_version.hpp"
|
||||||
@@ -61,11 +69,13 @@
|
|||||||
|
|
||||||
#include "base/logging.hpp"
|
#include "base/logging.hpp"
|
||||||
#include "base/math.hpp"
|
#include "base/math.hpp"
|
||||||
|
#include "base/stl_helpers.hpp"
|
||||||
#include "base/string_utils.hpp"
|
#include "base/string_utils.hpp"
|
||||||
|
|
||||||
#include "std/target_os.hpp"
|
#include "std/target_os.hpp"
|
||||||
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "private.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -93,6 +103,8 @@ std::string_view constexpr kAllow3dKey = "Allow3d";
|
|||||||
std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d";
|
std::string_view constexpr kAllow3dBuildingsKey = "Buildings3d";
|
||||||
std::string_view constexpr kAllowAutoZoom = "AutoZoom";
|
std::string_view constexpr kAllowAutoZoom = "AutoZoom";
|
||||||
std::string_view constexpr kTrafficEnabledKey = "TrafficEnabled";
|
std::string_view constexpr kTrafficEnabledKey = "TrafficEnabled";
|
||||||
|
std::string_view constexpr kTrafficHttpEnabledKey = "TrafficHttpEnabled";
|
||||||
|
std::string_view constexpr kTrafficHttpUrlKey = "TrafficHttpUrl";
|
||||||
std::string_view constexpr kTransitSchemeEnabledKey = "TransitSchemeEnabled";
|
std::string_view constexpr kTransitSchemeEnabledKey = "TransitSchemeEnabled";
|
||||||
std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled";
|
std::string_view constexpr kIsolinesEnabledKey = "IsolinesEnabled";
|
||||||
std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled";
|
std::string_view constexpr kOutdoorsEnabledKey = "OutdoorsEnabled";
|
||||||
@@ -273,8 +285,11 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
|
|||||||
[this]() -> StringsBundle const &
|
[this]() -> StringsBundle const &
|
||||||
{ return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }),
|
{ return m_stringsBundle; }, [this]() -> power_management::PowerManager const & { return m_powerManager; }),
|
||||||
static_cast<RoutingManager::Delegate &>(*this))
|
static_cast<RoutingManager::Delegate &>(*this))
|
||||||
, m_trafficManager(bind(&Framework::GetMwmsByRect, this, _1, false /* rough */), kMaxTrafficCacheSizeBytes,
|
, m_trafficManager(m_featuresFetcher.GetDataSource(),
|
||||||
m_routingManager.RoutingSession())
|
[this]() -> storage::CountryInfoGetter const & { return GetCountryInfoGetter(); },
|
||||||
|
[this](string const & id) -> string { return m_storage.GetParentIdFor(id); },
|
||||||
|
bind(&Framework::GetMwmsByRect, this, _1, false /* rough */),
|
||||||
|
kMaxTrafficCacheSizeBytes, m_routingManager.RoutingSession())
|
||||||
, m_lastReportedCountry(kInvalidCountryId)
|
, m_lastReportedCountry(kInvalidCountryId)
|
||||||
, m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG)
|
, m_popularityLoader(m_featuresFetcher.GetDataSource(), POPULARITY_RANKS_FILE_TAG)
|
||||||
, m_descriptionsLoader(std::make_unique<descriptions::Loader>(m_featuresFetcher.GetDataSource()))
|
, m_descriptionsLoader(std::make_unique<descriptions::Loader>(m_featuresFetcher.GetDataSource()))
|
||||||
@@ -348,10 +363,10 @@ Framework::Framework(FrameworkParams const & params, bool loadMaps)
|
|||||||
editor.SetDelegate(make_unique<search::EditorDelegate>(m_featuresFetcher.GetDataSource()));
|
editor.SetDelegate(make_unique<search::EditorDelegate>(m_featuresFetcher.GetDataSource()));
|
||||||
editor.SetInvalidateFn([this]() { InvalidateRect(GetCurrentViewport()); });
|
editor.SetInvalidateFn([this]() { InvalidateRect(GetCurrentViewport()); });
|
||||||
|
|
||||||
/// @todo Uncomment when we will integrate a traffic provider.
|
if (params.m_trafficTestMode)
|
||||||
// m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
|
m_trafficManager.SetTestMode();
|
||||||
// m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
|
m_trafficManager.SetCurrentDataVersion(m_storage.GetCurrentDataVersion());
|
||||||
// m_trafficManager.SetEnabled(LoadTrafficEnabled());
|
m_trafficManager.SetSimplifiedColorScheme(LoadTrafficSimplifiedColors());
|
||||||
|
|
||||||
m_isolinesManager.SetEnabled(LoadIsolinesEnabled());
|
m_isolinesManager.SetEnabled(LoadIsolinesEnabled());
|
||||||
|
|
||||||
@@ -383,6 +398,19 @@ Framework::~Framework()
|
|||||||
m_featuresFetcher.SetOnMapDeregisteredCallback(nullptr);
|
m_featuresFetcher.SetOnMapDeregisteredCallback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Framework::InitializeTraffic()
|
||||||
|
{
|
||||||
|
m_trafficManager.SetEnabled(LoadTrafficEnabled());
|
||||||
|
if (!m_trafficManager.IsTestMode() && LoadTrafficHttpEnabled())
|
||||||
|
// TODO handle invalid URLs
|
||||||
|
traffxml::HttpTraffSource::Create(m_trafficManager, LoadTrafficHttpUrl());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MockTraffSource for debugging purposes.
|
||||||
|
*/
|
||||||
|
//traffxml::MockTraffSource::Create(m_trafficManager);
|
||||||
|
}
|
||||||
|
|
||||||
void Framework::ShowNode(storage::CountryId const & countryId)
|
void Framework::ShowNode(storage::CountryId const & countryId)
|
||||||
{
|
{
|
||||||
StopLocationFollow();
|
StopLocationFollow();
|
||||||
@@ -398,14 +426,14 @@ void Framework::OnCountryFileDownloaded(storage::CountryId const &, storage::Loc
|
|||||||
m2::RectD rect = mercator::Bounds::FullRect();
|
m2::RectD rect = mercator::Bounds::FullRect();
|
||||||
|
|
||||||
if (localFile && localFile->OnDisk(MapFileType::Map))
|
if (localFile && localFile->OnDisk(MapFileType::Map))
|
||||||
{
|
m_trafficManager.RunSynchronized([this, localFile, &rect](){
|
||||||
auto const res = RegisterMap(*localFile);
|
auto const res = RegisterMap(*localFile);
|
||||||
MwmSet::MwmId const & id = res.first;
|
MwmSet::MwmId const & id = res.first;
|
||||||
if (id.IsAlive())
|
if (id.IsAlive())
|
||||||
rect = id.GetInfo()->m_bordersRect;
|
rect = id.GetInfo()->m_bordersRect;
|
||||||
}
|
m_trafficManager.Invalidate(id);
|
||||||
|
});
|
||||||
|
|
||||||
m_trafficManager.Invalidate();
|
|
||||||
m_transitManager.Invalidate();
|
m_transitManager.Invalidate();
|
||||||
m_isolinesManager.Invalidate();
|
m_isolinesManager.Invalidate();
|
||||||
|
|
||||||
@@ -487,6 +515,8 @@ void Framework::LoadMapsSync()
|
|||||||
LOG(LDEBUG, ("Editor initialized"));
|
LOG(LDEBUG, ("Editor initialized"));
|
||||||
|
|
||||||
GetStorage().RestoreDownloadQueue();
|
GetStorage().RestoreDownloadQueue();
|
||||||
|
|
||||||
|
InitializeTraffic();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small copy-paste with LoadMapsSync, but I don't have a better solution.
|
// Small copy-paste with LoadMapsSync, but I don't have a better solution.
|
||||||
@@ -509,6 +539,8 @@ void Framework::LoadMapsAsync(std::function<void()> && callback)
|
|||||||
|
|
||||||
GetStorage().RestoreDownloadQueue();
|
GetStorage().RestoreDownloadQueue();
|
||||||
|
|
||||||
|
InitializeTraffic();
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}).detach();
|
}).detach();
|
||||||
@@ -2475,6 +2507,42 @@ void Framework::SaveTrafficEnabled(bool trafficEnabled)
|
|||||||
settings::Set(kTrafficEnabledKey, trafficEnabled);
|
settings::Set(kTrafficEnabledKey, trafficEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Framework::SetTrafficHttpEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
m_trafficManager.SetHttpTraffSource(enabled, LoadTrafficHttpUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Framework::LoadTrafficHttpEnabled()
|
||||||
|
{
|
||||||
|
bool enabled;
|
||||||
|
if (!settings::Get(kTrafficHttpEnabledKey, enabled))
|
||||||
|
enabled = false;
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framework::SaveTrafficHttpEnabled(bool trafficHttpEnabled)
|
||||||
|
{
|
||||||
|
settings::Set(kTrafficHttpEnabledKey, trafficHttpEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framework::SetTrafficHttpUrl(std::string url)
|
||||||
|
{
|
||||||
|
m_trafficManager.SetHttpTraffSource(LoadTrafficHttpEnabled(), url);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Framework::LoadTrafficHttpUrl()
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
if (!settings::Get(kTrafficHttpUrlKey, url))
|
||||||
|
url = TRAFFIC_HTTP_URL_DEFAULT;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framework::SaveTrafficHttpUrl(std::string trafficHttpUrl)
|
||||||
|
{
|
||||||
|
settings::Set(kTrafficHttpUrlKey, trafficHttpUrl);
|
||||||
|
}
|
||||||
|
|
||||||
bool Framework::LoadTrafficSimplifiedColors()
|
bool Framework::LoadTrafficSimplifiedColors()
|
||||||
{
|
{
|
||||||
bool simplified;
|
bool simplified;
|
||||||
|
|||||||
@@ -103,11 +103,22 @@ class Loader;
|
|||||||
/// build version for screenshots.
|
/// build version for screenshots.
|
||||||
// #define FIXED_LOCATION
|
// #define FIXED_LOCATION
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialization parameters for the framework.
|
||||||
|
*
|
||||||
|
* `FrameworkParams` is intended for parameters which are hardcoded rather than read from a
|
||||||
|
* configuration. It allows test cases to run on a tailored configuration.
|
||||||
|
*/
|
||||||
struct FrameworkParams
|
struct FrameworkParams
|
||||||
{
|
{
|
||||||
bool m_enableDiffs = true;
|
bool m_enableDiffs = true;
|
||||||
size_t m_numSearchAPIThreads = 1;
|
size_t m_numSearchAPIThreads = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the traffic manager should start in test mode.
|
||||||
|
*/
|
||||||
|
bool m_trafficTestMode = false;
|
||||||
|
|
||||||
FrameworkParams() = default;
|
FrameworkParams() = default;
|
||||||
FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {}
|
FrameworkParams(bool enableDiffs) : m_enableDiffs(enableDiffs) {}
|
||||||
};
|
};
|
||||||
@@ -233,7 +244,34 @@ public:
|
|||||||
/// \note It works for group and leaf node.
|
/// \note It works for group and leaf node.
|
||||||
bool HasUnsavedEdits(storage::CountryId const & countryId);
|
bool HasUnsavedEdits(storage::CountryId const & countryId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads maps synchronously.
|
||||||
|
*
|
||||||
|
* Maps are loaded on the calling thread.
|
||||||
|
*
|
||||||
|
* This function also performs certain initialization operations which depend on map data being
|
||||||
|
* available, such as search, traffic and the download queue.
|
||||||
|
*
|
||||||
|
* @note This function is not suitable for use on platforms which enforce restrictions on
|
||||||
|
* time-consuming or potentially blocking operations on the UI thread (as Android does). On such
|
||||||
|
* platforms, `LoadMapsAsync()` should be used instead.
|
||||||
|
*/
|
||||||
void LoadMapsSync();
|
void LoadMapsSync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads maps asynchronously.
|
||||||
|
*
|
||||||
|
* Maps are loaded on a new thread. Some operations are executed as part of a separate task which
|
||||||
|
* is posted to the GUI thread.
|
||||||
|
*
|
||||||
|
* This function also performs certain initialization operations which depend on map data being
|
||||||
|
* available, such as search, traffic and the download queue.
|
||||||
|
*
|
||||||
|
* After finishing initialization, the caller-supplied callback function is called. This function
|
||||||
|
* also runs on the GUI thread and should therefore not perform any time-consuming operations.
|
||||||
|
*
|
||||||
|
* @param callback A callback function to run at the end of initialization.
|
||||||
|
*/
|
||||||
void LoadMapsAsync(std::function<void()> && callback);
|
void LoadMapsAsync(std::function<void()> && callback);
|
||||||
|
|
||||||
/// Registers all local map files in internal indexes.
|
/// Registers all local map files in internal indexes.
|
||||||
@@ -384,6 +422,16 @@ private:
|
|||||||
private:
|
private:
|
||||||
std::vector<m2::TriangleD> GetSelectedFeatureTriangles() const;
|
std::vector<m2::TriangleD> GetSelectedFeatureTriangles() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the traffic manager.
|
||||||
|
*
|
||||||
|
* This enables the traffic manager if defined in settings. If the traffic manager is not in test
|
||||||
|
* mode, all cunfigured sources are also added here.
|
||||||
|
*
|
||||||
|
* Maps must be loaded prior to calling this method.
|
||||||
|
*/
|
||||||
|
void InitializeTraffic();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @name GPS location updates routine.
|
/// @name GPS location updates routine.
|
||||||
void OnLocationError(location::TLocationError error);
|
void OnLocationError(location::TLocationError error);
|
||||||
@@ -489,11 +537,18 @@ private:
|
|||||||
std::unique_ptr<descriptions::Loader> m_descriptionsLoader;
|
std::unique_ptr<descriptions::Loader> m_descriptionsLoader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Moves viewport to the search result and taps on it.
|
/**
|
||||||
|
* @brief Moves viewport to the search result and taps on it.
|
||||||
|
* @param res
|
||||||
|
* @param animation
|
||||||
|
*/
|
||||||
void SelectSearchResult(search::Result const & res, bool animation);
|
void SelectSearchResult(search::Result const & res, bool animation);
|
||||||
|
|
||||||
// Cancels all searches, stops location follow and then selects
|
/**
|
||||||
// search result.
|
* @brief Cancels all searches, stops location follow and then selects search result.
|
||||||
|
* @param res
|
||||||
|
* @param animation
|
||||||
|
*/
|
||||||
void ShowSearchResult(search::Result const & res, bool animation = true);
|
void ShowSearchResult(search::Result const & res, bool animation = true);
|
||||||
|
|
||||||
void UpdateViewport(search::Results const & results);
|
void UpdateViewport(search::Results const & results);
|
||||||
@@ -721,6 +776,14 @@ public:
|
|||||||
bool LoadTrafficEnabled();
|
bool LoadTrafficEnabled();
|
||||||
void SaveTrafficEnabled(bool trafficEnabled);
|
void SaveTrafficEnabled(bool trafficEnabled);
|
||||||
|
|
||||||
|
void SetTrafficHttpEnabled(bool enabled);
|
||||||
|
bool LoadTrafficHttpEnabled();
|
||||||
|
void SaveTrafficHttpEnabled(bool trafficHttpEnabled);
|
||||||
|
|
||||||
|
void SetTrafficHttpUrl(std::string url);
|
||||||
|
std::string LoadTrafficHttpUrl();
|
||||||
|
void SaveTrafficHttpUrl(std::string trafficHttpUrl);
|
||||||
|
|
||||||
bool LoadTrafficSimplifiedColors();
|
bool LoadTrafficSimplifiedColors();
|
||||||
void SaveTrafficSimplifiedColors(bool simplified);
|
void SaveTrafficSimplifiedColors(bool simplified);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,15 @@
|
|||||||
|
|
||||||
#include "indexer/mwm_set.hpp"
|
#include "indexer/mwm_set.hpp"
|
||||||
|
|
||||||
|
#include "routing/routing_session.hpp"
|
||||||
|
|
||||||
|
#include "storage/country_info_getter.hpp"
|
||||||
|
|
||||||
|
#include "traffxml/traff_decoder.hpp"
|
||||||
|
#include "traffxml/traff_model.hpp"
|
||||||
|
#include "traffxml/traff_source.hpp"
|
||||||
|
#include "traffxml/traff_storage.hpp"
|
||||||
|
|
||||||
#include "geometry/point2d.hpp"
|
#include "geometry/point2d.hpp"
|
||||||
#include "geometry/polyline2d.hpp"
|
#include "geometry/polyline2d.hpp"
|
||||||
#include "geometry/screenbase.hpp"
|
#include "geometry/screenbase.hpp"
|
||||||
@@ -28,21 +37,68 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class TrafficManager final
|
class TrafficManager final : public traffxml::TraffSourceManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using CountryInfoGetterFn = std::function<storage::CountryInfoGetter const &()>;
|
||||||
|
using CountryParentNameGetterFn = std::function<std::string(std::string const &)>;
|
||||||
|
using TrafficUpdateCallbackFn = std::function<void(bool)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Global state of traffic information.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO clean out obsolete states.
|
||||||
|
* Only `Disabled` and `Enabled` are currently used, but some might be reactivated in the future
|
||||||
|
* and platforms (android/iphone) still evaluate all states.
|
||||||
|
* `ExpiredData` is definitely obsolete, as traffic data is no longer dependent on a particular
|
||||||
|
* map version, but still evaluated by android/iphone code.
|
||||||
|
*/
|
||||||
enum class TrafficState
|
enum class TrafficState
|
||||||
{
|
{
|
||||||
|
/** Traffic is disabled, no traffic data will be retrieved or considered for routing. */
|
||||||
Disabled,
|
Disabled,
|
||||||
|
/** Traffic is enabled and working normally (the first request may not have been scheduled yet). */
|
||||||
Enabled,
|
Enabled,
|
||||||
|
/** At least one request is currently pending. */
|
||||||
WaitingData,
|
WaitingData,
|
||||||
|
/** At least one MWM has stale traffic data. */
|
||||||
Outdated,
|
Outdated,
|
||||||
|
/** Traffic data for at least one MWM was invalid or not found on the server. */
|
||||||
NoData,
|
NoData,
|
||||||
|
/** At least one request failed or timed out. */
|
||||||
NetworkError,
|
NetworkError,
|
||||||
|
/** Traffic data could not be retrieved because the map data is outdated. */
|
||||||
ExpiredData,
|
ExpiredData,
|
||||||
|
/** Traffic data could not be retrieved because the app version is outdated. */
|
||||||
ExpiredApp
|
ExpiredApp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The mode for the traffic manager.
|
||||||
|
*
|
||||||
|
* Future versions may introduce further test modes. Therefore, always use `TrafficManager::IsTestMode()`
|
||||||
|
* to verify if the traffic manager is running in test mode.
|
||||||
|
*/
|
||||||
|
enum class Mode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Traffic manager mode for normal operation.
|
||||||
|
*
|
||||||
|
* This is the default mode unless something else is explicitly set.
|
||||||
|
*/
|
||||||
|
Normal,
|
||||||
|
/**
|
||||||
|
* Test mode.
|
||||||
|
*
|
||||||
|
* This mode will prevent the traffic manager from automatically subscribing to sources and
|
||||||
|
* polling them. It will still receive and process push feeds.
|
||||||
|
*
|
||||||
|
* Future versions may introduce further behavior changes, and/or introduce more test modes.
|
||||||
|
*/
|
||||||
|
Test
|
||||||
|
};
|
||||||
|
|
||||||
struct MyPosition
|
struct MyPosition
|
||||||
{
|
{
|
||||||
m2::PointD m_position = m2::PointD(0.0, 0.0);
|
m2::PointD m_position = m2::PointD(0.0, 0.0);
|
||||||
@@ -55,25 +111,122 @@ public:
|
|||||||
using TrafficStateChangedFn = std::function<void(TrafficState)>;
|
using TrafficStateChangedFn = std::function<void(TrafficState)>;
|
||||||
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
|
using GetMwmsByRectFn = std::function<std::vector<MwmSet::MwmId>(m2::RectD const &)>;
|
||||||
|
|
||||||
TrafficManager(GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
|
TrafficManager(DataSource & dataSource,
|
||||||
traffic::TrafficObserver & observer);
|
CountryInfoGetterFn countryInfoGetter,
|
||||||
|
CountryParentNameGetterFn const & countryParentNameGetter,
|
||||||
|
GetMwmsByRectFn const & getMwmsByRectFn, size_t maxCacheSizeBytes,
|
||||||
|
routing::RoutingSession & routingSession);
|
||||||
~TrafficManager();
|
~TrafficManager();
|
||||||
|
|
||||||
void Teardown();
|
void Teardown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a copy of the cache of all currently active TraFF messages.
|
||||||
|
*
|
||||||
|
* For testing purposes.
|
||||||
|
*
|
||||||
|
* Keys are message IDs, values are messages.
|
||||||
|
*
|
||||||
|
* This method is safe to call from any thread.
|
||||||
|
*/
|
||||||
|
std::map<std::string, traffxml::TraffMessage> GetMessageCache();
|
||||||
|
|
||||||
TrafficState GetState() const;
|
TrafficState GetState() const;
|
||||||
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
|
void SetStateListener(TrafficStateChangedFn const & onStateChangedFn);
|
||||||
|
|
||||||
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
void SetDrapeEngine(ref_ptr<df::DrapeEngine> engine);
|
||||||
|
/**
|
||||||
|
* @brief Sets the version of the MWM used locally.
|
||||||
|
*/
|
||||||
void SetCurrentDataVersion(int64_t dataVersion);
|
void SetCurrentDataVersion(int64_t dataVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enables or disables the traffic manager.
|
||||||
|
*
|
||||||
|
* This sets the internal state and notifies the drape engine.
|
||||||
|
*
|
||||||
|
* Upon creation, the traffic manager is disabled. MWMs must be loaded before first enabling the
|
||||||
|
* traffic manager.
|
||||||
|
*
|
||||||
|
* While disabled, the traffic manager will not update its subscription area (upon being enabled
|
||||||
|
* again, it will do so if necessary). It will not poll any sources or process any messages. Feeds
|
||||||
|
* added via `ReceiveFeed()` will be added to the queue but will not be processed until the
|
||||||
|
* traffic manager is re-enabled.
|
||||||
|
*
|
||||||
|
* Calling this function with `enabled` identical to the current state is a no-op.
|
||||||
|
*
|
||||||
|
* @todo Currently, all MWMs must be loaded before calling `SetEnabled()`, as MWMs loaded after
|
||||||
|
* that will not get picked up. We need to extend `TrafficManager` to react to MWMs being added
|
||||||
|
* (and removed) – note that this affects the `DataSource`, not the set of active MWMs.
|
||||||
|
* See `Framework::OnMapDeregistered()` implementation for the opposite case (MWM deregistered).
|
||||||
|
*
|
||||||
|
* @param enabled True to enable, false to disable
|
||||||
|
*/
|
||||||
void SetEnabled(bool enabled);
|
void SetEnabled(bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the traffic manager is enabled.
|
||||||
|
*
|
||||||
|
* @return True if enabled, false if not
|
||||||
|
*/
|
||||||
bool IsEnabled() const;
|
bool IsEnabled() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the enabled state and URL for the `HttpTraffSource`.
|
||||||
|
*
|
||||||
|
* If the traffic manager is in test mode, this function is a no-op.
|
||||||
|
*
|
||||||
|
* Otherwise this function is expected to be called only if the enabled state and/or URL have
|
||||||
|
* actually changed. Setting both to the current state will remove the current source and create
|
||||||
|
* a new one with identical settings.
|
||||||
|
*
|
||||||
|
* This function currently assumes that there is never more than one `HttpTraffSource` configured
|
||||||
|
* at the same time.
|
||||||
|
*
|
||||||
|
* @param enabled Whether the HTTP TraFF source is enabled.
|
||||||
|
* @param url The URL for the TraFF API.
|
||||||
|
*/
|
||||||
|
void SetHttpTraffSource(bool enabled, std::string url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes all `TraffSource` instances which satisfy a predicate.
|
||||||
|
*
|
||||||
|
* This method iterates over all currently configured `TraffSource` instances and calls the
|
||||||
|
* caller-suppplied predicate function `pred` on each of them. If `pred` returns true, the source
|
||||||
|
* is removed, else it is kept.
|
||||||
|
*
|
||||||
|
* @todo For now, `pred` deliberately takes a non-const argument so we can do cleanup inside
|
||||||
|
* `pred`. If we manage to move any such cleanup into the destructor of the `TraffSource` subclass
|
||||||
|
* and get rid of any `Close()` methods in subclasses (which is preferable for other reasons as
|
||||||
|
* well), the argument can be made const.
|
||||||
|
*
|
||||||
|
* @param pred The predicate function, see description.
|
||||||
|
*/
|
||||||
|
void RemoveTraffSourceIf(const std::function<bool(traffxml::TraffSource*)>& pred);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts the traffic manager.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void Start();
|
||||||
|
|
||||||
void UpdateViewport(ScreenBase const & screen);
|
void UpdateViewport(ScreenBase const & screen);
|
||||||
void UpdateMyPosition(MyPosition const & myPosition);
|
void UpdateMyPosition(MyPosition const & myPosition);
|
||||||
|
|
||||||
void Invalidate();
|
/**
|
||||||
|
* @brief Invalidates traffic information for the specified MWM.
|
||||||
|
*
|
||||||
|
* Invalidation of traffic data is always per MWM and affects locations which refer to any version
|
||||||
|
* of this MWM, or whose enclosing rectangle overlaps with that of the MWM. The decoded segments
|
||||||
|
* for these locations are discarded and decoded again, ensuring they are based on the new MWM.
|
||||||
|
* The TraFF messages themselves remain unchanged.
|
||||||
|
*
|
||||||
|
* This method must either be called from a lambda function passed to `RunSynchronized()`,
|
||||||
|
* or the caller must explicitly lock the private `m_mutex` prior to calling this method.
|
||||||
|
*
|
||||||
|
* @param mwmId The newly addded MWM.
|
||||||
|
*/
|
||||||
|
void Invalidate(MwmSet::MwmId const & mwmId);
|
||||||
|
|
||||||
void OnDestroySurface();
|
void OnDestroySurface();
|
||||||
void OnRecoverSurface();
|
void OnRecoverSurface();
|
||||||
@@ -85,57 +238,295 @@ public:
|
|||||||
void SetSimplifiedColorScheme(bool simplified);
|
void SetSimplifiedColorScheme(bool simplified);
|
||||||
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; }
|
bool HasSimplifiedColorScheme() const { return m_hasSimplifiedColorScheme; }
|
||||||
|
|
||||||
private:
|
/**
|
||||||
struct CacheEntry
|
* @brief Whether the traffic manager is operating in test mode.
|
||||||
|
*/
|
||||||
|
bool IsTestMode() { return m_mode != Mode::Normal; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches the traffic manager into test mode.
|
||||||
|
*
|
||||||
|
* The mode can only be set before the traffic manager is first enabled. After that, this method
|
||||||
|
* will log a warning but otherwise do nothing.
|
||||||
|
*
|
||||||
|
* In test mode, the traffic manager will not subscribe to sources or poll them automatically.
|
||||||
|
* Expired messages will not get purged automatically, but `PurgeExpiredMessages()` can be called
|
||||||
|
* to purge expired messages once. The traffic manager will still receive and process push feeds.
|
||||||
|
*
|
||||||
|
* Future versions may introduce further behavior changes.
|
||||||
|
*/
|
||||||
|
void SetTestMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Processes a traffic feed.
|
||||||
|
*
|
||||||
|
* The feed may be a result of a pull operation, or received through a push operation.
|
||||||
|
* (Push operations are not supported by all sources.)
|
||||||
|
*
|
||||||
|
* This method is safe to call from any thread.
|
||||||
|
*
|
||||||
|
* @param feed The traffic feed.
|
||||||
|
*/
|
||||||
|
virtual void ReceiveFeed(traffxml::TraffFeed feed) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers a `TraffSource`.
|
||||||
|
* @param source The source.
|
||||||
|
*/
|
||||||
|
virtual void RegisterSource(std::unique_ptr<traffxml::TraffSource> source) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves all currently active MWMs.
|
||||||
|
*
|
||||||
|
* This method retrieves all MWMs in the viewport, within a certain distance of the current
|
||||||
|
* position (if there is a valid position) or part of the route (if any), and stores them in
|
||||||
|
* `activeMwms`.
|
||||||
|
*
|
||||||
|
* This method locks `m_mutex` and is therefore safe to call from any thread. Callers which
|
||||||
|
* already hold `m_mutex` can use the private `UniteActiveMwms()` method instead.
|
||||||
|
*
|
||||||
|
* @param activeMwms Retrieves the list of active MWMs.
|
||||||
|
*/
|
||||||
|
virtual void GetActiveMwms(std::set<MwmSet::MwmId> & activeMwms) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Purges expired messages from the cache.
|
||||||
|
*
|
||||||
|
* This method is safe to call from any thread, except for the traffic worker thread.
|
||||||
|
*/
|
||||||
|
void PurgeExpiredMessages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears the traffic message cache and feed queue.
|
||||||
|
*
|
||||||
|
* This is intended for testing purposes and clears the message cache, as well as the feed queue.
|
||||||
|
* Subscriptions are not changed.
|
||||||
|
*/
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers a callback function which gets called on traffic updates.
|
||||||
|
*
|
||||||
|
* Intended for testing.
|
||||||
|
*
|
||||||
|
* @param fn The callback function.
|
||||||
|
*/
|
||||||
|
void SetTrafficUpdateCallbackFn(TrafficUpdateCallbackFn && fn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Runs a function guarded by the traffic manager mutex.
|
||||||
|
*
|
||||||
|
* This locks `m_mutex`, then runs `f` and releases the mutex.
|
||||||
|
*
|
||||||
|
* @param f
|
||||||
|
*/
|
||||||
|
void RunSynchronized(std::function<void()> f)
|
||||||
{
|
{
|
||||||
CacheEntry();
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
explicit CacheEntry(std::chrono::time_point<std::chrono::steady_clock> const & requestTime);
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
bool m_isLoaded;
|
private:
|
||||||
size_t m_dataSize;
|
|
||||||
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_lastActiveTime;
|
/**
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_lastRequestTime;
|
* @brief Recalculates the TraFF subscription area.
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
|
*
|
||||||
|
* The subscription area needs to be recalculated when the traffic manager goes from disabled to
|
||||||
|
* enabled, or when it is resumed after being paused, as the subscription area is not updated
|
||||||
|
* while the traffic manager is disabled or paused.
|
||||||
|
*
|
||||||
|
* If the subscription area has changed, or if `forceRenewal` is true, TraFF subscriptions are
|
||||||
|
* renewed by calling `SubscribeOrChangeSubscription()`.
|
||||||
|
*
|
||||||
|
* No traffic data is discarded, but sources will be polled for an update, which may turn out
|
||||||
|
* larger than usual if the traffic manager was in disabled/paused state for an extended period of
|
||||||
|
* time or the subscription area has changed.
|
||||||
|
*
|
||||||
|
* @param forceRenewal If true, renew subscriptions even if the subscription area has not changed.
|
||||||
|
*/
|
||||||
|
void RecalculateSubscription(bool forceRenewal);
|
||||||
|
|
||||||
int m_retriesCount;
|
/**
|
||||||
bool m_isWaitingForResponse;
|
* @brief Ensures every TraFF source has a subscription covering all currently active MWMs.
|
||||||
|
*
|
||||||
|
* This method cycles through all TraFF sources in `m_trafficSources` and calls
|
||||||
|
* `SubscribeOrChangeSubscription()` on each of them.
|
||||||
|
*/
|
||||||
|
void SubscribeOrChangeSubscription();
|
||||||
|
|
||||||
traffic::TrafficInfo::Availability m_lastAvailability;
|
/**
|
||||||
};
|
* @brief Unsubscribes from all traffic services we are subscribed to.
|
||||||
|
*
|
||||||
|
* This method cycles through all TraFF sources in `m_trafficSources` and calls `Unsubscribe()`
|
||||||
|
* on each of them.
|
||||||
|
*/
|
||||||
|
void Unsubscribe();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores the message cache from file storage.
|
||||||
|
*
|
||||||
|
* @note The caller must lock `m_mutex` prior to calling this function, as it makes unprotected
|
||||||
|
* changes to shared data structures.
|
||||||
|
*
|
||||||
|
* @note The return value indicates whether actions related to a traffic update should be taken,
|
||||||
|
* such as notifying the routing and drape engine. It is true if at least one message with a
|
||||||
|
* decoded location was read, and no messages without decoded locations. If messages without a
|
||||||
|
* decoded location were read, the return value is false, as the location decoding will trigger
|
||||||
|
* updates by itself. If errors occurred and no messages are read, the return value is also false.
|
||||||
|
*
|
||||||
|
* @return True if a traffic update needs to be sent, false if not
|
||||||
|
*/
|
||||||
|
bool RestoreCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Polls all traffic services for updates.
|
||||||
|
*
|
||||||
|
* This method cycles through all TraFF sources in `m_trafficSources` and calls `IsPollNeeded()`
|
||||||
|
* on each of them. If this method returns true, it then calls `Poll()` on the source.
|
||||||
|
*/
|
||||||
|
void Poll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Purges expired messages from the cache.
|
||||||
|
*
|
||||||
|
* This is the internal conterpart of `PurgeExpiredMessages()`. It is safe to call from any
|
||||||
|
* thread. Unlike `PurgeExpiredMessages()`, it does not wake the worker thread, making it suitable
|
||||||
|
* for use on the worker thread.
|
||||||
|
*
|
||||||
|
* @return true if messages were purged, false if not
|
||||||
|
*/
|
||||||
|
bool PurgeExpiredMessagesImpl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Consolidates the feed queue.
|
||||||
|
*
|
||||||
|
* If multiple feeds in the queue have the same message ID, only the message with the newest
|
||||||
|
* update time is kept (if two messages have the same ID and update time, the one in the feed
|
||||||
|
* with the higher index is kept); other messages with the same ID are discarded. Empty feeds
|
||||||
|
* are discarded.
|
||||||
|
*/
|
||||||
|
void ConsolidateFeedQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes the first message from the first feed and decodes it.
|
||||||
|
*/
|
||||||
|
void DecodeFirstMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event loop for the traffic worker thread.
|
||||||
|
*
|
||||||
|
* This method runs an event loop, which blocks until woken up or a timeout equivalent to the
|
||||||
|
* update interval elapses. It cycles through the list of MWMs for which updates have been
|
||||||
|
* scheduled, triggering a network request for each and processing the result.
|
||||||
|
*/
|
||||||
void ThreadRoutine();
|
void ThreadRoutine();
|
||||||
bool WaitForRequest(std::vector<MwmSet::MwmId> & mwms);
|
|
||||||
|
|
||||||
void OnTrafficDataResponse(traffic::TrafficInfo && info);
|
/**
|
||||||
void OnTrafficRequestFailed(traffic::TrafficInfo && info);
|
* @brief Blocks until a request for traffic data is received or a timeout expires.
|
||||||
|
*
|
||||||
|
* This method acts as the loop condition for `ThreadRoutine()`. It blocks until woken up or the
|
||||||
|
* update interval expires. In the latter case, it calls `RequestTrafficData()` to insert all
|
||||||
|
* currently active MWMs into the list of MWMs to update; otherwise, it leaves the list as it is.
|
||||||
|
* In either case, it populates `mwms` with the list and returns.
|
||||||
|
*
|
||||||
|
* @return `true` during normal operation, `false` during teardown (signaling the event loop to exit).
|
||||||
|
*/
|
||||||
|
bool WaitForRequest();
|
||||||
|
|
||||||
/// \brief Updates |activeMwms| and request traffic data.
|
/**
|
||||||
/// \param rect is a rectangle covering a new active mwm set.
|
* @brief Processes new traffic data.
|
||||||
/// \note |lastMwmsByRect|/|activeMwms| may be either |m_lastDrapeMwmsByRect/|m_activeDrapeMwms|
|
*
|
||||||
/// or |m_lastRoutingMwmsByRect|/|m_activeRoutingMwms|.
|
* The new per-MWM colorings (preprocessed traffic information) are taken from `m_allMmColoring`.
|
||||||
/// \note |m_mutex| is locked inside the method. So the method should be called without |m_mutex|.
|
* `m_allMwmColoring` is rebuilt from per-message colorings in `m_messageCache` as needed.
|
||||||
|
*
|
||||||
|
* This method is normally called from the traffic worker thread. Test tools may also call it from
|
||||||
|
* other threads.
|
||||||
|
*/
|
||||||
|
void OnTrafficDataUpdate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates `activeMwms` and requests traffic data.
|
||||||
|
*
|
||||||
|
* The old and new list of active MWMs may refer either to those used by the rendering engine
|
||||||
|
* (`m_lastDrapeMwmsByRect`/`m_activeDrapeMwms`) or to those around the current position.
|
||||||
|
* (`m_lastPositionMwmsByRect`/`m_activePositionMwms`).
|
||||||
|
*
|
||||||
|
* The method first determines the list of MWMs overlapping with `rect`. If it is identical to
|
||||||
|
* `lastMwmsByRect`, the method returns immediately. Otherwise, it stores the new set in
|
||||||
|
* `lastMwmsByRect` and populates `activeMwms` with the elements.
|
||||||
|
*
|
||||||
|
* This method locks `m_mutex` while populating `activeMwms`. There is no need for the caller to
|
||||||
|
* do that.
|
||||||
|
*
|
||||||
|
* @param rect Rectangle covering the new active MWM set.
|
||||||
|
* @param lastMwmsByRect Set of active MWMs, see description.
|
||||||
|
* @param activeMwms Vector of active MWMs, see description.
|
||||||
|
*/
|
||||||
void UpdateActiveMwms(m2::RectD const & rect, std::vector<MwmSet::MwmId> & lastMwmsByRect,
|
void UpdateActiveMwms(m2::RectD const & rect, std::vector<MwmSet::MwmId> & lastMwmsByRect,
|
||||||
std::set<MwmSet::MwmId> & activeMwms);
|
std::set<MwmSet::MwmId> & activeMwms);
|
||||||
|
|
||||||
// This is a group of methods that haven't their own synchronization inside.
|
// This is a group of methods that haven't their own synchronization inside.
|
||||||
void RequestTrafficData();
|
|
||||||
void RequestTrafficData(MwmSet::MwmId const & mwmId, bool force);
|
|
||||||
|
|
||||||
void Clear();
|
|
||||||
void ClearCache(MwmSet::MwmId const & mwmId);
|
|
||||||
void ShrinkCacheToAllowableSize();
|
|
||||||
|
|
||||||
void UpdateState();
|
|
||||||
void ChangeState(TrafficState newState);
|
void ChangeState(TrafficState newState);
|
||||||
|
|
||||||
bool IsInvalidState() const;
|
bool IsInvalidState() const;
|
||||||
|
|
||||||
|
void OnChangeRoutingSessionState(routing::SessionState previous, routing::SessionState current);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves all currently active MWMs.
|
||||||
|
*
|
||||||
|
* This method retrieves all MWMs in the viewport, within a certain distance of the current
|
||||||
|
* position (if there is a valid position) or part of the route (if any), and stores them in
|
||||||
|
* `activeMwms`.
|
||||||
|
*
|
||||||
|
* The caller must hold `m_mutex` prior to calling this method. `GetActiveMwms()` is available
|
||||||
|
* as a convenience wrapper which locks `m_mutex`, calls this method and releases it.
|
||||||
|
*
|
||||||
|
* @param activeMwms Retrieves the list of active MWMs.
|
||||||
|
*/
|
||||||
void UniteActiveMwms(std::set<MwmSet::MwmId> & activeMwms) const;
|
void UniteActiveMwms(std::set<MwmSet::MwmId> & activeMwms) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pauses the traffic manager.
|
||||||
|
*
|
||||||
|
* Upon creation, the traffic manager is not paused.
|
||||||
|
*
|
||||||
|
* While the traffic manager is paused and no route is active, the traffic manager will not update
|
||||||
|
* its subscription area (upon resuming, it will do so if necessary). It will not poll any sources
|
||||||
|
* or process any messages. Feeds added via `ReceiveFeed()` will be added to the queue but will
|
||||||
|
* not be processed until the traffic manager is resumed.
|
||||||
|
*
|
||||||
|
* Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except
|
||||||
|
* it does not change the external state, and an active route effectively overrides the paused
|
||||||
|
* state. It is intended for internal use by the framework.
|
||||||
|
*/
|
||||||
void Pause();
|
void Pause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resumes the traffic manager.
|
||||||
|
*
|
||||||
|
* Upon creation, the traffic manager is not paused. Resuming a traffic manager that is not paused
|
||||||
|
* is a no-op.
|
||||||
|
*
|
||||||
|
* Upon resume, the traffic manager will recalculate its subscription area and change its
|
||||||
|
* subscription if necessary. It will continue processing feeds in the queue, including those
|
||||||
|
* received before or while the traffic manager was paused.
|
||||||
|
*
|
||||||
|
* Pausing and resuming is similar in effect to disabling and enabling the traffic manager, except
|
||||||
|
* it does not change the external state, and an active route effectively overrides the paused
|
||||||
|
* state. It is intended for internal use by the framework.
|
||||||
|
*/
|
||||||
void Resume();
|
void Resume();
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void ForEachMwm(F && f) const
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<MwmInfo>> allMwmInfo;
|
||||||
|
m_dataSource.GetMwmsInfo(allMwmInfo);
|
||||||
|
std::for_each(allMwmInfo.begin(), allMwmInfo.end(), std::forward<F>(f));
|
||||||
|
}
|
||||||
|
|
||||||
template <class F>
|
template <class F>
|
||||||
void ForEachActiveMwm(F && f) const
|
void ForEachActiveMwm(F && f) const
|
||||||
{
|
{
|
||||||
@@ -144,44 +535,233 @@ private:
|
|||||||
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(f));
|
std::for_each(activeMwms.begin(), activeMwms.end(), std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether updates to the observer are currently inhibited.
|
||||||
|
*
|
||||||
|
* Updates are inhibited while a route calculation is in progress. In this state, the observer
|
||||||
|
* receives traffic updates only if the queue has run empty, not if nore locations are waiting
|
||||||
|
* to be decoded.
|
||||||
|
*
|
||||||
|
* Inhibtiting the observer is necessary as traffic updates during route calculation will cause
|
||||||
|
* it to restart from scratch. Once the route has been calculated, updates will trigger a
|
||||||
|
* recalculation, which is much faster (seconds or less).
|
||||||
|
*/
|
||||||
|
bool IsObserverInhibited() const { return (m_routingSessionState == routing::SessionState::RouteBuilding)
|
||||||
|
|| (m_routingSessionState == routing::SessionState::RouteRebuilding); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether we are currently routing.
|
||||||
|
*/
|
||||||
|
bool IsRouting() const { return m_routingSessionState != routing::SessionState::NoValidRoute; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the traffic manager is paused and not routing.
|
||||||
|
*
|
||||||
|
* This is used to inhibit polling and message decoding.
|
||||||
|
*/
|
||||||
|
bool IsPausedAndNotRouting() const { return m_isPaused && !IsRouting(); }
|
||||||
|
|
||||||
|
DataSource & m_dataSource;
|
||||||
|
CountryInfoGetterFn m_countryInfoGetterFn;
|
||||||
|
CountryParentNameGetterFn m_countryParentNameGetterFn;
|
||||||
GetMwmsByRectFn m_getMwmsByRectFn;
|
GetMwmsByRectFn m_getMwmsByRectFn;
|
||||||
traffic::TrafficObserver & m_observer;
|
|
||||||
|
/*
|
||||||
|
* Originally this was m_observer, of type traffic::TrafficObserver. Since routing::RoutingSession
|
||||||
|
* inherits from that class, and an interface to the routing session is needed in order to
|
||||||
|
* determine the MWMs for which we need traffic information, the type was changed and the member
|
||||||
|
* renamed to reflect that.
|
||||||
|
*/
|
||||||
|
routing::RoutingSession & m_routingSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cached state of the routing session.
|
||||||
|
*
|
||||||
|
* `m_routingSession` methods which query the state may only be called from the GUI thread,
|
||||||
|
* therefore we are caching this value when we get notified of a change.
|
||||||
|
*/
|
||||||
|
routing::SessionState m_routingSessionState = routing::SessionState::NoValidRoute;
|
||||||
|
|
||||||
df::DrapeEngineSafePtr m_drapeEngine;
|
df::DrapeEngineSafePtr m_drapeEngine;
|
||||||
std::atomic<int64_t> m_currentDataVersion;
|
std::atomic<int64_t> m_currentDataVersion;
|
||||||
|
|
||||||
// These fields have a flag of their initialization.
|
// These fields have a flag of their initialization.
|
||||||
|
/*
|
||||||
|
* The lazy ones get updated only if they are not initialized, or if their new position is more
|
||||||
|
* than a certain distance from the previously stored one.
|
||||||
|
*/
|
||||||
std::pair<MyPosition, bool> m_currentPosition = {MyPosition(), false};
|
std::pair<MyPosition, bool> m_currentPosition = {MyPosition(), false};
|
||||||
|
std::pair<MyPosition, bool> m_currentPositionLazy = m_currentPosition;
|
||||||
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false};
|
std::pair<ScreenBase, bool> m_currentModelView = {ScreenBase(), false};
|
||||||
|
std::pair<ScreenBase, bool> m_currentModelViewLazy = m_currentModelView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode in which the traffic manager is running.
|
||||||
|
*/
|
||||||
|
Mode m_mode = Mode::Normal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the traffic manager accepts mode changes.
|
||||||
|
*
|
||||||
|
* Mode cannot be set after the traffic manager has been enabled for the first time.
|
||||||
|
*/
|
||||||
|
bool m_canSetMode = true;
|
||||||
|
|
||||||
std::atomic<TrafficState> m_state;
|
std::atomic<TrafficState> m_state;
|
||||||
TrafficStateChangedFn m_onStateChangedFn;
|
TrafficStateChangedFn m_onStateChangedFn;
|
||||||
|
|
||||||
bool m_hasSimplifiedColorScheme = true;
|
bool m_hasSimplifiedColorScheme = true;
|
||||||
|
|
||||||
size_t m_maxCacheSizeBytes;
|
/**
|
||||||
size_t m_currentCacheSizeBytes = 0;
|
* @brief The TraFF sources from which we get traffic information.
|
||||||
|
*
|
||||||
std::map<MwmSet::MwmId, CacheEntry> m_mwmCache;
|
* Threads must lock `m_trafficSourceMutex` prior to accessing this member.
|
||||||
|
*/
|
||||||
|
std::vector<std::unique_ptr<traffxml::TraffSource>> m_trafficSources;
|
||||||
|
|
||||||
bool m_isRunning;
|
bool m_isRunning;
|
||||||
std::condition_variable m_condition;
|
std::condition_variable m_condition;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To determine for which MWMs we need traffic data, we need to keep track of 3 groups of MWMs:
|
||||||
|
* those used by the renderer (i.e. in or just around the viewport), those within a certain area
|
||||||
|
* around the current position, and those used by the routing engine (only if currently routing).
|
||||||
|
*
|
||||||
|
* Routing MWMs are stored as a set.
|
||||||
|
*
|
||||||
|
* The other groups are stored twice: as a set and as a vector. The set always holds the MWMs which
|
||||||
|
* were last seen in use. Both get updated together when active MWMs are added or removed.
|
||||||
|
* However, the vector is used as a reference to detect changes. Clear() clears the vector but not
|
||||||
|
* the set, invalidating the set without destroying its contents.
|
||||||
|
*
|
||||||
|
* Methods which use only the set:
|
||||||
|
*
|
||||||
|
* * RequestTrafficSubscription(), exits if empty, otherwise cycles through the set.
|
||||||
|
* * UniteActiveMwms(), build the list of active MWMs (used by RequestTrafficSubscription()).
|
||||||
|
*
|
||||||
|
* Methods which use both, but in a different way:
|
||||||
|
*
|
||||||
|
* * UpdateActiveMwms(), uses the vector to detect changes (not for routing MWMs). If so, it
|
||||||
|
* updates both vector and set, but adds MWMs to the set only if they are alive.
|
||||||
|
*/
|
||||||
std::vector<MwmSet::MwmId> m_lastDrapeMwmsByRect;
|
std::vector<MwmSet::MwmId> m_lastDrapeMwmsByRect;
|
||||||
std::set<MwmSet::MwmId> m_activeDrapeMwms;
|
std::set<MwmSet::MwmId> m_activeDrapeMwms;
|
||||||
std::vector<MwmSet::MwmId> m_lastRoutingMwmsByRect;
|
std::vector<MwmSet::MwmId> m_lastPositionMwmsByRect;
|
||||||
|
std::set<MwmSet::MwmId> m_activePositionMwms;
|
||||||
std::set<MwmSet::MwmId> m_activeRoutingMwms;
|
std::set<MwmSet::MwmId> m_activeRoutingMwms;
|
||||||
|
|
||||||
// The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.
|
/**
|
||||||
// It is one of several mechanisms that HTTP provides for web cache validation,
|
* @brief Whether active MWMs have changed since the last request.
|
||||||
// which allows a client to make conditional requests.
|
*/
|
||||||
std::map<MwmSet::MwmId, std::string> m_trafficETags;
|
bool m_activeMwmsChanged = false;
|
||||||
|
|
||||||
std::atomic<bool> m_isPaused;
|
std::atomic<bool> m_isPaused;
|
||||||
|
|
||||||
std::vector<MwmSet::MwmId> m_requestedMwms;
|
/**
|
||||||
|
* @brief Mutex for access to shared members.
|
||||||
|
*
|
||||||
|
* Threads which access shared members (see documentation) must lock this mutex while doing so.
|
||||||
|
*
|
||||||
|
* @note To access `m_trafficSource`, lock `m_trafficSourceMutex`, not this mutex.
|
||||||
|
*/
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mutex for access to `m_trafficSources`.
|
||||||
|
*
|
||||||
|
* Threads which access `m_trafficSources` must lock this mutex while doing so.
|
||||||
|
*/
|
||||||
|
std::mutex m_trafficSourceMutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Worker thread which fetches traffic updates.
|
||||||
|
*/
|
||||||
threads::SimpleThread m_thread;
|
threads::SimpleThread m_thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When the last response was received.
|
||||||
|
*/
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_lastResponseTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When the last update notification to the Drape engine was posted.
|
||||||
|
*/
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_lastDrapeUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When the last update notification to the traffic observer was posted.
|
||||||
|
*/
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_lastObserverUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When the cache file was last updated.
|
||||||
|
*/
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_lastStorageUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether a poll operation is needed.
|
||||||
|
*
|
||||||
|
* Used in the worker thread to indicate we need to poll all sources. The poll operation may still
|
||||||
|
* be inhibited for individual sources.
|
||||||
|
*/
|
||||||
|
bool m_isPollNeeded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Queue of feeds waiting to be processed.
|
||||||
|
*
|
||||||
|
* Threads must lock `m_mutex` before accessing `m_feedQueue`, as some platforms may receive feeds
|
||||||
|
* on multiple threads.
|
||||||
|
*/
|
||||||
|
std::vector<traffxml::TraffFeed> m_feedQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the feed queue needs to be resorted.
|
||||||
|
*
|
||||||
|
* Resorting is needed when a new feed is added, or the current position or the viewport center
|
||||||
|
* has changed by more than a certain threshold.
|
||||||
|
*/
|
||||||
|
std::atomic<bool> m_isFeedQueueSortInvalid = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cache of all currently active TraFF messages.
|
||||||
|
*
|
||||||
|
* Keys are message IDs, values are messages.
|
||||||
|
*
|
||||||
|
* Threads must lock `m_mutex` before accessing `m_messageCache`, as access can happen from
|
||||||
|
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
|
||||||
|
* thread).
|
||||||
|
*/
|
||||||
|
std::map<std::string, traffxml::TraffMessage> m_messageCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The storage instance.
|
||||||
|
*
|
||||||
|
* Used to persist the TraFF message cache between sessions.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<traffxml::LocalStorage> m_storage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The TraFF decoder instance.
|
||||||
|
*
|
||||||
|
* Used to decode TraFF locations into road segments on the map.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<traffxml::DefaultTraffDecoder> m_traffDecoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map between MWM IDs and their colorings.
|
||||||
|
*
|
||||||
|
* Threads must lock `m_mutex` before accessing `m_allMwmColoring`, as access can happen from
|
||||||
|
* multiple threads (messages are added by the worker thread, `Clear()` can be called from the UI
|
||||||
|
* thread).
|
||||||
|
*/
|
||||||
|
std::map<MwmSet::MwmId, traffic::TrafficInfo::Coloring> m_allMwmColoring;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback function which gets called on traffic updates.
|
||||||
|
*
|
||||||
|
* Intended for testing.
|
||||||
|
*/
|
||||||
|
std::optional<TrafficUpdateCallbackFn> m_trafficUpdateCallbackFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::string DebugPrint(TrafficManager::TrafficState state);
|
extern std::string DebugPrint(TrafficManager::TrafficState state);
|
||||||
|
|||||||
@@ -36,20 +36,61 @@ public:
|
|||||||
RoadWarningFirstFerry,
|
RoadWarningFirstFerry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief User mark types.
|
||||||
|
*
|
||||||
|
* `UserMark` subclasses are assigned a value from this enum.
|
||||||
|
*/
|
||||||
enum Type : uint32_t
|
enum Type : uint32_t
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* `Bookmark`
|
||||||
|
*/
|
||||||
BOOKMARK, // Should always be the first one
|
BOOKMARK, // Should always be the first one
|
||||||
|
/**
|
||||||
|
* `ApiMarkPoint`
|
||||||
|
*/
|
||||||
API,
|
API,
|
||||||
|
/**
|
||||||
|
* `SearchMarkPoint`
|
||||||
|
*/
|
||||||
SEARCH,
|
SEARCH,
|
||||||
|
/**
|
||||||
|
* `StaticMarkPoint`
|
||||||
|
*/
|
||||||
STATIC,
|
STATIC,
|
||||||
|
/**
|
||||||
|
* `RouteMarkPoint`
|
||||||
|
*/
|
||||||
ROUTING,
|
ROUTING,
|
||||||
|
/**
|
||||||
|
* `SpeedCameraMark`
|
||||||
|
*/
|
||||||
SPEED_CAM,
|
SPEED_CAM,
|
||||||
|
/**
|
||||||
|
* `RoadWarningMark`
|
||||||
|
*/
|
||||||
ROAD_WARNING,
|
ROAD_WARNING,
|
||||||
|
/**
|
||||||
|
* `TransitMark`
|
||||||
|
*/
|
||||||
TRANSIT,
|
TRANSIT,
|
||||||
LOCAL_ADS,
|
LOCAL_ADS,
|
||||||
|
/**
|
||||||
|
* `TrackInfoMark`
|
||||||
|
*/
|
||||||
TRACK_INFO,
|
TRACK_INFO,
|
||||||
|
/**
|
||||||
|
* `TrackSelectionMark`
|
||||||
|
*/
|
||||||
TRACK_SELECTION,
|
TRACK_SELECTION,
|
||||||
|
/**
|
||||||
|
* `DebugMarkPoint`
|
||||||
|
*/
|
||||||
DEBUG_MARK, // Plain "DEBUG" results in a name collision.
|
DEBUG_MARK, // Plain "DEBUG" results in a name collision.
|
||||||
|
/**
|
||||||
|
* `ColoredMarkPoint`
|
||||||
|
*/
|
||||||
COLORED,
|
COLORED,
|
||||||
USER_MARK_TYPES_COUNT,
|
USER_MARK_TYPES_COUNT,
|
||||||
USER_MARK_TYPES_COUNT_MAX = 1000,
|
USER_MARK_TYPES_COUNT_MAX = 1000,
|
||||||
@@ -133,6 +174,9 @@ private:
|
|||||||
bool m_hasPosition = false;
|
bool m_hasPosition = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A mark in the shape of a dot.
|
||||||
|
*/
|
||||||
class DebugMarkPoint : public UserMark
|
class DebugMarkPoint : public UserMark
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -141,6 +185,9 @@ public:
|
|||||||
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
drape_ptr<SymbolNameZoomInfo> GetSymbolNames() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A mark in the shape of a dot, of caller-defined color and radius.
|
||||||
|
*/
|
||||||
class ColoredMarkPoint : public UserMark
|
class ColoredMarkPoint : public UserMark
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -13,53 +13,88 @@
|
|||||||
|
|
||||||
namespace platform
|
namespace platform
|
||||||
{
|
{
|
||||||
// This class represents a path to disk files corresponding to some
|
/**
|
||||||
// country region.
|
* @brief Represents a path to disk files corresponding to some country region.
|
||||||
//
|
*
|
||||||
// This class also wraps World.mwm and WorldCoasts.mwm
|
* This class also wraps World.mwm and WorldCoasts.mwm files from resource bundle, when they can't
|
||||||
// files from resource bundle, when they can't be found in a data
|
* be found in a data directory. In this exceptional case, directory will be empty and
|
||||||
// directory. In this exceptional case, directory will be empty and
|
* `SyncWithDisk()`/`DeleteFromDisk()`/`GetPath()`/`GetSize()` will return incorrect results.
|
||||||
// SyncWithDisk()/DeleteFromDisk()/GetPath()/GetSize() will return
|
*
|
||||||
// incorrect results.
|
* In any case, when you're going to read a file LocalCountryFile points to, use
|
||||||
//
|
* `platform::GetCountryReader()`.
|
||||||
// In any case, when you're going to read a file LocalCountryFile points to,
|
*/
|
||||||
// use platform::GetCountryReader().
|
|
||||||
class LocalCountryFile
|
class LocalCountryFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LocalCountryFile();
|
LocalCountryFile();
|
||||||
|
|
||||||
// Creates an instance holding a path to countryFile's in a
|
/**
|
||||||
// directory. Note that no disk operations are not performed until
|
* @brief Creates an instance holding a path to countryFile's in a directory.
|
||||||
// SyncWithDisk() is called.
|
*
|
||||||
// The directory must contain a full path to the country file.
|
* Note that no disk operations are performed until `SyncWithDisk()` is called.
|
||||||
|
*
|
||||||
|
* @param directory full path to the country file
|
||||||
|
* @param countryFile
|
||||||
|
* @param version
|
||||||
|
*/
|
||||||
LocalCountryFile(std::string directory, CountryFile countryFile, int64_t version);
|
LocalCountryFile(std::string directory, CountryFile countryFile, int64_t version);
|
||||||
|
|
||||||
// Syncs internal state like availability of files, their sizes etc. with disk.
|
/**
|
||||||
// Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in
|
* @brief Syncs internal state like availability of files, their sizes etc. with disk.
|
||||||
// this method but it's not implemented by performance reasons. This check is done on
|
*
|
||||||
// building routes stage.
|
* Generality speaking it's not always true. To know it for sure it's necessary to read a mwm in
|
||||||
|
* this method but it's not implemented by performance reasons. This check is done on
|
||||||
|
* building routes stage.
|
||||||
|
*/
|
||||||
void SyncWithDisk();
|
void SyncWithDisk();
|
||||||
|
|
||||||
// Removes specified file from disk if it is known for LocalCountryFile, i.e.
|
/**
|
||||||
// it was found by a previous SyncWithDisk() call.
|
* @brief Deletes a file from disk.
|
||||||
|
*
|
||||||
|
* Removes the specified file from disk for `LocalCountryFile`, if it is known, i.e. it was found
|
||||||
|
* by a previous SyncWithDisk() call.
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
void DeleteFromDisk(MapFileType type) const;
|
void DeleteFromDisk(MapFileType type) const;
|
||||||
|
|
||||||
// Returns path to a file.
|
/**
|
||||||
// Return value may be empty until SyncWithDisk() is called.
|
* @brief Returns the path to a file.
|
||||||
|
*
|
||||||
|
* Return value may be empty until SyncWithDisk() is called.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
std::string GetPath(MapFileType type) const;
|
std::string GetPath(MapFileType type) const;
|
||||||
std::string GetFileName(MapFileType type) const;
|
std::string GetFileName(MapFileType type) const;
|
||||||
|
|
||||||
// Returns size of a file.
|
/**
|
||||||
// Return value may be zero until SyncWithDisk() is called.
|
* @brief Returns the size of a file.
|
||||||
|
*
|
||||||
|
* Return value may be zero until SyncWithDisk() is called.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
uint64_t GetSize(MapFileType type) const;
|
uint64_t GetSize(MapFileType type) const;
|
||||||
|
|
||||||
// Returns true when some files are found during SyncWithDisk.
|
/**
|
||||||
// Return value is false until SyncWithDisk() is called.
|
* @brief Returns true when files are found during `SyncWithDisk()`.
|
||||||
|
*
|
||||||
|
* Return value is false until `SyncWithDisk()` is called.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
bool HasFiles() const;
|
bool HasFiles() const;
|
||||||
|
|
||||||
// Checks whether files specified in filesMask are on disk.
|
/**
|
||||||
// Return value will be false until SyncWithDisk() is called.
|
* @brief Checks whether files specified in filesMask are on disk.
|
||||||
|
*
|
||||||
|
* Return value will be false until SyncWithDisk() is called.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
bool OnDisk(MapFileType type) const;
|
bool OnDisk(MapFileType type) const;
|
||||||
|
|
||||||
bool IsInBundle() const { return m_directory.empty(); }
|
bool IsInBundle() const { return m_directory.empty(); }
|
||||||
@@ -74,8 +109,17 @@ public:
|
|||||||
|
|
||||||
bool ValidateIntegrity() const;
|
bool ValidateIntegrity() const;
|
||||||
|
|
||||||
// Creates LocalCountryFile for test purposes, for a country region
|
//
|
||||||
// with countryFileName (without any extensions). Automatically performs sync with disk.
|
/**
|
||||||
|
* @brief Creates a `LocalCountryFile` for test purposes.
|
||||||
|
*
|
||||||
|
* Creates a `LocalCountryFile` for test purposes, for a country region with `countryFileName`.
|
||||||
|
* Automatically performs sync with disk.
|
||||||
|
*
|
||||||
|
* @param countryFileName The filename, without any extension.
|
||||||
|
* @param version The data version.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
static LocalCountryFile MakeForTesting(std::string countryFileName, int64_t version = 0);
|
static LocalCountryFile MakeForTesting(std::string countryFileName, int64_t version = 0);
|
||||||
|
|
||||||
// Used in generator only to simplify getting instance from path.
|
// Used in generator only to simplify getting instance from path.
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ AbsentRegionsFinder::AbsentRegionsFinder(CountryFileGetterFn const & countryFile
|
|||||||
|
|
||||||
void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate)
|
void AbsentRegionsFinder::GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate)
|
||||||
{
|
{
|
||||||
|
m_regions.clear();
|
||||||
|
|
||||||
if (m_routerThread)
|
if (m_routerThread)
|
||||||
{
|
{
|
||||||
m_routerThread->Cancel();
|
m_routerThread->Cancel();
|
||||||
@@ -48,18 +50,21 @@ void AbsentRegionsFinder::GetAbsentRegions(std::set<std::string> & regions)
|
|||||||
|
|
||||||
void AbsentRegionsFinder::GetAllRegions(std::set<std::string> & countries)
|
void AbsentRegionsFinder::GetAllRegions(std::set<std::string> & countries)
|
||||||
{
|
{
|
||||||
countries.clear();
|
// Note: if called from `RoutingSession` callback, m_state will still have its pre-update value.
|
||||||
|
if (m_routerThread)
|
||||||
|
{
|
||||||
|
m_routerThread->Join();
|
||||||
|
|
||||||
if (!m_routerThread)
|
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
|
||||||
return;
|
{
|
||||||
|
if (!mwmName.empty())
|
||||||
|
m_regions.emplace(mwmName);
|
||||||
|
}
|
||||||
|
|
||||||
m_routerThread->Join();
|
m_routerThread.reset();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto const & mwmName : m_routerThread->GetRoutineAs<RegionsRouter>()->GetMwmNames())
|
countries = m_regions;
|
||||||
if (!mwmName.empty())
|
|
||||||
countries.emplace(mwmName);
|
|
||||||
|
|
||||||
m_routerThread.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const
|
bool AbsentRegionsFinder::AreCheckpointsInSameMwm(Checkpoints const & checkpoints) const
|
||||||
|
|||||||
@@ -14,19 +14,45 @@ namespace routing
|
|||||||
{
|
{
|
||||||
using LocalFileCheckerFn = std::function<bool(std::string const &)>;
|
using LocalFileCheckerFn = std::function<bool(std::string const &)>;
|
||||||
|
|
||||||
// Encapsulates generation of mwm names of absent regions needed for building the route between
|
/**
|
||||||
// |checkpoints|. For this purpose the new thread is used.
|
* @brief Generates a list of MWMs needed to build a route.
|
||||||
|
*
|
||||||
|
* The `AbsentRegionsFinder` class encapsulates generation of MWM names of absent regions needed
|
||||||
|
* for building the route between `checkpoints`. For this purpose a separate worker thread is used.
|
||||||
|
*/
|
||||||
class AbsentRegionsFinder
|
class AbsentRegionsFinder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AbsentRegionsFinder(CountryFileGetterFn const & countryFileGetter, LocalFileCheckerFn const & localFileChecker,
|
AbsentRegionsFinder(CountryFileGetterFn const & countryFileGetter, LocalFileCheckerFn const & localFileChecker,
|
||||||
std::shared_ptr<NumMwmIds> numMwmIds, DataSource & dataSource);
|
std::shared_ptr<NumMwmIds> numMwmIds, DataSource & dataSource);
|
||||||
|
|
||||||
// Creates new thread |m_routerThread| and starts routing in it.
|
/**
|
||||||
|
* @brief Creates new thread `m_routerThread` and starts routing in it.
|
||||||
|
* @param checkpoints The checkpoints of the route (start, optional intermediate points, destination)
|
||||||
|
* @param delegate
|
||||||
|
*/
|
||||||
void GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate);
|
void GenerateAbsentRegions(Checkpoints const & checkpoints, RouterDelegate const & delegate);
|
||||||
// Waits for the routing thread |m_routerThread| to finish and returns results from it.
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the MWMs needed to build the route.
|
||||||
|
*
|
||||||
|
* When called for the first time after `GenerateAbsentRegions()`, this method waits for the
|
||||||
|
* routing thread `m_routerThread` to finish and returns results from it. Results are cached and
|
||||||
|
* subsequent calls are served from the cache.
|
||||||
|
*
|
||||||
|
* @param countries Receives the list of MWM names.
|
||||||
|
*/
|
||||||
void GetAllRegions(std::set<std::string> & countries);
|
void GetAllRegions(std::set<std::string> & countries);
|
||||||
// Waits for the results from GetAllRegions() and returns only regions absent on the device.
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the missing MWMs needed to build the route.
|
||||||
|
*
|
||||||
|
* This calls `GetAllRegions()` and strips from the result all regions already present on the
|
||||||
|
* device, leaving only the missing ones. If the call to `GetAllRegions()` is the first one after
|
||||||
|
* calling `GenerateAbsentRegions()`, this involves waiting for the router thread to finish.
|
||||||
|
*
|
||||||
|
* @param absentCountries Receives the list of missing MWM names.
|
||||||
|
*/
|
||||||
void GetAbsentRegions(std::set<std::string> & absentCountries);
|
void GetAbsentRegions(std::set<std::string> & absentCountries);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -39,5 +65,19 @@ private:
|
|||||||
DataSource & m_dataSource;
|
DataSource & m_dataSource;
|
||||||
|
|
||||||
std::unique_ptr<threads::Thread> m_routerThread;
|
std::unique_ptr<threads::Thread> m_routerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mutex for access to `m_regions`.
|
||||||
|
*
|
||||||
|
* Threads which access `m_regions` must lock this mutex while doing so.
|
||||||
|
*/
|
||||||
|
std::mutex m_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Regions required for building the last route.
|
||||||
|
*
|
||||||
|
* This member is cleared by `GenerateAbsentRegions()` and populated by `GetAllRegions()`.
|
||||||
|
*/
|
||||||
|
std::set<std::string> m_regions;
|
||||||
};
|
};
|
||||||
} // namespace routing
|
} // namespace routing
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user