001 /*
002 * Java GPX Library (jpx-3.1.0).
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package io.jenetics.jpx.format;
017
018 import static java.lang.Math.abs;
019 import static java.lang.Math.floor;
020 import static java.math.RoundingMode.DOWN;
021 import static java.math.RoundingMode.HALF_EVEN;
022 import static java.util.Objects.requireNonNull;
023
024 import java.text.DecimalFormat;
025 import java.text.DecimalFormatSymbols;
026 import java.text.NumberFormat;
027 import java.text.ParsePosition;
028 import java.util.Locale;
029 import java.util.Optional;
030 import java.util.concurrent.atomic.AtomicReference;
031
032 /**
033 * Represents one of the existing location fields: latitude, longitude and
034 * elevation.
035 *
036 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
037 * @version 3.0
038 * @since 1.4
039 */
040 abstract class Field implements Format {
041
042 private static final DecimalFormatSymbols SYMBOLS =
043 DecimalFormatSymbols.getInstance(Locale.US);
044
045 private final String _pattern;
046 private final char _type;
047
048 private boolean _prefixSign = false;
049 private boolean _absolute = false;
050
051 private final AtomicReference<NumberFormat> _format = new AtomicReference<>();
052
053 Field(final String pattern, final char type) {
054 _pattern = requireNonNull(pattern);
055 _type = type;
056 _format.set(new DecimalFormat(toDecimalPattern(pattern, type), SYMBOLS));
057 }
058
059 private static String toDecimalPattern(final String pattern, final char type) {
060 return pattern.replace(type, '0');
061 }
062
063 final void setPrefixSign(final boolean b) {
064 _prefixSign = b;
065
066 final String decimalPattern = toDecimalPattern(_pattern, _type);
067 final String pattern = _prefixSign
068 ? "+%1$s;-%1$s".formatted(decimalPattern)
069 : decimalPattern;
070
071 setFormat(new DecimalFormat(pattern, SYMBOLS));
072 }
073
074 final boolean isPrefixSign() {
075 return _prefixSign;
076 }
077
078 final void setAbsolute(final boolean b) {
079 _absolute = b;
080 }
081
082 final boolean isAbsolute() {
083 return _absolute;
084 }
085
086 final void setTruncate(final boolean b) {
087 _format.get().setRoundingMode(b ? DOWN : HALF_EVEN);
088 }
089
090 final void setFormat(final NumberFormat format) {
091 _format.set(requireNonNull(format));
092 }
093
094 final int getMinimumFractionDigits() {
095 return _format.get().getMinimumFractionDigits();
096 }
097
098 @Override
099 public String toPattern() {
100 return _pattern;
101 }
102
103 /**
104 * Formatting the given double value with the field formatter.
105 *
106 * @param value the double value to format
107 * @return the formatted double value
108 */
109 final String format(final double value) {
110 synchronized (_format) {
111 return _format.get().format(value);
112 }
113 }
114
115 /**
116 * Parsers the given input string.
117 *
118 * @param in the input string to parse
119 * @param pos the parse position
120 * @return the parsed double value
121 */
122 final double parse(final CharSequence in, final ParsePosition pos) {
123 int i = pos.getIndex();
124 String s = in.toString();
125 boolean strictWidth = 1 < _format.get().getMinimumIntegerDigits();
126 if (strictWidth) {
127 int end = i + toPattern().length();
128 s = in.subSequence(0, end).toString();
129 }
130
131 final Number n = parse0(s, pos);
132
133 if (i == pos.getIndex()) {
134 pos.setErrorIndex(i);
135 throw new ParseException("Not found " + toPattern(), in, i);
136 }
137
138 return n.doubleValue();
139 }
140
141 private Number parse0(final String value, final ParsePosition pos) {
142 synchronized (_format) {
143 return _format.get().parse(value, pos);
144 }
145 }
146
147
148 static double toMinutes(final double degrees) {
149 final double dd = abs(degrees);
150 return (dd - floor(dd)) * 60.0;
151 }
152
153 static double toSeconds(final double degrees) {
154 final double dd = abs(degrees);
155 final double d = floor(dd);
156 final double m = floor((dd - d) * 60.0);
157 return (dd - d - m / 60.0) * 3600.0;
158 }
159
160 static Optional<Field> ofPattern(final String pattern) {
161 // TODO better?
162 for (int i = 0; i < pattern.length(); ++i) {
163 switch (pattern.charAt(i)) {
164 case 'L': {
165 final String p = pattern.replace('L', 'D');
166 return Optional.of(new LatitudeDegree(p));
167 }
168 case 'D': return Optional.of(new LatitudeDegree(pattern));
169 case 'M': return Optional.of(new LatitudeMinute(pattern));
170 case 'S': return Optional.of(new LatitudeSecond(pattern));
171 case 'l': {
172 final String p = pattern.replace('l','d');
173 return Optional.of(new LongitudeDegree(p));
174 }
175 case 'd': return Optional.of(new LongitudeDegree(pattern));
176 case 'm': return Optional.of(new LongitudeMinute(pattern));
177 case 's': return Optional.of(new LongitudeSecond(pattern));
178 case 'E': return Optional.of(new Elevation(pattern));
179 case 'H': {
180 final String p = pattern.replace('H', 'E');
181 return Optional.of(new Elevation(p));
182 }
183 }
184 }
185
186 return Optional.empty();
187 }
188
189 }
|