Subversion Repositories mkgmap

Rev

Rev 3701 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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,
        }
}