[android] Add Speed class

Signed-off-by: Gonzalo Pesquero <gpesquero@yahoo.es>
This commit is contained in:
Gonzalo Pesquero 2024-04-16 20:32:00 +02:00
parent 5a8ccd9115
commit 2bb8b7aa50
7 changed files with 236 additions and 4 deletions

View file

@ -1953,4 +1953,10 @@ Java_app_organicmaps_Framework_nativeGetKayakHotelLink(JNIEnv * env, jclass, jst
return url.empty() ? nullptr : jni::ToJavaString(env, url);
}
JNIEXPORT jint JNICALL
Java_app_organicmaps_Framework_nativeGetUnits(JNIEnv *, jclass)
{
return static_cast<jint>(measurement_utils::GetMeasurementUnits());
}
} // extern "C"

View file

@ -57,6 +57,14 @@ JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatSpee
platform::GetLocalizedSpeedUnits(units));
}
JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeStringFormatSpeedAndUnits(
JNIEnv * env, jclass thiz, jdouble metersPerSecond)
{
auto const units = measurement_utils::GetMeasurementUnits();
return jni::ToJavaString(env, measurement_utils::FormatSpeedNumeric(metersPerSecond, units) +
";" + platform::GetLocalizedSpeedUnits(units));
}
JNIEXPORT jobject JNICALL
Java_app_organicmaps_util_StringUtils_nativeFormatDistance(JNIEnv * env, jclass, jdouble distanceInMeters)
{

View file

@ -60,6 +60,10 @@ public class Framework
public static final int ROUTER_TYPE_TRANSIT = 3;
public static final int ROUTER_TYPE_RULER = 4;
// Units values shall be the same as the ones defined in c++ in <platform/measurement_utils.hpp>.
public static final int UNITS_METRIC = 0;
public static final int UNITS_IMPERIAL = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ROUTE_REBUILD_AFTER_POINTS_LOADING})
public @interface RouteRecommendationType {}
@ -460,4 +464,6 @@ public class Framework
@Nullable
public static native String nativeGetKayakHotelLink(@NonNull String countryIsoCode, @NonNull String uri,
long firstDaySec, long lastDaySec, boolean isReferral);
public static native int nativeGetUnits();
}

View file

@ -155,7 +155,7 @@ public class NavigationController implements TrafficManager.TrafficCallback,
updateVehicle(info);
updateStreetView(info);
mNavMenu.update(info);
mNavMenu.update(info, Framework.nativeGetUnits());
}
private void updateStreetView(@NonNull RoutingInfo info)

View file

@ -0,0 +1,171 @@
package app.organicmaps.util;
import android.content.Context;
import android.util.Pair;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Formatter;
import java.util.Locale;
import app.organicmaps.Framework;
import app.organicmaps.util.log.Logger;
public class Speed
{
private static String mUnitStringKmh = "km/h";
private static String mUnitStringMiph = "mph";
private static char mDecimalSeparator = Character.MIN_VALUE;
public static double MpsToKmph(double mps) { return mps * 3.6; }
public static double MpsToMiph(double mps) { return mps * 2.236936; }
public static void setUnitStringKmh(String unitStringKmh) { mUnitStringKmh = unitStringKmh; }
public static void setUnitStringMiph(String unitStringMiph) { mUnitStringMiph = unitStringMiph; }
private final static DecimalFormat mDecimalFormatNoDecimal = new DecimalFormat("#");
private final static DecimalFormat mDecimalFormatOneDecimal = new DecimalFormat("0.0");
private final static Locale mLocale = Locale.getDefault();
private final static StringBuilder mSb = new StringBuilder();
private final static Formatter mFormatter = new Formatter(mSb, mLocale);
private final static NumberFormat mNumberFormatNoDecimal = NumberFormat.getInstance(mLocale);
private final static NumberFormat mNumberFormatOneDecimal = NumberFormat.getInstance(mLocale);
public static Pair<String, String> formatMeasurements(double speedInMetersPerSecond, int units,
Context context)
{
double speedValue;
String unitsString;
if (units == Framework.UNITS_IMPERIAL)
{
speedValue = MpsToMiph(speedInMetersPerSecond);
unitsString = mUnitStringMiph;
}
else
{
speedValue = MpsToKmph(speedInMetersPerSecond);
unitsString = mUnitStringKmh;
}
// Option 1: String.format()
long start1 = System.nanoTime();
String formatString = (speedValue < 10.0)? "%.1f" : "%.0f";
String speedString = String.format(mLocale, formatString, speedValue);
long elapsed1 = System.nanoTime() - start1;
Logger.i("LOCALE_MEASURE", "1) " + speedString);
// Option 2: DecimalFormat class
long start2 = System.nanoTime();
if (speedValue < 10.0)
speedString = mDecimalFormatOneDecimal.format(speedValue);
else
speedString = mDecimalFormatNoDecimal.format(speedValue);
long elapsed2 = System.nanoTime() - start2;
Logger.i("LOCALE_MEASURE", "2) " + speedString);
// Option 3: Long.toString() + StringBuffer.insert()
long start3 = System.nanoTime();
if (speedValue < 10.0)
{
speedString = Long.toString(Math.round(speedValue * 10.0));
StringBuffer buffer = new StringBuffer(speedString);
if (mDecimalSeparator == Character.MIN_VALUE)
mDecimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
// For low values (< 1.0), force to have 2 characters in string.
if (buffer.length() < 2)
buffer.insert(0, "0");
buffer.insert(1, mDecimalSeparator);
speedString = buffer.toString();
}
else
speedString = Long.toString(Math.round(speedValue));
long elapsed3 = System.nanoTime() - start3;
Logger.i("LOCALE_MEASURE", "3) " + speedString);
// Option 4: Formatter class
mSb.setLength(0);
long start4 = System.nanoTime();
if (speedValue < 10.0)
mFormatter.format("%.1f", speedValue);
else
mFormatter.format("%d", Math.round(speedValue));
speedString = mSb.toString();
long elapsed4 = System.nanoTime() - start4;
Logger.i("LOCALE_MEASURE", "4) " + speedString);
// Option 5: NumberFormat class
mNumberFormatNoDecimal.setMaximumFractionDigits(0);
mNumberFormatOneDecimal.setMaximumFractionDigits(1);
long start5 = System.nanoTime();
if (speedValue < 10.0)
speedString = mNumberFormatOneDecimal.format(speedValue);
else
speedString = mNumberFormatNoDecimal.format(Math.round(speedValue));
long elapsed5 = System.nanoTime() - start5;
Logger.i("LOCALE_MEASURE", "5) " + speedString);
String text = String.format(Locale.US,
"Java calls: %5d / %5d / %5d / %5d / %5d",
Math.round(0.001 * elapsed1),
Math.round(0.001 * elapsed2),
Math.round(0.001 * elapsed3),
Math.round(0.001 * elapsed4),
Math.round(0.001 * elapsed5));
Logger.i("LOCALE_MEASURE", text);
return new Pair<>(speedString, unitsString);
}
public static Pair<String, String> format(double speedInMetersPerSecond, int units,
Context context)
{
double speedValue;
String unitsString;
if (units == Framework.UNITS_IMPERIAL)
{
speedValue = MpsToMiph(speedInMetersPerSecond);
unitsString = mUnitStringMiph;
}
else
{
speedValue = MpsToKmph(speedInMetersPerSecond);
unitsString = mUnitStringKmh;
}
String speedString;
if (speedValue < 10.0)
{
speedString = Long.toString(Math.round(speedValue * 10.0));
StringBuffer buffer = new StringBuffer(speedString);
if (mDecimalSeparator == Character.MIN_VALUE)
mDecimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
// For low values (< 1.0), force to have 2 characters in string.
if (buffer.length() < 2)
buffer.insert(0, "0");
buffer.insert(1, mDecimalSeparator);
speedString = buffer.toString();
}
else
speedString = Long.toString(Math.round(speedValue));
return new Pair<>(speedString, unitsString);
}
}

View file

@ -30,6 +30,7 @@ public class StringUtils
public static native String[] nativeFilterContainsNormalized(String[] strings, String substr);
public static native Pair<String, String> nativeFormatSpeedAndUnits(double metersPerSecond);
public static native String nativeStringFormatSpeedAndUnits(double metersPerSecond);
public static native Distance nativeFormatDistance(double meters);
@NonNull
public static native Pair<String, String> nativeGetLocalizedDistanceUnits();

View file

@ -1,6 +1,7 @@
package app.organicmaps.widget.menu;
import android.location.Location;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.Button;
@ -15,12 +16,16 @@ import app.organicmaps.location.LocationHelper;
import app.organicmaps.routing.RoutingInfo;
import app.organicmaps.sound.TtsPlayer;
import app.organicmaps.util.Graphics;
import app.organicmaps.util.Speed;
import app.organicmaps.util.StringUtils;
import app.organicmaps.util.UiUtils;
import app.organicmaps.util.log.Logger;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
public class NavMenu
@ -203,22 +208,57 @@ public class NavMenu
mTimeEstimate.setText(localTime.format(DateTimeFormatter.ofPattern(format)));
}
private void updateSpeedView(@NonNull RoutingInfo info)
private void updateSpeedView(@NonNull RoutingInfo info, int units)
{
final Location last = LocationHelper.from(mActivity).getSavedLocation();
if (last == null)
return;
// Log measurements for different Java implementations.
Speed.formatMeasurements(last.getSpeed(), units, mActivity.getApplicationContext());
// Speed formatting using native calls.
long start1 = System.nanoTime();
Pair<String, String> speedAndUnits = StringUtils.nativeFormatSpeedAndUnits(last.getSpeed());
long elapsed1 = System.nanoTime() - start1;
mSpeedUnits.setText(speedAndUnits.second);
mSpeedValue.setText(speedAndUnits.first);
// Speed formatting using native calls. Speed and units is returned in a single string.
long start2 = System.nanoTime();
String speedAndUnitsString = StringUtils.nativeStringFormatSpeedAndUnits(last.getSpeed());
// Speed and units are separated by semicolon ";" in returned string.
int separatorPos = speedAndUnitsString.indexOf(";");
String speedString = speedAndUnitsString.substring(0, separatorPos);
String unitsString = speedAndUnitsString.substring(separatorPos + 1);
long elapsed2 = System.nanoTime() - start2;
mSpeedUnits.setText(speedString);
mSpeedValue.setText(unitsString);
// Speed formatting using Android Java calls.
long start3 = System.nanoTime();
speedAndUnits = Speed.format(last.getSpeed(), units, mActivity.getApplicationContext());
long elapsed3 = System.nanoTime() - start3;
mSpeedUnits.setText(speedAndUnits.second);
mSpeedValue.setText(speedAndUnits.first);
String text = String.format(Locale.US,
"Native calls: %5d / %5d / %5d",
Math.round(0.001 * elapsed1),
Math.round(0.001 * elapsed2),
Math.round(0.001 * elapsed3));
Logger.i("LOCALE_MEASURE", text);
mSpeedViewContainer.setActivated(info.isSpeedLimitExceeded());
}
public void update(@NonNull RoutingInfo info)
public void update(@NonNull RoutingInfo info, int units)
{
updateSpeedView(info);
updateSpeedView(info, units);
updateTime(info.totalTimeInSeconds);
mDistanceValue.setText(info.distToTarget.mDistanceStr);
mDistanceUnits.setText(info.distToTarget.getUnitsStr(mActivity.getApplicationContext()));