X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Ffunction%2Festimate%2FEstimationParameters.java;fp=src%2Ftim%2Fprune%2Ffunction%2Festimate%2FEstimationParameters.java;h=1b8046740b767b21bc8e55cdda4134831d092efa;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/function/estimate/EstimationParameters.java b/src/tim/prune/function/estimate/EstimationParameters.java new file mode 100644 index 0000000..1b80467 --- /dev/null +++ b/src/tim/prune/function/estimate/EstimationParameters.java @@ -0,0 +1,291 @@ +package tim.prune.function.estimate; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +import tim.prune.I18nManager; +import tim.prune.config.Config; +import tim.prune.data.RangeStats; +import tim.prune.data.Unit; +import tim.prune.data.UnitSetLibrary; + +/** + * Class to hold, parse and convert the parameters for time estimation. + * These are five (metric) values which can be loaded and saved from config, + * and are used by the EstimateTime function + */ +public class EstimationParameters +{ + /** Minutes required for flat travel, fixed metric distance */ + private double _flatMins = 0.0; + /** Minutes required for climbing, fixed metric distance */ + private double _gentleClimbMins = 0.0, _steepClimbMins; + /** Minutes required for descending, fixed metric distance */ + private double _gentleDescentMins = 0.0, _steepDescentMins; + /** True if parsing from a string failed */ + private boolean _parseFailed = false; + + /** Kilometres unit for comparison */ + private static final Unit KILOMETRES = UnitSetLibrary.UNITS_KILOMETRES; + + + /** + * Bare constructor using default values + */ + public EstimationParameters() + { + resetToDefaults(); + } + + /** + * Constructor from config string + * @param inConfigString single, semicolon-separated string from config + */ + public EstimationParameters(String inConfigString) + { + populateWithString(inConfigString); + if (_parseFailed) { + resetToDefaults(); + } + } + + /** + * Reset all the values to their hardcoded defaults + */ + public void resetToDefaults() + { + _flatMins = 60.0; + _gentleClimbMins = 12.0; _steepClimbMins = 18.0; + _gentleDescentMins = 0.0; _steepDescentMins = 12.0; + _parseFailed = false; + } + + /** + * @return true if this set of parameters is the same as the default set + */ + public boolean sameAsDefaults() + { + EstimationParameters defaultParams = new EstimationParameters(); + return _flatMins == defaultParams._flatMins + && _gentleClimbMins == defaultParams._gentleClimbMins + && _steepClimbMins == defaultParams._steepClimbMins + && _gentleDescentMins == defaultParams._gentleDescentMins + && _steepDescentMins == defaultParams._steepDescentMins; + } + + /** + * Populate the values from the config, which means all values are metric + * @param inString semicolon-separated string of five parameters + */ + private void populateWithString(String inString) + { + if (inString != null && !inString.trim().equals("")) + { + String[] params = inString.trim().split(";"); + _parseFailed = (params == null || params.length != 5); + if (!_parseFailed) + { + for (String p : params) + { + if (!isParamStringValid(p)) { + _parseFailed = true; + } + } + } + if (!_parseFailed) + { + try + { + // Use fixed UK locale to parse these, because of fixed "." formatting + NumberFormat twoDpFormatter = NumberFormat.getNumberInstance(Locale.UK); + _flatMins = twoDpFormatter.parse(params[0]).doubleValue(); + _gentleClimbMins = twoDpFormatter.parse(params[1]).doubleValue(); + _steepClimbMins = twoDpFormatter.parse(params[2]).doubleValue(); + _gentleDescentMins = twoDpFormatter.parse(params[3]).doubleValue(); + _steepDescentMins = twoDpFormatter.parse(params[4]).doubleValue(); + } + catch (Exception e) { + _parseFailed = true; + } + } + } + else _parseFailed = true; + } + + /** + * Populate the values using five user-entered strings (now Units-specific!) + * @param inFlat minutes for flat + * @param inGClimb minutes for gentle climb + * @param inSClimb minutes for steep climb + * @param inGDescent minutes for gentle descent + * @param inSDescent minutes for steep descent + */ + public void populateWithStrings(String inFlat, String inGClimb, String inSClimb, String inGDescent, String inSDescent) + { + if (isParamStringValid(inFlat) && isParamStringValid(inGClimb) && isParamStringValid(inSClimb) + && isParamStringValid(inGDescent) && isParamStringValid(inSDescent)) + { + Unit distUnit = Config.getUnitSet().getDistanceUnit(); + Unit altUnit = Config.getUnitSet().getAltitudeUnit(); + final double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd())); + final double altFactor = (altUnit.isStandard() ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd())); + NumberFormat localFormatter = NumberFormat.getNumberInstance(); + try + { + _flatMins = localFormatter.parse(inFlat).doubleValue() * distFactor; + _gentleClimbMins = localFormatter.parse(inGClimb).doubleValue() * altFactor; + _steepClimbMins = localFormatter.parse(inSClimb).doubleValue() * altFactor; + _gentleDescentMins = localFormatter.parse(inGDescent).doubleValue() * altFactor; + _steepDescentMins = localFormatter.parse(inSDescent).doubleValue() * altFactor; + } + catch (Exception e) {_parseFailed = true;} + } + else _parseFailed = true; + } + + /** + * Populate with double metric values, for example the results of a Learning process + * @param inFlat time for 5km on the flat + * @param inGClimb time for 100m gentle climb + * @param inSClimb time for 100m steep climb + * @param inGDescent time for 100m gentle descent + * @param inSDescent time for 100m steep descent + */ + public void populateWithMetrics(double inFlat, double inGClimb, double inSClimb, double inGDescent, double inSDescent) + { + _flatMins = inFlat; + _gentleClimbMins = inGClimb; + _steepClimbMins = inSClimb; + _gentleDescentMins = inGDescent; + _steepDescentMins = inSDescent; + } + + /** + * @param inString parameter string to check + * @return true if it looks valid (no letters, at least one digit) + */ + private static boolean isParamStringValid(String inString) + { + if (inString == null) {return false;} + boolean hasDigit = false, currPunc = false, prevPunc = false; + for (int i=0; i 0.0 && _gentleClimbMins >= 0.0 && _steepClimbMins >= 0.0; + } + + /** + * @return five strings for putting in text fields for editing / display + */ + public String[] getStrings() + { + Unit distUnit = Config.getUnitSet().getDistanceUnit(); + Unit altUnit = Config.getUnitSet().getAltitudeUnit(); + double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd())); + double altFactor = (altUnit.isStandard() ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd())); + // Use locale-specific number formatting, eg commas for german + NumberFormat numFormatter = NumberFormat.getNumberInstance(); + if (numFormatter instanceof DecimalFormat) { + ((DecimalFormat) numFormatter).applyPattern("0.00"); + } + // Conversion between units + return new String[] { + numFormatter.format(_flatMins / distFactor), + numFormatter.format(_gentleClimbMins / altFactor), numFormatter.format(_steepClimbMins / altFactor), + numFormatter.format(_gentleDescentMins / altFactor), numFormatter.format(_steepDescentMins / altFactor) + }; + } + + /** + * @return unit-specific string describing the distance for the flat time (5km/3mi/3NM) + */ + public static String getStandardDistance() + { + Unit distUnit = Config.getUnitSet().getDistanceUnit(); + return (distUnit == KILOMETRES ? "5 " : "3 ") + I18nManager.getText(distUnit.getShortnameKey()); + } + + /** + * @return unit-specific string describing the height difference for the climbs/descents (100m/300ft) + */ + public static String getStandardClimb() + { + Unit altUnit = Config.getUnitSet().getAltitudeUnit(); + return (altUnit.isStandard() ? "100 " : "300 ") + I18nManager.getText(altUnit.getShortnameKey()); + } + + /** + * @return contents of parameters as a semicolon-separated (metric) string for the config + */ + public String toConfigString() + { + return "" + twoDp(_flatMins) + ";" + twoDp(_gentleClimbMins) + ";" + twoDp(_steepClimbMins) + ";" + + twoDp(_gentleDescentMins) + ";" + twoDp(_steepDescentMins); + } + + /** + * @param inNum number to output + * @return formatted string to two decimal places, with decimal point + */ + private static String twoDp(double inNum) + { + if (inNum < 0.0) return "-" + twoDp(-inNum); + int hundreds = (int) (inNum * 100 + 0.5); + return "" + (hundreds / 100) + "." + (hundreds % 100); + } + + /** + * Apply the parameters to the given range stats + * @param inStats stats of current range + * @return estimated number of minutes required + */ + public double applyToStats(RangeStats inStats) + { + if (inStats == null || !inStats.isValid()) return 0.0; + final Unit METRES = UnitSetLibrary.UNITS_METRES; + final double STANDARD_CLIMB = 100.0; // metres + final double STANDARD_DISTANCE = 5.0; // kilometres + return _flatMins * inStats.getMovingDistanceKilometres() / STANDARD_DISTANCE + + _gentleClimbMins * inStats.getGentleAltitudeRange().getClimb(METRES) / STANDARD_CLIMB + + _steepClimbMins * inStats.getSteepAltitudeRange().getClimb(METRES) / STANDARD_CLIMB + + _gentleDescentMins * inStats.getGentleAltitudeRange().getDescent(METRES) / STANDARD_CLIMB + + _steepDescentMins * inStats.getSteepAltitudeRange().getDescent(METRES) / STANDARD_CLIMB; + } + + /** + * Combine two sets of parameters together + * @param inOther other set + * @param inFraction fractional weight + * @return combined set + */ + public EstimationParameters combine(EstimationParameters inOther, double inFraction) + { + if (inFraction < 0.0 || inFraction > 1.0 || inOther == null) { + return null; + } + // inFraction is the weight of this one, weight of the other one is 1-inFraction + final double fraction2 = 1 - inFraction; + EstimationParameters combined = new EstimationParameters(); + combined._flatMins = inFraction * _flatMins + fraction2 * inOther._flatMins; + combined._gentleClimbMins = inFraction * _gentleClimbMins + fraction2 * inOther._gentleClimbMins; + combined._gentleDescentMins = inFraction * _gentleDescentMins + fraction2 * inOther._gentleDescentMins; + combined._steepClimbMins = inFraction * _steepClimbMins + fraction2 * inOther._steepClimbMins; + combined._steepDescentMins = inFraction * _steepDescentMins + fraction2 * inOther._steepDescentMins; + return combined; + } +}