mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-09 13:54:37 +00:00
Compare commits
16 Commits
traffic
...
4691de7a66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4691de7a66 | ||
|
|
3c082b0629 | ||
|
|
a468bd3fab | ||
|
|
fc87316184 | ||
|
|
298518ae72 | ||
|
|
3bad6d25f0 | ||
|
|
d36361d669 | ||
|
|
688e20b1a6 | ||
|
|
85462161b2 | ||
|
|
b929823f6b | ||
|
|
22dd799585 | ||
|
|
6864d101e2 | ||
|
|
4bfb62b373 | ||
|
|
f1cf844986 | ||
|
|
f20c3bf50c | ||
|
|
e7cc602904 |
@@ -1,69 +1,55 @@
|
||||
# All non-assigned.
|
||||
* @organicmaps/mergers
|
||||
# Visual design.
|
||||
/android/app/src/main/res/drawable*/ @organicmaps/design
|
||||
/android/app/src/main/res/font/ @organicmaps/design
|
||||
/android/app/src/main/res/mipmap*/ @organicmaps/design
|
||||
/data/*.ttf @organicmaps/design
|
||||
/data/resources-svg/ @organicmaps/design
|
||||
/data/search-icons/ @organicmaps/design
|
||||
/iphone/Maps/Images.xcassets/ @organicmaps/design
|
||||
/android/app/src/main/res/drawable*/ @comaps/design
|
||||
/android/app/src/main/res/font/ @comaps/design
|
||||
/android/app/src/main/res/mipmap*/ @comaps/design
|
||||
/data/*.ttf @comaps/design
|
||||
/data/resources-svg/ @comaps/design
|
||||
/data/search-icons/ @comaps/design
|
||||
/iphone/Maps/Images.xcassets/ @comaps/design
|
||||
# Android.
|
||||
/android/ @organicmaps/android
|
||||
/android/app/src/main/java/app/organicmaps/car/ @organicmaps/android-auto
|
||||
/docs/ANDROID_LOCATION_TEST.md @organicmaps/android
|
||||
/docs/JAVA_STYLE.md @organicmaps/android
|
||||
/android/ @comaps/android
|
||||
/android/app/src/main/java/app/comaps/car/ @comaps/android-auto
|
||||
/docs/ANDROID_LOCATION_TEST.md @comaps/android
|
||||
/docs/JAVA_STYLE.md @comaps/android
|
||||
# no owner for translation changes
|
||||
/android/app/src/main/res/values*/strings.xml
|
||||
# iOS.
|
||||
/iphone/ @organicmaps/ios
|
||||
/xcode/ @organicmaps/ios
|
||||
/docs/OBJC_STYLE.md @organicmaps/ios
|
||||
/iphone/ @comaps/ios
|
||||
/xcode/ @comaps/ios
|
||||
/docs/OBJC_STYLE.md @comaps/ios
|
||||
# no owner for translation changes
|
||||
/iphone/plist.txt
|
||||
/iphone/Maps/LocalizedStrings/
|
||||
# Qt
|
||||
/qt/ @organicmaps/qt
|
||||
# Rendering
|
||||
/drape/ @organicmaps/rendering
|
||||
/drape_frontend/ @organicmaps/rendering
|
||||
# Map Data.
|
||||
/tools/python/maps_generator/ @organicmaps/data
|
||||
/generator/ @organicmaps/data
|
||||
/topography_generator/ @organicmaps/data
|
||||
/data/borders/ @organicmaps/data
|
||||
/data/conf/isolines/ @organicmaps/data
|
||||
/docs/SUBWAY_GENERATION.md @organicmaps/data
|
||||
/docs/MAPS.md @organicmaps/data
|
||||
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @organicmaps/data
|
||||
# no owner (changed often to add a new POI)
|
||||
/generator/generator_tests/osm_type_test.cpp
|
||||
# Map Styles.
|
||||
/data/styles/ @organicmaps/styles
|
||||
/data/types.txt @organicmaps/styles
|
||||
/data/visibility.txt @organicmaps/styles
|
||||
/data/mapcss-mapping.csv @organicmaps/styles
|
||||
/data/replaced_tags.txt @organicmaps/styles
|
||||
/data/classificator.txt @organicmaps/styles
|
||||
/data/drules_* @organicmaps/styles
|
||||
/data/styles/ @comaps/styles
|
||||
/data/types.txt @comaps/styles
|
||||
/data/visibility.txt @comaps/styles
|
||||
/data/mapcss-mapping.csv @comaps/styles
|
||||
/data/replaced_tags.txt @comaps/styles
|
||||
/data/classificator.txt @comaps/styles
|
||||
/data/drules_* @comaps/styles
|
||||
/docs/STYLES.md
|
||||
/tools/kothic/ @organicmaps/styles
|
||||
/tools/kothic/ @comaps/styles
|
||||
# DevOps.
|
||||
/.forgejo/workflows @organicmaps/devops
|
||||
/android/*gradle* @organicmaps/devops
|
||||
/docs/RELEASE_MANAGEMENT.md @organicmaps/devops
|
||||
/xcode/fastlane/ @organicmaps/devops
|
||||
# Growth.
|
||||
README.md @organicmaps/growth
|
||||
/.forgejo/FUNDING.yml @organicmaps/growth
|
||||
/android/app/src/fdroid/play/ @organicmaps/growth
|
||||
/android/app/src/google/play/ @organicmaps/growth
|
||||
/iphone/metadata/ @organicmaps/growth
|
||||
/.forgejo/workflows @comaps/devops
|
||||
/android/*gradle* @comaps/devops
|
||||
/docs/RELEASE_MANAGEMENT.md @comaps/devops
|
||||
/xcode/fastlane/ @comaps/devops
|
||||
/tools/python/maps_generator/ @comaps/devops
|
||||
/generator/ @comaps/devops
|
||||
/topography_generator/ @comaps/devops
|
||||
/data/borders/ @comaps/devops
|
||||
/data/conf/isolines/ @comaps/devops
|
||||
/docs/SUBWAY_GENERATION.md @comaps/devops
|
||||
/docs/MAPS.md @comaps/devops
|
||||
/docs/EXPERIMENTAL_PUBLIC_TRANSPORT_SUPPORT.md @comaps/devops
|
||||
# Legal.
|
||||
LEGAL @organicmaps/legal
|
||||
LICENSE @organicmaps/legal
|
||||
NOTICE @organicmaps/legal
|
||||
CONTRIBUTORS @organicmaps/legal
|
||||
/docs/CODE_OF_CONDUCT.md @organicmaps/legal
|
||||
/docs/DCO.md @organicmaps/legal
|
||||
/docs/GOVERNANCE.md @organicmaps/legal
|
||||
LEGAL @comaps/admins
|
||||
LICENSE @comaps/admins
|
||||
NOTICE @comaps/admins
|
||||
CONTRIBUTORS @comaps/admins
|
||||
/docs/CODE_OF_CONDUCT.md @comaps/admins
|
||||
/docs/DCO.md @comaps/admins
|
||||
/docs/GOVERNANCE.md @comaps/admins
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
open_collective: comaps
|
||||
liberapay: comaps
|
||||
custom: ["https://comaps.app/donate/"]
|
||||
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
open_collective: comaps
|
||||
liberapay: comaps
|
||||
custom: ["https://comaps.app/donate/"]
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,7 +21,6 @@ data/symbols/**/symbols.sdf
|
||||
|
||||
data/bookmarks
|
||||
data/edits.xml
|
||||
data/traffic.xml
|
||||
data/World.mwm
|
||||
data/WorldCoasts.mwm
|
||||
data/world_mwm/*
|
||||
@@ -156,6 +155,8 @@ android/huawei-appgallery.json
|
||||
android/res/xml/network_security_config.xml
|
||||
./server/
|
||||
iphone/Maps/app.omaps/
|
||||
# Generated file
|
||||
libs/indexer/localized_types_map.cpp
|
||||
|
||||
*.li
|
||||
|
||||
|
||||
5
android/app/proguard-rules.pro
vendored
5
android/app/proguard-rules.pro
vendored
@@ -28,8 +28,3 @@
|
||||
# R8 crypts the source line numbers in all log messages.
|
||||
# https://github.com/organicmaps/organicmaps/issues/6559#issuecomment-1812039926
|
||||
-dontoptimize
|
||||
|
||||
# Keep classes for Android TraFF support
|
||||
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_7 { *; }
|
||||
-keep class app.organicmaps.sdk.traffxml.SourceImplV0_8 { *; }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: 2025.07.23-4-FDroid+25072304
|
||||
version: 2025.03.02-7-FDroid+25030207
|
||||
|
||||
@@ -62,21 +62,6 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.GET_CAPABILITIES"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.POLL"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.SUBSCRIBE"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.SUBSCRIPTION_CHANGE"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.traffxml.traff.UNSUBSCRIBE"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<supports-screens
|
||||
|
||||
@@ -12,7 +12,6 @@ public class LayersUtils
|
||||
availableLayers.add(Mode.OUTDOORS);
|
||||
availableLayers.add(Mode.ISOLINES);
|
||||
availableLayers.add(Mode.SUBWAY);
|
||||
availableLayers.add(Mode.TRAFFIC);
|
||||
return availableLayers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,12 @@ package app.organicmaps.settings;
|
||||
import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
@@ -35,7 +28,6 @@ import app.organicmaps.sdk.routing.RoutingOptions;
|
||||
import app.organicmaps.sdk.search.SearchRecents;
|
||||
import app.organicmaps.sdk.settings.MapLanguageCode;
|
||||
import app.organicmaps.sdk.settings.UnitLocale;
|
||||
import app.organicmaps.sdk.traffxml.AndroidTransport;
|
||||
import app.organicmaps.sdk.util.Config;
|
||||
import app.organicmaps.sdk.util.NetworkPolicy;
|
||||
import app.organicmaps.sdk.util.PowerManagment;
|
||||
@@ -43,13 +35,11 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils;
|
||||
import app.organicmaps.sdk.util.log.LogsManager;
|
||||
import app.organicmaps.util.ThemeSwitcher;
|
||||
import app.organicmaps.util.Utils;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements LanguagesFragment.Listener
|
||||
{
|
||||
@@ -71,10 +61,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
initAutoDownloadPrefsCallbacks();
|
||||
initLargeFontSizePrefsCallbacks();
|
||||
initTransliterationPrefsCallbacks();
|
||||
initTrafficHttpEnabledPrefsCallbacks();
|
||||
initTrafficHttpUrlPrefsCallbacks();
|
||||
initTrafficAppsPrefs();
|
||||
initTrafficLegacyEnabledPrefsCallbacks();
|
||||
init3dModePrefsCallbacks();
|
||||
initPerspectivePrefsCallbacks();
|
||||
initAutoZoomPrefsCallbacks();
|
||||
@@ -150,46 +136,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
pref.setSummary(locale.getDisplayLanguage());
|
||||
}
|
||||
|
||||
private void updateTrafficHttpUrlSummary()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||
String summary = Config.getTrafficHttpUrl();
|
||||
if (summary.length() == 0)
|
||||
pref.setSummary(R.string.traffic_http_url_not_set);
|
||||
else
|
||||
pref.setSummary(summary);
|
||||
}
|
||||
|
||||
private void updateTrafficAppsSummary()
|
||||
{
|
||||
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||
/*
|
||||
* If the preference is disabled, it has not been initialized. This is the case if no TraFF
|
||||
* apps were found. The code below would crash when trying to access the entries, and there
|
||||
* is no need to update the summary if the setting cannot be changed.
|
||||
*/
|
||||
if (!pref.isEnabled())
|
||||
return;
|
||||
String[] apps = Config.getTrafficApps();
|
||||
if (apps.length == 0)
|
||||
pref.setSummary(R.string.traffic_apps_none_selected);
|
||||
else
|
||||
{
|
||||
String summary = "";
|
||||
for (int i = 0; i < apps.length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
summary = summary + ", ";
|
||||
int index = pref.findIndexOfValue(apps[i]);
|
||||
if (i >= 0)
|
||||
summary = summary + pref.getEntries()[index];
|
||||
else
|
||||
summary = summary + apps[i];
|
||||
}
|
||||
pref.setSummary(summary);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRoutingSettingsPrefsSummary()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.prefs_routing));
|
||||
@@ -217,8 +163,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
updateVoiceInstructionsPrefsSummary();
|
||||
updateRoutingSettingsPrefsSummary();
|
||||
updateMapLanguageCodeSummary();
|
||||
updateTrafficHttpUrlSummary();
|
||||
updateTrafficAppsSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -280,91 +224,6 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficHttpEnabledPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_enabled));
|
||||
|
||||
((TwoStatePreference)pref).setChecked(Config.getTrafficHttpEnabled());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final boolean oldVal = Config.getTrafficHttpEnabled();
|
||||
final boolean newVal = (Boolean) newValue;
|
||||
if (oldVal != newVal)
|
||||
Config.setTrafficHttpEnabled(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficHttpUrlPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_http_url));
|
||||
|
||||
((EditTextPreference)pref).setText(Config.getTrafficHttpUrl());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final String oldVal = Config.getTrafficHttpUrl();
|
||||
final String newVal = (String) newValue;
|
||||
if (!oldVal.equals(newVal))
|
||||
Config.setTrafficHttpUrl(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficAppsPrefs()
|
||||
{
|
||||
final MultiSelectListPreference pref = getPreference(getString(R.string.pref_traffic_apps));
|
||||
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(new Intent(AndroidTransport.ACTION_TRAFF_GET_CAPABILITIES), 0);
|
||||
|
||||
if (receivers == null || receivers.isEmpty())
|
||||
{
|
||||
pref.setSummary(R.string.traffic_apps_not_available);
|
||||
pref.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
pref.setEnabled(true);
|
||||
|
||||
List<String> entryList = new ArrayList<>(receivers.size());
|
||||
List<String> valueList = new ArrayList<>(receivers.size());
|
||||
|
||||
for (ResolveInfo receiver : receivers)
|
||||
{
|
||||
// friendly name
|
||||
entryList.add(receiver.loadLabel(pm).toString());
|
||||
// actual value (we just need the package name, broadcasts are sent to any receiver in the package)
|
||||
valueList.add(receiver.activityInfo.applicationInfo.packageName);
|
||||
}
|
||||
|
||||
pref.setEntries(entryList.toArray(new CharSequence[0]));
|
||||
pref.setEntryValues(valueList.toArray(new CharSequence[0]));
|
||||
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
// newValue is a Set<String>, each item is a package ID
|
||||
String[] apps = ((Set<String>)newValue).toArray(new String[0]);
|
||||
Config.setTrafficApps(apps);
|
||||
updateTrafficAppsSummary();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrafficLegacyEnabledPrefsCallbacks()
|
||||
{
|
||||
final Preference pref = getPreference(getString(R.string.pref_traffic_legacy_enabled));
|
||||
|
||||
((TwoStatePreference)pref).setChecked(Config.getTrafficLegacyEnabled());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final boolean oldVal = Config.getTrafficLegacyEnabled();
|
||||
final boolean newVal = (Boolean) newValue;
|
||||
if (oldVal != newVal)
|
||||
Config.setTrafficLegacyEnabled(newVal);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initUseMobileDataPrefsCallbacks()
|
||||
{
|
||||
final ListPreference mobilePref = getPreference(getString(R.string.pref_use_mobile_data));
|
||||
|
||||
@@ -90,8 +90,7 @@ public enum ThemeSwitcher
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_YES);
|
||||
else
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
|
||||
if (RoutingController.get().isVehicleNavigation())
|
||||
style = MapStyle.VehicleDark;
|
||||
@@ -104,8 +103,7 @@ public enum ThemeSwitcher
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_NO);
|
||||
else
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
|
||||
if (RoutingController.get().isVehicleNavigation())
|
||||
style = MapStyle.VehicleClear;
|
||||
|
||||
@@ -154,6 +154,7 @@ public class PlacePageView extends Fragment
|
||||
private MaterialTextView mTvLastChecked;
|
||||
private View mEditPlace;
|
||||
private View mAddPlace;
|
||||
private View mMapTooOld;
|
||||
private View mEditTopSpace;
|
||||
private ShapeableImageView mColorIcon;
|
||||
private MaterialTextView mTvCategory;
|
||||
@@ -318,6 +319,7 @@ public class PlacePageView extends Fragment
|
||||
mTvLastChecked = mFrame.findViewById(R.id.place_page_last_checked);
|
||||
mEditPlace = mFrame.findViewById(R.id.ll__place_editor);
|
||||
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);
|
||||
latlon.setOnLongClickListener(this);
|
||||
address.setOnLongClickListener(this);
|
||||
@@ -684,39 +686,73 @@ public class PlacePageView extends Fragment
|
||||
|
||||
if (RoutingController.get().isNavigating() || RoutingController.get().isPlanning())
|
||||
{
|
||||
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace);
|
||||
UiUtils.hide(mEditPlace, mAddPlace, mEditTopSpace, mMapTooOld);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace);
|
||||
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
|
||||
UiUtils.hide(mMapTooOld);
|
||||
MaterialButton mTvEditPlace = mEditPlace.findViewById(R.id.mb__place_editor);
|
||||
MaterialButton mTvAddPlace = mAddPlace.findViewById(R.id.mb__place_add);
|
||||
mTvEditPlace.setOnClickListener(this);
|
||||
mTvAddPlace.setOnClickListener(this);
|
||||
mTvEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
|
||||
mTvAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
|
||||
final int editTextButtonColor =
|
||||
Editor.nativeShouldEnableEditPlace()
|
||||
|
||||
boolean shouldEnableEditPlace = Editor.nativeShouldEnableEditPlace();
|
||||
|
||||
if (shouldEnableEditPlace)
|
||||
{
|
||||
mTvEditPlace.setOnClickListener(this);
|
||||
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);
|
||||
});
|
||||
|
||||
String countryId = MapManager.nativeGetSelectedCountry();
|
||||
|
||||
if (countryId != null)
|
||||
{
|
||||
CountryItem map = CountryItem.fill(countryId);
|
||||
|
||||
if (map.status == CountryItem.STATUS_UPDATABLE || map.status == CountryItem.STATUS_DONE
|
||||
|| map.status == CountryItem.STATUS_FAILED)
|
||||
{
|
||||
UiUtils.show(mMapTooOld);
|
||||
|
||||
boolean canUpdateMap = map.status != CountryItem.STATUS_DONE;
|
||||
MaterialButton mTvUpdateTooOldMap = mMapTooOld.findViewById(R.id.mb__update_too_old_map);
|
||||
UiUtils.showIf(canUpdateMap, mTvUpdateTooOldMap);
|
||||
|
||||
MaterialTextView mapTooOldDescription = mMapTooOld.findViewById(R.id.tv__map_too_old_description);
|
||||
if (canUpdateMap)
|
||||
{
|
||||
mapTooOldDescription.setText(R.string.place_page_map_too_old_description);
|
||||
mTvUpdateTooOldMap.setOnClickListener((v) -> {
|
||||
MapManagerHelper.warn3gAndDownload(requireActivity(), map.id, null);
|
||||
UiUtils.hide(mMapTooOld);
|
||||
});
|
||||
}
|
||||
else
|
||||
mapTooOldDescription.setText(R.string.place_page_app_too_old_description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final int editButtonColor =
|
||||
shouldEnableEditPlace
|
||||
? ContextCompat.getColor(
|
||||
getContext(),
|
||||
UiUtils.getStyledResourceId(getContext(), com.google.android.material.R.attr.colorSecondary))
|
||||
: ContextCompat.getColor(getContext(), R.color.button_accent_text_disabled);
|
||||
final ColorStateList editStrokeButtonColor = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{android.R.attr.state_enabled}, // enabled
|
||||
new int[]{-android.R.attr.state_enabled} // disabled
|
||||
},
|
||||
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);
|
||||
|
||||
mTvEditPlace.setTextColor(editButtonColor);
|
||||
mTvAddPlace.setTextColor(editButtonColor);
|
||||
mTvEditPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
||||
mTvAddPlace.setStrokeColor(ColorStateList.valueOf(editButtonColor));
|
||||
UiUtils.showIf(
|
||||
UiUtils.isVisible(mEditPlace) || UiUtils.isVisible(mAddPlace),
|
||||
mEditTopSpace);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M240,520q-17,0 -28.5,-11.5T200,480q0,-17 11.5,-28.5T240,440h480q17,0 28.5,11.5T760,480q0,17 -11.5,28.5T720,520L240,520Z"
|
||||
android:pathData="M19,13H5v-2h14v2z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M440,520L240,520q-17,0 -28.5,-11.5T200,480q0,-17 11.5,-28.5T240,440h200v-200q0,-17 11.5,-28.5T480,200q17,0 28.5,11.5T520,240v200h200q17,0 28.5,11.5T760,480q0,17 -11.5,28.5T720,520L520,520v200q0,17 -11.5,28.5T480,760q-17,0 -28.5,-11.5T440,720v-200Z"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</vector>
|
||||
|
||||
10
android/app/src/main/res/drawable/info_icon.xml
Normal file
10
android/app/src/main/res/drawable/info_icon.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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"/>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/head_message"
|
||||
style="?fontHeadline6"
|
||||
style="@style/TextAppearance.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
@@ -36,7 +36,7 @@
|
||||
android:text="@string/download_map_title" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/download_message"
|
||||
style="?fontBody2"
|
||||
style="@style/TextAppearance.Body2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/aa_connected_title"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
|
||||
android:textAppearance="@style/TextAppearance.Headline4"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
@@ -36,7 +36,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/car_used_on_the_car_screen"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
|
||||
android:textAppearance="@style/TextAppearance.Body1" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
android:layout_marginStart="32dp"
|
||||
android:text="@string/aa_request_permission_activity_text"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
|
||||
android:textAppearance="@style/TextAppearance.Headline4"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?fontHeadline6"
|
||||
android:textAppearance="@style/TextAppearance.Headline6"
|
||||
tools:text="Title" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:text="@string/download_country_ask"
|
||||
android:textAppearance="?fontBody2"
|
||||
android:textAppearance="@style/TextAppearance.Body2"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
android:textAppearance="@style/MwmTextAppearance.Title" />
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/et__input_layout"
|
||||
style="?fontBody1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:hintEnabled="false">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
android:layout_marginTop="@dimen/margin_base">
|
||||
|
||||
<RadioButton
|
||||
style="?fontSubtitle1"
|
||||
style="@style/TextAppearance.Subtitle1"
|
||||
android:id="@+id/sort_by_default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -37,7 +37,7 @@
|
||||
app:buttonTint="@null"/>
|
||||
|
||||
<RadioButton
|
||||
style="?fontSubtitle1"
|
||||
style="@style/TextAppearance.Subtitle1"
|
||||
android:id="@+id/sort_by_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -47,7 +47,7 @@
|
||||
app:buttonTint="@null"/>
|
||||
|
||||
<RadioButton
|
||||
style="?fontSubtitle1"
|
||||
style="@style/TextAppearance.Subtitle1"
|
||||
android:id="@+id/sort_by_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -57,7 +57,7 @@
|
||||
app:buttonTint="@null"/>
|
||||
|
||||
<RadioButton
|
||||
style="?fontSubtitle1"
|
||||
style="@style/TextAppearance.Subtitle1"
|
||||
android:id="@+id/sort_by_distance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -67,7 +67,7 @@
|
||||
app:buttonTint="@null"/>
|
||||
|
||||
<RadioButton
|
||||
style="?fontSubtitle1"
|
||||
style="@style/TextAppearance.Subtitle1"
|
||||
android:id="@+id/sort_by_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
android:orientation="vertical">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/edit_bookmark_name_input"
|
||||
style="?fontBody1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/name"
|
||||
@@ -88,7 +87,6 @@
|
||||
tools:src="@drawable/ic_bookmark_none" />
|
||||
</RelativeLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="?fontBody1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_half"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
android:layout_marginEnd="@dimen/margin_base"
|
||||
android:paddingTop="@dimen/margin_half"
|
||||
android:paddingBottom="@dimen/margin_base"
|
||||
android:textAppearance="?fontHeadline6"
|
||||
android:textAppearance="@style/TextAppearance.Headline6"
|
||||
tools:text="Xindian Shitoushan Trail" />
|
||||
<include
|
||||
layout="@layout/item_divider"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
android:layout_marginStart="@dimen/margin_base"
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
android:text="@string/layers_title"
|
||||
android:textAppearance="?fontHeadline6"
|
||||
android:textAppearance="@style/TextAppearance.Headline6"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="?fontBody1"
|
||||
android:textAppearance="@style/TextAppearance.Body1"
|
||||
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_line_container"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?fontBody1"
|
||||
android:textAppearance="@style/TextAppearance.Body1"
|
||||
tools:text="Bookmark name looooooooooooooooooongasdasdasd" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/size"
|
||||
@@ -37,7 +37,7 @@
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
style="?fontBody2"
|
||||
style="@style/TextAppearance.Body2"
|
||||
tools:text="42000000" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
android:paddingBottom="@dimen/margin_half_plus">
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_message"
|
||||
style="?fontHeadline6"
|
||||
style="@style/TextAppearance.Headline6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/text"
|
||||
style="?fontHeadline6"
|
||||
style="@style/TextAppearance.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?windowBackgroundForced"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
android:gravity="center"
|
||||
android:maxWidth="500dp"
|
||||
android:text="@string/editor_category_unsuitable_title"
|
||||
android:textAppearance="@style/TextAppearance.MdcTypographyStyles.Headline6"
|
||||
android:textAppearance="@style/TextAppearance.Headline6"
|
||||
android:textStyle="bold" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/editor_category_unsuitable_text"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/input_layout"
|
||||
style="?fontBody1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/allday"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/editor_height_allday"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?clickableBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/margin_half_plus">
|
||||
android:padding="@dimen/margin_half">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="0dp"
|
||||
@@ -164,7 +164,8 @@
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/sw__allday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_quarter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
android:layout_marginTop="@dimen/margin_base"
|
||||
android:layout_marginEnd="@dimen/bookmark_collection_item_end_margin"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?fontBody1"
|
||||
android:textAppearance="@style/TextAppearance.Body1"
|
||||
tools:text="@string/tracks_title" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/tv__bookmark_distance"
|
||||
@@ -39,7 +39,7 @@
|
||||
android:layout_marginBottom="@dimen/margin_half_plus"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textAppearance="?fontBody2"
|
||||
android:textAppearance="@style/TextAppearance.Body2"
|
||||
tools:text="@string/by_distance" />
|
||||
</LinearLayout>
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
style="@style/MwmWidget.M3.FAB.MapButton.Zoom"
|
||||
android:tint="?iconTint"
|
||||
app:srcCompat="@drawable/ic_plus"
|
||||
app:shapeAppearance="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_marginBottom="@dimen/margin_half"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.MapButton.Zoom.Minus"
|
||||
android:layout_marginBottom="@dimen/margin_eighth"
|
||||
android:contentDescription="@string/zoom_in"/>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/nav_zoom_out"
|
||||
style="@style/MwmWidget.M3.FAB.MapButton.Zoom"
|
||||
android:tint="?iconTint"
|
||||
app:srcCompat="@drawable/ic_minus"
|
||||
app:shapeAppearance="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.MapButton.Zoom.Plus"
|
||||
android:contentDescription="@string/zoom_out"/>
|
||||
</LinearLayout>
|
||||
@@ -82,13 +82,15 @@
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/place_page_last_checked"
|
||||
style="?fontCaption"
|
||||
style="@style/TextAppearance.Caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_half"
|
||||
android:paddingHorizontal="@dimen/margin_base"
|
||||
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_add"/>
|
||||
|
||||
65
android/app/src/main/res/layout/place_page_map_too_old.xml
Normal file
65
android/app/src/main/res/layout/place_page_map_too_old.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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
|
||||
android:id="@+id/oh_check_date"
|
||||
style="?fontCaption"
|
||||
style="@style/TextAppearance.Caption"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
|
||||
@@ -88,19 +88,19 @@
|
||||
<item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
|
||||
<item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
|
||||
<item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
|
||||
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
|
||||
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
|
||||
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
|
||||
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
|
||||
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
|
||||
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
|
||||
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
|
||||
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
|
||||
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
|
||||
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
|
||||
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
|
||||
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
|
||||
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
|
||||
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
|
||||
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
|
||||
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
|
||||
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
|
||||
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
|
||||
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
|
||||
<item name="fontBody1">@style/TextAppearance.Body1</item>
|
||||
<item name="fontBody2">@style/TextAppearance.Body2</item>
|
||||
<item name="fontButton">@style/TextAppearance.Button</item>
|
||||
<item name="fontCaption">@style/TextAppearance.Caption</item>
|
||||
<item name="fontOverline">@style/TextAppearance.Overline</item>
|
||||
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
||||
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
||||
<item name="elevationProfilePropIconTint">@color/white_secondary</item>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<dimen name="nav_menu_landscape_width">360dp</dimen>
|
||||
|
||||
<dimen name="nav_frame_padding">@dimen/margin_half</dimen>
|
||||
<dimen name="zoom_buttons_margin">64dp</dimen>
|
||||
<dimen name="zoom_buttons_margin">58dp</dimen>
|
||||
<dimen name="map_buttons_bottom_margin">136dp</dimen>
|
||||
<dimen name="map_buttons_bottom_max_width">300dp</dimen>
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
<!-- Editor -->
|
||||
<dimen name="editor_height_days">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_margin_left">56dp</dimen>
|
||||
<dimen name="editor_auth_btn_height">@dimen/primary_button_min_height</dimen>
|
||||
|
||||
@@ -35,11 +35,6 @@
|
||||
<string name="pref_settings_general" translatable="false">GeneralSettings</string>
|
||||
<string name="pref_navigation" translatable="false">Navigation</string>
|
||||
<string name="pref_information" translatable="false">Information</string>
|
||||
<string name="pref_traffic" translatable="false">Traffic</string>
|
||||
<string name="pref_traffic_http_enabled" translatable="false">TrafficHttpEnabled</string>
|
||||
<string name="pref_traffic_http_url" translatable="false">TrafficHttpUrl</string>
|
||||
<string name="pref_traffic_apps" translatable="false">TrafficApps</string>
|
||||
<string name="pref_traffic_legacy_enabled" translatable="false">TrafficLegacyEnabled</string>
|
||||
<string name="pref_transliteration" translatable="false">Transliteration</string>
|
||||
<string name="pref_power_management" translatable="false">PowerManagment</string>
|
||||
<string name="pref_keep_screen_on" translatable="false">KeepScreenOn</string>
|
||||
|
||||
@@ -215,7 +215,6 @@
|
||||
<!-- Settings information group in settings screen -->
|
||||
<string name="prefs_group_information">Information</string>
|
||||
<string name="prefs_group_route">Navigation</string>
|
||||
<string name="prefs_group_traffic">Traffic information</string>
|
||||
<string name="pref_zoom_title">Zoom buttons</string>
|
||||
<string name="pref_zoom_summary">Display on the map</string>
|
||||
<!-- Settings «Map» category: «Night style» title -->
|
||||
@@ -574,6 +573,16 @@
|
||||
<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="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_toast">Note will be sent to OpenStreetMap</string>
|
||||
<!-- Displayed when saving some edits to the map to warn against publishing personal data -->
|
||||
@@ -790,24 +799,6 @@
|
||||
<string name="enable_show_on_lock_screen_description">When enabled, the app will work on the lockscreen even when the device is locked.</string>
|
||||
<!-- Current language of the map! -->
|
||||
<string name="change_map_locale">Map language</string>
|
||||
<!-- Enable live traffic data via HTTP (title) -->
|
||||
<string name="traffic_http_enabled">Enable live traffic data</string>
|
||||
<!-- Enable live traffic data via HTTP (description) -->
|
||||
<string name="traffic_http_enabled_description">When enabled, the app will periodically retrieve traffic information from the configured URL.</string>
|
||||
<!-- URL for live traffic data -->
|
||||
<string name="traffic_http_url">Traffic service URL</string>
|
||||
<!-- Status message indicating that user did not set a traffic URL yet. -->
|
||||
<string name="traffic_http_url_not_set">Not set</string>
|
||||
<!-- TraFF 0.8 apps from which to receive data (title) -->
|
||||
<string name="traffic_apps">Use data from TraFF applications</string>
|
||||
<!-- Status message indicating that no TraFF 0.8 apps are installed -->
|
||||
<string name="traffic_apps_not_available">No apps installed</string>
|
||||
<!-- Status message indicating that no TraFF 0.8 apps are currently selected -->
|
||||
<string name="traffic_apps_none_selected">No apps salected</string>
|
||||
<!-- Enable traffic data from TraFF 0.7 apps (title) -->
|
||||
<string name="traffic_legacy_enabled">Use data from legacy TraFF applications</string>
|
||||
<!-- Enable traffic data from TraFF 0.7 apps (description) -->
|
||||
<string name="traffic_legacy_enabled_description">When enabled, the app will receive and process traffic data from legacy TraFF applications.</string>
|
||||
<!-- OpenStreetMap text on splash screen -->
|
||||
<string name="splash_subtitle">Map data from OpenStreetMap</string>
|
||||
<!-- Telegram group url for the "?" About page -->
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
<item name="android:textStyle">normal</item>
|
||||
</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">
|
||||
<item name="android:textSize">@dimen/text_size_title</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
@@ -190,91 +185,91 @@
|
||||
<item name="android:textColor">@color/white_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline1"
|
||||
parent="TextAppearance.MaterialComponents.Headline1">
|
||||
<style name="TextAppearance.Headline1"
|
||||
parent="TextAppearance.Material3.DisplayLarge">
|
||||
<item name="fontFamily">@string/robotoLight</item>
|
||||
<item name="android:fontFamily">@string/robotoLight</item>
|
||||
<item name="android:textSize">96sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline2"
|
||||
parent="TextAppearance.MaterialComponents.Headline2">
|
||||
<style name="TextAppearance.Headline2"
|
||||
parent="TextAppearance.Material3.DisplayLarge">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">60sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline3"
|
||||
parent="TextAppearance.MaterialComponents.Headline3">
|
||||
<style name="TextAppearance.Headline3"
|
||||
parent="TextAppearance.Material3.DisplayMedium">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">48sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline4"
|
||||
parent="TextAppearance.MaterialComponents.Headline4">
|
||||
<style name="TextAppearance.Headline4"
|
||||
parent="TextAppearance.Material3.HeadlineLarge">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">34sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline5"
|
||||
parent="TextAppearance.MaterialComponents.Headline5">
|
||||
<style name="TextAppearance.Headline5"
|
||||
parent="TextAppearance.Material3.HeadlineMedium">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">24sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Headline6"
|
||||
parent="TextAppearance.MaterialComponents.Headline6">
|
||||
<style name="TextAppearance.Headline6"
|
||||
parent="TextAppearance.Material3.HeadlineSmall">
|
||||
<item name="fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Subtitle1"
|
||||
parent="TextAppearance.MaterialComponents.Subtitle1">
|
||||
<style name="TextAppearance.Subtitle1"
|
||||
parent="TextAppearance.Material3.TitleMedium">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Subtitle2"
|
||||
parent="TextAppearance.MaterialComponents.Subtitle2">
|
||||
<style name="TextAppearance.Subtitle2"
|
||||
parent="TextAppearance.Material3.TitleSmall">
|
||||
<item name="fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Body1"
|
||||
parent="TextAppearance.MaterialComponents.Body1">
|
||||
<style name="TextAppearance.Body1"
|
||||
parent="TextAppearance.Material3.BodyLarge">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Body2"
|
||||
parent="TextAppearance.MaterialComponents.Body2">
|
||||
<style name="TextAppearance.Body2"
|
||||
parent="TextAppearance.Material3.BodyMedium">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Button"
|
||||
<style name="TextAppearance.Button"
|
||||
parent="TextAppearance.MaterialComponents.Button">
|
||||
<item name="fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:fontFamily">@string/robotoMedium</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Caption"
|
||||
parent="TextAppearance.MaterialComponents.Caption">
|
||||
<style name="TextAppearance.Caption"
|
||||
parent="TextAppearance.Material3.LabelMedium">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.MdcTypographyStyles.Overline"
|
||||
<style name="TextAppearance.Overline"
|
||||
parent="TextAppearance.MaterialComponents.Overline">
|
||||
<item name="fontFamily">@string/robotoRegular</item>
|
||||
<item name="android:fontFamily">@string/robotoRegular</item>
|
||||
|
||||
@@ -139,12 +139,13 @@
|
||||
<item name="cornerSize">50%</item>
|
||||
</style>
|
||||
|
||||
<style name="MwmWidget.ToolbarStyle" parent="ThemeOverlay.Material3.Dark.ActionBar">
|
||||
<style name="MwmWidget.ToolbarStyle" parent="Widget.Material3.Toolbar">
|
||||
<item name="android:background">?colorPrimary</item>
|
||||
<item name="android:displayOptions">homeAsUp|showTitle</item>
|
||||
<item name="contentInsetStart">0dp</item>
|
||||
<item name="android:titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
||||
<item name="titleTextAppearance">@style/MwmTextAppearance.Toolbar.Title</item>
|
||||
<item name="buttonGravity">center_vertical</item>
|
||||
</style>
|
||||
|
||||
<style name="MwmWidget.ToolbarStyle.Light">
|
||||
@@ -424,4 +425,17 @@
|
||||
<item name="android:maxWidth">@dimen/map_buttons_bottom_max_width</item>
|
||||
<item name="android:padding">@dimen/nav_frame_padding</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearance.MapButton.Zoom.Plus" parent="">
|
||||
<item name="cornerSizeTopLeft">10%</item>
|
||||
<item name="cornerSizeTopRight">10%</item>
|
||||
<item name="cornerSizeBottomRight">50%</item>
|
||||
<item name="cornerSizeBottomLeft">50%</item>
|
||||
</style>
|
||||
<style name="ShapeAppearance.MapButton.Zoom.Minus" parent="">
|
||||
<item name="cornerSizeBottomLeft">10%</item>
|
||||
<item name="cornerSizeBottomRight">10%</item>
|
||||
<item name="cornerSizeTopLeft">50%</item>
|
||||
<item name="cornerSizeTopRight">50%</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -93,19 +93,19 @@
|
||||
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
|
||||
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
|
||||
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
|
||||
<item name="fontHeadline1">@style/TextAppearance.MdcTypographyStyles.Headline1</item>
|
||||
<item name="fontHeadline2">@style/TextAppearance.MdcTypographyStyles.Headline2</item>
|
||||
<item name="fontHeadline3">@style/TextAppearance.MdcTypographyStyles.Headline3</item>
|
||||
<item name="fontHeadline4">@style/TextAppearance.MdcTypographyStyles.Headline4</item>
|
||||
<item name="fontHeadline1">@style/TextAppearance.Headline1</item>
|
||||
<item name="fontHeadline2">@style/TextAppearance.Headline2</item>
|
||||
<item name="fontHeadline3">@style/TextAppearance.Headline3</item>
|
||||
<item name="fontHeadline4">@style/TextAppearance.Headline4</item>
|
||||
<item name="fontHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||
<item name="fontHeadline6">@style/TextAppearance.MdcTypographyStyles.Headline6</item>
|
||||
<item name="fontSubtitle1">@style/TextAppearance.MdcTypographyStyles.Subtitle1</item>
|
||||
<item name="fontSubtitle2">@style/TextAppearance.MdcTypographyStyles.Subtitle2</item>
|
||||
<item name="fontBody1">@style/TextAppearance.MdcTypographyStyles.Body1</item>
|
||||
<item name="fontBody2">@style/TextAppearance.MdcTypographyStyles.Body2</item>
|
||||
<item name="fontButton">@style/TextAppearance.MdcTypographyStyles.Button</item>
|
||||
<item name="fontCaption">@style/TextAppearance.MdcTypographyStyles.Caption</item>
|
||||
<item name="fontOverline">@style/TextAppearance.MdcTypographyStyles.Overline</item>
|
||||
<item name="fontHeadline6">@style/TextAppearance.Headline6</item>
|
||||
<item name="fontSubtitle1">@style/TextAppearance.Subtitle1</item>
|
||||
<item name="fontSubtitle2">@style/TextAppearance.Subtitle2</item>
|
||||
<item name="fontBody1">@style/TextAppearance.Body1</item>
|
||||
<item name="fontBody2">@style/TextAppearance.Body2</item>
|
||||
<item name="fontButton">@style/TextAppearance.Button</item>
|
||||
<item name="fontCaption">@style/TextAppearance.Caption</item>
|
||||
<item name="fontOverline">@style/TextAppearance.Overline</item>
|
||||
<item name="drivingOptionsViewBg">@color/bg_primary_dark</item>
|
||||
<item name="elevationProfilePropertyBg">@drawable/bg_rounded_rect</item>
|
||||
<item name="elevationProfilePropIconTint">@color/black_secondary</item>
|
||||
|
||||
@@ -191,36 +191,6 @@
|
||||
android:widgetLayout="@layout/preference_switch"
|
||||
android:order="5"/>
|
||||
</PreferenceCategory>
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:key="@string/pref_traffic"
|
||||
android:title="@string/prefs_group_traffic"
|
||||
android:order="4">
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_traffic_http_enabled"
|
||||
android:title="@string/traffic_http_enabled"
|
||||
app:singleLineTitle="false"
|
||||
android:summary="@string/traffic_http_enabled_description"
|
||||
android:defaultValue="true"
|
||||
android:order="1"/>
|
||||
<EditTextPreference
|
||||
android:key="@string/pref_traffic_http_url"
|
||||
android:title="@string/traffic_http_url"
|
||||
app:singleLineTitle="false"
|
||||
android:order="2"/>
|
||||
<MultiSelectListPreference
|
||||
android:key="@string/pref_traffic_apps"
|
||||
android:title="@string/traffic_apps"
|
||||
app:singleLineTitle="false"
|
||||
android:order="3"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/pref_traffic_legacy_enabled"
|
||||
android:title="@string/traffic_legacy_enabled"
|
||||
app:singleLineTitle="false"
|
||||
android:summary="@string/traffic_legacy_enabled_description"
|
||||
android:defaultValue="true"
|
||||
android:order="4"/>
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:key="@string/pref_privacy"
|
||||
android:title="@string/privacy"
|
||||
|
||||
@@ -17,7 +17,6 @@ set(SRC
|
||||
app/organicmaps/sdk/opengl/gl3stub.h
|
||||
app/organicmaps/sdk/platform/GuiThread.hpp
|
||||
app/organicmaps/sdk/platform/AndroidPlatform.hpp
|
||||
app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp
|
||||
app/organicmaps/sdk/util/Distance.hpp
|
||||
app/organicmaps/sdk/util/FeatureIdBuilder.hpp
|
||||
app/organicmaps/sdk/vulkan/android_vulkan_context_factory.hpp
|
||||
@@ -77,8 +76,6 @@ set(SRC
|
||||
app/organicmaps/sdk/platform/PThreadImpl.cpp
|
||||
app/organicmaps/sdk/platform/SecureStorage.cpp
|
||||
app/organicmaps/sdk/platform/SocketImpl.cpp
|
||||
app/organicmaps/sdk/traffxml/AndroidTraffSource.cpp
|
||||
app/organicmaps/sdk/traffxml/SourceImpl.cpp
|
||||
app/organicmaps/sdk/util/Config.cpp
|
||||
app/organicmaps/sdk/util/GeoUtils.cpp
|
||||
app/organicmaps/sdk/util/HttpClient.cpp
|
||||
@@ -130,7 +127,6 @@ target_link_libraries(${PROJECT_NAME}
|
||||
# icu
|
||||
# agg
|
||||
# vulkan_wrapper
|
||||
traffxml
|
||||
|
||||
# Android libs
|
||||
log
|
||||
|
||||
@@ -182,8 +182,6 @@ public:
|
||||
void Set3dMode(bool allow3d, bool allow3dBuildings);
|
||||
void Get3dMode(bool & allow3d, bool & allow3dBuildings);
|
||||
|
||||
TrafficManager & GetTrafficManager() { return m_work.GetTrafficManager(); }
|
||||
|
||||
void SetMapLanguageCode(std::string const & languageCode);
|
||||
std::string GetMapLanguageCode();
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "app/organicmaps/sdk/Framework.hpp"
|
||||
|
||||
#include "app/organicmaps/sdk/platform/AndroidPlatform.hpp"
|
||||
#include "app/organicmaps/sdk/traffxml/AndroidTraffSource.hpp"
|
||||
|
||||
#include "app/organicmaps/sdk/core/jni_helper.hpp"
|
||||
|
||||
@@ -35,26 +34,6 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_OrganicMaps_nativeInitFramework(
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
jmethodID const methodId = jni::GetMethodID(env, *onComplete, "run", "()V");
|
||||
env->CallVoidMethod(*onComplete, methodId);
|
||||
|
||||
ASSERT(g_framework, ("g_framework must be non-null"));
|
||||
|
||||
/*
|
||||
* Add traffic sources for Android.
|
||||
*/
|
||||
jclass configClass = env->FindClass("app/organicmaps/sdk/util/Config");
|
||||
jmethodID const getTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||
"getTrafficLegacyEnabled", "()Z");
|
||||
jmethodID const applyTrafficLegacyEnabledId = jni::GetStaticMethodID(env, configClass,
|
||||
"applyTrafficLegacyEnabled", "(Z)V");
|
||||
jmethodID const getTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||
"getTrafficApps", "()[Ljava/lang/String;");
|
||||
jmethodID const applyTrafficAppsId = jni::GetStaticMethodID(env, configClass,
|
||||
"applyTrafficApps", "([Ljava/lang/String;)V");
|
||||
|
||||
env->CallStaticVoidMethod(configClass, applyTrafficLegacyEnabledId,
|
||||
env->CallStaticBooleanMethod(configClass, getTrafficLegacyEnabledId));
|
||||
env->CallStaticVoidMethod(configClass, applyTrafficAppsId,
|
||||
(jobjectArray)env->CallStaticObjectMethod(configClass, getTrafficAppsId));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
#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
|
||||
@@ -1,199 +0,0 @@
|
||||
#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
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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,74 +119,4 @@ JNIEXPORT void JNICALL Java_app_organicmaps_sdk_util_Config_nativeSetTranslitera
|
||||
frm()->SaveTransliteration(value);
|
||||
frm()->AllowTransliteration(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpEnabled(JNIEnv * env, jclass thiz)
|
||||
{
|
||||
return frm()->LoadTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpEnabled(JNIEnv * env, jclass thiz,
|
||||
jboolean value)
|
||||
{
|
||||
frm()->SaveTrafficHttpEnabled(value);
|
||||
frm()->SetTrafficHttpEnabled(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeGetTrafficHttpUrl(JNIEnv * env, jclass thiz)
|
||||
{
|
||||
std::string value = frm()->LoadTrafficHttpUrl();
|
||||
return jni::ToJavaString(env, value);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_nativeSetTrafficHttpUrl(JNIEnv * env, jclass thiz,
|
||||
jstring value)
|
||||
{
|
||||
frm()->SaveTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||
frm()->SetTrafficHttpUrl(jni::ToNativeString(env, value));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_applyTrafficLegacyEnabled(JNIEnv * env, jclass thiz,
|
||||
jboolean value)
|
||||
{
|
||||
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||
if (traffxml::AndroidTraffSourceV0_7* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_7*>(source))
|
||||
{
|
||||
traffSource->Close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
if (value)
|
||||
traffxml::AndroidTraffSourceV0_7::Create(tm);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_sdk_util_Config_applyTrafficApps(JNIEnv * env, jclass thiz, jobjectArray value)
|
||||
{
|
||||
jsize valueLen = env->GetArrayLength(value);
|
||||
TrafficManager & tm = g_framework->GetTrafficManager();
|
||||
tm.RemoveTraffSourceIf([](traffxml::TraffSource* source) {
|
||||
if (traffxml::AndroidTraffSourceV0_8* traffSource = dynamic_cast<traffxml::AndroidTraffSourceV0_8*>(source))
|
||||
{
|
||||
traffSource->Close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
});
|
||||
for (jsize i = 0; i < valueLen; i++)
|
||||
{
|
||||
jstring jAppId = (jstring)env->GetObjectArrayElement(value, i);
|
||||
std::string appId = jni::ToNativeString(env, jAppId);
|
||||
traffxml::AndroidTraffSourceV0_8::Create(tm, appId);
|
||||
env->DeleteLocalRef(jAppId);
|
||||
}
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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,16 +70,6 @@ public final class Config
|
||||
* True if the first start animation has been seen.
|
||||
*/
|
||||
private static final String KEY_MISC_FIRST_START_DIALOG_SEEN = "FirstStartDialogSeen";
|
||||
|
||||
/**
|
||||
* Whether feeds from legacy TraFF applications (TraFF 0.7, Android transport) are enabled.
|
||||
*/
|
||||
private static final String KEY_TRAFFIC_LEGACY_ENABLED = "TrafficLegacyEnabled";
|
||||
|
||||
/**
|
||||
* TraFF (0.8+) applications from which to request traffic data.
|
||||
*/
|
||||
private static final String KEY_TRAFFIC_APPS = "TrafficApps";
|
||||
|
||||
private Config() {}
|
||||
|
||||
@@ -403,63 +393,6 @@ public final class Config
|
||||
nativeSetTransliteration(value);
|
||||
}
|
||||
|
||||
public static boolean getTrafficHttpEnabled()
|
||||
{
|
||||
return nativeGetTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
public static void setTrafficHttpEnabled(boolean value)
|
||||
{
|
||||
nativeSetTrafficHttpEnabled(value);
|
||||
}
|
||||
|
||||
public static String getTrafficHttpUrl()
|
||||
{
|
||||
return nativeGetTrafficHttpUrl();
|
||||
}
|
||||
|
||||
public static void setTrafficHttpUrl(String value)
|
||||
{
|
||||
nativeSetTrafficHttpUrl(value);
|
||||
}
|
||||
|
||||
public static String[] getTrafficApps()
|
||||
{
|
||||
String appString = getString(KEY_TRAFFIC_APPS, "");
|
||||
if (appString.length() == 0)
|
||||
return new String[0];
|
||||
return appString.split(",");
|
||||
}
|
||||
|
||||
public static void setTrafficApps(String[] value)
|
||||
{
|
||||
String valueString = "";
|
||||
for (int i = 0; i < value.length; i++)
|
||||
{
|
||||
valueString = valueString + value[i];
|
||||
if ((i + 1) < value.length)
|
||||
valueString = valueString + ",";
|
||||
}
|
||||
setString(KEY_TRAFFIC_APPS, valueString);
|
||||
applyTrafficApps(value);
|
||||
}
|
||||
|
||||
public static boolean getTrafficLegacyEnabled()
|
||||
{
|
||||
return getBool(KEY_TRAFFIC_LEGACY_ENABLED, false);
|
||||
}
|
||||
|
||||
public static void setTrafficLegacyEnabled(boolean value)
|
||||
{
|
||||
setBool(KEY_TRAFFIC_LEGACY_ENABLED, value);
|
||||
applyTrafficLegacyEnabled(value);
|
||||
}
|
||||
|
||||
public static boolean isNY()
|
||||
{
|
||||
return getBool("NY");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getDonateUrl()
|
||||
{
|
||||
@@ -603,10 +536,4 @@ public final class Config
|
||||
private static native void nativeSetLargeFontsSize(boolean value);
|
||||
private static native boolean nativeGetTransliteration();
|
||||
private static native void nativeSetTransliteration(boolean value);
|
||||
private static native boolean nativeGetTrafficHttpEnabled();
|
||||
private static native void nativeSetTrafficHttpEnabled(boolean value);
|
||||
private static native String nativeGetTrafficHttpUrl();
|
||||
private static native void nativeSetTrafficHttpUrl(String value);
|
||||
private static native void applyTrafficApps(String[] value);
|
||||
private static native void applyTrafficLegacyEnabled(boolean value);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,9 @@ fi
|
||||
echo "Generating search categories / synonyms..."
|
||||
./tools/unix/generate_categories.sh
|
||||
|
||||
echo "Generating Desktop UI strings..."
|
||||
./tools/unix/generate_desktop_ui_strings.sh
|
||||
|
||||
if [ -z "$SKIP_GENERATE_SYMBOLS" ]; then
|
||||
if Diff data/symbols_hash data/styles/*/*/symbols/* || [ ! -z "$SYMBOLS_NOT_GENERATED" ]; then
|
||||
echo "Generating symbols..."
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"place-state": "Щат|провинция",
|
||||
"place-region": "Район|Регион",
|
||||
"place-island|place-islet": "Остров",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|окръг|квартал|Предградие",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|окръг|квартал|Предградие|Жилищен комплекс|ж.к.|ЖК",
|
||||
"place-hamlet": "Село",
|
||||
"place-village": "Село",
|
||||
"place-locality": "местност|регион|Място",
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
"place-state": "Штат|провинция",
|
||||
"place-region": "Район",
|
||||
"place-island|place-islet": "Остров",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|квартал|Жилая зона",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|микрорайон|квартал|Жилая зона|Жилой комплекс|ж.к.|ЖК",
|
||||
"place-hamlet": "Посёлок|деревня",
|
||||
"place-village": "Деревня|поселок",
|
||||
"place-locality": "Местность|регион",
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
"place-state": "Штат|провінція|регіон",
|
||||
"place-region": "Район|область",
|
||||
"place-island|place-islet": "Острів",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|мікрорайон|квартал|округа|Сусідство",
|
||||
"place-suburb|place-quarter|place-neighbourhood|landuse-residential": "Район|мікрорайон|квартал|округа|Сусідство|Житловий комплекс|ж.к.|ЖК",
|
||||
"place-hamlet": "Поселення|селище|село",
|
||||
"place-village": "Село|хутір|поселення",
|
||||
"place-locality": "Місцевість|регіон|Місце",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,17 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,16 +0,0 @@
|
||||
<!--
|
||||
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>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,22 +0,0 @@
|
||||
<!--
|
||||
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>
|
||||
@@ -1,58 +0,0 @@
|
||||
<!--
|
||||
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>
|
||||
@@ -1,23 +0,0 @@
|
||||
<!--
|
||||
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,6 +108,8 @@ auto constexpr TMP_OFFSETS_EXT = OFFSET_EXT EXTENSION_TMP;
|
||||
#define CROSS_MWM_OSM_WAYS_DIR "cross_mwm_osm_ways"
|
||||
#define TEMP_ADDR_EXTENSION ".tempaddr"
|
||||
|
||||
#define TRAFFIC_FILE_EXTENSION ".traffic"
|
||||
|
||||
#define SKIPPED_ELEMENTS_FILE "skipped_elements.json"
|
||||
|
||||
#define MAPCSS_MAPPING_FILE "mapcss-mapping.csv"
|
||||
|
||||
@@ -38,7 +38,7 @@ See [translations instructions](TRANSLATIONS.md) for details.
|
||||
CoMaps has a strong focus on easy to use UI and smooth user experience. Feel free to join UI/UX discussions in relevant issues. Mockups are very welcome!
|
||||
|
||||
If you're into graphic design then CoMaps needs good, clear and free-to-use icons for hundreds of map features / POIs.
|
||||
Check CoMaps' [design principles](https://codeberg.org/comaps/comaps/wiki/Design-Principles). Post your icons onto relevant issues or take a next step and [integrate them](STYLES.md) yourself.
|
||||
Check CoMaps' [design principles](https://codeberg.org/comaps/comaps/wiki/Design-Principles) and [color scheme](https://codeberg.org/comaps/Governance/src/branch/main/assets/comaps-color-scheme.png). Post your icons onto relevant issues or take a next step and [integrate them](STYLES.md) yourself.
|
||||
|
||||
Check the [map styling instructions](STYLES.md) and work on adding new map features and other open map styles issues.
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ The project consists of multiple components, each with its own translation files
|
||||
|
||||
Components without links haven't been integrated into Weblate and must be translated directly via [Codeberg Pull Requests](CONTRIBUTING.md).
|
||||
|
||||
TTS translations can be improved with [the TTS locale-viewer](https://comaps.codeberg.page/locale-viewer/).
|
||||
|
||||
## Translating
|
||||
|
||||
### Workflow
|
||||
|
||||
@@ -207,6 +207,8 @@ set(SRC
|
||||
tesselator.hpp
|
||||
towns_dumper.cpp
|
||||
towns_dumper.hpp
|
||||
traffic_generator.cpp
|
||||
traffic_generator.hpp
|
||||
transit_generator.cpp
|
||||
transit_generator.hpp
|
||||
transit_generator_experimental.cpp
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "generator/routing_world_roads_generator.hpp"
|
||||
#include "generator/search_index_builder.hpp"
|
||||
#include "generator/statistics.hpp"
|
||||
#include "generator/traffic_generator.hpp"
|
||||
#include "generator/transit_generator.hpp"
|
||||
#include "generator/transit_generator_experimental.hpp"
|
||||
#include "generator/unpack_mwm.hpp"
|
||||
@@ -170,6 +171,7 @@ DEFINE_string(unpack_borders, "", "Convert packed_polygons to a directory of pol
|
||||
DEFINE_bool(unpack_mwm, false, "Unpack each section of mwm into a separate file with name filePath.sectionName.");
|
||||
DEFINE_bool(check_mwm, false, "Check map file to be correct.");
|
||||
DEFINE_string(delete_section, "", "Delete specified section (defines.hpp) from container.");
|
||||
DEFINE_bool(generate_traffic_keys, false, "Generate keys for the traffic map (road segment -> speed group).");
|
||||
|
||||
DEFINE_bool(dump_mwm_tmp, false, "Prints feature builder objects from .mwm.tmp");
|
||||
|
||||
@@ -544,6 +546,12 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
|
||||
BuildPopularPlacesFromDescriptions(dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_generate_traffic_keys)
|
||||
{
|
||||
if (!traffic::GenerateTrafficKeysFromDataFile(dataFile))
|
||||
LOG(LCRITICAL, ("Error generating traffic keys."));
|
||||
}
|
||||
}
|
||||
|
||||
string const dataFile = base::JoinPath(path, FLAGS_output + DATA_FILE_EXTENSION);
|
||||
|
||||
@@ -134,6 +134,8 @@ TagMapping const kDefaultTagMapping = {
|
||||
{OsmElement::Tag("access", "agricultural"), RoadAccess::Type::Private},
|
||||
{OsmElement::Tag("access", "forestry"), RoadAccess::Type::Private},
|
||||
{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
|
||||
|
||||
44
generator/traffic_generator.cpp
Normal file
44
generator/traffic_generator.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#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
|
||||
8
generator/traffic_generator.hpp
Normal file
8
generator/traffic_generator.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace traffic
|
||||
{
|
||||
bool GenerateTrafficKeysFromDataFile(std::string const & mwmPath);
|
||||
} // namespace traffic
|
||||
@@ -1,12 +1,6 @@
|
||||
NS_SWIFT_NAME(SettingsBridge)
|
||||
@interface MWMSettings : NSObject
|
||||
|
||||
+ (BOOL)liveTrafficEnabled;
|
||||
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||
|
||||
+ (NSURL *)liveTrafficUrl;
|
||||
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||
|
||||
+ (BOOL)buildings3dViewEnabled;
|
||||
+ (void)setBuildings3dViewEnabled:(BOOL)buildings3dViewEnabled;
|
||||
|
||||
|
||||
@@ -27,40 +27,6 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
|
||||
|
||||
@implementation MWMSettings
|
||||
|
||||
+ (BOOL)liveTrafficEnabled;
|
||||
{
|
||||
return GetFramework().LoadTrafficHttpEnabled();
|
||||
}
|
||||
|
||||
+ (void)setLiveTrafficEnabled:(BOOL)liveTrafficEnabled;
|
||||
{
|
||||
auto &f = GetFramework();
|
||||
f.SaveTrafficHttpEnabled(liveTrafficEnabled);
|
||||
f.SetTrafficHttpEnabled(liveTrafficEnabled);
|
||||
}
|
||||
|
||||
+ (NSURL *)liveTrafficUrl;
|
||||
{
|
||||
NSString * link = @(GetFramework().LoadTrafficHttpUrl().c_str());
|
||||
if ([link length] == 0) {
|
||||
return nil;
|
||||
} else {
|
||||
return [NSURL URLWithString:link];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setLiveTrafficUrl:(NSURL *)liveTrafficUrl;
|
||||
{
|
||||
auto &f = GetFramework();
|
||||
if (liveTrafficUrl == nil) {
|
||||
f.SaveTrafficHttpUrl(@"".UTF8String);
|
||||
f.SetTrafficHttpUrl(@"".UTF8String);
|
||||
} else {
|
||||
f.SaveTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||
f.SetTrafficHttpUrl(liveTrafficUrl.absoluteString.UTF8String);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)buildings3dViewEnabled;
|
||||
{
|
||||
bool _ = true, on = true;
|
||||
|
||||
@@ -717,13 +717,6 @@
|
||||
"editor_place_doesnt_exist" = "Place does not exist";
|
||||
"text_more_button" = "…more";
|
||||
|
||||
/* Live traffic data */
|
||||
"traffic_http" = "Live Traffic";
|
||||
"traffic_http_enabled" = "Enable live traffic data";
|
||||
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||
"traffic_http_url" = "Traffic service URL";
|
||||
"traffic_http_url_not_set" = "Not set";
|
||||
|
||||
/* Phone number error message */
|
||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||
"error_enter_correct_web" = "Enter a valid web address";
|
||||
@@ -823,7 +816,6 @@
|
||||
"privacy_policy" = "Privacy policy";
|
||||
"terms_of_use" = "Terms of use";
|
||||
"button_layer_subway" = "Metro";
|
||||
"button_layer_traffic" = "Traffic";
|
||||
"layers_title" = "Map Styles and Layers";
|
||||
"subway_data_unavailable" = "Metro map is unavailable";
|
||||
"title_error_downloading_bookmarks" = "An error occurred";
|
||||
|
||||
@@ -737,13 +737,6 @@
|
||||
"editor_place_doesnt_exist_description" = "Describe what the place looks like now to send an error note to the OpenStreetMap community";
|
||||
"text_more_button" = "…more";
|
||||
|
||||
/* Live traffic data */
|
||||
"traffic_http" = "Live Traffic";
|
||||
"traffic_http_enabled" = "Enable live traffic data";
|
||||
"traffic_http_enabled_description" = "When enabled, the app will periodically retrieve traffic information from the configured URL.";
|
||||
"traffic_http_url" = "Traffic service URL";
|
||||
"traffic_http_url_not_set" = "Not set";
|
||||
|
||||
/* Phone number error message */
|
||||
"error_enter_correct_phone" = "Enter a valid phone number";
|
||||
"error_enter_correct_web" = "Enter a valid web address";
|
||||
@@ -844,7 +837,6 @@
|
||||
"privacy_policy" = "Privacy policy";
|
||||
"terms_of_use" = "Terms of use";
|
||||
"button_layer_subway" = "Subway";
|
||||
"button_layer_traffic" = "Traffic";
|
||||
"layers_title" = "Map Styles and Layers";
|
||||
"subway_data_unavailable" = "Subway map is unavailable";
|
||||
"title_error_downloading_bookmarks" = "An error occurred";
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
272F1F3B2E0EE0A300FA52EF /* NoExistingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */; };
|
||||
272F1F3D2E0EE0C800FA52EF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */; };
|
||||
272F1F462E0EEF9400FA52EF /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272F1F452E0EEF8B00FA52EF /* SafariView.swift */; };
|
||||
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 274720592E439FBA00C516DF /* libtraffxml.a */; };
|
||||
2752B6CA2E31197500887CC4 /* MapLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6C92E31197000887CC4 /* MapLanguage.swift */; };
|
||||
2752B6CE2E3121D900887CC4 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752B6CD2E3121D800887CC4 /* Language.swift */; };
|
||||
2765D1D02E13F9C20005CA2B /* BridgeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */; };
|
||||
@@ -768,7 +767,6 @@
|
||||
272F1F3A2E0EE09500FA52EF /* NoExistingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoExistingProfileView.swift; sourceTree = "<group>"; };
|
||||
272F1F3C2E0EE0C400FA52EF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
272F1F452E0EEF8B00FA52EF /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
274720592E439FBA00C516DF /* libtraffxml.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libtraffxml.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2752B6C92E31197000887CC4 /* MapLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLanguage.swift; sourceTree = "<group>"; };
|
||||
2752B6CD2E3121D800887CC4 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = "<group>"; };
|
||||
2765D1CD2E13F9BC0005CA2B /* BridgeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeControllers.swift; sourceTree = "<group>"; };
|
||||
@@ -1794,7 +1792,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2747205A2E439FBA00C516DF /* libtraffxml.a in Frameworks */,
|
||||
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
|
||||
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
|
||||
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
|
||||
@@ -2032,7 +2029,6 @@
|
||||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
274720592E439FBA00C516DF /* libtraffxml.a */,
|
||||
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
|
||||
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
|
||||
FA853BF226BC5DE50026D455 /* libshaders.a */,
|
||||
|
||||
@@ -448,28 +448,6 @@ import AVFoundation
|
||||
}
|
||||
|
||||
|
||||
/// If live traffic data should be used
|
||||
@objc static var hasLiveTraffic: Bool {
|
||||
get {
|
||||
return SettingsBridge.liveTrafficEnabled()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setLiveTrafficEnabled(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The url of the live traffic data server
|
||||
@objc static var liveTrafficServerUrl: URL? {
|
||||
get {
|
||||
return SettingsBridge.liveTrafficUrl()
|
||||
}
|
||||
set {
|
||||
SettingsBridge.setLiveTrafficUrl(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
|
||||
@@ -18,11 +18,6 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
updateOutdoorButton()
|
||||
}
|
||||
}
|
||||
@IBOutlet private var trafficButton: BottomMenuLayerButton! {
|
||||
didSet {
|
||||
updateTrafficButton()
|
||||
}
|
||||
}
|
||||
|
||||
var onClose: (()->())?
|
||||
|
||||
@@ -37,7 +32,6 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
|
||||
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
|
||||
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
|
||||
trafficButton.setupWith(image: UIImage(resource: .btnMenuTraffic), text: L("button_layer_traffic"))
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -62,11 +56,6 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
let enabled = MapOverlayManager.outdoorEnabled()
|
||||
outdoorButton.setStyleAndApply(styleFor(enabled))
|
||||
}
|
||||
|
||||
private func updateTrafficButton() {
|
||||
let enabled = MapOverlayManager.trafficEnabled()
|
||||
trafficButton.setStyleAndApply(styleFor(enabled))
|
||||
}
|
||||
|
||||
@IBAction func onCloseButtonPressed(_ sender: Any) {
|
||||
onClose?()
|
||||
@@ -86,11 +75,6 @@ class BottomMenuLayersCell: UITableViewCell {
|
||||
let enable = !MapOverlayManager.outdoorEnabled()
|
||||
MapOverlayManager.setOutdoorEnabled(enable)
|
||||
}
|
||||
|
||||
@IBAction func onTrafficButton(_ sender: Any) {
|
||||
let enable = !MapOverlayManager.trafficEnabled()
|
||||
MapOverlayManager.setTrafficEnabled(enable)
|
||||
}
|
||||
}
|
||||
|
||||
extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
||||
@@ -105,10 +89,6 @@ extension BottomMenuLayersCell: MapOverlayManagerObserver {
|
||||
func onOutdoorStateUpdated() {
|
||||
updateOutdoorButton()
|
||||
}
|
||||
|
||||
func onTrafficStateUpdated() {
|
||||
updateTrafficButton()
|
||||
}
|
||||
}
|
||||
|
||||
private extension BottomMenuLayersCell {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<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">
|
||||
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -81,35 +81,26 @@
|
||||
<rect key="frame" x="16" y="58" width="308" height="64"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="64"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="1024" y="0.0" width="1024" height="64"/>
|
||||
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="2048" y="0.0" width="1024" height="64"/>
|
||||
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" id="95L-lU-yCQ" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="-358" y="-352" width="1024" height="768"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="ZQs-ua-gDZ"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<action selector="onTrafficButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="tie-i7-JaB"/>
|
||||
</connections>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
|
||||
@@ -153,7 +144,6 @@
|
||||
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
|
||||
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
|
||||
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
|
||||
<outlet property="trafficButton" destination="95L-lU-yCQ" id="O9W-En-8Rc"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
|
||||
</tableViewCell>
|
||||
|
||||
@@ -60,14 +60,6 @@ struct SettingsNavigationView: View {
|
||||
@State var forceRefreshDate: Date = Date.now
|
||||
|
||||
|
||||
/// If live traffic data should be used
|
||||
@State var hasLiveTraffic: Bool = false
|
||||
|
||||
|
||||
/// The url of the live traffic data server
|
||||
@State var liveTrafficServerUrlString: String = ""
|
||||
|
||||
|
||||
/// The actual view
|
||||
var body: some View {
|
||||
List {
|
||||
@@ -220,24 +212,6 @@ struct SettingsNavigationView: View {
|
||||
} header: {
|
||||
Text("driving_options_title")
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $hasLiveTraffic) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("traffic_http_enabled")
|
||||
|
||||
Text("traffic_http_enabled_description")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.tint(.accent)
|
||||
|
||||
TextField("traffic_http_url", text: $liveTrafficServerUrlString, prompt: Text("traffic_http_url_not_set"))
|
||||
.tint(.accent)
|
||||
} header: {
|
||||
Text("traffic_http")
|
||||
}
|
||||
}
|
||||
.accentColor(.accent)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
@@ -255,8 +229,6 @@ struct SettingsNavigationView: View {
|
||||
shouldAvoidFerriesWhileRouting = Settings.shouldAvoidFerriesWhileRouting
|
||||
shouldAvoidMotorwaysWhileRouting = Settings.shouldAvoidMotorwaysWhileRouting
|
||||
shouldAvoidStepsWhileRouting = Settings.shouldAvoidStepsWhileRouting
|
||||
hasLiveTraffic = Settings.hasLiveTraffic
|
||||
liveTrafficServerUrlString = Settings.liveTrafficServerUrl?.absoluteString ?? ""
|
||||
}
|
||||
.onChange(of: scenePhase) { _ in
|
||||
forceRefreshDate = Date.now
|
||||
@@ -304,15 +276,6 @@ struct SettingsNavigationView: View {
|
||||
}
|
||||
.onChange(of: shouldAvoidStepsWhileRouting) { changedShouldAvoidStepsWhileRouting in
|
||||
Settings.shouldAvoidStepsWhileRouting = changedShouldAvoidStepsWhileRouting
|
||||
.onChange(of: hasLiveTraffic) { changedHasLiveTraffic in
|
||||
Settings.hasLiveTraffic = changedHasLiveTraffic
|
||||
}
|
||||
.onChange(of: liveTrafficServerUrlString) { changedLiveTrafficServerUrlString in
|
||||
if !changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, let changedLiveTrafficServerUrl = URL(string: changedLiveTrafficServerUrlString.trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||
Settings.liveTrafficServerUrl = changedLiveTrafficServerUrl
|
||||
} else {
|
||||
Settings.liveTrafficServerUrl = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,4 @@ add_subdirectory(shaders)
|
||||
add_subdirectory(storage)
|
||||
add_subdirectory(tracking)
|
||||
add_subdirectory(traffic)
|
||||
add_subdirectory(traffxml)
|
||||
add_subdirectory(transit)
|
||||
|
||||
@@ -38,9 +38,9 @@ namespace
|
||||
{
|
||||
// The first zoom level in kAverageSegmentsCount.
|
||||
int constexpr kFirstZoomInAverageSegments = 10;
|
||||
std::array<size_t, 11> constexpr kAverageSegmentsCount = {
|
||||
// 10 11 12 13 14 15 16 17 18 19 20
|
||||
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500, 500};
|
||||
std::array<size_t, 10> constexpr kAverageSegmentsCount = {
|
||||
// 10 11 12 13 14 15 16 17 18 19
|
||||
10000, 5000, 10000, 5000, 2500, 5000, 2000, 1000, 500, 500};
|
||||
|
||||
double constexpr kMetersPerLevel = 3.0;
|
||||
|
||||
|
||||
@@ -243,14 +243,11 @@ void ChangesetWrapper::Modify(editor::XMLFeature node)
|
||||
|
||||
void ChangesetWrapper::AddChangesetTag(std::string key, std::string value)
|
||||
{
|
||||
value = strings::EscapeForXML(value);
|
||||
// Truncate to 254 characters as OSM has a length limit of 255
|
||||
if (strings::Truncate(value, kMaximumOsmChars))
|
||||
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("...");
|
||||
}
|
||||
value = strings::EscapeForXML(value);
|
||||
|
||||
m_changesetComments.insert_or_assign(std::move(key), std::move(value));
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ void CreateCafeAtPoint(m2::PointD const & point, MwmSet::MwmId const & mwmId, os
|
||||
|
||||
editor.CreatePoint(classif().GetTypeByPath({"amenity", "cafe"}), point, mwmId, emo);
|
||||
emo.SetHouseNumber("12");
|
||||
emo.LogDiffInJournal({});
|
||||
TEST_EQUAL(editor.SaveEditedFeature(emo), osm::Editor::SaveResult::SavedSuccessfully, ());
|
||||
}
|
||||
|
||||
@@ -157,6 +158,7 @@ void GenerateUploadedFeature(MwmSet::MwmId const & mwmId, osm::EditableMapObject
|
||||
pugi::xml_node created = mwmNode.append_child("create");
|
||||
|
||||
editor::XMLFeature xf = editor::ToXML(emo, true);
|
||||
xf.SetEditJournal(emo.GetJournal());
|
||||
xf.SetMWMFeatureIndex(emo.GetID().m_index);
|
||||
xf.SetModificationTime(time(nullptr));
|
||||
xf.SetUploadTime(time(nullptr));
|
||||
@@ -870,7 +872,7 @@ void EditorTest::CreateNoteTest()
|
||||
};
|
||||
|
||||
// Should match a piece of text in the editor note.
|
||||
constexpr char const * kPlaceDoesNotExistMessage = "The place has gone or never existed";
|
||||
constexpr char const * kPlaceDoesNotExistMessage = "This place does not exist:";
|
||||
|
||||
ForEachCafeAtPoint(m_dataSource, m2::PointD(1.0, 1.0), [&editor, &createAndCheckNote](FeatureType & ft)
|
||||
{
|
||||
|
||||
@@ -115,6 +115,42 @@ UNIT_TEST(XMLFeature_UintLang)
|
||||
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)
|
||||
{
|
||||
XMLFeature feature(XMLFeature::Type::Node);
|
||||
@@ -267,104 +303,52 @@ UNIT_TEST(XMLFeature_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)
|
||||
{
|
||||
classificator::Load();
|
||||
|
||||
std::string const xmlNoTypeStr = R"(<?xml version="1.0"?>
|
||||
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
|
||||
<tag k="name" v="Gorki Park" />
|
||||
<tag k="name:en" v="Gorki Park" />
|
||||
<tag k="name:ru" v="Парк Горького" />
|
||||
<tag k="addr:housenumber" v="10" />
|
||||
<tag k="leisure" v="park" />
|
||||
<tag k="name" v="Gorki Park" />
|
||||
<tag k="name:en" v="Gorki Park" />
|
||||
<tag k="name:ru" v="Парк Горького" />
|
||||
<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>
|
||||
)";
|
||||
|
||||
char const kTimestamp[] = "2015-11-27T21:13:32Z";
|
||||
|
||||
editor::XMLFeature xmlNoType(xmlNoTypeStr);
|
||||
editor::XMLFeature xmlWithType = xmlNoType;
|
||||
xmlWithType.SetTagValue("amenity", "atm");
|
||||
editor::XMLFeature xmlFeature(xmlNoTypeStr);
|
||||
|
||||
osm::EditableMapObject emo;
|
||||
editor::FromXML(xmlWithType, emo);
|
||||
auto fromFtWithType = editor::ToXML(emo, true);
|
||||
fromFtWithType.SetAttribute("timestamp", kTimestamp);
|
||||
TEST_EQUAL(fromFtWithType, xmlWithType, ());
|
||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
||||
emo.ApplyEditsFromJournal(journal);
|
||||
emo.SetJournal(std::move(journal));
|
||||
|
||||
auto fromFtWithoutType = editor::ToXML(emo, false);
|
||||
fromFtWithoutType.SetAttribute("timestamp", kTimestamp);
|
||||
TEST_EQUAL(fromFtWithoutType, xmlNoType, ());
|
||||
auto xmlFromMapObject = editor::ToXML(emo, true);
|
||||
xmlFromMapObject.SetEditJournal(emo.GetJournal());
|
||||
xmlFromMapObject.SetAttribute("timestamp", kTimestamp);
|
||||
TEST_EQUAL(xmlFromMapObject, xmlFeature, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
||||
@@ -373,8 +357,14 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
||||
{
|
||||
std::string const recyclingCentreStr = R"(<?xml version="1.0"?>
|
||||
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
||||
<tag k="amenity" v="recycling" />
|
||||
<tag k="recycling_type" v="centre" />
|
||||
<tag k="amenity" v="recycling" />
|
||||
<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>
|
||||
)";
|
||||
|
||||
@@ -383,21 +373,30 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
||||
editor::XMLFeature xmlFeature(recyclingCentreStr);
|
||||
|
||||
osm::EditableMapObject emo;
|
||||
editor::FromXML(xmlFeature, emo);
|
||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
||||
emo.ApplyEditsFromJournal(journal);
|
||||
emo.SetJournal(std::move(journal));
|
||||
|
||||
auto const th = emo.GetTypes();
|
||||
TEST_EQUAL(th.Size(), 1, ());
|
||||
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "centre"}), ());
|
||||
|
||||
auto convertedFt = editor::ToXML(emo, true);
|
||||
convertedFt.SetEditJournal(emo.GetJournal());
|
||||
convertedFt.SetAttribute("timestamp", kTimestamp);
|
||||
TEST_EQUAL(xmlFeature, convertedFt, ());
|
||||
}
|
||||
{
|
||||
std::string const recyclingContainerStr = R"(<?xml version="1.0"?>
|
||||
<node lat="55.8047445" lon="37.5865532" timestamp="2018-07-11T13:24:41Z">
|
||||
<tag k="amenity" v="recycling" />
|
||||
<tag k="recycling_type" v="container" />
|
||||
<tag k="amenity" v="recycling" />
|
||||
<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>
|
||||
)";
|
||||
|
||||
@@ -406,13 +405,16 @@ UNIT_TEST(XMLFeature_AmenityRecyclingFromAndToXml)
|
||||
editor::XMLFeature xmlFeature(recyclingContainerStr);
|
||||
|
||||
osm::EditableMapObject emo;
|
||||
editor::FromXML(xmlFeature, emo);
|
||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
||||
emo.ApplyEditsFromJournal(journal);
|
||||
emo.SetJournal(std::move(journal));
|
||||
|
||||
auto const th = emo.GetTypes();
|
||||
TEST_EQUAL(th.Size(), 1, ());
|
||||
TEST_EQUAL(th.front(), classif().GetTypeByPath({"amenity", "recycling", "container"}), ());
|
||||
|
||||
auto convertedFt = editor::ToXML(emo, true);
|
||||
convertedFt.SetEditJournal(emo.GetJournal());
|
||||
convertedFt.SetAttribute("timestamp", kTimestamp);
|
||||
TEST_EQUAL(xmlFeature, convertedFt, ());
|
||||
}
|
||||
@@ -466,58 +468,43 @@ UNIT_TEST(XMLFeature_Diet)
|
||||
TEST_EQUAL(ft.GetCuisine(), "", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(XMLFeature_SocialContactsProcessing)
|
||||
{
|
||||
{
|
||||
std::string const nightclubStr = R"(<?xml version="1.0"?>
|
||||
<node lat="50.4082862" lon="30.5130017" timestamp="2022-02-24T05:07:00Z">
|
||||
<tag k="amenity" v="nightclub" />
|
||||
<tag k="name" v="Stereo Plaza" />
|
||||
<tag k="contact:facebook" v="http://www.facebook.com/pages/Stereo-Plaza/118100041593935" />
|
||||
<tag k="contact:instagram" v="https://www.instagram.com/p/CSy87IhMhfm/" />
|
||||
<tag k="contact:line" v="liff.line.me/1645278921-kWRPP32q/?accountId=673watcr" />
|
||||
</node>
|
||||
)";
|
||||
|
||||
editor::XMLFeature xmlFeature(nightclubStr);
|
||||
|
||||
osm::EditableMapObject emo;
|
||||
editor::FromXML(xmlFeature, emo);
|
||||
|
||||
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" />
|
||||
<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" />
|
||||
<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>
|
||||
)";
|
||||
|
||||
editor::XMLFeature xmlFeature(nightclubStr);
|
||||
|
||||
osm::EditableMapObject emo;
|
||||
editor::FromXML(xmlFeature, emo);
|
||||
osm::EditJournal journal = xmlFeature.GetEditJournal();
|
||||
emo.ApplyEditsFromJournal(journal);
|
||||
emo.SetJournal(std::move(journal));
|
||||
|
||||
auto convertedFt = editor::ToXML(emo, true);
|
||||
convertedFt.SetEditJournal(emo.GetJournal());
|
||||
|
||||
TEST(convertedFt.HasTag("contact:facebook"), ());
|
||||
TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ());
|
||||
|
||||
@@ -610,221 +610,101 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
|
||||
|
||||
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
|
||||
{
|
||||
if (useNewEditor)
|
||||
switch (fti.m_status)
|
||||
{
|
||||
LOG(LDEBUG, ("New Editor used\n"));
|
||||
|
||||
switch (fti.m_status)
|
||||
{
|
||||
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
|
||||
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:
|
||||
{
|
||||
// Todo: Remove old editor after transition period
|
||||
switch (fti.m_status)
|
||||
{
|
||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||
case FeatureStatus::Created:
|
||||
{
|
||||
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
||||
if (!fti.m_street.empty())
|
||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
||||
|
||||
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
||||
("Linear and area features creation is not supported yet."));
|
||||
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
|
||||
{
|
||||
auto const center = fti.m_object.GetMercator();
|
||||
// Throws, see catch below.
|
||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
||||
|
||||
// If we are here, it means that object already exists at the given point.
|
||||
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
||||
XMLFeature const osmFeatureCopy = osmFeature;
|
||||
osmFeature.ApplyPatch(feature);
|
||||
// Check to avoid uploading duplicates into OSM.
|
||||
if (osmFeature == osmFeatureCopy)
|
||||
// precision of OSM coordinates (WGS 84), ~= 1 cm
|
||||
constexpr double tolerance = 0.0000001;
|
||||
|
||||
if (AlmostEqualAbs(feature.GetCenter(), osmFeature.GetCenter(), tolerance))
|
||||
{
|
||||
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.
|
||||
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
||||
feature = osmFeature;
|
||||
mergeSameLocation = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.AddChangesetTag("info:features_merged", "yes");
|
||||
changeset.Modify(osmFeature);
|
||||
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
||||
}
|
||||
}
|
||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||
{
|
||||
// Object was never created by anyone else - it's safe to create it.
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
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");
|
||||
{}
|
||||
|
||||
// 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);
|
||||
}
|
||||
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
|
||||
{
|
||||
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Modify(osmFeature);
|
||||
}
|
||||
}
|
||||
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.AddChangesetTag("info:old_editor", "yes");
|
||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||
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;
|
||||
}
|
||||
uploadInfo.m_uploadStatus = kUploaded;
|
||||
uploadInfo.m_uploadError.clear();
|
||||
@@ -907,23 +787,7 @@ void Editor::SaveUploadedInformation(FeatureID const & fid, UploadInfo const & u
|
||||
bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, FeatureID const & fid,
|
||||
FeatureTypeInfo & fti) const
|
||||
{
|
||||
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
|
||||
if (status != FeatureStatus::Created)
|
||||
{
|
||||
auto const originalObjectPtr = GetOriginalMapObject(fid);
|
||||
if (!originalObjectPtr)
|
||||
@@ -933,13 +797,11 @@ bool Editor::FillFeatureInfo(FeatureStatus status, XMLFeature const & xml, Featu
|
||||
}
|
||||
|
||||
fti.m_object = *originalObjectPtr;
|
||||
|
||||
if (loadFromJournal)
|
||||
fti.m_object.ApplyEditsFromJournal(journal);
|
||||
else
|
||||
editor::ApplyPatch(xml, fti.m_object);
|
||||
}
|
||||
|
||||
EditJournal journal = xml.GetEditJournal();
|
||||
fti.m_object.ApplyEditsFromJournal(journal);
|
||||
|
||||
fti.m_object.SetJournal(std::move(journal));
|
||||
fti.m_object.SetID(fid);
|
||||
fti.m_street = xml.GetTagValue(kAddrStreetTag);
|
||||
|
||||
@@ -178,38 +178,6 @@ string XMLFeature::ToOSMString() const
|
||||
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
|
||||
{
|
||||
return mercator::FromLatLon(GetLatLonFromNode(GetRootNode()));
|
||||
@@ -670,6 +638,40 @@ void XMLFeature::RemoveTag(string_view key)
|
||||
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)
|
||||
{
|
||||
if (value.empty())
|
||||
@@ -807,26 +809,6 @@ XMLFeature::Type XMLFeature::StringToType(string const & type)
|
||||
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)
|
||||
{
|
||||
bool const isPoint = object.GetGeomType() == feature::GeomType::Point;
|
||||
@@ -842,6 +824,15 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
||||
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)
|
||||
{ toFeature.SetName(lang, name); });
|
||||
|
||||
@@ -856,61 +847,8 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType)
|
||||
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)
|
||||
{
|
||||
if (osm::isSocialContactTag(tag) && value.find('/') != std::string::npos)
|
||||
toFeature.SetTagValue(tag, osm::socialContactToURL(tag, value));
|
||||
else
|
||||
toFeature.SetTagValue(tag, value);
|
||||
});
|
||||
|
||||
@@ -923,105 +861,11 @@ XMLFeature TypeToXML(uint32_t type, feature::GeomType geomType, m2::PointD merca
|
||||
XMLFeature toFeature(XMLFeature::Type::Node);
|
||||
toFeature.SetCenter(mercator);
|
||||
|
||||
// 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;
|
||||
toFeature.SetOSMTagsForType(type);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
std::ostringstream ost;
|
||||
|
||||
@@ -73,9 +73,6 @@ public:
|
||||
void Save(std::ostream & ost) const;
|
||||
std::string ToOSMString() const;
|
||||
|
||||
/// Tags from featureWithChanges are applied to this(osm) feature.
|
||||
void ApplyPatch(XMLFeature const & featureWithChanges);
|
||||
|
||||
Type GetType() const;
|
||||
std::string GetTypeString() const;
|
||||
|
||||
@@ -185,6 +182,8 @@ public:
|
||||
void SetTagValue(std::string_view key, std::string_view value);
|
||||
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
|
||||
void UpdateOSMTag(std::string_view key, std::string_view value);
|
||||
/// Replace an old business with a new business
|
||||
@@ -205,22 +204,15 @@ private:
|
||||
pugi::xml_document m_document;
|
||||
};
|
||||
|
||||
/// Rewrites all but geometry and types.
|
||||
/// Should be applied to existing features only (in mwm files).
|
||||
void ApplyPatch(XMLFeature const & xml, osm::EditableMapObject & object);
|
||||
|
||||
/// @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.
|
||||
/// @param serializeType if false, type is not serialized.
|
||||
/// This function converts the current state of a MapObject to a format similar to OSM style XML.
|
||||
/// Tags written in this function are used to see POI details when debugging. Only the data stored
|
||||
/// in the EditJournal is used for OSM editing.
|
||||
XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType);
|
||||
|
||||
/// Used to generate XML for created objects in the new editor
|
||||
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::Type const type);
|
||||
} // namespace editor
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
@@ -10,20 +9,17 @@
|
||||
|
||||
namespace m2
|
||||
{
|
||||
/**
|
||||
* @brief This class holds a parametrization of the line segment between two points `p0` and `p1`.
|
||||
*
|
||||
* The parametrization is of the form
|
||||
* `p(t) = p0 + t * dir`.
|
||||
*
|
||||
* Other conditions:
|
||||
* * `dir` is the normalized `(p1 - p0)` vector.
|
||||
* * `length(dir) = 1`.
|
||||
* * `p(0) = p0`.
|
||||
* * `p(T) = p1` with `T = length(p1 - p0)`.
|
||||
*
|
||||
* The points with `t` in `[0, T]` are the points of the segment.
|
||||
*/
|
||||
// This class holds a parametrization of the
|
||||
// line segment between two points p0 and p1.
|
||||
// The parametrization is of the form
|
||||
// p(t) = p0 + t * dir.
|
||||
// Other conditions:
|
||||
// dir is the normalized (p1 - p0) vector.
|
||||
// length(dir) = 1.
|
||||
// p(0) = p0.
|
||||
// p(T) = p1 with T = length(p1 - p0).
|
||||
//
|
||||
// The points with t in [0, T] are the points of the segment.
|
||||
template <typename Point>
|
||||
class ParametrizedSegment
|
||||
{
|
||||
@@ -40,9 +36,7 @@ public:
|
||||
m_d = m_d / m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the squared (euclidean) distance from the segment to `p`.
|
||||
*/
|
||||
// Returns the squared (euclidean) distance from the segment to |p|.
|
||||
double SquaredDistanceToPoint(Point const & p) const
|
||||
{
|
||||
m2::PointD const diff(p - m_p0);
|
||||
@@ -58,22 +52,9 @@ public:
|
||||
return math::Pow2(CrossProduct(diff, m_d));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
// Returns the point of the segment that is closest to |p|.
|
||||
m2::PointD ClosestPointTo(Point const & p) const
|
||||
{
|
||||
if (snapToEnds)
|
||||
{
|
||||
if (mercator::DistanceOnEarth(p, m_p0) < mercator::DistanceOnEarth(p, m_p1))
|
||||
return m_p0;
|
||||
else
|
||||
return m_p1;
|
||||
}
|
||||
|
||||
m2::PointD const diff(p - m_p0);
|
||||
double const t = DotProduct(m_d, diff);
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@ set(SRC
|
||||
interval_index_builder.hpp
|
||||
isolines_info.cpp
|
||||
isolines_info.hpp
|
||||
|
||||
|
||||
localized_types_map.cpp
|
||||
map_object.cpp
|
||||
map_object.hpp
|
||||
map_style.cpp
|
||||
|
||||
@@ -19,31 +19,20 @@ public:
|
||||
/// Registers a new map.
|
||||
std::pair<MwmId, RegResult> RegisterMap(platform::LocalCountryFile const & localFile);
|
||||
|
||||
/**
|
||||
* @brief Deregisters a map from internal records.
|
||||
* @param countryFile A `CountryFile` denoting a map to be deregistered.
|
||||
* @return True if the map was successfully deregistered, false if the map is locked now.
|
||||
*/
|
||||
/// Deregisters a map from internal records.
|
||||
///
|
||||
/// \param countryFile A countryFile denoting a map to be deregistered.
|
||||
/// \return True if the map was successfully deregistered. If map is locked
|
||||
/// now, returns false.
|
||||
bool DeregisterMap(platform::CountryFile const & countryFile);
|
||||
|
||||
void ForEachFeatureIDInRect(FeatureIdCallback const & f, m2::RectD const & rect, int scale,
|
||||
covering::CoveringMode mode = covering::ViewportWithLowLevels) const;
|
||||
void ForEachInRect(FeatureCallback const & f, m2::RectD const & rect, int scale) const;
|
||||
|
||||
/**
|
||||
* @brief Iterates over features within a given distance of a center point.
|
||||
*
|
||||
* Calls `f` for features closest to `center` until `stopCallback` returns true or distance
|
||||
* `sizeM` from has been reached. Then for EditableDataSource calls `f` for each edited feature
|
||||
* inside square with center `center` and side `2 * sizeM`. Edited features are not in the same
|
||||
* hierarchy and there is no fast way to merge frozen and edited features.
|
||||
*
|
||||
* @brief f Callback function that is called on each feature.
|
||||
* @brief stopCallback Callback function which decides whether to continue searching or stop.
|
||||
* @brief center The center of the search area.
|
||||
* @brief sizeM The size of the search area, as a distance from the center point.
|
||||
* @brief scale
|
||||
*/
|
||||
// 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.
|
||||
void ForClosestToPoint(FeatureCallback const & f, StopSearchCallback const & stopCallback, m2::PointD const & center,
|
||||
double sizeM, int scale) const;
|
||||
void ForEachInScale(FeatureCallback const & f, int scale) const;
|
||||
@@ -77,24 +66,18 @@ private:
|
||||
std::unique_ptr<FeatureSourceFactory> m_factory;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A `DataSource` which operates with features from an MWM file and does not support
|
||||
* creation, deletion or modification of features.
|
||||
*/
|
||||
// DataSource which operates with features from mwm file and does not support features creation
|
||||
// deletion or modification.
|
||||
class FrozenDataSource : public DataSource
|
||||
{
|
||||
public:
|
||||
FrozenDataSource() : DataSource(std::make_unique<FeatureSourceFactory>()) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Guard for loading features from particular MWM by demand.
|
||||
*
|
||||
* @note If you need to work with `FeatureType` from different threads, you need to use
|
||||
* a unique `FeaturesLoaderGuard` instance for every thread.
|
||||
* For an example of concurrent extracting feature details please see `ConcurrentFeatureParsingTest`
|
||||
* in `routing/routing_integration_tests`.
|
||||
*/
|
||||
/// Guard for loading features from particular MWM by demand.
|
||||
/// @note If you need to work with FeatureType from different threads you need to use
|
||||
/// a unique FeaturesLoaderGuard instance for every thread.
|
||||
/// For an example of concurrent extracting feature details please see ConcurrentFeatureParsingTest.
|
||||
class FeaturesLoaderGuard
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -479,6 +479,9 @@ bool EditableMapObject::CheckHouseNumberWhenIsAddress() const
|
||||
// static
|
||||
bool EditableMapObject::ValidateFlats(string const & flats)
|
||||
{
|
||||
if (strings::CountChar(flats) > kMaximumOsmChars)
|
||||
return false;
|
||||
|
||||
for (auto it = strings::SimpleTokenizer(flats, ";"); it; ++it)
|
||||
{
|
||||
string_view token = *it;
|
||||
@@ -516,6 +519,9 @@ bool EditableMapObject::ValidatePhoneList(string const & phone)
|
||||
if (phone.empty())
|
||||
return true;
|
||||
|
||||
if (strings::CountChar(phone) > kMaximumOsmChars)
|
||||
return false;
|
||||
|
||||
auto constexpr kMaxNumberLen = 15;
|
||||
auto constexpr kMinNumberLen = 5;
|
||||
|
||||
@@ -556,6 +562,9 @@ bool EditableMapObject::ValidateEmail(string const & email)
|
||||
if (email.empty())
|
||||
return true;
|
||||
|
||||
if (strings::CountChar(email) > kMaximumOsmChars)
|
||||
return false;
|
||||
|
||||
if (strings::IsASCIIString(email))
|
||||
{
|
||||
static auto const s_emailRegex = regex(R"([^@\s]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$)");
|
||||
@@ -589,6 +598,9 @@ bool EditableMapObject::ValidateLevel(string const & level)
|
||||
if (level.empty())
|
||||
return true;
|
||||
|
||||
if (strings::CountChar(level) > kMaximumOsmChars)
|
||||
return false;
|
||||
|
||||
if (level.front() == ';' || level.back() == ';' || level.find(";;") != std::string::npos)
|
||||
return false;
|
||||
|
||||
@@ -633,6 +645,9 @@ bool EditableMapObject::ValidateName(string const & name)
|
||||
if (name.empty())
|
||||
return true;
|
||||
|
||||
if (strings::CountChar(name) > kMaximumOsmChars)
|
||||
return false;
|
||||
|
||||
static std::u32string_view constexpr excludedSymbols = U"^§><*=_±√•÷×¶";
|
||||
|
||||
using Iter = utf8::unchecked::iterator<string::const_iterator>;
|
||||
|
||||
@@ -70,6 +70,7 @@ class EditableMapObject : public MapObject
|
||||
{
|
||||
public:
|
||||
static uint8_t constexpr kMaximumLevelsEditableByUsers = 50;
|
||||
static int constexpr kMaximumOsmChars = 255;
|
||||
|
||||
bool IsNameEditable() const;
|
||||
bool IsAddressEditable() const;
|
||||
|
||||
@@ -76,18 +76,6 @@ public:
|
||||
// (number of points in inner triangle-strips).
|
||||
using PointsBufferT = buffer_vector<m2::PointD, 32>;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the points of the feature.
|
||||
*
|
||||
* Depending on `scale`, the geometry may be simplified by reducing groups of nearby points to
|
||||
* one point. If `scale` equals `FeatureType::BEST_GEOMETRY`, no such simplification takes place.
|
||||
*
|
||||
* Points are cached between calls and `scale` may not be honored if cached points are returned.
|
||||
* To reliably enforce `scale`, call `ResetGemoetry()` immediately prior to `GetPoints()`.
|
||||
*
|
||||
* @param scale The map scale
|
||||
* @return The points of the feature, simplified according to `scale`.
|
||||
*/
|
||||
PointsBufferT const & GetPoints(int scale);
|
||||
PointsBufferT const & GetTrianglesAsPoints(int scale);
|
||||
|
||||
@@ -97,13 +85,6 @@ public:
|
||||
void ParseHeader2();
|
||||
void ParseRelations();
|
||||
void ParseAllBeforeGeometry() { ParseRelations(); }
|
||||
|
||||
/**
|
||||
* @brief Resets the geometry.
|
||||
*
|
||||
* This discards any cached points, resulting in points being re-fetched the next time
|
||||
* `GetPoints()` or `GetTrianglesAsPoints()` is called.
|
||||
*/
|
||||
void ResetGeometry();
|
||||
void ParseGeometry(int scale);
|
||||
void ParseTriangles(int scale);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user