/** * Copyright (C)2009 Nicholas Killewald * * This file is distributed under the terms of the BSD license. * The source package should have a LICENSE file at the toplevel. */ package net.exclaimindustries.geohashdroid.util; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Locale; import android.content.Context; import android.content.SharedPreferences; import android.location.Location; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.util.Log; import com.google.android.gms.maps.model.LatLng; /** * This is a simple utility class which converts a distance output (in meters) * into whatever is needed for the job (kilometers, miles, feet). It also turns * coordinates into whatever needs to be displayed (minutes/seconds, etc). * * @author Nicholas Killewald */ public class UnitConverter { /** The number of feet per meter. */ public static final double FEET_PER_METER = 3.2808399; /** The number of feet per mile. */ public static final int FEET_PER_MILE = 5280; /** Output should be short, with fewer decimal places. */ public static final int OUTPUT_SHORT = 0; /** Output should be long, with more decimal places. */ public static final int OUTPUT_LONG = 1; /** Output should be even longer, with even more decimal places. */ public static final int OUTPUT_DETAILED = 2; // All of the coordinate formats use Locale.US for the locale to force the // decimal delimiter to be a period instead of a comma. Coordinates, as far // as I can tell, are always represented with a period, even if the country // in question uses commas. protected static final DecimalFormat SHORT_FORMAT = new DecimalFormat("##0.000", new DecimalFormatSymbols(Locale.US)); protected static final DecimalFormat LONG_FORMAT = new DecimalFormat("##0.00000", new DecimalFormatSymbols(Locale.US)); protected static final DecimalFormat DETAIL_FORMAT = new DecimalFormat("##0.00000000", new DecimalFormatSymbols(Locale.US)); protected static final DecimalFormat SHORT_SECONDS_FORMAT = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US)); protected static final DecimalFormat LONG_SECONDS_FORMAT = new DecimalFormat("##0.0000", new DecimalFormatSymbols(Locale.US)); /** The standard short-form distance format. */ public static final DecimalFormat DISTANCE_FORMAT_SHORT = new DecimalFormat("###.###"); private static final String DEBUG_TAG = "UnitConverter"; /** * Perform a distance conversion. This will attempt to get whatever * preference is set for the job and, using the given DecimalFormat, convert * it into a string, suitable for displaying. * * @param c * the context from which to get the preferences * @param df * the format of the string * @param distance * the distance, as returned by Location's distanceTo method * @return a String of the distance, with units marked */ @NonNull public static String makeDistanceString(@NonNull Context c, @NonNull DecimalFormat df, float distance) { // First, get the current unit preference. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); String units = prefs.getString(GHDConstants.PREF_DIST_UNITS, GHDConstants.PREFVAL_DIST_METRIC); // Second, run the conversion. switch(units) { case GHDConstants.PREFVAL_DIST_METRIC: // Meters are easy, if only for the fact that, by default, the // Location object returns distances in meters. And the fact // that it's in powers of ten. if(distance >= 1000) { return df.format(distance / 1000) + "km"; } else { return df.format(distance) + "m"; } case GHDConstants.PREFVAL_DIST_IMPERIAL: // Convert! double feet = distance * FEET_PER_METER; if(feet >= FEET_PER_MILE) { return df.format(feet / FEET_PER_MILE) + "mi"; } else { return df.format(feet) + "ft"; } default: return units + "???"; } } /** * Perform a coordinate conversion. This will read in whatever preference * is currently in play (degrees, minutes, seconds) and return a string with * both latitude and longitude separated by a space. * * @param c * Context from whence the preference comes * @param l * Location to calculate * @param useNegative * true to use positive/negative values, false to use N/S or E/W * @param format * specify the output format using one of the OUTPUT_ statics * @return * a string form of the coordinates given */ @NonNull public static String makeFullCoordinateString(@NonNull Context c, @NonNull Location l, boolean useNegative, int format) { return makeLatitudeCoordinateString(c, l.getLatitude(), useNegative, format) + " " + makeLongitudeCoordinateString(c, l.getLongitude(), useNegative, format); } /** * Perform a coordinate conversion. This will read in whatever preference * is currently in play (degrees, minutes, seconds) and return a string with * both latitude and longitude separated by a space. * * @param c * Context from whence the preference comes * @param ll * LatLng to calculate * @param useNegative * true to use positive/negative values, false to use N/S or E/W * @param format * specify the output format using one of the OUTPUT_ statics * @return * a string form of the coordinates given */ @NonNull public static String makeFullCoordinateString(@NonNull Context c, @NonNull LatLng ll, boolean useNegative, int format) { Location l = new Location(""); l.setLatitude(ll.latitude); l.setLongitude(ll.longitude); return makeFullCoordinateString(c, l, useNegative, format); } /** * This is the latitude half of makeFullCoordinateString. * * @param c * Context from whence the preference comes * @param lat * Latitude to calculate * @param useNegative * true to use positive/negative values, false to use N/S * @param format * specify the output format using one of the OUTPUT_ statics * @return * a string form of the latitude of the coordinates given */ @NonNull public static String makeLatitudeCoordinateString(@NonNull Context c, double lat, boolean useNegative, int format) { String units = getCoordUnitPreference(c); // Keep track of whether or not this is negative. We'll attach the // prefix or suffix later. boolean isNegative = lat < 0; // Make this absolute so we know we won't have to juggle negatives until // we know what they'll wind up being. double rawCoord = Math.abs(lat); String coord; coord = makeCoordinateString(units, rawCoord, format); // Now, attach negative or suffix, as need be. if(useNegative) { if(isNegative) return "-" + coord; else return coord; } else { if(isNegative) return coord + "S"; else return coord + "N"; } } /** * This is the longitude half of makeFullCoordinateString. * * @param c * Context from whence the preference comes * @param lon * Longitude to calculate * @param useNegative * true to use positive/negative values, false to use E/W * @param format * specify the output format using one of the OUTPUT_ statics * @return * a string form of the longitude of the coordinates given */ @NonNull public static String makeLongitudeCoordinateString(@NonNull Context c, double lon, boolean useNegative, int format) { String units = getCoordUnitPreference(c); // Keep track of whether or not this is negative. We'll attach the // prefix or suffix later. boolean isNegative = lon < 0; // Make this absolute so we know we won't have to juggle negatives until // we know what they'll wind up being. double rawCoord = Math.abs(lon); String coord; coord = makeCoordinateString(units, rawCoord, format); // Now, attach negative or suffix, as need be. if(useNegative) { if(isNegative) return "-" + coord; else return coord; } else { if(isNegative) return coord + "W"; else return coord + "E"; } } @NonNull private static String makeCoordinateString(@NonNull String units, double coord, int format) { // Just does the generic coordinate conversion stuff for coordinates. NumberFormat nf = NumberFormat.getInstance(); try { switch(units) { case GHDConstants.PREFVAL_COORD_DEGREES: // Easy case: Use the result Location gives us, modified by // the longForm boolean. switch(format) { case OUTPUT_SHORT: return SHORT_FORMAT.format(coord) + "\u00b0"; case OUTPUT_LONG: return LONG_FORMAT.format(coord) + "\u00b0"; default: return DETAIL_FORMAT.format(coord) + "\u00b0"; } case GHDConstants.PREFVAL_COORD_MINUTES: { // Harder case 1: Minutes. String temp = Location.convert(coord, Location.FORMAT_MINUTES); String[] split = temp.split(":"); // Get the double form of the minutes... double minutes = nf.parse(split[1]).doubleValue(); switch(format) { case OUTPUT_SHORT: return split[0] + "\u00b0" + SHORT_SECONDS_FORMAT.format(minutes) + "\u2032"; case OUTPUT_LONG: return split[0] + "\u00b0" + LONG_SECONDS_FORMAT.format(minutes) + "\u2032"; default: return split[0] + "\u00b0" + split[1] + "\u2032"; } } case GHDConstants.PREFVAL_COORD_SECONDS: { // Harder case 2: Seconds. String temp = Location.convert(coord, Location.FORMAT_SECONDS); String[] split = temp.split(":"); // Get the double form of the seconds... double seconds = nf.parse(split[2]).doubleValue(); switch(format) { case OUTPUT_SHORT: return split[0] + "\u00b0" + split[1] + "\u2032" + SHORT_SECONDS_FORMAT.format(seconds) + "\u2033"; case OUTPUT_LONG: return split[0] + "\u00b0" + split[1] + "\u2032" + LONG_SECONDS_FORMAT.format(seconds) + "\u2033"; default: return split[0] + "\u00b0" + split[1] + "\u2032" + split[2] + "\u2033"; } } default: return "???"; } } catch (Exception ex) { Log.e(DEBUG_TAG, "Exception thrown during coordinate conversion: " + ex.toString()); ex.printStackTrace(); return "???"; } } /** * Grab the current coordinate unit preference. * * @param c Context from whence the preferences arise * @return "Degrees", "Minutes", or "Seconds" */ @NonNull public static String getCoordUnitPreference(@NonNull Context c) { // Units GO!!! SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); return prefs.getString(GHDConstants.PREF_COORD_UNITS, "Degrees"); } }