com.anrisoftware.globalpom.format.degree.DegreeSexagesimalFormat.java Source code

Java tutorial

Introduction

Here is the source code for com.anrisoftware.globalpom.format.degree.DegreeSexagesimalFormat.java

Source

/*
 * Copyright 2013-2014 Erwin Mller <erwin.mueller@deventm.org>
 *
 * This file is part of globalpomutils-math.
 *
 * globalpomutils-math is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * globalpomutils-math 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.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with globalpomutils-math. If not, see <http://www.gnu.org/licenses/>.
 */
package com.anrisoftware.globalpom.format.degree;

import static com.anrisoftware.globalpom.format.degree.Direction.N;
import static com.anrisoftware.globalpom.measurement.RoundToSignificantFigures.roundToDecimal;
import static com.google.inject.Guice.createInjector;
import static java.lang.Double.parseDouble;
import static java.util.regex.Pattern.compile;
import static javax.measure.unit.NonSI.DEGREE_ANGLE;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.math3.util.FastMath.abs;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.measure.quantity.Angle;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.FastMath;
import org.jscience.physics.amount.Amount;

import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;

/**
 * Parses angle degree sexagesimal.
 * 
 * @author Erwin Mueller, erwin.mueller@deventm.org
 * @since 1.9
 */
@SuppressWarnings("serial")
public class DegreeSexagesimalFormat extends Format {

    private static final String SEC_SUB = "\"";

    private static final String MIN_SUB = "'";

    private static final String DEGREE_SUB = "";

    /**
     * @see #create()
     */
    public static DegreeSexagesimalFormat createDegreeSexagesimalFormat() {
        return create();
    }

    /**
     * @see #create(int)
     */
    public static DegreeSexagesimalFormat create() {
        return InjectorInstance.factory.create(4);
    }

    /**
     * Create the sexagesimal degree format.
     * 
     * @return the {@link DegreeSexagesimalFormat}.
     */
    public static DegreeSexagesimalFormat create(int decimal) {
        return InjectorInstance.factory.create(decimal);
    }

    private static class InjectorInstance {
        static final DegreeSexagesimalFormatFactory factory = createInjector(new DegreeFormatModule())
                .getInstance(DegreeSexagesimalFormatFactory.class);
    }

    /**
     * Tests whether the input string is a valid sexagesimal degree format.
     * 
     * @param string
     *            the {@link String} to test.
     * 
     * @return {@code true} if the input string is a valid sexagesimal degree
     *         format.
     */
    public static boolean isValidFormat(String string) {
        return PATTERN.matcher(string).matches();
    }

    private static final Pattern PATTERN = compile(String
            .format("^((\\d+)%s)((\\d+)%s)?((\\d+(\\.\\d*)?)%s)?(\\s[NSEW])?$", DEGREE_SUB, MIN_SUB, SEC_SUB));

    private static final double MIN = 1d / 60d;

    private static final double SEC = 1d / 3600d;

    @Inject
    private DegreeSexagesimalFormatLogger log;

    private final int decimal;

    private final double epsilon;

    /**
     * @see DegreeSexagesimalFormatFactory#create()
     */
    @AssistedInject
    DegreeSexagesimalFormat() {
        this(3);
    }

    /**
     * @see DegreeSexagesimalFormatFactory#create(int)
     */
    @AssistedInject
    DegreeSexagesimalFormat(@Assisted int decimal) {
        this.decimal = decimal;
        this.epsilon = FastMath.pow(10, -(decimal + 1));
    }

    /**
     * Formats the specified degree sexagesimal.
     * 
     * @param obj
     *            the {@link Amount}.
     */
    @SuppressWarnings("unchecked")
    @Override
    public StringBuffer format(Object obj, StringBuffer buff, FieldPosition pos) {
        if (obj instanceof Amount) {
            formatFormat(buff, ((Amount<Angle>) obj).doubleValue(DEGREE_ANGLE));
        }
        if (obj instanceof Number) {
            formatFormat(buff, ((Number) obj).doubleValue());
        }
        return buff;
    }

    private void formatFormat(StringBuffer buff, double value) {
        NumberFormat format = DecimalFormat.getNumberInstance();
        double degree = (long) value;
        double dmin = abs((value - degree) / MIN);
        double min = roundToDecimal((long) dmin, decimal);
        double sec = roundToDecimal((dmin - min) / MIN, decimal);
        buff.append(format.format(degree));
        buff.append(DEGREE_SUB);
        if (min > epsilon) {
            buff.append(format.format(min));
            buff.append(MIN_SUB);
        }
        if (sec > epsilon) {
            buff.append(format.format(sec));
            buff.append(SEC_SUB);
        }
    }

    /**
     * Parses the specified string to degree sexagesimal.
     * <p>
     * <h2>Format</h2>
     * <p>
     * <ul>
     * <li>{@code "DM'S.s" [NSEW]"}
     * </ul>
     * 
     * @return the parsed {@link Amount}.
     */
    @Override
    public Object parseObject(String source, ParsePosition pos) {
        try {
            return parse(source, pos);
        } catch (ParseException e) {
            pos.setErrorIndex(pos.getIndex() + e.getErrorOffset());
            return null;
        }
    }

    /**
     * @see #parse(String, ParsePosition)
     */
    public Amount<Angle> parse(String source) throws ParseException {
        ParsePosition pos = new ParsePosition(0);
        Amount<Angle> result = parse(source, pos);
        if (pos.getIndex() == 0) {
            throw log.errorParse(source, pos);
        }
        return result;
    }

    /**
     * @see #parseObject(String)
     * 
     * @param pos
     *            the index {@link ParsePosition} position from where to start
     *            parsing.
     * 
     * @throws ParseException
     *             if the string is not in the correct format.
     */
    public Amount<Angle> parse(String source, ParsePosition pos) throws ParseException {
        try {
            source = source.substring(pos.getIndex());
            Amount<Angle> result = decodeAngle(source, pos);
            pos.setErrorIndex(-1);
            pos.setIndex(pos.getIndex() + source.length());
            return result;
        } catch (NumberFormatException e) {
            log.errorParseNumber(e, source);
            pos.setIndex(0);
            pos.setErrorIndex(0);
            return null;
        }
    }

    private Amount<Angle> decodeAngle(String source, ParsePosition pos) throws ParseException {
        Matcher matcher = PATTERN.matcher(source);
        log.checkMatches(matcher.find(), source, pos);
        double degree = parseDoubleSave(matcher.group(2));
        double min = parseDoubleSave(matcher.group(4)) * MIN;
        double sec = parseDoubleSave(matcher.group(6)) * SEC;
        Direction direction = parseDirectionSave(matcher.group(8));
        double value = (degree + min + sec) * direction.getDirection();
        return Amount.valueOf(roundToDecimal(value, decimal), 0, DEGREE_ANGLE);
    }

    private Direction parseDirectionSave(String string) {
        string = StringUtils.trim(string);
        return isEmpty(string) ? N : Direction.valueOf(string.toUpperCase());
    }

    private double parseDoubleSave(String string) {
        return isEmpty(string) ? 0 : parseDouble(string);
    }
}