package com.maciekjanusz.compassproject.util;
import android.content.Context;
import com.maciekjanusz.compassproject.R;
import com.maciekjanusz.compassproject.preferences.AppPreferences;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import static com.maciekjanusz.compassproject.util.CompassMath.decimalToDegrees;
import static com.maciekjanusz.compassproject.util.CompassMath.metersToMiles;
import static com.maciekjanusz.compassproject.util.CompassMath.msToKmH;
import static com.maciekjanusz.compassproject.util.CompassMath.msToMpH;
/**
* Use this class for formatting compass & navigation values. Resolves several resource strings
* during init, checks preferences for displaying coordinates and unit system.
* Contains {@link #stringBuilder} that enables memory allocation optimized formatting of
* pitch & roll values.
*/
public class ValueFormatter {
private static final char DEGREE = '\u00B0';
/**
* StringBuilder for formatting pitch & roll values
*/
private final StringBuilder stringBuilder = new StringBuilder();
/**
* Flag indicating whether to use metric system
*/
private boolean metricSystem;
/**
* Flag indicating whether to use decimal coordinates
*/
private boolean decimalCoordinates;
/**
* Array with cardinal direction strings (N, NE, E...)
*/
private String[] cardinalDirections;
private String daysString;
private String hoursString;
private String minutesString;
private String kmhUnit;
private String kmUnit;
private String mphUnit;
private String mileUnit;
private String meterUnit;
private String decimalCoordinatesFormat;
private String dmsCoordinatesFormat;
private String speedFormat;
private String durationFormat;
private String metricDistanceFormat;
private String imperialDistanceFormat;
public ValueFormatter(Context context) {
loadCardinalDirections(context);
loadPrefs(context);
loadUnitStrings(context);
loadFormatStrings(context);
}
public void loadCardinalDirections(Context context) {
// cardinal directions string array
cardinalDirections = context.getResources().getStringArray(R.array.cardinal_directions);
}
public void loadPrefs(Context context) {
// preferences
metricSystem = AppPreferences.isMetricSystem(context);
decimalCoordinates = AppPreferences.isDecimalCoordinates(context);
}
public void loadUnitStrings(Context context) {
/*
Resolution of unit strings
*/
daysString = context.getString(R.string.days);
hoursString = context.getString(R.string.hours);
minutesString = context.getString(R.string.minutes);
kmUnit = context.getString(R.string.unit_kilometer);
mileUnit = context.getString(R.string.unit_mile);
meterUnit = context.getString(R.string.unit_meter);
kmhUnit = context.getString(R.string.unit_kmh);
mphUnit = context.getString(R.string.unit_mph);
}
public void loadFormatStrings(Context context) {
/*
Resolution of format strings
*/
decimalCoordinatesFormat = context.getString(R.string.decimal_coordinates_format);
dmsCoordinatesFormat = context.getString(R.string.dms_coordinates_format);
speedFormat = context.getString(R.string.speed_format);
durationFormat = context.getString(R.string.duration_format);
metricDistanceFormat = context.getString(R.string.metric_distance_format);
imperialDistanceFormat = context.getString(R.string.imperial_distance_format);
}
/**
* This function formats time duration: if duration is larger than a day, returns number of
* full days. If shorter than a day, but longer than an hour, shows number of full hours.
* If shorter than hour, shows number of full minutes.
* @param duration duration, in seconds
* @return formatted duration
*/
public String formatDuration(long duration) {
long days = TimeUnit.SECONDS.toDays(duration);
long hours = TimeUnit.SECONDS.toHours(duration - TimeUnit.DAYS.toSeconds(days));
long minutes = TimeUnit.SECONDS.toMinutes(duration - TimeUnit.DAYS.toSeconds(days) - TimeUnit.HOURS.toMillis(hours));
if (days > 0) {
if (hours > 12) {
days++;
}
return String.format(Locale.getDefault(), durationFormat, days, daysString);
} else if (hours > 0) {
if (minutes > 30) {
hours++;
}
return String.format(Locale.getDefault(), durationFormat, hours, hoursString);
} else {
return String.format(Locale.getDefault(), durationFormat, minutes, minutesString);
}
}
/**
* This function formats meter distance value to desired unit (km, m or mi)
* @param distance distance [m]
* @return formatted distance
*/
public String formatDistance(float distance) {
if (metricSystem) {
if (distance >= 1000) {
distance /= 1000;
return String.format(Locale.getDefault(), metricDistanceFormat, (int) distance, kmUnit);
} else {
return String.format(Locale.getDefault(), metricDistanceFormat, (int) distance, meterUnit);
}
} else { // imperial
return String.format(Locale.US, imperialDistanceFormat, metersToMiles(distance), mileUnit);
}
}
/**
* This function formats m/s speed value to desired unit (km/h or mph)
* @param speed speed [m/s]
* @return formatted speed
*/
public String formatSpeed(float speed) {
int convertedSpeed;
String unit;
if (metricSystem) {
convertedSpeed = (int) msToKmH(speed);
unit = kmhUnit;
} else {
convertedSpeed = (int) msToMpH(speed);
unit = mphUnit;
}
return String.format(Locale.getDefault(), speedFormat, convertedSpeed, unit);
}
/**
* This function checks if lat/lon should be formatted as decimal or DMS and
* returns properly formatted string.
* @param latitude latitude, decimal
* @param longitude longitude, decimal
* @return formatted string
*/
public String formatCoordinates(double latitude, double longitude) {
if (decimalCoordinates) {
return String.format(Locale.getDefault(), decimalCoordinatesFormat, latitude, longitude);
} else {
float[] degMinSecLat = decimalToDegrees(latitude);
float[] degMinSecLon = decimalToDegrees(longitude);
return String.format(Locale.getDefault(), dmsCoordinatesFormat,
(int) degMinSecLat[0], (int) degMinSecLat[1], degMinSecLat[2],
(int) degMinSecLon[0], (int) degMinSecLon[1], degMinSecLon[2]);
}
}
/**
* This function converts bearing in degrees to cardinal direction symbol, ie.: 0 degrees
* converts to "N"
* @param bearingDegrees bearing to convert, in degrees
* @return cardinal direction symbol string
*/
public String getCardinalDirection(float bearingDegrees) {
int directions = cardinalDirections.length;
int angleStep = 360 / directions;
int halfStep = angleStep / 2;
for(int i = 0; i < directions - 1; i++) {
int angle = i * angleStep;
if(bearingDegrees >= angle - halfStep
&& bearingDegrees < angle + halfStep) {
return cardinalDirections[i];
}
}
return cardinalDirections[directions - 1];
}
/**
* Format float degree value, such as pitch or roll, by casting to int and appending degree
* symbol.
* This function uses stringBuilder for memory allocation optimization purposes; the pitch & roll
* values are updated very quickly, and using String.format function everytime they are updated
* would cause excessive string allocations.
* @param degreeValue value to format
* @return formatted value
*/
public String formatDegreeValue(int degreeValue) {
stringBuilder.setLength(0);
stringBuilder.append(degreeValue).append(DEGREE);
return stringBuilder.toString();
}
}