/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* 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.geotoolkit.openoffice;
import java.text.ParseException;
import java.text.DecimalFormatSymbols;
import com.sun.star.lang.Locale;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.beans.XPropertySet;
import com.sun.star.uno.AnyConverter;
import org.opengis.util.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.opengis.geometry.DirectPosition;
import org.apache.sis.measure.Angle;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.AngleFormat;
import org.geotoolkit.referencing.GeodeticCalculator;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.geotoolkit.resources.Errors;
/**
* Exports methods from the {@link org.geotoolkit.referencing} package as
* <A HREF="http://www.openoffice.org">OpenOffice</A> add-ins.
*
* @author Martin Desruisseaux (IRD)
* @author Richard Deplanque (IRD)
* @version 3.09
*
* @since 3.09 (derived from 2.2)
* @module
*/
public final class Referencing extends Formulas implements XReferencing {
/**
* The name for the registration of this component.
* <strong>NOTE:</strong> OpenOffice expects a field with exactly that name; do not rename!
*/
static final String __serviceName = "org.geotoolkit.openoffice.Referencing";
/**
* The decimal separator. Will be computed when a no locale is set.
*/
private char decimalSeparator = '.';
/**
* The pattern for the {@link #angleFormat}. Used in order to avoid creating
* new formats when the pattern didn't changed.
*/
private transient String anglePattern;
/**
* The format to use for parsing and formatting angles.
* Will be created only when first needed.
*/
private transient AngleFormat angleFormat;
/**
* The CRS authority factory. Will be created only when first needed.
*/
private transient CRSAuthorityFactory crsFactory;
/**
* The last geodetic calculator used, or {@code null} if none. Cached for better
* performance when many orthodromic distances are computed on the same ellipsoid.
*/
private transient GeodeticCalculator calculator;
/**
* The CRS authority code used for {@link #calculator} setup,
* or {@code null} if not yet defined.
*/
private transient String calculatorCRS;
/**
* Constructs a default implementation of {@code XReferencing} interface.
*/
public Referencing() {
methods.put("getValueAngle", new MethodInfo("Text", "VALUE.ANGLE",
"Converts text in degrees-minutes-seconds to an angle in decimal degrees.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"text", "The text to be converted to an angle.",
"pattern", "The text that describes the format (example: \"D°MM.m'\")."
}));
methods.put("getTextAngle", new MethodInfo("Text", "TEXT.ANGLE",
"Converts an angle to text according to a given format.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"value", "The angle value (in decimal degrees) to be converted.",
"pattern", "The text that describes the format (example: \"D°MM.m'\")."
}));
methods.put("getTextLongitude", new MethodInfo("Text", "TEXT.LONGITUDE",
"Converts a longitude to text according to a given format.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"value", "The longitude value (in decimal degrees) to be converted.",
"pattern", "The text that describes the format (example: \"D°MM.m'\")."
}));
methods.put("getTextLatitude", new MethodInfo("Text", "TEXT.LATITUDE",
"Converts a latitude to text according to a given format.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"value", "The latitude value (in decimal degrees) to be converted.",
"pattern", "The text that describes the format (example: \"D°MM.m'\")."
}));
methods.put("getOrthodromicDistance", new MethodInfo("Referencing", "ORTHODROMIC.DISTANCE",
"Computes the orthodromic distance and azimuth between two coordinates.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"source", "The source positions.",
"target", "The target positions.",
"CRS", "Authority code of the coordinate reference system."
}));
methods.put("getOrthodromicForward", new MethodInfo("Referencing", "ORTHODROMIC.FORWARD",
"Computes the coordinates after a displacement of the specified distance.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"source", "The source positions.",
"displacement","The distance and azimuth.",
"CRS", "Authority code of the coordinate reference system."
}));
}
/**
* The service name that can be used to create such an object by a factory.
*/
@Override
public String getServiceName() {
return __serviceName;
}
/**
* Sets the locale to be used by this object.
*/
@Override
public void setLocale(final Locale locale) {
anglePattern = null;
angleFormat = null;
super.setLocale(locale);
}
// --------------------------------------------------------------------------------
// H E L P E R M E T H O D S
// --------------------------------------------------------------------------------
/**
* Returns the CRS authority factory.
*/
private CRSAuthorityFactory crsFactory() {
if (crsFactory == null) try {
crsFactory = org.apache.sis.referencing.CRS.getAuthorityFactory(null);
} catch (FactoryException e) {
// Should never happen.
throw new RuntimeException(e); // TODO
}
return crsFactory;
}
/**
* Returns the geodetic calculator for the specified CRS, datum or ellipsoid.
* This method caches the last calculator used for better performance when many
* orthodromic distances are computed on the same ellipsoid.
*
* @throws IllegalArgumentException if {@code authorityCode} is not a string value or void.
* @throws FactoryException if the geodetic calculator can't be created.
*/
private GeodeticCalculator getGeodeticCalculator(final Object authorityCode)
throws IllegalArgumentException, FactoryException
{
final String authorityString;
if (AnyConverter.isVoid(authorityCode)) {
authorityString = "EPSG:4326";
} else {
authorityString = AnyConverter.toString(authorityCode);
}
if (calculatorCRS==null || !calculatorCRS.equals(authorityString)) {
final IdentifiedObject object = crsFactory().createObject(authorityString);
if (object instanceof Ellipsoid) {
calculator = new GeodeticCalculator((Ellipsoid) object);
} else if (object instanceof GeodeticDatum) {
calculator = new GeodeticCalculator(((GeodeticDatum) object).getEllipsoid());
} else if (object instanceof CoordinateReferenceSystem) {
calculator = new GeodeticCalculator((CoordinateReferenceSystem) object);
} else {
throw new FactoryException(Errors.format(
Errors.Keys.IllegalCoordinateReferenceSystem));
}
calculatorCRS = authorityString;
}
return calculator;
}
/**
* Returns the angle format to use for the specified pattern.
*
* @param pattern he text that describes the format (example: "D°MM.m'").
* @return The angle format.
* @throws IllegalArgumentException if {@code pattern} is not a string value or void.
*/
private AngleFormat getAngleFormat(final Object pattern) throws IllegalArgumentException {
final String patternString;
if (AnyConverter.isVoid(pattern)) {
patternString = "D°MM'SS.s\"";
} else {
patternString = AnyConverter.toString(pattern);
}
if (angleFormat == null) {
final java.util.Locale locale = getJavaLocale();
angleFormat = new AngleFormat(patternString, locale);
anglePattern = patternString;
decimalSeparator = new DecimalFormatSymbols(locale).getDecimalSeparator();
} else if (!patternString.equals(anglePattern)) {
angleFormat.applyPattern(patternString);
anglePattern = patternString;
}
return angleFormat;
}
// --------------------------------------------------------------------------------
// F O R M U L A I M P L E M E N T A T I O N S
// --------------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public double getValueAngle(final XPropertySet xOptions,
final String text,
final Object pattern)
throws IllegalArgumentException
{
final AngleFormat angleFormat = getAngleFormat(pattern);
try {
return angleFormat.parse(text).degrees();
} catch (ParseException exception) {
/*
* Parse failed. Tries to replace the dot by the decimal separator in current locale.
*/
final String localized = text.replace('.', decimalSeparator);
if (!text.equals(localized)) try {
return angleFormat.parse(localized).degrees();
} catch (ParseException ignore) {
exception.addSuppressed(ignore);
}
reportException("getValueAngle", exception, THROW_EXCEPTION);
return Double.NaN;
}
}
/**
* {@inheritDoc}
*/
@Override
public String getTextAngle(final XPropertySet xOptions,
final double value,
final Object pattern)
throws IllegalArgumentException
{
return getAngleFormat(pattern).format(new Angle(value));
}
/**
* {@inheritDoc}
*/
@Override
public String getTextLongitude(final XPropertySet xOptions,
final double value,
final Object pattern)
throws IllegalArgumentException
{
return getAngleFormat(pattern).format(new Longitude(value));
}
/**
* {@inheritDoc}
*/
@Override
public String getTextLatitude(final XPropertySet xOptions,
final double value,
final Object pattern)
throws IllegalArgumentException
{
return getAngleFormat(pattern).format(new Latitude(value));
}
/**
* {@inheritDoc}
*/
@Override
public double[][] getOrthodromicDistance(final XPropertySet xOptions,
final double[][] source,
final double[][] target,
final Object CRS)
{
final GeodeticCalculator calculator;
try {
calculator = getGeodeticCalculator(CRS);
} catch (Throwable exception) {
reportException("getOrthodromicDistance", exception, THROW_EXCEPTION);
return getFailure(source.length, 2);
}
boolean failureReported = false;
final int dim = calculator.getCoordinateReferenceSystem().getCoordinateSystem().getDimension();
final GeneralDirectPosition sourcePt = new GeneralDirectPosition(dim);
final GeneralDirectPosition targetPt = new GeneralDirectPosition(dim);
final double[][] result = new double[getLength(source, target)][];
for (int j=0; j<result.length; j++) {
final double[] src = source[j % source.length];
final double[] dst = target[j % target.length];
if (src == null || dst == null) {
continue;
}
for (int i=dim; --i>=0;) {
sourcePt.ordinates[i] = (i<src.length) ? src[i] : 0;
targetPt.ordinates[i] = (i<dst.length) ? dst[i] : 0;
}
try {
calculator.setStartingPosition (sourcePt);
calculator.setDestinationPosition(targetPt);
} catch (TransformException exception) {
if (!failureReported) {
reportException("getOrthodromicDistance", exception, false);
failureReported = true;
}
continue;
}
result[j] = new double[] {
calculator.getOrthodromicDistance(),
calculator.getAzimuth()
};
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("fallthrough")
public double[][] getOrthodromicForward(final XPropertySet xOptions,
final double[][] source,
final double[][] displacement,
final Object CRS)
{
final GeodeticCalculator calculator;
try {
calculator = getGeodeticCalculator(CRS);
} catch (Throwable exception) {
reportException("getOrthodromicForward", exception, THROW_EXCEPTION);
return getFailure(source.length, 2);
}
boolean failureReported = false;
final int dim = calculator.getCoordinateReferenceSystem().getCoordinateSystem().getDimension();
final GeneralDirectPosition sourcePt = new GeneralDirectPosition(dim);
final double[][] result = new double[getLength(source, displacement)][];
for (int j=0; j<result.length; j++) {
final double[] src = source [j % source.length];
final double[] mov = displacement[j % displacement.length];
if (src == null || mov == null) {
continue;
}
for (int i=dim; --i>=0;) {
sourcePt.ordinates[i] = (i < src.length) ? src[i] : 0;
}
double distance=0, azimuth=0;
switch (mov.length) {
default: // Fall through
case 2: azimuth = mov[1]; // Fall through
case 1: distance = mov[0]; // Fall through
case 0: break;
}
final DirectPosition targetPt;
try {
calculator.setStartingPosition(sourcePt);
calculator.setDirection(azimuth, distance);
targetPt = calculator.getDestinationPosition();
} catch (TransformException exception) {
if (!failureReported) {
reportException("getOrthodromicForward", exception, false);
failureReported = true;
}
continue;
}
result[j] = targetPt.getCoordinate();
}
return result;
}
}