Files
OpenTracks/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java
T
2011-11-05 11:44:19 -07:00

468 lines
16 KiB
Java

/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Various string manipulation methods.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StringUtils implements DescriptionGenerator {
private final Context context;
/**
* Formats a number of milliseconds as a string.
*
* @param time - A period of time in milliseconds.
* @return A string of the format M:SS, MM:SS or HH:MM:SS
*/
public static String formatTime(long time) {
return formatTimeInternal(time, false);
}
/**
* Formats a number of milliseconds as a string. To be used when we need the
* hours to be shown even when it is zero, e.g. exporting data to a
* spreadsheet.
*
* @param time - A period of time in milliseconds
* @return A string of the format HH:MM:SS even if time is less than 1 hour
*/
public static String formatTimeAlwaysShowingHours(long time) {
return formatTimeInternal(time, true);
}
private static final DecimalFormat SINGLE_DECIMAL_PLACE_FORMAT = new DecimalFormat("#.#");
/**
* Formats a double precision number as decimal number with a single decimal
* place.
*
* @param number A double precision number
* @return A string representation of a decimal number, derived from the input
* double, with a single decimal place
*/
public static final String formatSingleDecimalPlace(double number) {
return SINGLE_DECIMAL_PLACE_FORMAT.format(number);
}
/**
* Formats the given text as a CDATA element to be used in a XML file. This
* includes adding the starting and ending CDATA tags. Please notice that this
* may result in multiple consecutive CDATA tags.
*
* @param unescaped the unescaped text to be formatted
* @return the formatted text, inside one or more CDATA tags
*/
public static String stringAsCData(String unescaped) {
// "]]>" needs to be broken into multiple CDATA segments, like:
// "Foo]]>Bar" becomes "<![CDATA[Foo]]]]><![CDATA[>Bar]]>"
// (the end of the first CDATA has the "]]", the other has ">")
String escaped = unescaped.replaceAll("]]>", "]]]]><![CDATA[>");
return "<![CDATA[" + escaped + "]]>";
}
private static final SimpleDateFormat BASE_XML_DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
static {
BASE_XML_DATE_FORMAT.setTimeZone(new SimpleTimeZone(0, "UTC"));
}
private static final Pattern XML_DATE_EXTRAS_PATTERN =
Pattern.compile("^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
/**
* Parses an XML dateTime element as defined by the XML standard.
*
* @see <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">dateTime</a>
*/
public static long parseXmlDateTime(String xmlTime) {
// Parse the base date (fixed format)
ParsePosition position = new ParsePosition(0);
Date date = BASE_XML_DATE_FORMAT.parse(xmlTime, position);
if (date == null) {
throw new IllegalArgumentException("Invalid XML dateTime value: '" + xmlTime
+ "' (at position " + position.getErrorIndex() + ")");
}
// Parse the extras
Matcher matcher =
XML_DATE_EXTRAS_PATTERN.matcher(xmlTime.substring(position.getIndex()));
if (!matcher.matches()) {
// This will match even an empty string as all groups are optional,
// so a non-match means some other garbage was there
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlTime);
}
long time = date.getTime();
// Account for fractional seconds
String fractional = matcher.group(1);
if (fractional != null) {
// Regex ensures fractional part is in (0,1(
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
time += fractionalMillis;
}
// Account for timezones
String sign = matcher.group(2);
String offsetHoursStr = matcher.group(3);
String offsetMinsStr = matcher.group(4);
if (sign != null && offsetHoursStr != null && offsetMinsStr != null) {
// Regex ensures sign is + or -
boolean plusSign = sign.equals("+");
int offsetHours = Integer.parseInt(offsetHoursStr);
int offsetMins = Integer.parseInt(offsetMinsStr);
// Regex ensures values are >= 0
if (offsetHours > 14 || offsetMins > 59) {
throw new IllegalArgumentException("Bad timezone in " + xmlTime);
}
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
// Make time go back to UTC
if (plusSign) {
time -= totalOffsetMillis;
} else {
time += totalOffsetMillis;
}
}
return time;
}
/**
* Formats a number of milliseconds as a string.
*
* @param time - A period of time in milliseconds
* @param alwaysShowHours - Whether to display 00 hours if time is less than 1
* hour
* @return A string of the format HH:MM:SS
*/
private static String formatTimeInternal(long time, boolean alwaysShowHours) {
int[] parts = getTimeParts(time);
StringBuilder builder = new StringBuilder();
if (parts[2] > 0 || alwaysShowHours) {
builder.append(parts[2]);
builder.append(':');
if (parts[1] <= 9) {
builder.append("0");
}
}
builder.append(parts[1]);
builder.append(':');
if (parts[0] <= 9) {
builder.append("0");
}
builder.append(parts[0]);
return builder.toString();
}
/**
* Gets the time as an array of parts.
*/
public static int[] getTimeParts(long time) {
if (time < 0) {
int[] parts = getTimeParts(time * -1);
parts[0] *= -1;
parts[1] *= -1;
parts[2] *= -1;
return parts;
}
int[] parts = new int[3];
long seconds = time / 1000;
parts[0] = (int) (seconds % 60);
int tmp = (int) (seconds / 60);
parts[1] = tmp % 60;
parts[2] = tmp / 60;
return parts;
}
public StringUtils(Context context) {
this.context = context;
}
/**
* Generates a description for a track (with information about the
* statistics).
*
* @param track the track
* @return a track description
*/
public String generateTrackDescription(Track track, Vector<Double> distances,
Vector<Double> elevations) {
boolean displaySpeed = true;
SharedPreferences preferences =
context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (preferences != null) {
displaySpeed =
preferences.getBoolean(context.getString(R.string.report_speed_key), true);
}
TripStatistics trackStats = track.getStatistics();
final double distanceInKm = trackStats.getTotalDistance() / 1000;
final double distanceInMiles = distanceInKm * UnitConversions.KM_TO_MI;
final long minElevationInMeters = Math.round(trackStats.getMinElevation());
final long minElevationInFeet =
Math.round(trackStats.getMinElevation() * UnitConversions.M_TO_FT);
final long maxElevationInMeters = Math.round(trackStats.getMaxElevation());
final long maxElevationInFeet =
Math.round(trackStats.getMaxElevation() * UnitConversions.M_TO_FT);
final long elevationGainInMeters =
Math.round(trackStats.getTotalElevationGain());
final long elevationGainInFeet = Math.round(
trackStats.getTotalElevationGain() * UnitConversions.M_TO_FT);
long minGrade = 0;
long maxGrade = 0;
double trackMaxGrade = trackStats.getMaxGrade();
double trackMinGrade = trackStats.getMinGrade();
if (!Double.isNaN(trackMaxGrade)
&& !Double.isInfinite(trackMaxGrade)) {
maxGrade = Math.round(trackMaxGrade * 100);
}
if (!Double.isNaN(trackMinGrade) && !Double.isInfinite(trackMinGrade)) {
minGrade = Math.round(trackMinGrade * 100);
}
String category = context.getString(R.string.unknown);
String trackCategory = track.getCategory();
if (trackCategory != null && trackCategory.length() > 0) {
category = trackCategory;
}
String averageSpeed =
getSpeedString(trackStats.getAverageSpeed(),
R.string.average_speed_label,
R.string.average_pace_label,
displaySpeed);
String averageMovingSpeed =
getSpeedString(trackStats.getAverageMovingSpeed(),
R.string.average_moving_speed_label,
R.string.average_moving_pace_label,
displaySpeed);
String maxSpeed =
getSpeedString(trackStats.getMaxSpeed(),
R.string.max_speed_label,
R.string.min_pace_label,
displaySpeed);
return String.format("%s<p>"
+ "%s: %.2f %s (%.1f %s)<br>"
+ "%s: %s<br>"
+ "%s: %s<br>"
+ "%s %s %s"
+ "%s: %d %s (%d %s)<br>"
+ "%s: %d %s (%d %s)<br>"
+ "%s: %d %s (%d %s)<br>"
+ "%s: %d %%<br>"
+ "%s: %d %%<br>"
+ "%s: %tc<br>"
+ "%s: %s<br>"
+ "<img border=\"0\" src=\"%s\"/>",
// Line 1
context.getString(R.string.send_to_google_by_mytracks),
// Line 2
context.getString(R.string.total_distance_label),
distanceInKm, context.getString(R.string.kilometer),
distanceInMiles, context.getString(R.string.mile),
// Line 3
context.getString(R.string.total_time_label),
StringUtils.formatTime(trackStats.getTotalTime()),
// Line 4
context.getString(R.string.moving_time_label),
StringUtils.formatTime(trackStats.getMovingTime()),
// Line 5
averageSpeed, averageMovingSpeed, maxSpeed,
// Line 6
context.getString(R.string.min_elevation_label),
minElevationInMeters, context.getString(R.string.meter),
minElevationInFeet, context.getString(R.string.feet),
// Line 7
context.getString(R.string.max_elevation_label),
maxElevationInMeters, context.getString(R.string.meter),
maxElevationInFeet, context.getString(R.string.feet),
// Line 8
context.getString(R.string.elevation_gain_label),
elevationGainInMeters, context.getString(R.string.meter),
elevationGainInFeet, context.getString(R.string.feet),
// Line 9
context.getString(R.string.max_grade_label), maxGrade,
// Line 10
context.getString(R.string.min_grade_label), minGrade,
// Line 11
context.getString(R.string.recorded_date),
new Date(trackStats.getStartTime()),
// Line 12
context.getString(R.string.category), category,
// Line 13
ChartURLGenerator.getChartUrl(distances, elevations, track, context));
}
private String getSpeedString(double speed, int speedLabel, int paceLabel,
boolean displaySpeed) {
double speedInKph = speed * 3.6;
double speedInMph = speedInKph * UnitConversions.KMH_TO_MPH;
if (displaySpeed) {
return String.format("%s: %.2f %s (%.1f %s)<br>",
context.getString(speedLabel),
speedInKph, context.getString(R.string.kilometer_per_hour),
speedInMph, context.getString(R.string.mile_per_hour));
} else {
double paceInKm;
double paceInMi;
if (speed == 0) {
paceInKm = 0.0;
paceInMi = 0.0;
} else {
paceInKm = 60.0 / speedInKph;
paceInMi = 60.0 / speedInMph;
}
return String.format("%s: %.2f %s (%.1f %s)<br>",
context.getString(paceLabel),
paceInKm, context.getString(R.string.min_per_kilometer),
paceInMi, context.getString(R.string.min_per_mile));
}
}
/**
* Generates a description for a waypoint (with information about the
* statistics).
*
* @return a track description
*/
public String generateWaypointDescription(Waypoint waypoint) {
TripStatistics stats = waypoint.getStatistics();
final double distanceInKm = stats.getTotalDistance() / 1000;
final double distanceInMiles = distanceInKm * UnitConversions.KM_TO_MI;
final double averageSpeedInKmh = stats.getAverageSpeed() * 3.6;
final double averageSpeedInMph =
averageSpeedInKmh * UnitConversions.KMH_TO_MPH;
final double movingSpeedInKmh = stats.getAverageMovingSpeed() * 3.6;
final double movingSpeedInMph =
movingSpeedInKmh * UnitConversions.KMH_TO_MPH;
final double maxSpeedInKmh = stats.getMaxSpeed() * 3.6;
final double maxSpeedInMph = maxSpeedInKmh * UnitConversions.KMH_TO_MPH;
final long minElevationInMeters = Math.round(stats.getMinElevation());
final long minElevationInFeet =
Math.round(stats.getMinElevation() * UnitConversions.M_TO_FT);
final long maxElevationInMeters = Math.round(stats.getMaxElevation());
final long maxElevationInFeet =
Math.round(stats.getMaxElevation() * UnitConversions.M_TO_FT);
final long elevationGainInMeters =
Math.round(stats.getTotalElevationGain());
final long elevationGainInFeet = Math.round(
stats.getTotalElevationGain() * UnitConversions.M_TO_FT);
long theMinGrade = 0;
long theMaxGrade = 0;
double maxGrade = stats.getMaxGrade();
double minGrade = stats.getMinGrade();
if (!Double.isNaN(maxGrade) &&
!Double.isInfinite(maxGrade)) {
theMaxGrade = Math.round(maxGrade * 100);
}
if (!Double.isNaN(minGrade) &&
!Double.isInfinite(minGrade)) {
theMinGrade = Math.round(minGrade * 100);
}
final String percent = "%";
return String.format(
"%s: %.2f %s (%.1f %s)\n"
+ "%s: %s\n"
+ "%s: %s\n"
+ "%s: %.2f %s (%.1f %s)\n"
+ "%s: %.2f %s (%.1f %s)\n"
+ "%s: %.2f %s (%.1f %s)\n"
+ "%s: %d %s (%d %s)\n"
+ "%s: %d %s (%d %s)\n"
+ "%s: %d %s (%d %s)\n"
+ "%s: %d %s\n"
+ "%s: %d %s\n",
context.getString(R.string.total_distance_label),
distanceInKm, context.getString(R.string.kilometer),
distanceInMiles, context.getString(R.string.mile),
context.getString(R.string.total_time_label),
StringUtils.formatTime(stats.getTotalTime()),
context.getString(R.string.moving_time_label),
StringUtils.formatTime(stats.getMovingTime()),
context.getString(R.string.average_speed_label),
averageSpeedInKmh, context.getString(R.string.kilometer_per_hour),
averageSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.average_moving_speed_label),
movingSpeedInKmh, context.getString(R.string.kilometer_per_hour),
movingSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.max_speed_label),
maxSpeedInKmh, context.getString(R.string.kilometer_per_hour),
maxSpeedInMph, context.getString(R.string.mile_per_hour),
context.getString(R.string.min_elevation_label),
minElevationInMeters, context.getString(R.string.meter),
minElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.max_elevation_label),
maxElevationInMeters, context.getString(R.string.meter),
maxElevationInFeet, context.getString(R.string.feet),
context.getString(R.string.elevation_gain_label),
elevationGainInMeters, context.getString(R.string.meter),
elevationGainInFeet, context.getString(R.string.feet),
context.getString(R.string.max_grade_label),
theMaxGrade, percent,
context.getString(R.string.min_grade_label),
theMinGrade, percent);
}
}