diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp index d29a67a69e..05a5114f4b 100644 --- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp +++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp @@ -7,6 +7,7 @@ #include "app/organicmaps/util/Distance.hpp" #include "app/organicmaps/util/FeatureIdBuilder.hpp" #include "app/organicmaps/util/NetworkPolicy.hpp" +#include "app/organicmaps/util/SpeedFormatted.hpp" #include "app/organicmaps/vulkan/android_vulkan_context_factory.hpp" #include "map/bookmark_helpers.hpp" @@ -48,6 +49,7 @@ #include "platform/platform.hpp" #include "platform/preferred_languages.hpp" #include "platform/settings.hpp" +#include "platform/speed_formatted.hpp" #include "platform/utm_mgrs_utils.hpp" #include "base/assert.hpp" @@ -1259,7 +1261,9 @@ Java_app_organicmaps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass) jni::GetConstructorID(env, klass, "(Lapp/organicmaps/util/Distance;Lapp/organicmaps/util/Distance;" "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DIIIII" - "[Lapp/organicmaps/routing/SingleLaneInfo;DZZ)V"); + "[Lapp/organicmaps/routing/SingleLaneInfo;" + "Lapp/organicmaps/util/SpeedFormatted;" + "Lapp/organicmaps/util/SpeedFormatted;ZZ)V"); vector const & lanes = info.m_lanes; jobjectArray jLanes = nullptr; @@ -1287,6 +1291,7 @@ Java_app_organicmaps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass) } auto const & rm = frm()->GetRoutingManager(); + auto const speed = rm.RoutingSession().GetLastSpeed(); auto const isSpeedCamLimitExceeded = rm.IsRoutingActive() ? rm.IsSpeedCamLimitExceeded() : false; auto const shouldPlaySignal = frm()->GetRoutingManager().GetSpeedCamManager().ShouldPlayBeepSignal(); jobject const result = env->NewObject( @@ -1294,8 +1299,9 @@ Java_app_organicmaps_Framework_nativeGetRouteFollowingInfo(JNIEnv * env, jclass) ToJavaDistance(env, info.m_distToTurn), jni::ToJavaString(env, info.m_currentStreetName), jni::ToJavaString(env, info.m_nextStreetName), jni::ToJavaString(env, info.m_nextNextStreetName), info.m_completionPercent, info.m_turn, info.m_nextTurn, info.m_pedestrianTurn, info.m_exitNum, - info.m_time, jLanes, info.m_speedLimitMps, static_cast(isSpeedCamLimitExceeded), - static_cast(shouldPlaySignal)); + info.m_time, jLanes, ToJavaSpeedFormatted(env, platform::SpeedFormatted(speed)), + ToJavaSpeedFormatted(env, platform::SpeedFormatted(info.m_speedLimitMps)), + static_cast(isSpeedCamLimitExceeded), static_cast(shouldPlaySignal)); ASSERT(result, (jni::DescribeException())); return result; } diff --git a/android/app/src/main/cpp/app/organicmaps/util/SpeedFormatted.hpp b/android/app/src/main/cpp/app/organicmaps/util/SpeedFormatted.hpp new file mode 100644 index 0000000000..9e37d6e862 --- /dev/null +++ b/android/app/src/main/cpp/app/organicmaps/util/SpeedFormatted.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "app/organicmaps/core/jni_helper.hpp" + +#include "platform/speed_formatted.hpp" + +inline jobject ToJavaSpeedFormatted(JNIEnv * env, platform::SpeedFormatted const & speedFormatted) +{ + static jclass const speedFormattedClass = jni::GetGlobalClassRef(env, "app/organicmaps/util/SpeedFormatted"); + + static jmethodID const speedFormattedConstructor = jni::GetConstructorID(env, speedFormattedClass, "(DLjava/lang/String;B)V"); + + jobject distanceObject = env->NewObject( + speedFormattedClass, speedFormattedConstructor, + speedFormatted.GetSpeed(), jni::ToJavaString(env, speedFormatted.GetSpeedString()), static_cast(speedFormatted.GetUnits())); + + return distanceObject; +} diff --git a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp index e99a34b0ed..a4009d316f 100644 --- a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp +++ b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp @@ -49,14 +49,6 @@ Java_app_organicmaps_util_StringUtils_nativeFilterContainsNormalized(JNIEnv * en return jni::ToJavaStringArray(env, filtered); } -JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatSpeedAndUnits( - JNIEnv * env, jclass thiz, jdouble metersPerSecond) -{ - auto const units = measurement_utils::GetMeasurementUnits(); - return MakeJavaPair(env, measurement_utils::FormatSpeedNumeric(metersPerSecond, units), - platform::GetLocalizedSpeedUnits(units)); -} - JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatDistance(JNIEnv * env, jclass, jdouble distanceInMeters) { diff --git a/android/app/src/main/java/app/organicmaps/Framework.java b/android/app/src/main/java/app/organicmaps/Framework.java index b99bcaf85c..0e56f0b5be 100644 --- a/android/app/src/main/java/app/organicmaps/Framework.java +++ b/android/app/src/main/java/app/organicmaps/Framework.java @@ -187,11 +187,11 @@ public class Framework public static native DistanceAndAzimut nativeGetDistanceAndAzimuthFromLatLon(double dstLat, double dstLon, double srcLat, double srcLon, double north); - public static native String nativeFormatLatLon(double lat, double lon, int coordFormat); + public static native String nativeFormatLatLon(double lat, double lon, int coordFormat); // TODO: move this method to StringUtils class. - public static native String nativeFormatAltitude(double alt); + public static native String nativeFormatAltitude(double alt); // TODO: move this method to StringUtils class. - public static native String nativeFormatSpeed(double speed); + public static native String nativeFormatSpeed(double speed); // TODO: move this method to StringUtils class. public static native String nativeGetGe0Url(double lat, double lon, double zoomLevel, String name); diff --git a/android/app/src/main/java/app/organicmaps/routing/RoutingInfo.java b/android/app/src/main/java/app/organicmaps/routing/RoutingInfo.java index 6b0a393783..73d85c267d 100644 --- a/android/app/src/main/java/app/organicmaps/routing/RoutingInfo.java +++ b/android/app/src/main/java/app/organicmaps/routing/RoutingInfo.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import app.organicmaps.R; import app.organicmaps.util.Distance; +import app.organicmaps.util.SpeedFormatted; // Called from JNI. @Keep @@ -36,7 +37,8 @@ public class RoutingInfo public final PedestrianTurnDirection pedestrianTurnDirection; // Current speed limit in meters per second. // If no info about speed limit then speedLimitMps < 0. - public final double speedLimitMps; + public final SpeedFormatted speed; + public final SpeedFormatted speedLimit; private final boolean speedCamLimitExceeded; private final boolean shouldPlayWarningSignal; @@ -143,8 +145,8 @@ public class RoutingInfo public RoutingInfo(Distance distToTarget, Distance distToTurn, String currentStreet, String nextStreet, String nextNextStreet, double completionPercent, int vehicleTurnOrdinal, int vehicleNextTurnOrdinal, int pedestrianTurnOrdinal, int exitNum, - int totalTime, SingleLaneInfo[] lanes, double speedLimitMps, boolean speedLimitExceeded, - boolean shouldPlayWarningSignal) + int totalTime, SingleLaneInfo[] lanes, SpeedFormatted speed, SpeedFormatted speedLimit, + boolean speedCamLimitExceeded, boolean shouldPlayWarningSignal) { this.distToTarget = distToTarget; this.distToTurn = distToTurn; @@ -158,8 +160,9 @@ public class RoutingInfo this.lanes = lanes; this.exitNum = exitNum; this.pedestrianTurnDirection = PedestrianTurnDirection.values()[pedestrianTurnOrdinal]; - this.speedLimitMps = speedLimitMps; - this.speedCamLimitExceeded = speedLimitExceeded; + this.speed = speed; + this.speedLimit = speedLimit; + this.speedCamLimitExceeded = speedCamLimitExceeded; this.shouldPlayWarningSignal = shouldPlayWarningSignal; } diff --git a/android/app/src/main/java/app/organicmaps/util/SpeedFormatted.java b/android/app/src/main/java/app/organicmaps/util/SpeedFormatted.java new file mode 100644 index 0000000000..a6c7043f29 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/util/SpeedFormatted.java @@ -0,0 +1,55 @@ +package app.organicmaps.util; + +import android.content.Context; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import app.organicmaps.R; + +// Used by JNI. +@Keep +@SuppressWarnings("unused") +public class SpeedFormatted +{ + /** + * IMPORTANT : Order of enum values MUST BE the same as native measurement_utils::Units enum. + */ + public enum Units + { + KilometersPerHour(R.string.kilometers_per_hour), + MilesPerHour(R.string.miles_per_hour); + + @StringRes + public final int mStringRes; + + Units(@StringRes int stringRes) + { + mStringRes = stringRes; + } + } + + public final double mSpeed; + @NonNull + public final String mSpeedStr; + public final SpeedFormatted.Units mUnits; + + public SpeedFormatted(double mSpeed, @NonNull String mSpeedStr, byte unitsIndex) + { + this.mSpeed = mSpeed; + this.mSpeedStr = mSpeedStr; + this.mUnits = Units.values()[unitsIndex]; + } + + public boolean isValid() + { + return mSpeed >= 0.0; + } + + @NonNull + public String getUnitsStr(@NonNull final Context context) + { + return context.getString(mUnits.mStringRes); + } +} diff --git a/android/app/src/main/java/app/organicmaps/util/StringUtils.java b/android/app/src/main/java/app/organicmaps/util/StringUtils.java index d82b80a092..d503ccb1b3 100644 --- a/android/app/src/main/java/app/organicmaps/util/StringUtils.java +++ b/android/app/src/main/java/app/organicmaps/util/StringUtils.java @@ -29,7 +29,6 @@ public class StringUtils public static native boolean nativeContainsNormalized(String str, String substr); public static native String[] nativeFilterContainsNormalized(String[] strings, String substr); - public static native Pair nativeFormatSpeedAndUnits(double metersPerSecond); public static native Distance nativeFormatDistance(double meters); @NonNull public static native Pair nativeGetLocalizedDistanceUnits(); diff --git a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java index 763a3c86d1..3b1ee9c90d 100644 --- a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java +++ b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java @@ -17,6 +17,7 @@ import app.organicmaps.location.LocationHelper; import app.organicmaps.routing.RoutingInfo; import app.organicmaps.sound.TtsPlayer; import app.organicmaps.util.Graphics; +import app.organicmaps.util.SpeedFormatted; import app.organicmaps.util.StringUtils; import app.organicmaps.util.ThemeUtils; import app.organicmaps.util.UiUtils; @@ -208,21 +209,20 @@ public class NavMenu private void updateSpeedView(@NonNull RoutingInfo info) { - final Location last = LocationHelper.from(mActivity).getSavedLocation(); - if (last == null) - return; + SpeedFormatted speed = info.speed; + boolean speedLimitExceeded = false; - Pair speedAndUnits = StringUtils.nativeFormatSpeedAndUnits(last.getSpeed()); - - if (info.speedLimitMps > 0.0) + if (info.speedLimit != null && info.speedLimit.isValid()) { - Pair speedLimitAndUnits = StringUtils.nativeFormatSpeedAndUnits(info.speedLimitMps); - mSpeedValue.setText(speedAndUnits.first + "\u202F/\u202F" + speedLimitAndUnits.first); + SpeedFormatted speedLimit = info.speedLimit; + mSpeedValue.setText(speed.mSpeedStr + "\u202F/\u202F" + speedLimit.mSpeedStr); + + speedLimitExceeded = (int)speed.mSpeed > (int)speedLimit.mSpeed; } else - mSpeedValue.setText(speedAndUnits.first); + mSpeedValue.setText(speed.mSpeedStr); - if (info.speedLimitMps > 0.0 && last.getSpeed() > info.speedLimitMps) + if (speedLimitExceeded) { if (info.isSpeedCamLimitExceeded()) mSpeedValue.setTextColor(ContextCompat.getColor(mActivity, R.color.white_primary)); @@ -232,7 +232,7 @@ public class NavMenu else mSpeedValue.setTextColor(ThemeUtils.getColor(mActivity, android.R.attr.textColorPrimary)); - mSpeedUnits.setText(speedAndUnits.second); + mSpeedUnits.setText(info.speed.getUnitsStr(mActivity)); mSpeedViewContainer.setActivated(info.isSpeedCamLimitExceeded()); } diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 1f6482e547..747d759e96 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -56,6 +56,8 @@ set(SRC socket.hpp string_storage_base.cpp string_storage_base.hpp + speed_formatted.cpp + speed_formatted.hpp utm_mgrs_utils.cpp utm_mgrs_utils.hpp ) diff --git a/platform/measurement_utils.hpp b/platform/measurement_utils.hpp index 906d4960e5..a518bba4bf 100644 --- a/platform/measurement_utils.hpp +++ b/platform/measurement_utils.hpp @@ -23,6 +23,7 @@ inline double MilesToFeet(double mi) { return mi * 5280.0; } inline double MiphToKmph(double miph) { return MilesToMeters(miph) / 1000.0; } inline double KmphToMiph(double kmph) { return MetersToMiles(kmph * 1000.0); } inline double MpsToKmph(double mps) { return mps * 3.6; } +inline double MpsToMiph(double mps) { return mps * 3.6 * 0.621371192; } inline double MetersToFeet(double m) { return m * 3.2808399; } inline double FeetToMeters(double ft) { return ft * 0.3048; } inline double FeetToMiles(double ft) { return ft * 0.00018939; } diff --git a/platform/speed_formatted.cpp b/platform/speed_formatted.cpp new file mode 100644 index 0000000000..c0a9562022 --- /dev/null +++ b/platform/speed_formatted.cpp @@ -0,0 +1,68 @@ +#include "speed_formatted.hpp" + +#include "platform/locale.hpp" +#include "platform/localization.hpp" +#include "platform/measurement_utils.hpp" + +#include "base/assert.hpp" + +namespace platform +{ +using namespace measurement_utils; + +SpeedFormatted::SpeedFormatted(double speedMps) : SpeedFormatted(speedMps, GetMeasurementUnits()) {} + +SpeedFormatted::SpeedFormatted(double speedMps, Units units) : m_units(units) +{ + switch (units) + { + case Units::Metric: + m_speed = speedMps < 0 ? speedMps : MpsToKmph(speedMps); + break; + case Units::Imperial: + m_speed = speedMps < 0 ? speedMps : MpsToMiph(speedMps); + break; + default: UNREACHABLE(); + } +} + +bool SpeedFormatted::IsValid() const { return m_speed >= 0.0; } + +double SpeedFormatted::GetSpeed() const { return m_speed; } + +Units SpeedFormatted::GetUnits() const { return m_units; } + +std::string SpeedFormatted::GetSpeedString() const +{ + if (!IsValid()) + return ""; + + // Default precision is 0 (no decimals). + int precision = 0; + + // Set 1 decimal precision for speed per hour (km/h, miles/h) lower than 10.0 (9.5, 7.0,...). + if (m_speed < 10.0) + precision = 1; + + return ToStringPrecision(m_speed, precision); +} + +std::string SpeedFormatted::GetUnitsString() const +{ + switch (m_units) + { + case Units::Metric: return GetLocalizedString("kilometers_per_hour"); + case Units::Imperial: return GetLocalizedString("miles_per_hour"); + default: UNREACHABLE(); + } +} + +std::string SpeedFormatted::ToString() const +{ + if (!IsValid()) + return ""; + + return GetSpeedString() + kNarrowNonBreakingSpace + GetUnitsString(); +} + +} // namespace platform diff --git a/platform/speed_formatted.hpp b/platform/speed_formatted.hpp new file mode 100644 index 0000000000..6cbcb58fa0 --- /dev/null +++ b/platform/speed_formatted.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "platform/measurement_utils.hpp" + +#include + +namespace platform +{ +class SpeedFormatted +{ +public: + SpeedFormatted(double speedInMps); // Initialize with m/s value and default measurement units + SpeedFormatted(double speedInMps, measurement_utils::Units units); + + bool IsValid() const; + + double GetSpeed() const; + measurement_utils::Units GetUnits() const; + + std::string GetSpeedString() const; + std::string GetUnitsString() const; + + std::string ToString() const; + + friend std::string DebugPrint(SpeedFormatted const & d) { return d.ToString(); } + +private: + double m_speed; // Speed in km/h or mile/h depending on m_units. + measurement_utils::Units m_units; +}; + +} // namespace platform diff --git a/routing/routing_session.cpp b/routing/routing_session.cpp index 89ea50a9de..f3204ada79 100644 --- a/routing/routing_session.cpp +++ b/routing/routing_session.cpp @@ -273,6 +273,7 @@ SessionState RoutingSession::OnLocationPositionChanged(GpsInfo const & info) return m_state; m_turnNotificationsMgr.SetSpeedMetersPerSecond(info.m_speed); + m_lastSpeed = info.m_speed; auto const formerIter = m_route->GetCurrentIteratorTurn(); if (m_route->MoveIterator(info)) diff --git a/routing/routing_session.hpp b/routing/routing_session.hpp index 21041c24b1..33b3a6323f 100644 --- a/routing/routing_session.hpp +++ b/routing/routing_session.hpp @@ -173,6 +173,7 @@ public: void SetGuidesForTests(GuidesTracks guides) { m_router->SetGuidesTracks(std::move(guides)); } double GetCompletionPercent() const; + double GetLastSpeed() const { return m_lastSpeed; } private: struct DoReadyCallback @@ -208,6 +209,7 @@ private: double m_lastDistance = 0.0; int m_moveAwayCounter = 0; m2::PointD m_lastGoodPosition; + double m_lastSpeed = -1.0; m2::PointD m_userCurrentPosition; bool m_userCurrentPositionValid = false;