/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2000-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library 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;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.measure;
import java.io.ObjectStreamException;
import javax.measure.converter.UnitConverter;
import javax.measure.converter.ConversionException;
/**
* A converter from fractional degrees to sexagesimal degrees.
* Sexagesimal degrees are pseudo-unit in the format
*
* <cite>sign - degrees - decimal point - minutes (two digits) - integer seconds (two digits) -
* fraction of seconds (any precision)</cite>.
*
* Unfortunatly, this pseudo-unit is extensively used in the EPSG database.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
class SexagesimalConverter extends UnitConverter {
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = 3873494343412121773L;
/**
* Small tolerance factor for rounding errors.
*/
private static final double EPS = 1E-8;
/**
* The converter for DMS units.
*/
static final SexagesimalConverter INTEGER = new SexagesimalConverter(1);
/**
* The converter for D.MS units.
*/
static final SexagesimalConverter FRACTIONAL = new SexagesimalConverter(10000);
/**
* The value to divide DMS unit by.
* For "degree minute second" (EPSG code 9107), this is 1.
* For "sexagesimal degree" (EPSG code 9110), this is 10000.
*/
final int divider;
/**
* The inverse of this converter.
*/
private final UnitConverter inverse;
/**
* Constructs a converter for sexagesimal units.
*
* @param divider The value to divide DMS unit by.
* For "degree minute second" (EPSG code 9107), this is 1.
* For "sexagesimal degree" (EPSG code 9110), this is 10000.
*/
private SexagesimalConverter(final int divider) {
this.divider = divider;
this.inverse = new Inverse(this);
}
/**
* Constructs a converter for sexagesimal units.
* This constructor is for {@link Inverse} usage only.
*/
private SexagesimalConverter(final int divider, final UnitConverter inverse) {
this.divider = divider;
this.inverse = inverse;
}
/**
* Returns the inverse of this converter.
*/
public final UnitConverter inverse() {
return inverse;
}
/**
* Performs a conversion from fractional degrees to sexagesimal degrees.
*/
public double convert(double value) throws ConversionException {
final int deg,min,sec; deg = (int) value; // Round toward 0
value = (value-deg)*60; min = (int) value; // Round toward 0
value = (value-min)*60; sec = (int) value; // Round toward 0
value -= sec; // The remainer (fraction of seconds)
return (((deg*100 + min)*100 + sec) + value)/divider;
}
/**
* Returns this converter derivative for the specified {@code x} value.
*/
public final double derivative(double x) {
return 1;
}
/**
* Returns {@code false} since this converter is non-linear.
*/
public final boolean isLinear() {
return false;
}
/**
* Compares this converter with the specified object.
*/
@Override
public final boolean equals(final Object object) {
return object != null && object.getClass().equals(getClass()) &&
((SexagesimalConverter) object).divider == divider;
}
/**
* Returns a hash value for this converter.
*/
@Override
public int hashCode() {
return (int) serialVersionUID + divider;
}
/**
* On deserialization, returns an existing instance.
*/
protected Object readResolve() throws ObjectStreamException {
UnitConverter candidate = INTEGER;
for (int i=0; i<=3; i++) {
switch (i) {
case 0: break; // Do nothing since candidate is already set to INTEGER/
case 2: candidate = FRACTIONAL; break;
default: candidate = candidate.inverse(); break;
}
if (equals(candidate)) {
return candidate;
}
}
return this;
}
/**
* The inverse of {@link SexagesimalConverter}.
*/
private static final class Inverse extends SexagesimalConverter {
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = -7171869900634417819L;
/**
* Constructs a converter.
*/
public Inverse(final SexagesimalConverter inverse) {
super(inverse.divider, inverse);
}
/**
* Performs a conversion from sexagesimal degrees to fractional degrees.
*/
@Override
public double convert(double value) throws ConversionException {
value *= this.divider;
int deg,min;
deg = (int) (value/10000); value -= 10000*deg;
min = (int) (value/ 100); value -= 100*min;
if (min<=-60 || min>=60) { // Accepts NaN
if (Math.abs(Math.abs(min) - 100) <= EPS) {
if (min >= 0) deg++; else deg--;
min = 0;
} else {
throw new ConversionException("Invalid minutes: "+min);
}
}
if (value<=-60 || value>=60) { // Accepts NaN
if (Math.abs(Math.abs(value) - 100) <= EPS) {
if (value >= 0) min++; else min--;
value = 0;
} else {
throw new ConversionException("Invalid secondes: "+value);
}
}
value = ((value/60) + min)/60 + deg;
return value;
}
/**
* Returns a hash value for this converter.
*/
@Override
public int hashCode() {
return (int) serialVersionUID + divider;
}
}
}