/* * Copyright (C) 2008 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * Author: Steve Ratcliffe * Create date: 07-Dec-2008 */ package uk.me.parabola.mkgmap.osmstyle.eval; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import uk.me.parabola.mkgmap.scan.SyntaxException; /** * Converting quantities from one unit to another. * * @author Steve Ratcliffe */ public class UnitConversions { private static final Pattern CODE_RE = Pattern.compile("(.*)=>(.*)"); private static final Map<UnitType, Map<String, Double>> CONVERSIONS = new HashMap<>(); private static final Map<String, Double> LENGTH_FACTORS = new HashMap<>(); private static final Map<String, Double> SPEED_FACTORS = new HashMap<>(); private static final Map<String, Double> WEIGHT_FACTORS = new HashMap<>(); static { Map<String, Double> m = LENGTH_FACTORS; m.put("m", 1.0); m.put("km", 1000.0); m.put("ft", 0.3048); m.put("feet", 0.3048); m.put("mi", 1_609.344); CONVERSIONS.put(UnitType.LENGTH, LENGTH_FACTORS); m = SPEED_FACTORS; m.put("kmh", 1.0); m.put("km/h", 1.0); m.put("kmph", 1.0); m.put("mph", 1.60934); m.put("knots", 1.852); CONVERSIONS.put(UnitType.SPEED, SPEED_FACTORS); m = WEIGHT_FACTORS; m.put("t", 1.0); m.put("kg", 0.001); m.put("lb", 0.00045359237); m.put("lbs", 0.00045359237); CONVERSIONS.put(UnitType.WEIGHT, WEIGHT_FACTORS); } /** The type of unit, speed, length etc. */ private final UnitType unitType; /** The target unit */ private final String target; /** The factor to convert from the default source unit to the target */ private final double defaultFactor; /** * Create a converter between the units given in the {@code code} argument. * * This will be something like m=>ft to convert from meters to feet. If the input is a plain * value such as 10, then the input is in meters and it is converted to feet. If the * input already has a unit, eg 10ft, then the conversion will be from that unit to * the target unit. So in this case, since the input is already in feet, then result is 10. * * @param code A specifier from=>to. */ public static UnitConversions createConversion(String code) { Matcher matcher = CODE_RE.matcher(code); if (!matcher.matches()) throw new SyntaxException(String.format("Unrecognised unit conversion: '%s'", code)); String source = matcher.group(1); String target = matcher.group(2); return new UnitConversions(source, target); } private UnitConversions(String source, String target) { this.target = target; UnitType type = getType(source); if (type != null && type == getType(target)) { unitType = type; defaultFactor = getConversion(source); } else { unitType = null; defaultFactor = 1; } } /** * The conversion factor; multiply by this value to convert to the target unit. * @param source If this is not null, then use this as the source unit. * @return The factor to multiply the value by to convert from the source to target units. */ public Double convertFactor(String source) { // The value has no unit, so use the default one. We already know the conversion factor for the // default source unit, so just return it. if (source == null) return defaultFactor; if (unitType == null) return null; return getConversion(source); } public double convertFrom(String source) { assert source != null && unitType != null; if (CONVERSIONS.get(unitType).containsKey(source)) return getConversion(source); else return 0; } public boolean isValid() { return unitType != null; } /** * Find the unit type that corresponds to the unit abbreviation. * * For example for km, this would be LENGTH. * @param source A unit specifier. * @return The type of unit. */ private static UnitType getType(String source) { for (UnitType t : UnitType.values()) { Map<String, Double> map = CONVERSIONS.get(t); for (String unit : map.keySet()) { if (unit.equals(source)) return t; } } return null; } private double getFactor(String unit) { assert isValid(); Double d = CONVERSIONS.get(unitType).get(unit); return d == null? 0: d; } private Double getConversion(String source) { Double in = getInFactor(unitType, source); if (in == null) return null; double out = getOutFactor(unitType, target); return in * out; } private static Double getInFactor(UnitType type, String source) { if (source.isEmpty()) return 1.0; Map<String, Double> map = CONVERSIONS.get(type); assert map != null; return map.get(source); } private static double getOutFactor(UnitType type, String target) { return 1.0 / getInFactor(type, target); } public double getDefaultFactor() { return defaultFactor; } public static enum UnitType { LENGTH, SPEED, WEIGHT, } }