/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2012, 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.referencing.factory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.geotools.referencing.CRS; import org.geotools.referencing.operation.DefaultMathTransformFactory; import org.geotools.referencing.operation.DefaultOperation; import org.geotools.referencing.operation.DefaultOperationMethod; import org.geotools.referencing.operation.transform.AbstractMathTransform; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.util.SimpleInternationalString; import org.opengis.metadata.citation.Citation; import org.opengis.referencing.FactoryException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.operation.OperationMethod; import org.opengis.util.InternationalString; /** * A {@link CoordinateOperationAuthorityFactory} backed by a properties file. * Allows custom transform definitions across two CRSs, expressed as WKT math transforms. * Entries in the properties file take this format: * <pre> * [source crs code],[target crs code]=[WKT math transform] * </pre> * Examples: * <pre> * 4230,4258=PARAM_MT["NTv2", PARAMETER["Latitude and longitude difference file", "100800401.gsb"]] * 23031,25831=PARAM_MT["Similarity transformation", \ * PARAMETER["Ordinate 1 of evaluation point in target CRS", -129.549], \ * PARAMETER["Ordinate 2 of evaluation point in target CRS", -208.185], \ * PARAMETER["Scale difference", 1.0000015504], \ * PARAMETER["Rotation angle of source coordinate reference system axes", 1.56504]] * </pre> * For more compact definitions, parameter names can be replaced by their corresponding EPSG codes. * Following examples are the same as former ones: * <pre> * 4230,4258=PARAM_MT["9615", PARAMETER["8656", "100800401.gsb"]] * 23031,25831=PARAM_MT["9621", \ * PARAMETER["8621", -129.549], \ * PARAMETER["8622", -208.185], \ * PARAMETER["8611", 1.0000015504], \ * PARAMETER["8614", 1.56504]] * </pre> * References: * <p> * See <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html"> * <cite>Well-Known Text format</cite></a> for math transform syntax. * Visit the <a href="http://www.epsg-registry.org/"> <cite>EPSG Geodetic Parameter Registry</cite> * </a> for EPSG parameter codes and values. * <p> * Note that invertible transforms will be used in both directions. * <p> * This factory doesn't cache any result. Any call to a {@code createFoo} method will trig a new * WKT parsing. For caching, this factory should be wrapped in some buffered factory like * {@link BufferedAuthorityFactory}. * * @source $URL$ * @version $Id$ * @author Oscar Fonts */ public class PropertyCoordinateOperationAuthorityFactory extends DirectAuthorityFactory implements CoordinateOperationAuthorityFactory { /** * The authority for this factory. */ private final Citation authority; /** * The properties object for our properties file. Keys are CRS code pairs * separated by a comma. The associated value is a WKT string for the Math * Transform. See {@link PropertyCoordinateOperationAuthorityFactory}. */ private final Properties definitions = new Properties(); /** * An unmodifiable view of the authority keys. This view is always up to date * even if entries are added or removed in the {@linkplain #definitions} map. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private final Set<String> codes = Collections.unmodifiableSet((Set) definitions.keySet()); /** * Creates a factory for the specified authority from the specified file. * * @param factories The underlying factories used for objects creation. * @param authority The organization or party responsible for definition and maintenance of * the database. * @param definitions URL to the definition file. * @throws IOException if the definitions can't be read. */ public PropertyCoordinateOperationAuthorityFactory( final ReferencingFactoryContainer factories, final Citation authority, final URL definitions) throws IOException { // Set priority low super(factories, MINIMUM_PRIORITY + 10); // Set authority this.authority = authority; ensureNonNull("authority", authority); // Load properties final InputStream in = definitions.openStream(); this.definitions.load(in); in.close(); } /** * Creates an operation from a single operation code. * * @param code Coded value for operation. * @return The operation for the given code. * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. * @throws FactoryException if the object creation failed for some other reason. */ @Override public CoordinateOperation createCoordinateOperation(String code) throws NoSuchAuthorityCodeException, FactoryException { String[] crsPair = trimAuthority(code).split(","); if (crsPair.length == 2) { Set<CoordinateOperation> coordopset = createFromCoordinateReferenceSystemCodes( trimAuthority(crsPair[0]), trimAuthority(crsPair[1])); if (!coordopset.isEmpty()) { return coordopset.iterator().next(); } } return null; } /** * Creates a {@link CoordinateOperation} from * {@linkplain CoordinateReferenceSystem coordinate reference system} codes. * This method returns a single operation from the properties file. * If operation is invertible, will check also for the inverse one. * If operation not found, it will return an empty set. * * @param sourceCRS Coded value of source coordinate reference system. * @param targetCRS Coded value of target coordinate reference system. * @return The operation from {@code sourceCRS} to {@code targetCRS} (one single element). * @throws NoSuchAuthorityCodeException if a specified code was not found. * @throws FactoryException if the object creation failed for some other reason. */ @Override public Set<CoordinateOperation> createFromCoordinateReferenceSystemCodes( String sourceCRS, String targetCRS) throws NoSuchAuthorityCodeException, FactoryException { Set<CoordinateOperation> coordops = new HashSet<CoordinateOperation>(1); CoordinateOperation coordop = createFromCoordinateReferenceSystemCodes(sourceCRS, targetCRS, false); if (coordop == null) { // Not found. Try to create from the inverse. coordop = createFromCoordinateReferenceSystemCodes(targetCRS, sourceCRS, true); } if (coordop != null) { // Add to set if found. coordops.add(coordop); } return coordops; } /** * Seeks for a WKT definition in the properties file from a CRS pair, parses it, * and creates the corresponding CoordinateOperation. Returns {@code null} * if something went wrong. * <p> * Will log a WARNING message if a parsing error occurred. * * @param sourceCRS Coded value of source coordinate reference system. * @param targetCRS Coded value of target coordinate reference system. * @param inverse {@code true} to create operation from the inverse definition. * @return The operation from {@code sourceCRS} to {@code targetCRS}, * or {@code null} if not found. * @throws NoSuchAuthorityCodeException if a specified code was not found. * @throws FactoryException if the object creation failed for some other reason. */ CoordinateOperation createFromCoordinateReferenceSystemCodes( String sourceCRS, String targetCRS, boolean inverse) throws NoSuchAuthorityCodeException, FactoryException { // Get WKT definition from properties sourceCRS = trimAuthority(sourceCRS); targetCRS = trimAuthority(targetCRS); String id = sourceCRS+","+targetCRS; String WKT = definitions.getProperty(id); if(WKT == null) { // No definition found. return null; } // Create MathTransform from WKT MathTransform mt = null; try { mt = factories.getMathTransformFactory().createFromWKT(WKT); } catch (FactoryException e) { // Probably malformed WKT. LOGGER.warning("Error creating transformation: " + WKT); return null; } // Create the CRS definitions String s = this.authority.getIdentifiers().iterator().next().getCode(); CoordinateReferenceSystem source = CRS.decode(s+":"+sourceCRS); CoordinateReferenceSystem target = CRS.decode(s+":"+targetCRS); // Need to create a derived MathTransform that will handle axis order and units // as defined in CRS. Had to cast to DefaultMathTransformFactory because // createBaseToDerived is not defined in MathTransformFactory interface (GeoAPI). DefaultMathTransformFactory mtf = (DefaultMathTransformFactory)factories. getMathTransformFactory(); MathTransform mt2 = mtf.createBaseToDerived(source, mt, target.getCoordinateSystem()); // Extract name from the transform, if possible, or use class name. String methodName; try { if (mt instanceof AbstractMathTransform) { methodName = ((AbstractMathTransform)mt).getParameterValues().getDescriptor().getName().getCode(); } else if (mt instanceof AffineTransform2D) { methodName = ((AffineTransform2D)mt).getParameterValues().getDescriptor().getName().getCode(); } else { methodName = mt.getClass().getSimpleName(); } } catch (NullPointerException e) { methodName = mt.getClass().getSimpleName(); } Map<String, String> props = new HashMap<String, String>(); props.put("name", methodName); // Create the OperationMethod OperationMethod method = new DefaultOperationMethod(props, mt2.getSourceDimensions(), mt2.getTargetDimensions(), null); // Finally create CoordinateOperation CoordinateOperation coordop = null; if (!inverse) { // Direct operation props.put("name", sourceCRS + " \u21E8 " + targetCRS); coordop = DefaultOperation.create(props, source, target, mt2, method, CoordinateOperation.class); } else { // Inverse operation try { props.put("name", targetCRS + " \u21E8 " + sourceCRS); coordop = DefaultOperation.create(props, target, source, mt2.inverse(), method, CoordinateOperation.class); } catch (NoninvertibleTransformException e) { return null; } } return coordop; } /** * Returns the set of authority codes of the given type. * Only CoordinateOperation.class is accepted as type. * * This factory will not filter codes for its subclasses. * * @param type The CoordinateOperation type (or null, same effect). * @return All of available authority codes, or an empty set. */ @Override public Set<String> getAuthorityCodes(Class<? extends IdentifiedObject> type) { if (type==null || type.isAssignableFrom(CoordinateOperation.class)) { return codes; } else { return Collections.emptySet(); } } /** * Gets a description of the object corresponding to a code. * * @param code Value allocated by authority. * @return A description of the object, or {@code null} if the object * corresponding to the specified {@code code} has no description. * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. * @throws FactoryException if the query failed for some other reason. */ @Override public InternationalString getDescriptionText(String code) throws NoSuchAuthorityCodeException, FactoryException { final String wkt = definitions.getProperty(trimAuthority(code)); if (wkt == null) { throw noSuchAuthorityCode(IdentifiedObject.class, code); } // The first string literal in WKT will be considered the description text. int start = wkt.indexOf('"'); if (start >= 0) { final int end = wkt.indexOf('"', ++start); if (end >= 0) { return new SimpleInternationalString(wkt.substring(start, end).trim()); } } return null; } /** * Returns the organization or party responsible for definition and maintenance of the * database. */ @Override public Citation getAuthority() { return authority; } }