Compare commits

..

16 Commits

Author SHA1 Message Date
Dobri Dabar
4691de7a66 [desktop] Display type names
Signed-off-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
Co-authored-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
Co-committed-by: Dobri Dabar <dobridabar@noreply.codeberg.org>
2025-12-15 19:36:29 +01:00
Jean-Baptiste
3c082b0629 [android] Rework shape of zoom buttons
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-15 18:59:54 +01:00
Jean-Baptiste
a468bd3fab [docs] Update docs and funding config
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-15 13:09:27 +01:00
x7z4w
fc87316184 [search] Fix apartment blocks search
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2025-12-14 20:52:17 +00:00
map-per
298518ae72 [Android] Fix crash when selectedCountry is null
Signed-off-by: map-per <map-per@gmx.de>
2025-12-13 22:58:25 +01:00
Jean-Baptiste
3bad6d25f0 [android] Remove usage of attr fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 19:28:28 +01:00
Jean-Baptiste
d36361d669 [android] Fix aligment of items in AppBar
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 17:08:35 +01:00
Jean-Baptiste
688e20b1a6 [android] Revert M3 changes about main fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-13 10:39:47 +01:00
Jean-Baptiste
85462161b2 [android] Fix theme not apply correctly
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-12 21:11:04 +01:00
Henry Sternberg
b929823f6b [routing] Avoid using parking aisles
Signed-off-by: Henry Sternberg <henry@bluelightmaps.com>
Co-authored-by: Henry Sternberg <henry@bluelightmaps.com>
Co-committed-by: Henry Sternberg <henry@bluelightmaps.com>
2025-12-12 15:37:21 +01:00
Jean-Baptiste
22dd799585 [android] Remove hardcoded MaterialComponents fonts styles
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 20:33:24 +01:00
Jean-Baptiste
6864d101e2 [android] Migrate text appearance to Material 3
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 19:36:54 +01:00
Jean-Baptiste
4bfb62b373 [android] Fix height of all day view
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2025-12-11 16:22:22 +01:00
map-per
f1cf844986 [editor] Check OSM max char length in value validation
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 09:09:47 +01:00
map-per
f20c3bf50c [Android] Add info about outdated maps and update button to place page
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:11:44 +01:00
map-per
e7cc602904 [editor] Remove unused code from the old OSM editor
Signed-off-by: map-per <map-per@gmx.de>
2025-12-11 08:03:31 +01:00
200 changed files with 1790 additions and 14156 deletions

View File

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

View File

@@ -1 +1,3 @@
open_collective: comaps
liberapay: comaps
custom: ["https://comaps.app/donate/"]

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
open_collective: comaps
liberapay: comaps
custom: ["https://comaps.app/donate/"]

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,111 +0,0 @@
/*
* Copyright © 20172020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
import java.util.List;
import app.organicmaps.sdk.traffxml.Version;
import app.organicmaps.sdk.traffxml.AndroidTransport;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
public class AndroidConsumer {
/**
* Creates an Intent filter which matches the Intents a TraFF consumer needs to receive.
*
* <p>Different filters are available for consumers implementing different versions of the TraFF
* specification.
*
* @param version The version of the TraFF specification (one of the constants in {@link org.traffxml.traff.Version})
*
* @return An intent filter matching the necessary Intents
*/
public static IntentFilter createIntentFilter(int version) {
IntentFilter res = new IntentFilter();
switch (version) {
case Version.V0_7:
res.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
break;
case Version.V0_8:
res.addAction(AndroidTransport.ACTION_TRAFF_PUSH);
res.addDataScheme(AndroidTransport.CONTENT_SCHEMA);
try {
res.addDataType(AndroidTransport.MIME_TYPE_TRAFF);
} catch (MalformedMimeTypeException e) {
// as long as the constant is a well-formed MIME type, this exception never gets thrown
e.printStackTrace();
}
break;
default:
throw new IllegalArgumentException("Invalid version code: " + version);
}
return res;
}
/**
* Sends a TraFF intent to a source.
*
* <p>This encapsulates most of the low-level Android handling.
*
* <p>If the recipient specified in {@code packageName} declares multiple receivers for the intent in its
* manifest, a separate intent will be delivered to each of them. The intent will not be delivered to
* receivers registered at runtime.
*
* <p>All intents are sent as explicit ordered broadcasts. This means two things:
*
* <p>Any app which declares a matching receiver in its manifest will be woken up to process the intent.
* This works even with certain Android 7 builds which restrict intent delivery to apps which are not
* currently running.
*
* <p>It is safe for the recipient to unconditionally set result data. If the recipient does not set
* result data, the result will have a result code of
* {@link org.traffxml.transport.android.AndroidTransport#RESULT_INTERNAL_ERROR}, no data and no extras.
*
* @param context The context
* @param action The intent action.
* @param data The intent data (for TraFF, this is the content provider URI), or null
* @param extras The extras for the intent
* @param packageName The package name for the intent recipient, or null to deliver the intent to all matching receivers
* @param receiverPermission A permission which the recipient must hold, or null if not required
* @param resultReceiver A BroadcastReceiver which will receive the result for the intent
*/
public static void sendTraffIntent(Context context, String action, Uri data, Bundle extras, String packageName,
String receiverPermission, BroadcastReceiver resultReceiver) {
Intent outIntent = new Intent(action);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(outIntent, 0);
if (receivers != null)
for (ResolveInfo receiver : receivers) {
if ((packageName != null) && !packageName.equals(receiver.activityInfo.applicationInfo.packageName))
continue;
ComponentName cn = new ComponentName(receiver.activityInfo.applicationInfo.packageName,
receiver.activityInfo.name);
outIntent = new Intent(action);
if (data != null)
outIntent.setData(data);
if (extras != null)
outIntent.putExtras(extras);
outIntent.setComponent(cn);
context.sendOrderedBroadcast (outIntent,
receiverPermission,
resultReceiver,
null, // scheduler,
AndroidTransport.RESULT_INTERNAL_ERROR, // initialCode,
null, // initialData,
null);
}
}
}

View File

@@ -1,222 +0,0 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
public class AndroidTransport {
/**
* Intent to poll a peer for its capabilities.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*/
public static final String ACTION_TRAFF_GET_CAPABILITIES = "org.traffxml.traff.GET_CAPABILITIES";
/**
* Intent to send a heartbeat to a peer.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*/
public static final String ACTION_TRAFF_HEARTBEAT = "org.traffxml.traff.GET_HEARTBEAT";
/**
* Intent to poll a source for information.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>Polling is a legacy feature on Android and deprecated in TraFF 0.8 (rather than polling, TraFF 0.8
* applications query the content provider). Therefore, poll operations are subscriptionless, and the
* source should either reply with all messages it currently holds, or ignore the request.
*/
@Deprecated
public static final String ACTION_TRAFF_POLL = "org.traffxml.traff.POLL";
/**
* Intent for a push feed.
*
* <p>This is a broadcast intent. It can be used in different forms:
*
* <p>As of TraFF 0.8, it must be sent as an explicit broadcast and include the
* {@link #EXTRA_SUBSCRIPTION_ID} extra. The intent data must be a URI to the content provider from which
* the messages can be retrieved. The {@link #EXTRA_FEED} extra is not supported. The feed is part of a
* subscription and will contain only changes over feeds sent previously as part of the same
* subscription.
*
* <p>Legacy applications omit the {@link #EXTRA_SUBSCRIPTION_ID} extra and may send it as an implicit
* broadcast. If an application supports both legacy transport and TraFF 0.8 or later, it must include
* the {@link #EXTRA_PACKAGE} extra. The feed is sent in the {@link #EXTRA_FEED} extra, as legacy
* applications may not support content providers. If sent as a response to a subscriptionless poll, the
* source should include all messages it holds, else the set of messages included is at the discretion of
* the source.
*
* <p>Future applications may reintroduce unsolicited push operations for certain scenarios.
*/
public static final String ACTION_TRAFF_PUSH = "org.traffxml.traff.FEED";
/**
* Intent for a subscription request.
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>The filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra.
*
* <p>The sender must indicate its package name in the {@link #EXTRA_PACKAGE} extra.
*/
public static final String ACTION_TRAFF_SUBSCRIBE = "org.traffxml.traff.SUBSCRIBE";
/**
* Intent for a subscription change request,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between
* the calling consumer and the source which receives the broadcast.
*
* <p>The new filter list must be specified in the {@link #EXTRA_FILTER_LIST} extra.
*/
public static final String ACTION_TRAFF_SUBSCRIPTION_CHANGE = "org.traffxml.traff.SUBSCRIPTION_CHANGE";
/**
* Intent for an unsubscribe request,
*
* <p>This is a broadcast intent and must be sent as an explicit broadcast.
*
* <p>This intent must have {@link #EXTRA_SUBSCRIPTION_ID} set to the ID of an existing subscription between
* the calling consumer and the source which receives the broadcast. It signals that the consumer is no
* longer interested in receiving messages related to that subscription, and that the source should stop
* sending updates. Unsubscribing from a nonexistent subscription is a no-op.
*/
public static final String ACTION_TRAFF_UNSUBSCRIBE = "org.traffxml.traff.UNSUBSCRIBE";
/**
* Name for the column which holds the message data.
*/
public static final String COLUMN_DATA = "data";
/**
* Schema for TraFF content URIs.
*/
public static final String CONTENT_SCHEMA = "content";
/**
* String representations of TraFF result codes
*/
public static final String[] ERROR_STRINGS = {
"unknown (0)",
"invalid request (1)",
"subscription rejected by the source (2)",
"requested area not covered (3)",
"requested area partially covered (4)",
"subscription ID not recognized by the source (5)",
"unknown (6)",
"source reported an internal error (7)"
};
/**
* Extra which contains the capabilities of the peer.
*
* <p>This is a String extra. It contains a {@code capabilities} XML element.
*/
public static final String EXTRA_CAPABILITIES = "capabilities";
/**
* Extra which contains a TraFF feed.
*
* <p>This is a String extra. It contains a {@code feed} XML element.
*
* <p>The sender should be careful to keep the size of this extra low, as Android has a 1 MByte limit on all
* pending Binder transactions. However, there is no feedback to the sender about the capacity still
* available, or whether a request exceeds that limit. Therefore, senders should keep the size if each
* feed significantly below that limit. If necessary, they should split up a feed into multiple smaller
* ones and send them with a delay in between.
*
* <p>This mechanism is deprecated since TraFF 0.8 and peers are no longer required to support it. Peers
* which support TraFF 0.8 must rely on content providers for message transport.
*/
@Deprecated
public static final String EXTRA_FEED = "feed";
/**
* Extra which contains a filter list.
*
* <p>This is a String extra. It contains a {@code filter_list} XML element.
*/
public static final String EXTRA_FILTER_LIST = "filter_list";
/**
* Extra which contains the package name of the app sending it.
*
* <p>This is a String extra.
*/
public static final String EXTRA_PACKAGE = "package";
/**
* Extra which contains a subscription ID.
*
* <p>This is a String extra.
*/
public static final String EXTRA_SUBSCRIPTION_ID = "subscription_id";
/**
* Extra which contains the timeout duration for a subscription.
*
* <p>This is an integer extra.
*/
public static final String EXTRA_TIMEOUT = "timeout";
/**
* The MIME type for TraFF content providers.
*/
public static final String MIME_TYPE_TRAFF = "vnd.android.cursor.dir/org.traffxml.message";
/**
* The operation completed successfully.
*/
public static final int RESULT_OK = -1;
/**
* An internal error prevented the recipient from fulfilling the request.
*/
public static final int RESULT_INTERNAL_ERROR = 7;
/**
* A nonexistent operation was attempted, or an operation was attempted with incomplete or otherwise
* invalid data.
*/
public static final int RESULT_INVALID = 1;
/**
* The subscription was rejected, and no messages will be sent.
*/
public static final int RESULT_SUBSCRIPTION_REJECTED = 2;
/**
* The subscription was rejected because the source will never provide messages matching the selection.
*/
public static final int RESULT_NOT_COVERED = 3;
/**
* The subscription was accepted but the source can only provide messages for parts of the selection.
*/
public static final int RESULT_PARTIALLY_COVERED = 4;
/**
* The request failed because it refers to a subscription which does not exist between the source and
* consumer involved.
*/
public static final int RESULT_SUBSCRIPTION_UNKNOWN = 5;
/**
* The request failed because the aggregator does not accept unsolicited push requests from the sensor.
*/
public static final int RESULT_PUSH_REJECTED = 6;
public static String formatTraffError(int code) {
if ((code < 0) || (code >= ERROR_STRINGS.length))
return String.format("unknown (%d)", code);
else
return ERROR_STRINGS[code];
}
}

View File

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

View File

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

View File

@@ -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 dont recognize, or if we start keeping a persistent list of
* subscriptions, we need to delete the subscription from our list. Until then, there is
* nothing to do here: either the subscription isnt in the list, or we are about to shut
* down and the whole list is about to get discarded.
*/
} else if (intent.getAction().equals(AndroidTransport.ACTION_TRAFF_HEARTBEAT)) {
String subscriptionId = intent.getStringExtra(AndroidTransport.EXTRA_SUBSCRIPTION_ID);
if (subscriptionId.equals(this.subscriptionId)) {
Logger.d(this.getClass().getSimpleName(),
String.format("got a heartbeat from %s for subscription %s; sending result",
intent.getStringExtra(AndroidTransport.EXTRA_PACKAGE), subscriptionId));
this.setResult(AndroidTransport.RESULT_OK, null, null);
}
} // intent.getAction()
// TODO Auto-generated method stub
}
/**
* Fetches TraFF messages from a content provider.
*
* @param context The context to use for the content resolver
* @param uri The content provider URI
*/
private void fetchMessages(Context context, Uri uri) {
try {
Cursor cursor = context.getContentResolver().query(uri, new String[] {AndroidTransport.COLUMN_DATA}, null, null, null);
if (cursor == null)
return;
if (cursor.getCount() < 1) {
cursor.close();
return;
}
StringBuilder builder = new StringBuilder("<feed>\n");
while (cursor.moveToNext())
builder.append(cursor.getString(cursor.getColumnIndex(AndroidTransport.COLUMN_DATA))).append("\n");
builder.append("</feed>");
cursor.close();
onFeedReceived(builder.toString());
} catch (Exception e) {
Logger.w(this.getClass().getSimpleName(),
String.format("Unable to fetch messages from %s", uri.toString()), e);
e.printStackTrace();
}
}
}

View File

@@ -1,18 +0,0 @@
/*
* Copyright © 20192020 traffxml.org.
*
* Relicensed to CoMaps by the original author.
*/
package app.organicmaps.sdk.traffxml;
/**
* Constants for versions.
*/
public class Version {
/** Version 0.7: introduced transport on Android. */
public static final int V0_7 = 7;
/** Version 0.8: introduced subscriptions and HTTP transport. */
public static final int V0_8 = 8;
}

View File

@@ -70,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);
}

View File

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

View File

@@ -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": "местност|регион|Място",

View File

@@ -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": "Местность|регион",

View File

@@ -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": "Місцевість|регіон|Місце",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,9 +38,9 @@ namespace
{
// The first zoom level in kAverageSegmentsCount.
int constexpr kFirstZoomInAverageSegments = 10;
std::array<size_t, 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;

View File

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

View File

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

View File

@@ -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", ());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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