Compare commits

..

22 Commits

Author SHA1 Message Date
Konstantin Pastbin
f71058f0f5 WIP try 184
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2026-01-03 23:48:43 +07:00
Vraj Shah
b07c22fc91 [android] Adjust map buttons margins in route building
Signed-off-by: Vraj Shah <vrajshah006@gmail.com>
2026-01-03 23:48:41 +07:00
Konstantin Pastbin
f5a9973c73 [search] Enable Lithuanian for search categories/synonyms
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2026-01-03 23:15:26 +07:00
Konstantin Pastbin
1d6d071c1a [planet] Update map data to 260101-test
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2026-01-03 23:15:16 +07:00
gekeleda
dc259f2b0e [android] Increase logged info of rejected locations
Signed-off-by: gekeleda <git@davidgekeler.eu>
2026-01-03 23:15:16 +07:00
gekeleda
ca6b0286c8 [android] remove previous location quality check 2026-01-03 23:15:16 +07:00
gekeleda
f3fd814d07 [android] Fix most accurate location provider
Signed-off-by: gekeleda <mail@davidgekeler.eu>
2026-01-03 23:15:16 +07:00
x7z4w
6073cd1ca3 [drape] Adjust double- and long-tap delays
Signed-off-by: x7z4w <x7z4w@noreply.codeberg.org>
2026-01-03 23:15:16 +07:00
eisa01
d29afb5b95 [carplay] Add myPositionMode button to iOS <26 workaround
See PR #2474

Signed-off-by: eisa01 <eisa01@gmail.com>
2026-01-03 23:15:16 +07:00
eisa01
71cfe12164 [carplay] Use system buttons for better appearance
Signed-off-by: eisa01 <eisa01@gmail.com>
2026-01-03 23:15:16 +07:00
Johannes
59a6c6e630 make sure to use the correct myPositionMode terminology 2026-01-03 23:15:16 +07:00
Johannes
374667366b Add position mode toggle for CarPlay 2026-01-03 23:15:16 +07:00
Yannik Bloscheck
e18e85a49d [ios] Don't let custom map appearance influence CarPlay
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2026-01-03 23:15:16 +07:00
Yannik Bloscheck
c7db4f9c33 [ios] Always use light map appearance by default
Signed-off-by: Yannik Bloscheck <git@yannikbloscheck.com>
2026-01-03 23:15:16 +07:00
Leonardo Bishop
266c38ed78 [ios] Handle distinct FollowAndRotateRoute and FollowAndRotateCompass modes
Signed-off-by: Leonardo Bishop <me@leonardobishop.net>
2026-01-03 23:15:16 +07:00
Leonardo Bishop
f4d6e32605 [android] Handle distinct FollowAndRotateRoute and FollowAndRotateCompass modes
Signed-off-by: Leonardo Bishop <me@leonardobishop.net>
2026-01-03 23:15:16 +07:00
Leonardo Bishop
1b15810260 [drape] Add FollowAndRotateRoute mode
This adds a new mode to rotate the camera towards the route while driving.
This is done by looking ahead X number of seconds along the current route,
up to the next turn, and calculating the angle between the current position
and then.

Squashed commits:

[drape] Add bearing calculation via route lookahead while navigating
[drape] Extract NavigationContext from OnLocationUpdate params
[drape] Increase rotation animation duration during navigation
[drape] Seperate route and compass rotation into two modes
[drape] Reduce route lookahead maximum to 25 seconds
[drape] Fix case where rotation was incorrectly considered invalid
[drape] Make FollowAndRotateRoute mode only available during driving navigation
[drape] Rewrite FollowedPolyline::GetLookaheadPoint
[drape] Cleanup

Signed-off-by: Leonardo Bishop <me@leonardobishop.net>
2026-01-03 23:15:16 +07:00
thesupertechie
614ea4ec9c [core] remove random key file
Signed-off-by: thesupertechie <thesupertechie1@gmail.com>
2026-01-03 23:15:16 +07:00
thesupertechie
f5d3417997 [tts] add tamil TTS translation
Signed-off-by: thesupertechie <thesupertechie1@gmail.com>
2026-01-03 23:15:16 +07:00
Jean-Baptiste
f6ff08619e Fix link in FAQ
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2026-01-03 14:54:49 +01:00
Jean-Baptiste
a4df3eaad5 [android] Remove untranslated languages
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2026-01-03 12:41:22 +01:00
Jean-Baptiste
55f55bbde1 [android] Enable more languages on Android
Signed-off-by: Jean-Baptiste <jeanbaptiste.charron@outlook.fr>
2026-01-03 12:35:31 +01:00
46 changed files with 2959 additions and 2503 deletions

View File

@@ -9,7 +9,8 @@ import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_HELP_CODE;
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_RECORD_TRACK_CODE;
import static app.organicmaps.leftbutton.LeftButtonsHolder.BUTTON_SETTINGS_CODE;
import static app.organicmaps.sdk.location.LocationState.FOLLOW;
import static app.organicmaps.sdk.location.LocationState.FOLLOW_AND_ROTATE;
import static app.organicmaps.sdk.location.LocationState.FOLLOW_AND_ROTATE_COMPASS;
import static app.organicmaps.sdk.location.LocationState.FOLLOW_AND_ROTATE_ROUTE;
import static app.organicmaps.sdk.location.LocationState.LOCATION_TAG;
import static app.organicmaps.sdk.util.PowerManagment.POWER_MANAGEMENT_TAG;
import static app.organicmaps.sdk.util.Utils.dimen;
@@ -1900,7 +1901,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
locationHelper.restartWithNewMode();
if ((newMode == FOLLOW || newMode == FOLLOW_AND_ROTATE) && !LocationUtils.checkFineLocationPermission(this))
if ((newMode == FOLLOW || newMode == FOLLOW_AND_ROTATE_ROUTE || newMode == FOLLOW_AND_ROTATE_COMPASS)
&& !LocationUtils.checkFineLocationPermission(this))
{
// Try to optimistically request FINE permission for FOLLOW and FOLLOW_AND_ROTATE modes.
Logger.i(LOCATION_TAG, "Requesting ACCESS_FINE_LOCATION permission for " + LocationState.nameOf(newMode));

View File

@@ -183,11 +183,16 @@ public final class UiHelpers
drawableRes = R.drawable.ic_follow;
tintColor = Colors.LOCATION_TINT;
}
case LocationState.FOLLOW_AND_ROTATE ->
case LocationState.FOLLOW_AND_ROTATE_COMPASS ->
{
drawableRes = R.drawable.ic_follow_and_rotate;
tintColor = Colors.LOCATION_TINT;
}
case LocationState.FOLLOW_AND_ROTATE_ROUTE ->
{
drawableRes = R.drawable.ic_follow_route;
tintColor = Colors.LOCATION_TINT;
}
default -> throw new IllegalArgumentException("Invalid button mode: " + locationMode);
}

View File

@@ -49,8 +49,8 @@ public class MyPositionButton
int colorAttr = R.attr.iconTint;
@DimenRes
int sizeDimen = R.dimen.map_button_icon_size;
if (mode == LocationState.FOLLOW || mode == LocationState.FOLLOW_AND_ROTATE
|| mode == LocationState.PENDING_POSITION)
if (mode == LocationState.FOLLOW || mode == LocationState.FOLLOW_AND_ROTATE_ROUTE
|| mode == LocationState.FOLLOW_AND_ROTATE_COMPASS || mode == LocationState.PENDING_POSITION)
{
colorAttr = com.google.android.material.R.attr.colorSecondary;
if (mode == LocationState.PENDING_POSITION)
@@ -69,7 +69,8 @@ public class MyPositionButton
case LocationState.NOT_FOLLOW_NO_POSITION -> R.drawable.ic_location_off;
case LocationState.NOT_FOLLOW -> R.drawable.ic_location_crosshair;
case LocationState.FOLLOW -> R.drawable.ic_follow;
case LocationState.FOLLOW_AND_ROTATE -> R.drawable.ic_follow_and_rotate;
case LocationState.FOLLOW_AND_ROTATE_COMPASS -> R.drawable.ic_follow_and_rotate;
case LocationState.FOLLOW_AND_ROTATE_ROUTE -> R.drawable.ic_follow_route;
default -> throw new IllegalArgumentException("Invalid button mode: " + mode);
};
image = ResourcesCompat.getDrawable(resources, drawableRes, context.getTheme());

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeColor="@android:color/white"
android:fillColor="@android:color/white"
android:pathData="M15.15 16.92l-7.04 2.8v0.4l2.63 1.07 0.98 2.67h0.39z"/>
<path
android:strokeColor="@android:color/white"
android:strokeWidth="2"
android:strokeLineJoin="miter"
android:pathData="m 16.25,15.95 c 2.29,-2.29 1.91,-3.87 -4.06,-3.87"/>
<path
android:strokeColor="@android:color/white"
android:strokeWidth="2"
android:strokeLineJoin="miter"
android:pathData="M8.98 7.43c-2.97 2.96-2.72 4.66 3.24 4.66"/>
<path
android:strokeColor="@android:color/white"
android:strokeWidth="2"
android:strokeLineJoin="miter"
android:pathData="M8.89 7.51l8.83-8.79"/>
<path
android:strokeColor="@android:color/white"
android:strokeWidth="2"
android:strokeLineJoin="miter"
android:pathData="M9.2 22.83c-0.64 0.57-2.9 2.5-3.56 3.13"/>
</vector>

View File

@@ -27,6 +27,8 @@ import app.organicmaps.sdk.util.NetworkPolicy;
import app.organicmaps.sdk.util.log.Logger;
import org.chromium.base.ObserverList;
import java.util.HashMap;
public class LocationHelper implements BaseLocationProvider.Listener
{
private static final long INTERVAL_MS = 500;
@@ -56,6 +58,12 @@ public class LocationHelper implements BaseLocationProvider.Listener
private Handler mHandler;
private Runnable mLocationTimeoutRunnable = this::notifyLocationUpdateTimeout;
private static final double INTERVAL_PROVIDER_DECISION = 3.0; // seconds
private final HashMap<String, Integer> mProviderLocationCounts = new HashMap<>();
private final HashMap<String, Float> mProviderAccuracyMeans = new HashMap<>();
private double mTimeAtLastProviderChange = Double.NaN;
private String mCurrentProvider = null;
@NonNull
private final GnssStatusCompat.Callback mGnssStatusCallback = new GnssStatusCompat.Callback() {
@Override
@@ -187,7 +195,6 @@ public class LocationHelper implements BaseLocationProvider.Listener
@Override
public void onLocationChanged(@NonNull Location location)
{
Logger.d(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() + " location = " + location);
if (!isActive())
{
@@ -201,21 +208,59 @@ public class LocationHelper implements BaseLocationProvider.Listener
return;
}
if (mSavedLocation != null)
{
if (!LocationUtils.isLocationBetterThanLast(location, mSavedLocation))
{
Logger.d(TAG, "The new " + location + " is worse than the last " + mSavedLocation);
updateProviderDecision(location);
if(mCurrentProvider != null && !mCurrentProvider.equals(location.getProvider())) {
Logger.d(TAG, "REJECTED: provider = " + mLocationProvider.getClass().getSimpleName() + " location = " + location);
return;
}
}
Logger.d(TAG, "provider = " + mLocationProvider.getClass().getSimpleName() + " location = " + location);
mSavedLocation = location;
mMyPosition = null;
notifyLocationUpdated();
}
// Used by GoogleFusedLocationProvider.
private void updateProviderDecision(Location location) {
if(Double.isNaN(mTimeAtLastProviderChange))
mTimeAtLastProviderChange = location.getElapsedRealtimeNanos() * 1.0E-9;
String provider = location.getProvider();
int count = mProviderLocationCounts.getOrDefault(provider, 0);
float average = mProviderAccuracyMeans.getOrDefault(provider, 0.0f);
float accuracy = location.getAccuracy();
float newAverage = (count * average + accuracy) / (count + 1);
mProviderLocationCounts.put(provider, count + 1);
mProviderAccuracyMeans.put(provider, newAverage);
double currentTime = location.getElapsedRealtimeNanos();
double timeDiff = (currentTime - mTimeAtLastProviderChange) * 1.0E-9;
if(timeDiff > INTERVAL_PROVIDER_DECISION) {
mCurrentProvider = getMinAccuracyProvider();
Logger.d(TAG, "Selected: " + mCurrentProvider + ", with acc. " + mProviderAccuracyMeans.get(mCurrentProvider));
mTimeAtLastProviderChange = currentTime;
mProviderLocationCounts.clear();
mProviderAccuracyMeans.clear();
}
}
private String getMinAccuracyProvider() {
String minAccuracyProvider = null;
float minAccuracy = Float.MAX_VALUE;
for(String p : mProviderAccuracyMeans.keySet()) {
float pAccuracy = mProviderAccuracyMeans.get(p);
if(pAccuracy < minAccuracy) {
minAccuracy = pAccuracy;
minAccuracyProvider = p;
}
}
return minAccuracyProvider;
}
// Used by GoogleFusedLocationProvider.
@SuppressWarnings("unused")
@Override
@UiThread

View File

@@ -20,7 +20,8 @@ public final class LocationState
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({PENDING_POSITION, NOT_FOLLOW_NO_POSITION, NOT_FOLLOW, FOLLOW, FOLLOW_AND_ROTATE})
@IntDef({PENDING_POSITION, NOT_FOLLOW_NO_POSITION, NOT_FOLLOW, FOLLOW, FOLLOW_AND_ROTATE_COMPASS,
FOLLOW_AND_ROTATE_ROUTE})
@interface Value
{}
@@ -29,7 +30,8 @@ public final class LocationState
public static final int NOT_FOLLOW_NO_POSITION = 1;
public static final int NOT_FOLLOW = 2;
public static final int FOLLOW = 3;
public static final int FOLLOW_AND_ROTATE = 4;
public static final int FOLLOW_AND_ROTATE_COMPASS = 4;
public static final int FOLLOW_AND_ROTATE_ROUTE = 5;
// These constants should correspond to values defined in platform/location.hpp
// Leave 0-value as no any error.
@@ -69,7 +71,8 @@ public final class LocationState
case NOT_FOLLOW_NO_POSITION -> "NOT_FOLLOW_NO_POSITION";
case NOT_FOLLOW -> "NOT_FOLLOW";
case FOLLOW -> "FOLLOW";
case FOLLOW_AND_ROTATE -> "FOLLOW_AND_ROTATE";
case FOLLOW_AND_ROTATE_COMPASS -> "FOLLOW_AND_ROTATE_COMPASS";
case FOLLOW_AND_ROTATE_ROUTE -> "FOLLOW_AND_ROTATE_ROUTE";
default -> "Unknown: " + mode;
};
}

View File

@@ -81,20 +81,6 @@ public class LocationUtils
return location.getAccuracy() > 0.0f;
}
public static boolean isLocationBetterThanLast(@NonNull Location newLocation, @NonNull Location lastLocation)
{
if (newLocation.getElapsedRealtimeNanos() < lastLocation.getElapsedRealtimeNanos())
return false;
// As described in isAccuracySatisfied, GPS may have zero accuracy "for some reasons".
if (isFromGpsProvider(lastLocation) && lastLocation.getAccuracy() == 0.0f)
return true;
double speed = Math.max(DEFAULT_SPEED_MPS, (newLocation.getSpeed() + lastLocation.getSpeed()) / 2.0);
double lastAccuracy = lastLocation.getAccuracy() + speed * LocationUtils.getTimeDiff(lastLocation, newLocation);
return newLocation.getAccuracy() < lastAccuracy;
}
public static boolean areLocationServicesTurnedOn(@NonNull Context context)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)

File diff suppressed because it is too large Load Diff

View File

@@ -1674,57 +1674,57 @@ CoMaps 的地理位置数据共享应该是启用的。</p>
<dd lang="en">
<p>Default battery optimization settings on Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC and other devices may stop or kill CoMaps app in the background.</p>
<p>This is especially true for versions Android 11 and higher</p>
<p>The exact steps on how to make CoMaps (and other apps) work in the background are listed here: https://dontkillmyapp.com/</p>
<p>The exact steps on how to make CoMaps (and other apps) work in the background are listed here: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd>
<dd lang="ru">
<p>Настройки оптимизации батареи по умолчанию на устройствах Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC и других могут остановить или закрыть приложение CoMaps в фоновом режиме.</p>
<p>Это особенно актуально для версий Android 11 и выше.</p>
<p>На этом сайте описано, как настроить работу в фоновом режиме для CoMaps и других приложений: https://dontkillmyapp.com/</p>
<p>На этом сайте описано, как настроить работу в фоновом режиме для CoMaps и других приложений: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="de">
<p>Die Standardeinstellungen zur Akkuoptimierung auf Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC und anderen Geräten können die CoMaps-App im Hintergrund stoppen oder beenden.</p>
<p>Dies gilt insbesondere für Versionen Android 11 und höher</p>
<p>Die genauen Schritte, wie man CoMaps (und andere Apps) im Hintergrund zum Laufen bringt, sind hier aufgeführt: https://dontkillmyapp.com/</p>
<p>Die genauen Schritte, wie man CoMaps (und andere Apps) im Hintergrund zum Laufen bringt, sind hier aufgeführt: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="es">
<p>La configuración predeterminada de optimización de la batería en Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC y otros dispositivos puede detener o cerrar la aplicación CoMaps en segundo plano.</p>
<p>Esto es especialmente cierto para las versiones de Android 11 y superior</p>
<p>Los pasos exactos sobre cómo hacer que CoMaps (y otras aplicaciones) funcionen en segundo plano se enumeran aquí (en inglés): https://dontkillmyapp.com/</p>
<p>Los pasos exactos sobre cómo hacer que CoMaps (y otras aplicaciones) funcionen en segundo plano se enumeran aquí (en inglés): <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="fr">
<p>Les paramètres d'optimisation de la batterie par défaut sur Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC et d'autres appareils peuvent arrêter ou tuer l'application CoMaps en arrière-plan.</p>
<p>Cela est particulièrement vrai pour les versions Android 11 et supérieures</p>
<p>Les étapes exactes pour faire fonctionner CoMaps (et d'autres applications) en arrière-plan sont listées ici : https://dontkillmyapp.com/</p>
<p>Les étapes exactes pour faire fonctionner CoMaps (et d'autres applications) en arrière-plan sont listées ici : <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="pl">
<p>Domyślne ustawienia optymalizacji baterii na urządzeniach Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC i innych, mogą zatrzymać aplikację CoMaps działającą w tle.</p>
<p>Dotyczy to szczególnie wersji Android 11 i wyższych</p>
<p>Dokładne kroki, jak sprawić, by CoMaps (i inne aplikacje) działały w tle, są wymienione tutaj: https://dontkillmyapp.com/</p>
<p>Dokładne kroki, jak sprawić, by CoMaps (i inne aplikacje) działały w tle, są wymienione tutaj: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="pt">
<p>As definições predefinidas de otimização da bateria na Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC e outros dispositivos podem parar ou encerrar a aplicação CoMaps em segundo plano.</p>
<p>Isso é especialmente verdadeiro para as versões Android 11 e superior</p>
<p>Os passos exatos sobre como fazer com que o CoMaps (e outras aplicações) funcionem em segundo plano estão listados aqui: https://dontkillmyapp.com/</p>
<p>Os passos exatos sobre como fazer com que o CoMaps (e outras aplicações) funcionem em segundo plano estão listados aqui: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="pt-BR">
<p>As configurações padrão de otimização da bateria na Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC e outros dispositivos podem interromper ou fechar o aplicativo CoMaps em segundo plano.</p>
<p>Isso é especialmente verdadeiro para as versões Android 11 e superiores</p>
<p>O passo-a-passo sobre como fazer o CoMaps (e outros aplicativos) funcionar em segundo plano pode ser encontrado aqui: https://dontkillmyapp.com/</p>
<p>O passo-a-passo sobre como fazer o CoMaps (e outros aplicativos) funcionar em segundo plano pode ser encontrado aqui: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="tr">
<p>Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC ve diğer cihazlardaki varsayılan pil optimizasyon ayarları arka planda CoMaps uygulamasını durdurabilir veya öldürebilir.</p>
<p>Bu özellikle Android 11 ve üstü sürümler için geçerlidir</p>
<p>CoMaps'ın (ve diğer uygulamaların) arka planda nasıl çalışacağına ilişkin tam adımlar burada listelenmiştir: https://dontkillmyapp.com/</p>
<p>CoMaps'ın (ve diğer uygulamaların) arka planda nasıl çalışacağına ilişkin tam adımlar burada listelenmiştir: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="uk">
<p>Стандартні налаштування оптимізації роботи акумулятора на пристроях Samsung, Huawei, Google, Xiaomi, OnePlus, Meizu, Asus, Wiko, Lenovo, Oppo, Vivo, Realme, Sony, Motorola, HTC та інших пристроях можуть зупиняти або закривати додаток CoMaps у фоновому режимі.</p>
<p>Особливо це стосується версій Android 11 і вище</p>
<p>На цьому сайті описано, як налаштувати роботу у фоновому режимі для CoMaps та інших додатків: https://dontkillmyapp.com/</p>
<p>На цьому сайті описано, як налаштувати роботу у фоновому режимі для CoMaps та інших додатків: <a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd><dd lang="zh">
<p>三星、华为、谷歌、小米、OnePlus、美图、华硕、Wiko、联想、Oppo、Vivo、Realme、索尼、摩托罗拉、HTC 和其他设备上的默认电池优化设置可能会在后台停止或杀死CoMaps应用程序。</p>
<p>对于 Android 11 及更高版本来说尤其如此</p>
<p>如何让CoMaps和其他应用程序在后台工作的具体步骤在此列出https://dontkillmyapp.com/</p>
<p>如何让CoMaps和其他应用程序在后台工作的具体步骤在此列出<a href="https://dontkillmyapp.com/">https://dontkillmyapp.com/</a></p>
</dd>
</dl>

View File

@@ -0,0 +1,108 @@
{
"make_a_slight_right_turn":"லேசாக வலதுபுறம் திரும்புங்கள்.",
"make_a_slight_right_turn_street":"NULL",
"make_a_slight_right_turn_street_verb":"NULL",
"make_a_right_turn":"வலதுபுறமாக திரும்புங்கள்.",
"make_a_right_turn_street":"NULL",
"make_a_sharp_right_turn":"கூர்மையாக வலதுபுறம் திரும்புங்கள்.",
"make_a_sharp_right_turn_street":"NULL",
"enter_the_roundabout":"ரவுண்டபௌட்டை நுழையுங்கள்.",
"enter_the_roundabout_street":"NULL",
"enter_the_roundabout_street_verb":"NULL",
"leave_the_roundabout":"ரவுண்டபௌட்டை வெளியேறுங்கள்.",
"leave_the_roundabout_street":"NULL",
"leave_the_roundabout_street_verb":"NULL",
"make_a_slight_left_turn":"லேசாக இடதுபுறம் திரும்புங்கள்.",
"make_a_slight_left_turn_street":"NULL",
"make_a_slight_left_turn_street_verb":"NULL",
"make_a_left_turn":"இடதுபுறமாக திரும்புங்கள்.",
"make_a_left_turn_street":"NULL",
"make_a_sharp_left_turn":"கூர்மையாக இடதுபுறம் திரும்புங்கள்.",
"make_a_sharp_left_turn_street":"NULL",
"make_a_u_turn":"U-வழியில் திரும்புங்கள்.",
"make_a_u_turn_street":"NULL",
"make_a_u_turn_street_verb":"NULL",
"go_straight":"நேரா போங்க.",
"exit":"வெளியேறுங்கள்.",
"onto":"நோக்கி",
"take_exit_number":"வெளியேறும் வழி எடுங்கள்",
"take_exit_number_street_verb":"NULL",
"route_recalculating":"வழி மறுபடி கணிக்கிறது",
"destination":"நீங்கள் வருவீர்கள்.",
"you_have_reached_the_destination":"நீங்கள் வந்துவிட்டீர்கள்..",
"in_50_meters":"ஐம்பது மீட்டரில்",
"in_100_meters":"நூறு மீட்டரில்",
"in_200_meters":"இருநூறு மீட்டரில்",
"in_250_meters":"இருநூற்று ஐம்பது மீட்டரில்",
"in_300_meters":"முன்னூறு மீட்டரில்",
"in_400_meters":"நானூறு மீட்டரில்",
"in_500_meters":"ஐநூறு மீட்டரில்",
"in_600_meters":"அறுநூறு மீட்டரில்",
"in_700_meters":"எழுநூறு மீட்டரில்",
"in_750_meters":"எழுநூறு ஐம்பது மீட்டரில்",
"in_800_meters":"எண்ணூறு மீட்டரில்",
"in_900_meters":"தொள்ளாயிரம் மீட்டரில்",
"in_1_kilometer":"ஒரு கிலோமீட்டரில்",
"in_1_5_kilometers":"ஒன்றரை கிலோமீட்டரில்",
"in_2_kilometers":"இரண்டு கிலோமீட்டரில்",
"in_2_5_kilometers":"இரண்டரை கிலோமீட்டரில்",
"in_3_kilometers":"மூன்று கிலோமீட்டரில்",
"then":"அடுத்தது",
"dist_direction_onto_street":"%1$s %4$s %3$s %5$s %2$s",
"take_the_1_exit":"ஒன்றாம் வெளியேறும் வழி எடுங்கள்.",
"take_the_1_exit_street":"NULL",
"take_the_1_exit_street_verb":"NULL",
"take_the_2_exit":"இரண்டாம் வெளியேறும் வழி எடுங்கள்.",
"take_the_2_exit_street":"NULL",
"take_the_2_exit_street_verb":"NULL",
"take_the_3_exit":"மூன்றாம் வெளியேறும் வழி எடுங்கள்.",
"take_the_3_exit_street":"NULL",
"take_the_3_exit_street_verb":"NULL",
"take_the_4_exit":"நான்காவது வெளியேறும் வழி எடுங்கள்.",
"take_the_4_exit_street":"NULL",
"take_the_4_exit_street_verb":"NULL",
"take_the_5_exit":"ஐந்தாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_5_exit_street":"NULL",
"take_the_5_exit_street_verb":"NULL",
"take_the_6_exit":"ஆறாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_6_exit_street":"NULL",
"take_the_6_exit_street_verb":"NULL",
"take_the_7_exit":"ஏழாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_7_exit_street":"NULL",
"take_the_7_exit_street_verb":"NULL",
"take_the_8_exit":"எட்டாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_8_exit_street":"NULL",
"take_the_8_exit_street_verb":"NULL",
"take_the_9_exit":"ஒன்பதாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_9_exit_street":"NULL",
"take_the_9_exit_street_verb":"NULL",
"take_the_10_exit":"பத்தாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_10_exit_street":"NULL",
"take_the_10_exit_street_verb":"NULL",
"take_the_11_exit":"பதினொன்றாவது வெளியேறும் வழி எடுங்கள்.",
"take_the_11_exit_street":"NULL",
"take_the_11_exit_street_verb":"NULL",
"in_50_feet":"ஐம்பது அடியில்",
"in_100_feet":"நூறு அடியில்",
"in_200_feet":"இருநூறு அடியில்",
"in_300_feet":"முன்னூறு அடியில்",
"in_400_feet":"நானூறு அடியில்",
"in_500_feet":"ஐநூறு அடியில்",
"in_600_feet":"அறுநூறு அடியில்",
"in_700_feet":"எழுநூறு அடியில்",
"in_800_feet":"எண்ணூறு அடியில்",
"in_900_feet":"தொள்ளாயிரம் அடியில்",
"in_1000_feet":"ஆயிரம் அடியில்",
"in_1500_feet":"ஆயிரம் ஐநூறு அடியில்",
"in_2000_feet":"இரண்டு ஆயிரம் அடியில்",
"in_2500_feet":"இரண்டு ஆயிரம் ஐநூறு அடியில்",
"in_3000_feet":"மூன்று ஆயிரம் அடியில்",
"in_3500_feet":"மூன்று ஆயிரம் ஐநூறு அடியில்",
"in_4000_feet":"நான்கு ஆயிரம் அடியில்",
"in_4500_feet":"நான்கு ஆயிரம் ஐநூறு அடியில்",
"in_5000_feet":"ஐந்து ஆயிரம் அடியில்",
"in_1_mile":"ஒன்று மைலில்",
"in_1_5_miles":"ஒன்றரை மைலில்",
"in_2_miles":"இரண்டு மைலில்",
"unknown_camera":"கேமரா முன்னால் இருக்கிறது"
}

View File

@@ -239,7 +239,7 @@ final class CarPlayService: NSObject {
MapTemplateBuilder.configureBaseUI(mapTemplate: mapTemplate)
if currentPositionMode == .pendingPosition {
mapTemplate.leadingNavigationBarButtons = []
} else if currentPositionMode == .follow || currentPositionMode == .followAndRotate {
} else if currentPositionMode == .follow || currentPositionMode == .followAndRotateCompass || currentPositionMode == .followAndRotateRoute {
MapTemplateBuilder.setupDestinationButton(mapTemplate: mapTemplate)
} else {
MapTemplateBuilder.setupRecenterButton(mapTemplate: mapTemplate)
@@ -623,22 +623,32 @@ extension CarPlayService: CarPlayRouterListener {
extension CarPlayService: LocationModeListener {
func processMyPositionStateModeEvent(_ mode: MWMMyPositionMode) {
currentPositionMode = mode
guard let rootMapTemplate = rootMapTemplate,
let info = rootMapTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.main else {
// make sure we have a rootMapTemplate
guard let rootMapTemplate = rootMapTemplate else {
return
}
// exit if we're navigating
guard let info = rootMapTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.main else {
MapTemplateBuilder.updateMyPositionModeButton(mapTemplate: rootMapTemplate, newMode: mode)
return
}
switch mode {
case .follow, .followAndRotate:
case .follow, .followAndRotateCompass, .followAndRotateRoute:
if !rootMapTemplate.isPanningInterfaceVisible {
MapTemplateBuilder.setupDestinationButton(mapTemplate: rootMapTemplate)
MapTemplateBuilder.updateMyPositionModeButton(mapTemplate: rootMapTemplate, newMode: mode)
}
case .notFollow:
if !rootMapTemplate.isPanningInterfaceVisible {
MapTemplateBuilder.setupRecenterButton(mapTemplate: rootMapTemplate)
MapTemplateBuilder.updateMyPositionModeButton(mapTemplate: rootMapTemplate, newMode: mode)
}
case .pendingPosition, .notFollowNoPosition:
rootMapTemplate.leadingNavigationBarButtons = []
MapTemplateBuilder.updateMyPositionModeButton(mapTemplate: rootMapTemplate, newMode: mode)
}
}
}

View File

@@ -5,6 +5,7 @@ final class MapTemplateBuilder {
case startPanning
case zoomIn
case zoomOut
case myPositionMode
}
enum BarButtonType {
case dismissPaning
@@ -28,7 +29,7 @@ final class MapTemplateBuilder {
configureBaseUI(mapTemplate: mapTemplate)
if positionMode == .pendingPosition {
mapTemplate.leadingNavigationBarButtons = []
} else if positionMode == .follow || positionMode == .followAndRotate {
} else if positionMode == .follow || positionMode == .followAndRotateCompass || positionMode == .followAndRotateRoute {
setupDestinationButton(mapTemplate: mapTemplate)
} else {
setupRecenterButton(mapTemplate: mapTemplate)
@@ -69,7 +70,10 @@ final class MapTemplateBuilder {
let zoomOutButton = buildMapButton(type: .zoomOut) { _ in
FrameworkHelper.zoomMap(.out)
}
mapTemplate.mapButtons = [panningButton, zoomInButton, zoomOutButton]
let myPositionModeButton = buildMapButton(type: .myPositionMode) { _ in
FrameworkHelper.switchMyPositionMode()
}
mapTemplate.mapButtons = [myPositionModeButton, panningButton, zoomInButton, zoomOutButton]
let settingsButton = buildBarButton(type: .settings) { _ in
let gridTemplate = SettingsTemplateBuilder.buildGridTemplate()
@@ -99,7 +103,10 @@ final class MapTemplateBuilder {
let panningButton = buildMapButton(type: .startPanning) { _ in
mapTemplate.showPanningInterface(animated: true)
}
mapTemplate.mapButtons = [panningButton]
let myPositionModeButton = buildMapButton(type: .myPositionMode) { _ in
FrameworkHelper.switchMyPositionMode()
}
mapTemplate.mapButtons = [myPositionModeButton, panningButton]
setupMuteAndRedirectButtons(template: mapTemplate)
let endButton = buildBarButton(type: .endRoute) { _ in
CarPlayService.shared.cancelCurrentTrip()
@@ -117,6 +124,28 @@ final class MapTemplateBuilder {
mapTemplate.leadingNavigationBarButtons = [destinationButton]
}
class func updateMyPositionModeButton(mapTemplate: CPMapTemplate, newMode: MWMMyPositionMode) {
let button = CPMapButton(handler: { _ in
FrameworkHelper.switchMyPositionMode()
})
switch newMode {
case .pendingPosition:
button.image = UIImage(systemName: "location.fill")
case .notFollowNoPosition:
button.image = UIImage(systemName: "location")
case .notFollow:
button.image = UIImage(systemName: "location")
case .follow:
button.image = UIImage(systemName: "location.fill")
case .followAndRotate:
button.image = UIImage(systemName: "location.north.line.fill")
}
if mapTemplate.mapButtons.count > 0 {
mapTemplate.mapButtons[0] = button
}
}
class func setupRecenterButton(mapTemplate: CPMapTemplate) {
let recenterButton = buildBarButton(type: .recenter) { _ in
FrameworkHelper.switchMyPositionMode()
@@ -166,6 +195,8 @@ final class MapTemplateBuilder {
button.image = UIImage(systemName: "plus")
case .zoomOut:
button.image = UIImage(systemName: "minus")
case .myPositionMode:
button.image = UIImage(systemName: "location")
}
// Remove code below once Apple has fixed its issue with the button background
if #unavailable(iOS 26) {
@@ -176,6 +207,8 @@ final class MapTemplateBuilder {
button.focusedImage = UIImage(systemName: "plus.circle.fill")
case .zoomOut:
button.focusedImage = UIImage(systemName: "minus.circle.fill")
case .myPositionMode:
button.image = UIImage(systemName: "location.fill")
}
}
return button

View File

@@ -91,7 +91,8 @@ NSString * const kUDDidShowLongTapToShowSideButtonsToast = @"kUDDidShowLongTapTo
case MWMMyPositionModeNotFollow:
case MWMMyPositionModeNotFollowNoPosition: [locBtn setStyleNameAndApply: @"ButtonGetPosition"]; break;
case MWMMyPositionModeFollow: [locBtn setStyleNameAndApply: @"ButtonFollow"]; break;
case MWMMyPositionModeFollowAndRotate: [locBtn setStyleNameAndApply: @"ButtonFollowAndRotate"]; break;
case MWMMyPositionModeFollowAndRotateCompass: [locBtn setStyleNameAndApply: @"ButtonFollowAndRotateCompass"]; break;
case MWMMyPositionModeFollowAndRotateRoute: [locBtn setStyleNameAndApply: @"ButtonFollowAndRotateRoute"]; break;
}
}

View File

@@ -632,7 +632,8 @@ NSString *const kAboutSegue = @"Map2About";
case MWMMyPositionModeNotFollow:
break;
case MWMMyPositionModeFollow:
case MWMMyPositionModeFollowAndRotate:
case MWMMyPositionModeFollowAndRotateCompass:
case MWMMyPositionModeFollowAndRotateRoute:
self.disableStandbyOnLocationStateMode = YES;
break;
}

View File

@@ -32,7 +32,8 @@ static inline MWMMyPositionMode mwmMyPositionMode(location::EMyPositionMode mode
case location::EMyPositionMode::NotFollowNoPosition: return MWMMyPositionModeNotFollowNoPosition;
case location::EMyPositionMode::NotFollow: return MWMMyPositionModeNotFollow;
case location::EMyPositionMode::Follow: return MWMMyPositionModeFollow;
case location::EMyPositionMode::FollowAndRotate: return MWMMyPositionModeFollowAndRotate;
case location::EMyPositionMode::FollowAndRotateCompass: return MWMMyPositionModeFollowAndRotateCompass;
case location::EMyPositionMode::FollowAndRotateRoute: return MWMMyPositionModeFollowAndRotateRoute;
}
}
} // namespace location_helpers

View File

@@ -50,7 +50,8 @@ std::string DebugPrint(MWMMyPositionMode mode) {
case MWMMyPositionModeNotFollowNoPosition: return "MWMMyPositionModeNotFollowNoPosition";
case MWMMyPositionModeNotFollow: return "MWMMyPositionModeNotFollow";
case MWMMyPositionModeFollow: return "MWMMyPositionModeFollow";
case MWMMyPositionModeFollowAndRotate: return "MWMMyPositionModeFollowAndRotate";
case MWMMyPositionModeFollowAndRotateCompass: return "MWMMyPositionModeFollowAndRotateCompass";
case MWMMyPositionModeFollowAndRotateRoute: return "MWMMyPositionModeFollowAndRotateRoute";
}
CHECK(false, ("Unsupported value", static_cast<int>(mode)));
}
@@ -367,7 +368,8 @@ void setShowLocationAlert(BOOL needShow) {
case MWMMyPositionModeNotFollowNoPosition:
case MWMMyPositionModeNotFollow: manager.geoMode = GeoMode::NotInPosition; break;
case MWMMyPositionModeFollow: manager.geoMode = GeoMode::InPosition; break;
case MWMMyPositionModeFollowAndRotate: manager.geoMode = GeoMode::FollowAndRotate; break;
case MWMMyPositionModeFollowAndRotateCompass:
case MWMMyPositionModeFollowAndRotateRoute: manager.geoMode = GeoMode::FollowAndRotate; break;
}
}
}

View File

@@ -30,7 +30,7 @@ NSUInteger constexpr kMaxPredictionCount = 20;
- (void)setMyPositionMode:(MWMMyPositionMode)mode
{
self.isLastPositionModeValid = (mode == MWMMyPositionModeFollowAndRotate);
self.isLastPositionModeValid = (mode == MWMMyPositionModeFollowAndRotateCompass);
[self restart];
}

View File

@@ -3,5 +3,6 @@ typedef NS_CLOSED_ENUM(NSUInteger, MWMMyPositionMode) {
MWMMyPositionModeNotFollowNoPosition,
MWMMyPositionModeNotFollow,
MWMMyPositionModeFollow,
MWMMyPositionModeFollowAndRotate
MWMMyPositionModeFollowAndRotateCompass,
MWMMyPositionModeFollowAndRotateRoute
};

View File

@@ -41,13 +41,14 @@ final class ThemeManager: NSObject {
}
}(actualTheme)
if Settings.mapAppearance == .light {
let isCarPlayActive = CarPlayService.shared.isCarplayActivated
if !isCarPlayActive, Settings.mapAppearance == .light {
if actualTheme == .vehicleDay || actualTheme == .vehicleNight {
FrameworkHelper.setTheme(.vehicleDay)
} else {
FrameworkHelper.setTheme(.day)
}
} else if Settings.mapAppearance == .dark {
} else if !isCarPlayActive, Settings.mapAppearance == .dark {
if actualTheme == .vehicleDay || actualTheme == .vehicleNight {
FrameworkHelper.setTheme(.vehicleNight)
} else {

View File

@@ -7,7 +7,8 @@ enum MapStyleSheet: String, CaseIterable {
case mapButtonPending = "ButtonPending"
case mapButtonGetPosition = "ButtonGetPosition"
case mapButtonFollow = "ButtonFollow"
case mapButtonFollowAndRotate = "ButtonFollowAndRotate"
case mapButtonFollowAndRotateCompass = "ButtonFollowAndRotateCompass"
case mapButtonFollowAndRotateRoute = "ButtonFollowAndRotateRoute"
case mapButtonMapBookmarks = "ButtonMapBookmarks"
case mapPromoDiscoveryButton = "PromoDiscroveryButton"
case mapButtonBookmarksBack = "ButtonBookmarksBack"
@@ -67,10 +68,14 @@ extension MapStyleSheet: IStyleSheet {
return .add { s in
s.mwmImage = "btn_follow"
}
case .mapButtonFollowAndRotate:
case .mapButtonFollowAndRotateCompass:
return .add { s in
s.mwmImage = "btn_follow_and_rotate"
}
case .mapButtonFollowAndRotateRoute:
return .add { s in
s.mwmImage = "btn_follow_route"
}
case .mapButtonMapBookmarks:
return .add { s in
s.mwmImage = "ic_routing_bookmark"

View File

@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "btn_follow_route_dark.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "btn_follow_route_highlighted_dark.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "btn_follow_route_highlighted_light.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "btn_follow_route_light.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -186,7 +186,7 @@ import AVFoundation
return mapAppearance
}
return .auto
return .light
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: userDefaultsKeyMapAppearance)

View File

@@ -9,6 +9,7 @@
#include "drape/support_manager.hpp"
#include "platform/settings.hpp"
#include "routing/base/followed_polyline.hpp"
#include <unordered_map>
@@ -40,7 +41,7 @@ DrapeEngine::DrapeEngine(Params && params)
using namespace location;
EMyPositionMode mode = PendingPosition;
if (settings::Get(kLocationStateMode, mode) && mode == FollowAndRotate)
if (settings::Get(kLocationStateMode, mode) && mode == FollowAndRotateCompass)
{
// If the screen rect setting in follow and rotate mode is missing or invalid, it could cause
// invalid animations, so the follow and rotate mode should be discarded.
@@ -441,13 +442,12 @@ void DrapeEngine::SetCompassInfo(location::CompassInfo const & info)
MessagePriority::Normal);
}
void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit,
void DrapeEngine::SetGpsInfo(location::GpsInfo const & info, df::NavigationContext const & navigationContext,
location::RouteMatchingInfo const & routeInfo)
{
m_threadCommutator->PostMessage(
ThreadsCommutator::RenderThread,
make_unique_dp<GpsInfoMessage>(info, isNavigable, distToNextTurn, speedLimit, routeInfo),
MessagePriority::Normal);
m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread,
make_unique_dp<GpsInfoMessage>(info, navigationContext, routeInfo),
MessagePriority::Normal);
}
void DrapeEngine::SwitchMyPositionNextMode()
@@ -474,12 +474,13 @@ void DrapeEngine::StopLocationFollow()
MessagePriority::Normal);
}
void DrapeEngine::FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued)
void DrapeEngine::FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation)
{
m_threadCommutator->PostMessage(
ThreadsCommutator::RenderThread,
make_unique_dp<FollowRouteMessage>(preferredZoomLevel, preferredZoomLevel3d, enableAutoZoom, isArrowGlued),
MessagePriority::Normal);
m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread,
make_unique_dp<FollowRouteMessage>(preferredZoomLevel, preferredZoomLevel3d,
enableAutoZoom, isArrowGlued, allowRouteRotation),
MessagePriority::Normal);
}
void DrapeEngine::SetModelViewListener(ModelViewChangedHandler && fn)

View File

@@ -1,5 +1,7 @@
#pragma once
#include "drape_frontend/my_position_controller.hpp"
#include "routing/base/followed_polyline.hpp"
#include "traffic/traffic_info.hpp"
#include "drape_frontend/backend_renderer.hpp"
@@ -154,7 +156,7 @@ public:
void UpdateMapStyle();
void SetCompassInfo(location::CompassInfo const & info);
void SetGpsInfo(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit,
void SetGpsInfo(location::GpsInfo const & info, df::NavigationContext const & navigationContext,
location::RouteMatchingInfo const & routeInfo);
void SwitchMyPositionNextMode();
void LoseLocation();
@@ -171,7 +173,8 @@ public:
dp::DrapeID AddSubroute(SubrouteConstPtr subroute);
void RemoveSubroute(dp::DrapeID subrouteId, bool deactivateFollowing);
void FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued);
void FollowRoute(int preferredZoomLevel, int preferredZoomLevel3d, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation);
void DeactivateRouteFollowing();
void SetSubrouteVisibility(dp::DrapeID subrouteId, bool isVisible);
dp::DrapeID AddRoutePreviewSegment(m2::PointD const & startPt, m2::PointD const & finishPt);

View File

@@ -438,8 +438,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr<Message> message)
break;
#endif
ref_ptr<GpsInfoMessage> msg = message;
m_myPositionController->OnLocationUpdate(msg->GetInfo(), msg->IsNavigable(), msg->GetDistanceToNextTurn(),
msg->GetSpeedLimit(), m_userEventStream.GetCurrentScreen());
m_myPositionController->OnLocationUpdate(msg->GetInfo(), msg->GetNavigationContext(),
m_userEventStream.GetCurrentScreen());
location::RouteMatchingInfo const & info = msg->GetRouteInfo();
if (info.HasDistanceFromBegin())
@@ -512,7 +512,8 @@ void FrontendRenderer::AcceptMessage(ref_ptr<Message> message)
if (m_pendingFollowRoute != nullptr)
{
FollowRoute(m_pendingFollowRoute->m_preferredZoomLevel, m_pendingFollowRoute->m_preferredZoomLevelIn3d,
m_pendingFollowRoute->m_enableAutoZoom, m_pendingFollowRoute->m_isArrowGlued);
m_pendingFollowRoute->m_enableAutoZoom, m_pendingFollowRoute->m_isArrowGlued,
m_pendingFollowRoute->m_allowRouteRotation);
m_pendingFollowRoute.reset();
}
break;
@@ -584,13 +585,14 @@ void FrontendRenderer::AcceptMessage(ref_ptr<Message> message)
// receive FollowRoute message before FlushSubroute message, so we need to postpone its processing.
if (m_routeRenderer->GetSubroutes().empty())
{
m_pendingFollowRoute = std::make_unique<FollowRouteData>(
msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), msg->EnableAutoZoom(), msg->IsArrowGlued());
m_pendingFollowRoute =
std::make_unique<FollowRouteData>(msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(),
msg->EnableAutoZoom(), msg->IsArrowGlued(), msg->AllowRouteRotation());
}
else
{
FollowRoute(msg->GetPreferredZoomLevel(), msg->GetPreferredZoomLevelIn3d(), msg->EnableAutoZoom(),
msg->IsArrowGlued());
msg->IsArrowGlued(), msg->AllowRouteRotation());
}
break;
}
@@ -1057,10 +1059,11 @@ void FrontendRenderer::UpdateContextDependentResources()
}
void FrontendRenderer::FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom,
bool isArrowGlued)
bool isArrowGlued, bool allowRouteRotation)
{
m_myPositionController->ActivateRouting(
!m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, enableAutoZoom, isArrowGlued);
!m_enablePerspectiveInNavigation ? preferredZoomLevel : preferredZoomLevelIn3d, enableAutoZoom, isArrowGlued,
allowRouteRotation);
if (m_enablePerspectiveInNavigation)
AddUserEvent(make_unique_dp<SetAutoPerspectiveEvent>(true /* isAutoPerspective */));

View File

@@ -261,7 +261,8 @@ private:
using TRenderGroupRemovePredicate = std::function<bool(drape_ptr<RenderGroup> const &)>;
void RemoveRenderGroupsLater(TRenderGroupRemovePredicate const & predicate);
void FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued);
void FollowRoute(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation);
bool CheckRouteRecaching(ref_ptr<BaseSubrouteData> subrouteData);
@@ -372,17 +373,20 @@ private:
struct FollowRouteData
{
FollowRouteData(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued)
FollowRouteData(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation)
: m_preferredZoomLevel(preferredZoomLevel)
, m_preferredZoomLevelIn3d(preferredZoomLevelIn3d)
, m_enableAutoZoom(enableAutoZoom)
, m_isArrowGlued(isArrowGlued)
, m_allowRouteRotation(allowRouteRotation)
{}
int m_preferredZoomLevel;
int m_preferredZoomLevelIn3d;
bool m_enableAutoZoom;
bool m_isArrowGlued;
bool m_allowRouteRotation;
};
std::unique_ptr<FollowRouteData> m_pendingFollowRoute;

View File

@@ -10,6 +10,7 @@
#include "drape_frontend/gui/skin.hpp"
#include "drape_frontend/message.hpp"
#include "drape_frontend/my_position.hpp"
#include "drape_frontend/my_position_controller.hpp"
#include "drape_frontend/overlay_batcher.hpp"
#include "drape_frontend/postprocess_renderer.hpp"
#include "drape_frontend/render_node.hpp"
@@ -29,6 +30,7 @@
#include "geometry/rect2d.hpp"
#include "geometry/triangle2d.hpp"
#include "routing/base/followed_polyline.hpp"
#include <condition_variable>
#include <functional>
@@ -475,28 +477,22 @@ private:
class GpsInfoMessage : public Message
{
public:
GpsInfoMessage(location::GpsInfo const & info, bool isNavigable, double distToNextTurn, double speedLimit,
GpsInfoMessage(location::GpsInfo const & info, df::NavigationContext const & navigationContext,
location::RouteMatchingInfo const & routeInfo)
: m_info(info)
, m_isNavigable(isNavigable)
, m_distToNextTurn(distToNextTurn)
, m_speedLimit(speedLimit)
, m_navigationContext(navigationContext)
, m_routeInfo(routeInfo)
{}
Type GetType() const override { return Type::GpsInfo; }
location::GpsInfo const & GetInfo() const { return m_info; }
bool IsNavigable() const { return m_isNavigable; }
double const & GetSpeedLimit() const { return m_speedLimit; }
double const & GetDistanceToNextTurn() const { return m_distToNextTurn; }
df::NavigationContext const & GetNavigationContext() const { return m_navigationContext; }
location::RouteMatchingInfo const & GetRouteInfo() const { return m_routeInfo; }
private:
location::GpsInfo const m_info;
bool const m_isNavigable;
double const m_distToNextTurn;
double const m_speedLimit;
df::NavigationContext const m_navigationContext;
location::RouteMatchingInfo const m_routeInfo;
};
@@ -740,11 +736,13 @@ public:
class FollowRouteMessage : public Message
{
public:
FollowRouteMessage(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued)
FollowRouteMessage(int preferredZoomLevel, int preferredZoomLevelIn3d, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation)
: m_preferredZoomLevel(preferredZoomLevel)
, m_preferredZoomLevelIn3d(preferredZoomLevelIn3d)
, m_enableAutoZoom(enableAutoZoom)
, m_isArrowGlued(isArrowGlued)
, m_allowRouteRotation(allowRouteRotation)
{}
Type GetType() const override { return Type::FollowRoute; }
@@ -753,12 +751,14 @@ public:
int GetPreferredZoomLevelIn3d() const { return m_preferredZoomLevelIn3d; }
bool EnableAutoZoom() const { return m_enableAutoZoom; }
bool IsArrowGlued() const { return m_isArrowGlued; }
bool AllowRouteRotation() const { return m_allowRouteRotation; }
private:
int const m_preferredZoomLevel;
int const m_preferredZoomLevelIn3d;
bool const m_enableAutoZoom;
bool const m_isArrowGlued;
bool const m_allowRouteRotation;
};
class SwitchMapStyleMessage : public BaseBlockingMessage

View File

@@ -9,9 +9,11 @@
#include "geometry/mercator.hpp"
#include "platform/location.hpp"
#include "platform/measurement_utils.hpp"
#include "base/math.hpp"
#include "routing/base/followed_polyline.hpp"
#include <algorithm>
#include <array>
@@ -31,6 +33,9 @@ double constexpr kMaxTimeInBackgroundSec = 60.0 * 60 * 30; // 30 hours before s
double constexpr kMaxNotFollowRoutingTimeSec = 20.0;
double constexpr kMaxUpdateLocationInvervalSec = 30.0;
double constexpr kMaxBlockAutoZoomTimeSec = 10.0;
double constexpr kDefaultSpeedLimitKmpH = 50.0;
double constexpr kLookaheadTimeSpeedRatio = 0.3;
double constexpr kMaxLookaheadTimeSec = 25.0;
int constexpr kZoomThreshold = 10;
int constexpr kMaxScaleZoomLevel = 16;
@@ -66,7 +71,6 @@ double CalculateZoomByMaxSpeed(double speedMpS, bool isPerspectiveAllowed)
std::array<TSpeedScale, 2> const & scales = isPerspectiveAllowed ? scales3d : scales2d;
double constexpr kDefaultSpeedLimitKmpH = 50.0;
double const speedKmpH = speedMpS > 0 ? measurement_utils::MpsToKmph(speedMpS) : kDefaultSpeedLimitKmpH;
size_t i = 0;
@@ -143,7 +147,7 @@ void ResetNotification(uint64_t & notifyId)
bool IsModeChangeViewport(location::EMyPositionMode mode)
{
return mode == location::Follow || mode == location::FollowAndRotate;
return mode == location::Follow || mode == location::FollowAndRotateCompass || mode == location::FollowAndRotateRoute;
}
} // namespace
@@ -157,9 +161,11 @@ MyPositionController::MyPositionController(Params && params, ref_ptr<DrapeNotifi
, m_errorRadius(0.0)
, m_horizontalAccuracy(0.0)
, m_position(m2::PointD::Zero())
, m_drawDirection(0.0)
, m_direction(0.0)
, m_routeDirection(0.0)
, m_arrowDirection(0.0)
, m_oldPosition(m2::PointD::Zero())
, m_oldDrawDirection(0.0)
, m_oldArrowDirection(0.0)
, m_enablePerspectiveInRouting(false)
, m_enableAutoZoomInRouting(params.m_isAutozoomEnabled)
, m_autoScale2d(GetScreenScale(kDefaultAutoZoom))
@@ -171,7 +177,8 @@ MyPositionController::MyPositionController(Params && params, ref_ptr<DrapeNotifi
, m_isDirtyAutoZoom(false)
, m_isPendingAnimation(false)
, m_isPositionAssigned(false)
, m_isDirectionAssigned(false)
, m_isArrowDirectionAssigned(false)
, m_isRouteDirectionAssigned(false)
, m_isCompassAvailable(false)
, m_positionIsObsolete(false)
, m_needBlockAutoZoom(false)
@@ -290,7 +297,7 @@ void MyPositionController::ScaleEnded()
void MyPositionController::Rotated()
{
if (m_mode == location::FollowAndRotate)
if (m_mode == location::FollowAndRotateCompass)
m_wasRotationInScaling = true;
}
@@ -398,25 +405,32 @@ void MyPositionController::NextMode(ScreenBase const & screen)
// In routing not-follow -> follow-and-rotate, otherwise not-follow -> follow.
if (m_mode == location::NotFollow)
{
ChangeMode(m_isInRouting ? location::FollowAndRotate : location::Follow);
ChangeMode(m_isInRouting ? location::FollowAndRotateCompass : location::Follow);
UpdateViewport(preferredZoomLevel);
return;
}
// From follow mode we transit to follow-and-rotate if compass is available or
// routing is enabled.
// From follow mode we transit to follow-and-rotate-compass, if in routing
if (m_mode == location::Follow)
{
if (IsRotationAvailable() || m_isInRouting)
if (IsArrowRotationAvailable() || m_isInRouting)
{
ChangeMode(location::FollowAndRotate);
ChangeMode(location::FollowAndRotateCompass);
UpdateViewport(preferredZoomLevel);
}
return;
}
// From follow-and-rotate mode we can transit to follow mode.
if (m_mode == location::FollowAndRotate)
// From -rotate-compass mode we transit to -rotate-route mode, if allowed
if (m_mode == location::FollowAndRotateCompass && IsRouteRotationAvailable() && m_allowRouteRotationInRouting)
{
ChangeMode(location::FollowAndRotateRoute);
UpdateViewport(preferredZoomLevel);
return;
}
// From -rotate-route mode, or from -rotate-compass if -rotate-route is not allowed, we return to follow mode
if (m_mode == location::FollowAndRotateRoute || m_mode == location::FollowAndRotateCompass)
{
if (m_isInRouting && screen.isPerspective())
preferredZoomLevel = static_cast<int>(GetZoomLevel(ScreenBase::GetStartPerspectiveScale() * 1.1));
@@ -425,8 +439,8 @@ void MyPositionController::NextMode(ScreenBase const & screen)
}
}
void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool isNavigable, double distanceToNextTurn,
double speedLimit, ScreenBase const & screen)
void MyPositionController::OnLocationUpdate(location::GpsInfo const & info,
df::NavigationContext const & navigationContext, ScreenBase const & screen)
{
m2::PointD const oldPos = GetDrawablePosition();
double const oldAzimut = GetDrawableAzimut();
@@ -438,33 +452,62 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool
m_errorRadius = rect.SizeX() * 0.5;
m_horizontalAccuracy = info.m_horizontalAccuracy;
if (distanceToNextTurn >= 0.0 || speedLimit >= 0.0)
if (navigationContext.m_distanceToNextTurn >= 0.0 || navigationContext.m_speedLimit >= 0.0)
{
double const mercatorPerMeter = m_errorRadius / info.m_horizontalAccuracy;
m_autoScale2d =
mercatorPerMeter * CalculateAutoZoom(speedLimit, distanceToNextTurn, false /* isPerspectiveAllowed */);
mercatorPerMeter * CalculateAutoZoom(navigationContext.m_speedLimit, navigationContext.m_distanceToNextTurn,
false /* isPerspectiveAllowed */);
m_autoScale3d =
mercatorPerMeter * CalculateAutoZoom(speedLimit, distanceToNextTurn, true /* isPerspectiveAllowed */);
mercatorPerMeter * CalculateAutoZoom(navigationContext.m_speedLimit, navigationContext.m_distanceToNextTurn,
true /* isPerspectiveAllowed */);
}
else
{
m_autoScale2d = m_autoScale3d = kUnknownAutoZoom;
}
// Sets direction based on GPS if:
// Sets arrow direction based on GPS if:
// 1. Compass is not available.
// 2. Direction must be glued to the route during routing (route-corrected angle is set only in
// OnLocationUpdate(): in OnCompassUpdate() the angle always has the original value.
// 3. Device is moving faster then pedestrian.
bool const isMovingFast = info.HasSpeed() && info.m_speed > kMinSpeedThresholdMps;
bool const glueArrowInRouting = isNavigable && m_isArrowGluedInRouting;
bool const glueArrowInRouting = navigationContext.m_isNavigable && m_isArrowGluedInRouting;
// Calculate the route direction by looking ahead a distance in the route
// depending on the current speed
if (glueArrowInRouting && navigationContext.m_followedPolyline != nullptr)
{
double const speed =
(navigationContext.m_speedLimit > 0 ? measurement_utils::MpsToKmph(navigationContext.m_speedLimit)
: kDefaultSpeedLimitKmpH);
double const lookaheadTimeSec = std::min(speed * kLookaheadTimeSpeedRatio, kMaxLookaheadTimeSec);
double const lookaheadDistance = std::min(speed * lookaheadTimeSec, navigationContext.m_distanceToNextTurn);
auto const & iter = navigationContext.m_followedPolyline->GetCurrentIter();
m2::PointD const point = navigationContext.m_followedPolyline->GetLookaheadPoint(lookaheadDistance);
auto angle = math::RadToDeg(ang::AngleTo(iter.m_pt, point));
if (std::isnan(angle) || std::isinf(angle))
{
// fallback in case the current route becomes invalid for any reason
SetRouteDirection(info.m_bearing);
}
else
{
SetRouteDirection(math::DegToRad(location::AngleToBearing(angle)));
}
}
if ((!m_isCompassAvailable || glueArrowInRouting || isMovingFast) && info.HasBearing())
{
SetDirection(math::DegToRad(info.m_bearing));
SetArrowDirection(math::DegToRad(info.m_bearing));
m_lastGPSBearingTimer.Reset();
}
m_direction = m_mode == location::FollowAndRotateRoute ? m_routeDirection : m_arrowDirection;
if (m_isPositionAssigned && (!AlmostCurrentPosition(oldPos) || !AlmostCurrentAzimut(oldAzimut)))
{
CreateAnim(oldPos, oldAzimut, screen);
@@ -487,9 +530,9 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool
{
ChangeModelView(m_position, kDoNotChangeZoom);
}
else if (m_mode == location::FollowAndRotate)
else if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute)
{
ChangeModelView(m_position, m_drawDirection,
ChangeModelView(m_position, m_direction,
m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center(),
kDoNotChangeZoom);
}
@@ -499,7 +542,7 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool
{
if (m_isInRouting)
{
ChangeMode(location::FollowAndRotate);
ChangeMode(location::FollowAndRotateCompass);
UpdateViewport(kMaxScaleZoomLevel);
}
else
@@ -525,7 +568,7 @@ void MyPositionController::OnLocationUpdate(location::GpsInfo const & info, bool
{
if (m_isInRouting)
{
ChangeMode(location::FollowAndRotate);
ChangeMode(location::FollowAndRotateCompass);
UpdateViewport(kMaxScaleZoomLevel);
}
else
@@ -552,7 +595,8 @@ void MyPositionController::LoseLocation()
{
if (m_mode == location::NotFollowNoPosition)
return;
else if (m_mode == location::Follow || m_mode == location::FollowAndRotate)
else if (m_mode == location::Follow || m_mode == location::FollowAndRotateCompass ||
m_mode == location::FollowAndRotateRoute)
ChangeMode(location::PendingPosition);
else
ChangeMode(location::NotFollowNoPosition);
@@ -570,10 +614,11 @@ void MyPositionController::OnCompassUpdate(location::CompassInfo const & info, S
if ((IsInRouting() && m_isArrowGluedInRouting) || existsFreshGpsBearing)
return;
SetDirection(info.m_bearing);
SetArrowDirection(info.m_bearing);
if (m_isPositionAssigned && !AlmostCurrentAzimut(oldAzimut) && m_mode == location::FollowAndRotate)
if (m_isPositionAssigned && !AlmostCurrentAzimut(oldAzimut) && m_mode == location::FollowAndRotateCompass)
{
m_direction = info.m_bearing;
CreateAnim(GetDrawablePosition(), oldAzimut, screen);
m_isDirtyViewport = true;
}
@@ -582,10 +627,10 @@ void MyPositionController::OnCompassUpdate(location::CompassInfo const & info, S
bool MyPositionController::UpdateViewportWithAutoZoom()
{
double const autoScale = m_enablePerspectiveInRouting ? m_autoScale3d : m_autoScale2d;
if (autoScale > 0.0 && m_mode == location::FollowAndRotate && m_isInRouting && m_enableAutoZoomInRouting &&
!m_needBlockAutoZoom)
if (autoScale > 0.0 && (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute) &&
m_isInRouting && m_enableAutoZoomInRouting && !m_needBlockAutoZoom)
{
ChangeModelView(autoScale, m_position, m_drawDirection, GetRoutingRotationPixelCenter());
ChangeModelView(autoScale, m_position, m_direction, GetRoutingRotationPixelCenter());
return true;
}
return false;
@@ -617,7 +662,7 @@ void MyPositionController::Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<
m_shape->SetPositionObsolete(m_positionIsObsolete);
m_shape->SetPosition(m2::PointF(GetDrawablePosition()));
m_shape->SetAzimuth(static_cast<float>(GetDrawableAzimut()));
m_shape->SetIsValidAzimuth(IsRotationAvailable());
m_shape->SetIsValidAzimuth(IsArrowRotationAvailable());
m_shape->SetAccuracy(static_cast<float>(m_errorRadius));
m_shape->SetRoutingMode(IsInRouting());
@@ -631,7 +676,7 @@ void MyPositionController::Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<
bool MyPositionController::IsRouteFollowingActive() const
{
return IsInRouting() && m_mode == location::FollowAndRotate;
return IsInRouting() && m_mode == location::FollowAndRotateCompass;
}
bool MyPositionController::AlmostCurrentPosition(m2::PointD const & pos) const
@@ -643,18 +688,25 @@ bool MyPositionController::AlmostCurrentPosition(m2::PointD const & pos) const
bool MyPositionController::AlmostCurrentAzimut(double azimut) const
{
double constexpr kDirectionEqualityDelta = 1e-3;
return AlmostEqualAbs(azimut, m_drawDirection, kDirectionEqualityDelta);
return AlmostEqualAbs(azimut, m_direction, kDirectionEqualityDelta);
}
void MyPositionController::SetDirection(double bearing)
void MyPositionController::SetRouteDirection(double bearing)
{
m_drawDirection = bearing;
m_isDirectionAssigned = true;
m_routeDirection = bearing;
m_isRouteDirectionAssigned = true;
}
void MyPositionController::SetArrowDirection(double bearing)
{
m_arrowDirection = bearing;
m_isArrowDirectionAssigned = true;
}
void MyPositionController::ChangeMode(location::EMyPositionMode newMode)
{
if (m_isInRouting && (m_mode != newMode) && (newMode == location::FollowAndRotate))
if (m_isInRouting && (m_mode != newMode) &&
(newMode == location::FollowAndRotateCompass || newMode == location::FollowAndRotateRoute))
ResetBlockAutoZoomTimer();
m_mode = newMode;
@@ -675,7 +727,8 @@ bool MyPositionController::IsWaitingForLocation() const
void MyPositionController::StopLocationFollow()
{
if (m_mode == location::Follow || m_mode == location::FollowAndRotate)
if (m_mode == location::Follow || m_mode == location::FollowAndRotateCompass ||
m_mode == location::FollowAndRotateRoute)
ChangeMode(location::NotFollow);
m_desiredInitMode = location::NotFollow;
@@ -690,7 +743,7 @@ void MyPositionController::OnEnterForeground(double backgroundTime)
// When location was active during previous session the app will try to follow the user.
if (m_mode == location::NotFollow)
{
ChangeMode(m_isInRouting ? location::FollowAndRotate : location::Follow);
ChangeMode(m_isInRouting ? location::FollowAndRotateCompass : location::Follow);
UpdateViewport(kDoNotChangeZoom);
}
@@ -706,7 +759,7 @@ void MyPositionController::OnEnterBackground() {}
void MyPositionController::OnCompassTapped()
{
if (m_mode == location::FollowAndRotate)
if (m_mode == location::FollowAndRotateCompass)
{
ChangeMode(location::Follow);
ChangeModelView(m_position, 0.0, m_visiblePixelRect.Center(), kDoNotChangeZoom);
@@ -763,9 +816,9 @@ void MyPositionController::UpdateViewport(int zoomLevel)
{
ChangeModelView(m_position, zoomLevel);
}
else if (m_mode == location::FollowAndRotate)
else if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute)
{
ChangeModelView(m_position, m_drawDirection,
ChangeModelView(m_position, m_direction,
m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center(), zoomLevel);
}
}
@@ -775,7 +828,7 @@ m2::PointD MyPositionController::GetRotationPixelCenter() const
if (m_mode == location::Follow)
return m_visiblePixelRect.Center();
if (m_mode == location::FollowAndRotate)
if (m_mode == location::FollowAndRotateCompass || m_mode == location::FollowAndRotateRoute)
return m_isInRouting ? GetRoutingRotationPixelCenter() : m_visiblePixelRect.Center();
return m2::PointD::Zero();
@@ -817,15 +870,15 @@ double MyPositionController::GetDrawableAzimut()
}
if (m_isPendingAnimation)
return m_oldDrawDirection;
return m_oldArrowDirection;
return m_drawDirection;
return m_arrowDirection;
}
void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimut, ScreenBase const & screen)
{
double const moveDuration = PositionInterpolator::GetMoveDuration(oldPos, m_position, screen);
double const rotateDuration = AngleInterpolator::GetRotateDuration(oldAzimut, m_drawDirection);
double const rotateDuration = AngleInterpolator::GetRotateDuration(oldAzimut, m_arrowDirection);
if (df::IsAnimationAllowed(std::max(moveDuration, rotateDuration), screen))
{
if (IsModeChangeViewport())
@@ -834,7 +887,7 @@ void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimu
{
drape_ptr<Animation> anim = make_unique_dp<ArrowAnimation>(
GetDrawablePosition(), m_position, syncAnim == nullptr ? moveDuration : syncAnim->GetDuration(),
GetDrawableAzimut(), m_drawDirection);
GetDrawableAzimut(), m_arrowDirection);
if (syncAnim != nullptr)
{
anim->SetMaxDuration(syncAnim->GetMaxDuration());
@@ -843,13 +896,13 @@ void MyPositionController::CreateAnim(m2::PointD const & oldPos, double oldAzimu
return anim;
};
m_oldPosition = oldPos;
m_oldDrawDirection = oldAzimut;
m_oldArrowDirection = oldAzimut;
m_isPendingAnimation = true;
}
else
{
AnimationSystem::Instance().CombineAnimation(
make_unique_dp<ArrowAnimation>(oldPos, m_position, moveDuration, oldAzimut, m_drawDirection));
make_unique_dp<ArrowAnimation>(oldPos, m_position, moveDuration, oldAzimut, m_arrowDirection));
}
}
}
@@ -868,16 +921,18 @@ void MyPositionController::EnableAutoZoomInRouting(bool enableAutoZoom)
}
}
void MyPositionController::ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued)
void MyPositionController::ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued,
bool allowRouteRotation)
{
if (!m_isInRouting)
{
m_isInRouting = true;
m_isArrowGluedInRouting = isArrowGlued;
m_enableAutoZoomInRouting = enableAutoZoom;
m_allowRouteRotationInRouting = allowRouteRotation;
ChangeMode(location::FollowAndRotate);
ChangeModelView(m_position, m_isDirectionAssigned ? m_drawDirection : 0.0, GetRoutingRotationPixelCenter(),
ChangeMode(location::FollowAndRotateCompass);
ChangeModelView(m_position, m_isRouteDirectionAssigned ? m_routeDirection : 0.0, GetRoutingRotationPixelCenter(),
zoomLevel, [this](ref_ptr<Animation> anim) { UpdateViewport(kDoNotChangeZoom); });
ResetRoutingNotFollowTimer();
}
@@ -889,8 +944,10 @@ void MyPositionController::DeactivateRouting()
{
m_isInRouting = false;
m_isArrowGluedInRouting = false;
m_allowRouteRotationInRouting = false;
m_isDirectionAssigned = m_isCompassAvailable && m_isDirectionAssigned;
m_isArrowDirectionAssigned = m_isCompassAvailable && m_isArrowDirectionAssigned;
m_isRouteDirectionAssigned = false;
ChangeMode(location::Follow);
ChangeModelView(m_position, 0.0, m_visiblePixelRect.Center(), kDoNotChangeZoom);
@@ -919,7 +976,7 @@ void MyPositionController::CheckNotFollowRouting()
CHECK_ON_TIMEOUT(m_routingNotFollowNotifyId, kMaxNotFollowRoutingTimeSec, CheckNotFollowRouting);
if (m_routingNotFollowTimer.ElapsedSeconds() >= kMaxNotFollowRoutingTimeSec)
{
ChangeMode(location::FollowAndRotate);
ChangeMode(location::FollowAndRotateCompass);
UpdateViewport(kDoNotChangeZoom);
}
}

View File

@@ -7,6 +7,7 @@
#include "drape/pointers.hpp"
#include "routing/base/followed_polyline.hpp"
#include "shaders/program_manager.hpp"
#include "platform/location.hpp"
@@ -24,6 +25,24 @@ using TAnimationCreator = std::function<drape_ptr<Animation>(ref_ptr<Animation>)
class DrapeNotifier;
struct NavigationContext
{
bool m_isNavigable = false;
double m_distanceToNextTurn = 0.0;
double m_speedLimit = 0.0;
routing::FollowedPolyline const * m_followedPolyline = nullptr;
NavigationContext() = default;
NavigationContext(bool navigable, double distanceToTurn, double speedLimit,
routing::FollowedPolyline const & followedPolyline)
: m_isNavigable(navigable)
, m_distanceToNextTurn(distanceToTurn)
, m_speedLimit(speedLimit)
, m_followedPolyline(&followedPolyline)
{}
};
class MyPositionController
{
public:
@@ -102,7 +121,7 @@ public:
drape_ptr<MyPosition> && shape, Arrow3d::PreloadedData && preloadedData);
void ResetRenderShape();
void ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued);
void ActivateRouting(int zoomLevel, bool enableAutoZoom, bool isArrowGlued, bool allowRouteRotation);
void DeactivateRouting();
void EnablePerspectiveInRouting(bool enablePerspective);
@@ -117,14 +136,15 @@ public:
void OnEnterBackground();
void OnCompassTapped();
void OnLocationUpdate(location::GpsInfo const & info, bool isNavigable, double distanceToNextTurn, double speedLimit,
void OnLocationUpdate(location::GpsInfo const & info, df::NavigationContext const & navigationContext,
ScreenBase const & screen);
void OnCompassUpdate(location::CompassInfo const & info, ScreenBase const & screen);
void Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<gpu::ProgramManager> mng, ScreenBase const & screen,
int zoomLevel, FrameValues const & frameValues);
bool IsRotationAvailable() const { return m_isDirectionAssigned; }
bool IsArrowRotationAvailable() const { return m_isArrowDirectionAssigned; }
bool IsRouteRotationAvailable() const { return m_isRouteDirectionAssigned; }
bool IsInRouting() const { return m_isInRouting; }
bool IsRouteFollowingActive() const;
bool IsModeChangeViewport() const;
@@ -135,7 +155,8 @@ public:
private:
void ChangeMode(location::EMyPositionMode newMode);
void SetDirection(double bearing);
void SetRouteDirection(double bearing);
void SetArrowDirection(double bearing);
void ChangeModelView(m2::PointD const & center, int zoomLevel);
void ChangeModelView(double azimuth);
@@ -178,12 +199,15 @@ private:
double m_errorRadius; // error radius in mercator.
double m_horizontalAccuracy;
m2::PointD m_position; // position in mercator.
double m_drawDirection;
double m_direction;
double m_routeDirection;
double m_arrowDirection;
m2::PointD m_oldPosition; // position in mercator.
double m_oldDrawDirection;
double m_oldArrowDirection;
bool m_enablePerspectiveInRouting;
bool m_enableAutoZoomInRouting;
bool m_allowRouteRotationInRouting;
double m_autoScale2d;
double m_autoScale3d;
@@ -205,7 +229,8 @@ private:
TAnimationCreator m_animCreator;
bool m_isPositionAssigned;
bool m_isDirectionAssigned;
bool m_isArrowDirectionAssigned;
bool m_isRouteDirectionAssigned;
bool m_isCompassAvailable;
bool m_positionIsObsolete;

View File

@@ -31,8 +31,8 @@ namespace df
{
namespace
{
uint64_t constexpr kDoubleTapPauseMs = 250;
uint64_t constexpr kLongTouchMs = 500;
uint64_t constexpr kDoubleTapPauseMs = 200;
uint64_t constexpr kLongTouchMs = 700;
uint64_t constexpr kKineticDelayMs = 500;
float constexpr kForceTapThreshold = 0.75;

View File

@@ -58,15 +58,15 @@ public:
// Should match codes in the array below.
static int8_t constexpr kEnglishCode = 1;
static int8_t constexpr kUnsupportedLocaleCode = -1;
static int8_t constexpr kSimplifiedChineseCode = 44;
static int8_t constexpr kTraditionalChineseCode = 45;
static int8_t constexpr kSimplifiedChineseCode = 45;
static int8_t constexpr kTraditionalChineseCode = 46;
// *NOTE* These constants should be updated when adding new
// translation to categories.txt. When editing, keep in mind to check
// CategoriesHolder::MapLocaleToInteger() and
// CategoriesHolder::MapIntegerToLocale() as their implementations
// strongly depend on the contents of the variable.
// TODO: Refactor for more flexibility and to avoid breaking rules in two methods mentioned above.
static std::array<CategoriesHolder::Mapping, 45> constexpr kLocaleMapping = {{
static std::array<CategoriesHolder::Mapping, 46> constexpr kLocaleMapping = {{
{"en", kEnglishCode},
{"en-AU", 2},
{"en-GB", 3},
@@ -93,23 +93,24 @@ public:
{"it", 24},
{"ja", 25},
{"ko", 26},
{"lv", 27},
{"mr", 28},
{"nb", 29},
{"nl", 30},
{"pl", 31},
{"pt", 32},
{"pt-BR", 33},
{"ro", 34},
{"ru", 35},
{"sk", 36},
{"sr", 37},
{"sv", 38},
{"sw", 39},
{"th", 40},
{"tr", 41},
{"uk", 42},
{"vi", 43},
{"lt", 27},
{"lv", 28},
{"mr", 29},
{"nb", 30},
{"nl", 31},
{"pl", 32},
{"pt", 33},
{"pt-BR", 34},
{"ro", 35},
{"ru", 36},
{"sk", 37},
{"sr", 38},
{"sv", 39},
{"sw", 40},
{"th", 41},
{"tr", 42},
{"uk", 43},
{"vi", 44},
{"zh-Hans", kSimplifiedChineseCode},
{"zh-Hant", kTraditionalChineseCode},
}};

View File

@@ -7,6 +7,7 @@
#include "ge0/url_generator.hpp"
#include "platform/location.hpp"
#include "routing/route.hpp"
#include "routing/speed_camera_prohibition.hpp"
@@ -1453,7 +1454,7 @@ void Framework::CreateDrapeEngine(ref_ptr<dp::GraphicsContextFactory> contextFac
GetPlatform().RunTask(Platform::Thread::Gui, [this, mode, routingActive]()
{
// Deactivate selection (and hide place page) if we return to routing in F&R mode.
if (routingActive && mode == location::FollowAndRotate)
if (routingActive && (mode == location::FollowAndRotateCompass || mode == location::FollowAndRotateRoute))
DeactivateMapSelection();
if (m_myPositionListener != nullptr)
@@ -3208,6 +3209,7 @@ void Framework::ReadFeatures(function<void(FeatureType &)> const & reader, vecto
void Framework::OnRouteFollow(routing::RouterType type)
{
bool const isPedestrianRoute = type == RouterType::Pedestrian;
bool const allowRouteRotation = type == RouterType::Vehicle;
bool const enableAutoZoom = isPedestrianRoute ? false : LoadAutoZoom();
int const scale = isPedestrianRoute ? scales::GetPedestrianNavigationScale() : scales::GetNavigationScale();
int scale3d = isPedestrianRoute ? scales::GetPedestrianNavigation3dScale() : scales::GetNavigation3dScale();
@@ -3223,7 +3225,7 @@ void Framework::OnRouteFollow(routing::RouterType type)
// TODO. We need to sync two enums VehicleType and RouterType to be able to pass
// GetRoutingSettings(type).m_matchRoute to the FollowRoute() instead of |isPedestrianRoute|.
// |isArrowGlued| parameter fully corresponds to |m_matchRoute| in RoutingSettings.
m_drapeEngine->FollowRoute(scale, scale3d, enableAutoZoom, !isPedestrianRoute /* isArrowGlued */);
m_drapeEngine->FollowRoute(scale, scale3d, enableAutoZoom, !isPedestrianRoute /* isArrowGlued */, allowRouteRotation);
}
// RoutingManager::Delegate

View File

@@ -1,9 +1,12 @@
#include "routing_manager.hpp"
#include "drape_frontend/my_position_controller.hpp"
#include "geometry/angles.hpp"
#include "map/chart_generator.hpp"
#include "map/routing_mark.hpp"
#include "routing/absent_regions_finder.hpp"
#include "routing/base/followed_polyline.hpp"
#include "routing/checkpoint_predictor.hpp"
#include "routing/index_router.hpp"
#include "routing/route.hpp"
@@ -1170,9 +1173,9 @@ void RoutingManager::SetDrapeEngine(ref_ptr<df::DrapeEngine> engine, bool is3dAl
if (m_gpsInfoCache != nullptr)
{
auto routeMatchingInfo = GetRouteMatchingInfo(*m_gpsInfoCache);
m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, *m_gpsInfoCache, m_routingSession.IsNavigable(),
m_routingSession.GetDistanceToNextTurn(), m_routingSession.GetCurrentSpeedLimit(),
routeMatchingInfo);
df::NavigationContext navigationContext(m_routingSession.IsNavigable(), m_routingSession.GetDistanceToNextTurn(),
m_routingSession.GetCurrentSpeedLimit(), GetRoutePolyline());
m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, *m_gpsInfoCache, navigationContext, routeMatchingInfo);
m_gpsInfoCache.reset();
}
@@ -1516,9 +1519,9 @@ void RoutingManager::OnExtrapolatedLocationUpdate(location::GpsInfo const & info
m_gpsInfoCache = make_unique<location::GpsInfo>(gpsInfo);
auto routeMatchingInfo = GetRouteMatchingInfo(gpsInfo);
m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, gpsInfo, m_routingSession.IsNavigable(),
m_routingSession.GetDistanceToNextTurn(), m_routingSession.GetCurrentSpeedLimit(),
routeMatchingInfo);
df::NavigationContext navigationContext(m_routingSession.IsNavigable(), m_routingSession.GetDistanceToNextTurn(),
m_routingSession.GetCurrentSpeedLimit(), GetRoutePolyline());
m_drapeEngine.SafeCall(&df::DrapeEngine::SetGpsInfo, gpsInfo, navigationContext, routeMatchingInfo);
}
void RoutingManager::DeleteSavedRoutePoints()

View File

@@ -27,7 +27,7 @@ namespace routing::turns::sound
* - All other variants default to `zh-Hans` (Simplified Chinese).
*
*/
std::array<std::pair<std::string_view, std::string_view>, 42> constexpr kLanguageList = {{
std::array<std::pair<std::string_view, std::string_view>, 43> constexpr kLanguageList = {{
{"en", "English"},
{"id", "Bahasa Indonesia"},
{"ca", "Català"},
@@ -82,5 +82,6 @@ std::array<std::pair<std::string_view, std::string_view>, 42> constexpr kLanguag
#endif
{"ja", "日本語"},
{"ko", "한국어"},
{"ta", "தமிழ்"},
}};
} // namespace routing::turns::sound

View File

@@ -139,7 +139,8 @@ enum EMyPositionMode
NotFollowNoPosition,
NotFollow,
Follow,
FollowAndRotate
FollowAndRotateCompass,
FollowAndRotateRoute
};
using TMyPositionModeChanged = std::function<void(location::EMyPositionMode, bool)>;

View File

@@ -308,7 +308,8 @@ string ToString<location::EMyPositionMode>(location::EMyPositionMode const & v)
case location::NotFollow: return "NotFollow";
case location::NotFollowNoPosition: return "NotFollowNoPosition";
case location::Follow: return "Follow";
case location::FollowAndRotate: return "FollowAndRotate";
case location::FollowAndRotateCompass: return "FollowAndRotateCompass";
case location::FollowAndRotateRoute: return "FollowAndRotateRoute";
default: return "Pending";
}
}
@@ -324,8 +325,10 @@ bool FromString<location::EMyPositionMode>(string const & s, location::EMyPositi
v = location::NotFollowNoPosition;
else if (s == "Follow")
v = location::Follow;
else if (s == "FollowAndRotate")
v = location::FollowAndRotate;
else if (s == "FollowAndRotateCompass")
v = location::FollowAndRotateCompass;
else if (s == "FollowAndRotateRoute")
v = location::FollowAndRotateRoute;
else
return false;

View File

@@ -237,4 +237,34 @@ Iter FollowedPolyline::GetClosestMatchingProjectionInInterval(m2::RectD const &
return nearestIter;
}
m2::PointD FollowedPolyline::GetLookaheadPoint(double lookaheadDistanceM) const
{
if (!IsValid())
return m2::PointD();
size_t segmentIdx = m_current.m_ind;
m2::PointD prev = m_current.m_pt;
size_t const maxSegmentIdx = m_poly.GetSize() - 1;
double remaining = lookaheadDistanceM;
while (segmentIdx < maxSegmentIdx)
{
m2::PointD const & next = m_poly.GetPoint(segmentIdx + 1);
double segLen = mercator::DistanceOnEarth(prev, next);
if (remaining <= segLen)
{
double t = remaining / segLen;
return prev + (next - prev) * t;
}
remaining -= segLen;
prev = next;
segmentIdx += 1;
}
return m_poly.GetPoint(segmentIdx);
}
} // namespace routing

View File

@@ -125,6 +125,9 @@ public:
bool IsFakeSegment(size_t index) const;
/// \brief Obtain a point |lookaheadDistanceM| meters along the current route
m2::PointD GetLookaheadPoint(double lookaheadDistanceM) const;
private:
/// \returns iterator to the best projection of center of |posRect| to the |m_poly|.
/// If there's a good projection of center of |posRect| to two closest segments of |m_poly|