/*
* 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.geoapi;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
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.util.InternationalString;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.ReferenceSystem;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeneralDerivedCRS;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.SingleOperation;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.geometry.DirectPosition;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.metadata.extent.Extent;
import org.opengis.openoffice.XReferencing;
import org.geotoolkit.parameter.ParameterGroup;
import org.apache.sis.io.wkt.FormattableObject;
import org.apache.sis.io.wkt.Convention;
import org.geotoolkit.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.geotoolkit.openoffice.MethodInfo;
import org.geotoolkit.openoffice.Formulas;
import org.geotoolkit.resources.Loggings;
import org.geotoolkit.resources.Errors;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
/**
* Exports methods from the {@link org.geotoolkit.referencing} package as
* <A HREF="http://www.openoffice.org">OpenOffice</A> add-ins.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Richard Deplanque (IRD)
* @version 3.20
*
* @since 3.20 (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!
*/
public static final String __serviceName = "org.opengis.openoffice.Referencing";
/**
* The CRS authority factory. Will be created only when first needed.
*/
private transient CRSAuthorityFactory crsFactory;
/**
* The coordinate operation factory. Will be created only when first needed.
*/
private transient CoordinateOperationFactory opFactory;
/**
* The last coordinate operation used. Cached for performance reasons.
*/
private transient CoordinateOperation lastOperation;
/**
* The last source and target CRS used for fetching {@link #lastOperation}.
*/
private transient String lastSourceCRS, lastTargetCRS;
/**
* Constructs a default implementation of {@code XReferencing} interface.
*/
public Referencing() {
methods.put("getDescription", new MethodInfo("Referencing", "CRS.DESCRIPTION",
"Returns a description for an object identified by the given authority code.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority."
}));
methods.put("getScope", new MethodInfo("Referencing", "CRS.SCOPE",
"Returns the scope for an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority."
}));
methods.put("getValidArea", new MethodInfo("Referencing", "CRS.VALID.AREA",
"Returns the valid area as a textual description for an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority."
}));
methods.put("getBoundingBox", new MethodInfo("Referencing", "CRS.BOUNDING.BOX",
"Returns the valid area as a geographic bounding box for an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority."
}));
methods.put("getRemarks", new MethodInfo("Referencing", "CRS.REMARKS",
"Returns the remarks for an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority."
}));
methods.put("getAxis", new MethodInfo("Referencing", "CRS.AXIS",
"Returns the axis name for the specified dimension in an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority.",
"dimension", "The dimension (1, 2, ...)."
}));
methods.put("getParameter", new MethodInfo("Referencing", "CRS.PARAMETER",
"Returns the value for a coordinate reference system parameter.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority.",
"parameter", "The parameter name (e.g. \"False easting\")."
}));
methods.put("getWKT", new MethodInfo("Referencing", "CRS.WKT",
"Returns the Well Know Text (WKT) for an identified object.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority.",
"authority", "The authority name for choice of parameter names."
}));
methods.put("getTransformWKT", new MethodInfo("Referencing", "TRANSFORM.WKT",
"Returns the Well Know Text (WKT) of a transformation between two coordinate reference systems.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"code", "The code allocated by authority.",
"authority", "The authority name for choice of parameter names."
}));
methods.put("getAccuracy", new MethodInfo("Referencing", "TRANSFORM.ACCURACY",
"Returns the accuracy of a transformation between two coordinate reference systems.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"source CRS", "The source coordinate reference system.",
"target CRS", "The target coordinate reference system."
}));
methods.put("getTransformedCoordinates", new MethodInfo("Referencing", "TRANSFORM.COORD",
"Transform coordinates from the given source CRS to the given target CRS.",
new String[] {
"xOptions", "Provided by OpenOffice.",
"coordinates", "The coordinate values to transform.",
"source CRS", "The source coordinate reference system.",
"target CRS", "The target coordinate reference system."
}));
}
/**
* The service name that can be used to create such an object by a factory.
*/
@Override
public String getServiceName() {
return __serviceName;
}
// --------------------------------------------------------------------------------
// 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 coordinate operation for the two specified CRS.
*
* @param method The method invoking this {@code #getCoordinateOperation} method.
* For logging purpose only.
* @param source The source CRS authority code.
* @param target The target CRS authority code.
* @return The coordinate operation.
* @throws FactoryException if the coordinate operation can't be created.
*/
private CoordinateOperation getCoordinateOperation(final String method,
final String source,
final String target)
throws FactoryException
{
if (lastOperation!=null && lastSourceCRS.equals(source) && lastTargetCRS.equals(target)) {
return lastOperation;
}
final CoordinateReferenceSystem sourceCRS;
final CoordinateReferenceSystem targetCRS;
final CoordinateOperation operation;
final CRSAuthorityFactory crsFactory = crsFactory();
sourceCRS = crsFactory.createCoordinateReferenceSystem(source);
targetCRS = crsFactory.createCoordinateReferenceSystem(target);
if (opFactory == null) {
opFactory = CRS.getCoordinateOperationFactory(Boolean.TRUE);
}
operation = opFactory.createOperation(sourceCRS, targetCRS);
final Logger logger = getLogger();
if (logger.isLoggable(Level.FINER)) {
final LogRecord record = Loggings.format(Level.FINER,
Loggings.Keys.CreatedCoordinateOperation_3,
IdentifiedObjects.getIdentifierOrName(operation),
IdentifiedObjects.getIdentifierOrName(sourceCRS),
IdentifiedObjects.getIdentifierOrName(targetCRS));
record.setSourceClassName(Referencing.class.getName());
record.setSourceMethodName(method);
logger.log(record);
}
lastSourceCRS = source;
lastTargetCRS = target;
lastOperation = operation;
return operation;
}
/**
* Returns the Well Know Text (WKT) for the specified object using the parameter names
* from the specified authority.
*
* @param object The object to format.
* @param authority The authority name for choice of parameter names. Usually "OGC".
* @return The Well Know Text (WKT) for the specified object.
* @throws IllegalArgumentException if {@code authority} is not a string value or void.
* @throws UnsupportedOperationException if the object can't be formatted.
*
* @todo Use a formatter in order to set the authority, or remove the authority argument.
*/
private static String toWKT(final Object object, final Object authority)
throws IllegalArgumentException, UnsupportedOperationException
{
final String authorityString;
if (AnyConverter.isVoid(authority)) {
authorityString = "OGC";
} else {
authorityString = AnyConverter.toString(authority);
}
if (object instanceof FormattableObject) {
return ((FormattableObject) object).toString(Convention.WKT1);
}
if (object instanceof MathTransform) {
return ((MathTransform) object).toWKT();
}
return ((IdentifiedObject) object).toWKT();
}
// --------------------------------------------------------------------------------
// 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 String getDescription(final XPropertySet xOptions,
final String authorityCode)
{
final InternationalString description;
try {
description = crsFactory().getDescriptionText(authorityCode);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
return (description != null) ? description.toString(getJavaLocale()) : emptyString();
}
/**
* {@inheritDoc}
*/
@Override
public String getScope(final XPropertySet xOptions,
final String authorityCode)
{
final IdentifiedObject object;
try {
object = crsFactory().createObject(authorityCode);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
final InternationalString description;
if (object instanceof Datum) {
description = ((Datum) object).getScope();
} else if (object instanceof ReferenceSystem) {
description = ((ReferenceSystem) object).getScope();
} else if (object instanceof CoordinateOperation) {
description = ((CoordinateOperation) object).getScope();
} else {
description = null;
}
return (description != null) ? description.toString(getJavaLocale()) : emptyString();
}
/**
* {@inheritDoc}
*/
@Override
public String getValidArea(final XPropertySet xOptions,
final String authorityCode)
{
Extent validArea;
try {
validArea = crsFactory().createCoordinateReferenceSystem(authorityCode).getDomainOfValidity();
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
if (validArea != null) {
final InternationalString description = validArea.getDescription();
if (description != null) {
return description.toString(getJavaLocale());
}
final GeographicBoundingBox box = Extents.getGeographicBoundingBox(validArea);
if (box != null) {
return box.toString();
}
}
return emptyString();
}
/**
* {@inheritDoc}
*/
@Override
public double[][] getBoundingBox(final XPropertySet xOptions,
final String authorityCode)
{
Extent validArea;
try {
validArea = crsFactory().createCoordinateReferenceSystem(authorityCode).getDomainOfValidity();
} catch (Throwable exception) {
reportException("getBoundingBox", exception, THROW_EXCEPTION);
return getFailure(4,4);
}
final GeographicBoundingBox box = Extents.getGeographicBoundingBox(validArea);
if (box == null) {
return getFailure(4,4);
}
return new double[][] {
new double[] {box.getNorthBoundLatitude(),
box.getWestBoundLongitude()},
new double[] {box.getSouthBoundLatitude(),
box.getEastBoundLongitude()}};
}
/**
* {@inheritDoc}
*/
@Override
public String getRemarks(final XPropertySet xOptions,
final String authorityCode)
{
final IdentifiedObject object;
try {
object = crsFactory().createObject(authorityCode);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
final InternationalString remarks = object.getRemarks();
return (remarks!=null) ? remarks.toString(getJavaLocale()) : emptyString();
}
/**
* {@inheritDoc}
*/
@Override
public String getAxis(final XPropertySet xOptions,
final String authorityCode,
final int dimension)
{
CoordinateSystem cs;
try {
cs = crsFactory().createCoordinateReferenceSystem(authorityCode).getCoordinateSystem();
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
if (dimension >= 1 && dimension <= cs.getDimension()) {
return cs.getAxis(dimension - 1).getName().getCode();
} else {
return Errors.format(Errors.Keys.IndexOutOfBounds_1, dimension);
}
}
/**
* {@inheritDoc}
*/
@Override
public Object getParameter(final XPropertySet xOptions,
final String authorityCode,
final String parameter)
{
final IdentifiedObject object;
try {
object = crsFactory().createObject(authorityCode);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
final ParameterValueGroup parameters;
if (object instanceof SingleOperation) {
parameters = ((SingleOperation) object).getParameterValues();
} else if (object instanceof GeneralDerivedCRS) {
parameters = ((GeneralDerivedCRS) object).getConversionFromBase().getParameterValues();
} else {
parameters = ParameterGroup.EMPTY;
}
try {
return parameters.parameter(parameter).getValue();
} catch (ParameterNotFoundException exception) {
return Errors.format(Errors.Keys.UnknownParameter_1, parameter);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
}
/**
* {@inheritDoc}
*/
@Override
public String getWKT(final XPropertySet xOptions,
final String authorityCode,
final Object authority)
{
try {
return toWKT(crsFactory().createObject(authorityCode), authority);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
}
/**
* {@inheritDoc}
*/
@Override
public String getTransformWKT(final XPropertySet xOptions,
final String sourceCRS,
final String targetCRS,
final Object authority)
{
try {
return toWKT(getCoordinateOperation("getTransformWKT", sourceCRS, targetCRS).getMathTransform(), authority);
} catch (Throwable exception) {
return getLocalizedMessage(exception);
}
}
/**
* {@inheritDoc}
*/
@Override
public double getAccuracy(final XPropertySet xOptions,
final String sourceCRS,
final String targetCRS)
{
final CoordinateOperation operation;
try {
operation = getCoordinateOperation("getAccuracy", sourceCRS, targetCRS);
} catch (Throwable exception) {
reportException("getAccuracy", exception, THROW_EXCEPTION);
return Double.NaN;
}
return AbstractCoordinateOperation.castOrCopy(operation).getLinearAccuracy();
}
/**
* {@inheritDoc}
*/
@Override
public double[][] getTransformedCoordinates(final XPropertySet xOptions,
final double[][] coordinates,
final String sourceCRS,
final String targetCRS)
{
final CoordinateOperation operation;
try {
operation = getCoordinateOperation("getTransformedCoordinates", sourceCRS, targetCRS);
} catch (Throwable exception) {
reportException("getTransformedCoordinates", exception, THROW_EXCEPTION);
return getFailure(coordinates.length, 2);
}
/*
* We now have every information needed for applying the coordinate operations.
* Creates a result array and transform every point.
*/
boolean failureReported = false;
final MathTransform mt = operation.getMathTransform();
final GeneralDirectPosition sourcePt = new GeneralDirectPosition(mt.getSourceDimensions());
final GeneralDirectPosition targetPt = new GeneralDirectPosition(mt.getTargetDimensions());
final double[][] result = new double[coordinates.length][];
for (int j=0; j<coordinates.length; j++) {
double[] coords = coordinates[j];
if (coords == null) {
continue;
}
for (int i=sourcePt.ordinates.length; --i>=0;) {
sourcePt.ordinates[i] = (i < coords.length) ? coords[i] : 0;
}
final DirectPosition pt;
try {
pt = mt.transform(sourcePt, targetPt);
} catch (TransformException exception) {
/*
* The coordinate operation failed for this particular point. But maybe it will
* succeed for an other point. Set the values to NaN and continue the loop. Note:
* we will report the failure for logging purpose, but only the first one since
* all subsequent failures are likely to be the same one.
*/
if (!failureReported) {
reportException("getTransformedCoordinates", exception, false);
failureReported = true;
}
continue;
}
coords = new double[pt.getDimension()];
for (int i=coords.length; --i>=0;) {
coords[i] = pt.getOrdinate(i);
}
result[j] = coords;
}
return result;
}
}