001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2000, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.measure;
018:
019: // J2SE dependencies and extensions
020: import javax.units.Converter;
021: import javax.units.ConversionException;
022:
023: /**
024: * A converter from fractional degrees to sexagesimal degrees.
025: * Sexagesimal degrees are pseudo-unit in the format
026: *
027: * <cite>sign - degrees - decimal point - minutes (two digits) - integer seconds (two digits) -
028: * fraction of seconds (any precision)</cite>.
029: *
030: * Unfortunatly, this pseudo-unit is extensively used in the EPSG database.
031: *
032: * @since 2.1
033: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/measure/SexagesimalConverter.java $
034: * @version $Id: SexagesimalConverter.java 22482 2006-10-31 02:58:00Z desruisseaux $
035: * @author Martin Desruisseaux
036: */
037: class SexagesimalConverter extends Converter {
038: /**
039: * Serial number for compatibility with different versions.
040: */
041: private static final long serialVersionUID = 3873494343412121773L;
042:
043: /**
044: * Small tolerance factor for rounding errors.
045: */
046: private static final double EPS = 1E-8;
047:
048: /**
049: * The value to divide DMS unit by.
050: * For "degree minute second" (EPSG code 9107), this is 1.
051: * For "sexagesimal degree" (EPSG code 9110), this is 10000.
052: */
053: final int divider;
054:
055: /**
056: * The inverse of this converter.
057: */
058: private final Converter inverse;
059:
060: /**
061: * Constructs a converter for sexagesimal units.
062: *
063: * @param divider The value to divide DMS unit by.
064: * For "degree minute second" (EPSG code 9107), this is 1.
065: * For "sexagesimal degree" (EPSG code 9110), this is 10000.
066: */
067: public SexagesimalConverter(final int divider) {
068: this .divider = divider;
069: this .inverse = new Inverse(this );
070: }
071:
072: /**
073: * Constructs a converter for sexagesimal units.
074: * This constructor is for {@link Inverse} usage only.
075: */
076: private SexagesimalConverter(final int divider,
077: final Converter inverse) {
078: this .divider = divider;
079: this .inverse = inverse;
080: }
081:
082: /**
083: * Returns the inverse of this converter.
084: */
085: public final Converter inverse() {
086: return inverse;
087: }
088:
089: /**
090: * Performs a conversion from fractional degrees to sexagesimal degrees.
091: */
092: public double convert(double value) throws ConversionException {
093: final int deg, min, sec;
094: deg = (int) value; // Round toward 0
095: value = (value - deg) * 60;
096: min = (int) value; // Round toward 0
097: value = (value - min) * 60;
098: sec = (int) value; // Round toward 0
099: value -= sec; // The remainer (fraction of seconds)
100: return (((deg * 100 + min) * 100 + sec) + value) / divider;
101: }
102:
103: /**
104: * Returns this converter derivative for the specified {@code x} value.
105: */
106: public final double derivative(double x) {
107: return 1;
108: }
109:
110: /**
111: * Returns {@code false} since this converter is non-linear.
112: */
113: public final boolean isLinear() {
114: return false;
115: }
116:
117: /**
118: * Compares this converter with the specified object.
119: */
120: public final boolean equals(final Object object) {
121: return object != null && object.getClass().equals(getClass())
122: && ((SexagesimalConverter) object).divider == divider;
123: }
124:
125: /**
126: * Returns a hash value for this converter.
127: */
128: public int hashCode() {
129: return (int) serialVersionUID + divider;
130: }
131:
132: /**
133: * The inverse of {@link SexagesimalConverter}.
134: */
135: private static final class Inverse extends SexagesimalConverter {
136: /**
137: * Serial number for compatibility with different versions.
138: */
139: private static final long serialVersionUID = -7171869900634417819L;
140:
141: /**
142: * Constructs a converter.
143: */
144: public Inverse(final SexagesimalConverter inverse) {
145: super (inverse.divider, inverse);
146: }
147:
148: /**
149: * Performs a conversion from sexagesimal degrees to fractional degrees.
150: */
151: public double convert(double value) throws ConversionException {
152: value *= this .divider;
153: int deg, min;
154: deg = (int) (value / 10000);
155: value -= 10000 * deg;
156: min = (int) (value / 100);
157: value -= 100 * min;
158: if (min <= -60 || min >= 60) { // Accepts NaN
159: if (Math.abs(Math.abs(min) - 100) <= EPS) {
160: if (min >= 0)
161: deg++;
162: else
163: deg--;
164: min = 0;
165: } else {
166: throw new ConversionException("Invalid minutes: "
167: + min);
168: }
169: }
170: if (value <= -60 || value >= 60) { // Accepts NaN
171: if (Math.abs(Math.abs(value) - 100) <= EPS) {
172: if (value >= 0)
173: min++;
174: else
175: min--;
176: value = 0;
177: } else {
178: throw new ConversionException("Invalid secondes: "
179: + value);
180: }
181: }
182: value = ((value / 60) + min) / 60 + deg;
183: return value;
184: }
185:
186: /**
187: * Returns a hash value for this converter.
188: */
189: public int hashCode() {
190: return (int) serialVersionUID + divider;
191: }
192: }
193: }
|