com.anrisoftware.globalpom.format.degree.DegreeSexagesimalFormat Maven / Gradle / Ivy
/*
* Copyright 2016 Erwin Müller
*
* 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.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, [email protected]
* @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) 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.
*
*
Format
*
*
* - {@code "D°M'S.s" [NSEW]"}
*
*
* @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 parse(String source) throws ParseException {
ParsePosition pos = new ParsePosition(0);
Amount 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 parse(String source, ParsePosition pos)
throws ParseException {
try {
source = source.substring(pos.getIndex());
Amount 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 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);
}
}