/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2016, 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. */ /* * NOTICE OF RELEASE TO THE PUBLIC DOMAIN * * This work was created by employees of the USDA Forest Service's * Fire Science Lab for internal use. It is therefore ineligible for * copyright under title 17, section 105 of the United States Code. You * may treat it as you would treat any public domain work: it may be used, * changed, copied, or redistributed, with or without permission of the * authors, for free or for compensation. You may not claim exclusive * ownership of this code because it is already owned by everyone. Use this * software entirely at your own risk. No warranty of any kind is given. * * A copy of 17-USC-105 should have accompanied this distribution in the file * 17USC105.html. If not, you may access the law via the US Government's * public websites: * - http://www.copyright.gov/title17/92chap1.html#105 * - http://www.gpoaccess.gov/uscode/ (enter "17USC105" in the search box.) */ package org.geotools.coverage.grid.io.imageio.geotiff; import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; import org.geotools.coverage.grid.io.imageio.geotiff.codes.GeoTiffCoordinateTransformationsCodes; import org.geotools.coverage.grid.io.imageio.geotiff.codes.GeoTiffGCSCodes; import org.geotools.coverage.grid.io.imageio.geotiff.codes.GeoTiffPCSCodes; import org.geotools.factory.GeoTools; import org.geotools.factory.Hints; import org.geotools.metadata.iso.citation.CitationImpl; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.crs.DefaultProjectedCRS; import org.geotools.referencing.cs.DefaultCartesianCS; import org.geotools.referencing.cs.DefaultCoordinateSystemAxis; import org.geotools.referencing.cs.DefaultEllipsoidalCS; import org.geotools.referencing.datum.DefaultEllipsoid; import org.geotools.referencing.datum.DefaultGeodeticDatum; import org.geotools.referencing.datum.DefaultPrimeMeridian; import org.geotools.referencing.factory.AllAuthoritiesFactory; import org.geotools.referencing.operation.DefaultOperationMethod; import org.geotools.referencing.operation.DefiningConversion; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.Utilities; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchIdentifierException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.ImageCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CartesianCS; import org.opengis.referencing.datum.DatumFactory; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.datum.ImageDatum; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.datum.PrimeMeridian; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; /** * The <code>GeoTiffMetadata2CRSAdapter</code> is responsible for interpreting * the metadata provided by the <code>GeoTiffIIOMetadataDecoder</code> for the * purposes of constructing a CoordinateSystem object representative of the * information found in the tags. * * <p> * This class implements the flow indicated by the following diagram: * </p> * * <p align="center"> * <img src="../../../../../../../doc-files/GeoTiffFlow.png"> * </p> * * <p> * To use this class, the <CODE>GeoTiffReader</CODE> should create an instance * with the <code>CoordinateSystemAuthorityFactory</code> specified by the * <CODE>GeoTiffFormat</CODE> instance which created the reader. The image * specific metadata should then be set with the appropriate accessor methods. * Finally, the <code>createCoordinateSystem()</code> method is called to * produce the <code>CoordinateReferenceSystem</code> object specified by the * metadata. * </p> * * @author Bryce Nordgren / USDA Forest Service * @author Simone Giannecchini * @author Daniele Romagnoli * * * @source $URL$ */ public final class GeoTiffMetadata2CRSAdapter { /** {@link Logger}. */ private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GeoTiffMetadata2CRSAdapter.class); /** * This {@link AffineTransform} can be used when the underlying geotiff * declares to work with {@link GeoTiffConstants#RasterPixelIsArea} pixel * interpretation in order to convert the transformation back to using the * {@link GeoTiffConstants#RasterPixelIsPoint} convention which is the one * OGC requires for coverage. */ private static final AffineTransform PixelIsArea2PixelIsPoint = AffineTransform .getTranslateInstance(0.5, 0.5); /** EPSG Factory for creating {@link GeodeticDatum}objects. */ private final DatumFactory datumObjFactory; /** * {@link Hints} to control the creation of the factories for this * {@link GeoTiffMetadata2CRSAdapter} object. */ private Hints hints; /** * Cached {@link MathTransformFactory} for building {@link MathTransform} * objects. */ private final MathTransformFactory mtFactory; /** EPSG factories for various purposes. */ private final AllAuthoritiesFactory allAuthoritiesFactory; /** * Creates a new instance of GeoTiffMetadata2CRSAdapter * * @param hints * a map of hints to locate the authority and object factories. * (can be null) */ public GeoTiffMetadata2CRSAdapter(Hints hints) { this.hints = hints != null ? hints.clone() : GeoTools.getDefaultHints().clone(); // various authority related factories allAuthoritiesFactory = new AllAuthoritiesFactory(this.hints); // various factories datumObjFactory = ReferencingFactoryFinder.getDatumFactory(this.hints); mtFactory = ReferencingFactoryFinder.getMathTransformFactory(this.hints); } /** * This method creates a <code>CoordinateReferenceSystem</code> object * from the metadata which has been set earlier. If it cannot create the * <code>CoordinateReferenceSystem</code>, then one of three exceptions * is thrown to indicate the error. * * @return the <code>CoordinateReferenceSystem</code> object representing * the file data * * @throws IOException * if there is unexpected data in the GeoKey tags. * @throws FactoryException * @throws NullPointerException * if the <code>csAuthorityFactory</code>, * <code>datumFactory</code>, <code>crsFactory</code> or * <code>metadata</code> are uninitialized * @throws UnsupportedOperationException * if the coordinate system specified by the GeoTiff file is not * supported. */ public CoordinateReferenceSystem createCoordinateSystem( final GeoTiffIIOMetadataDecoder metadata) throws Exception{ // the first thing to check is the Model Type. // is it "Projected" or is it "Geographic"? // "Geocentric" is not supported. switch (getGeoKeyAsInt(GeoTiffConstants.GTModelTypeGeoKey, metadata)) { case GeoTiffPCSCodes.ModelTypeProjected: return createProjectedCoordinateReferenceSystem(metadata); case GeoTiffGCSCodes.ModelTypeGeographic: return createGeographicCoordinateReferenceSystem(metadata); default: throw new UnsupportedOperationException( "GeoTiffMetadata2CRSAdapter::createCoordinateSystem:Only Geographic & Projected Systems are supported. "); } } /** * This code is responsible for creating a projected coordinate reference * system as specified in the GeoTiff specification. User defined values are * supported throughout the evolution of this specification with except of * the coordinate transformation which must be one of the supported types. * * @param metadata * to use for building a {@link ProjectedCRS}. * * @return * @throws IOException * @throws FactoryException */ private ProjectedCRS createProjectedCoordinateReferenceSystem( GeoTiffIIOMetadataDecoder metadata) throws Exception { // // // // Get the projection reference system code in case we have one by // lookig for the ProjectedCSTypeGeoKey key // // // String projCode = metadata.getGeoKey(GeoTiffPCSCodes.ProjectedCSTypeGeoKey); if (projCode == null){ projCode = "unnamed".intern(); } else { projCode=projCode.trim(); } // // // // getting the linear unit used by this coordinate reference system // since we will use it anyway. // // // Unit<?> linearUnit; try { linearUnit = createUnit( GeoTiffPCSCodes.ProjLinearUnitsGeoKey, GeoTiffPCSCodes.ProjLinearUnitSizeGeoKey, SI.METER, SI.METER, metadata); } catch (GeoTiffException e) { linearUnit = null; } // // // // if it's user defined, there's a lot of work to do, we have to parse // many information. // // // if (projCode.equalsIgnoreCase("unnamed") || projCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { return createUserDefinedPCS(metadata, linearUnit); } // // // // if it's not user defined, just use the EPSG factory to create the // coordinate system // // // try { if (!projCode.toUpperCase().startsWith("EPSG:") ) { projCode="EPSG:"+projCode; } // it is an EPSG crs let's create it. final ProjectedCRS pcrs = (ProjectedCRS)allAuthoritiesFactory.createCoordinateReferenceSystem(projCode.toString()); // // // // We have nothing to do with the unit of measure // // // boolean isDefaultUnit = metadata.getGeoKey(GeoTiffPCSCodes.ProjLinearUnitsGeoKey) == null && linearUnit != null && linearUnit.equals(SI.METER); if (linearUnit == null || isDefaultUnit || linearUnit.equals(pcrs.getCoordinateSystem().getAxis(0).getUnit())){ return pcrs; } // // // // Creating a new projected CRS replacing the CS unit // // // final Conversion conversionFromBase=new DefiningConversion( pcrs.getConversionFromBase().getName().getCode(), pcrs.getConversionFromBase().getParameterValues()); return new DefaultProjectedCRS( java.util.Collections.singletonMap("name",DefaultEllipsoidalCS.getName(pcrs,new CitationImpl("EPSG"))), conversionFromBase, pcrs.getBaseCRS(), pcrs.getConversionFromBase().getMathTransform(), createProjectedCS(linearUnit) ); } catch (FactoryException fe) { final IOException ex = new GeoTiffException(metadata, fe .getLocalizedMessage(), fe); throw ex; } } /** * Creation of a geographic coordinate reference system as specified in the * GeoTiff specification. User defined values are supported for all the * possible levels of the above mentioned specification. * * @param metadata * to use for building a {@link GeographicCRS}. * * @return * @throws IOException */ private GeographicCRS createGeographicCoordinateReferenceSystem( final GeoTiffIIOMetadataDecoder metadata) throws IOException { GeographicCRS gcs = null; // // Get the crs code // final String tempCode = metadata.getGeoKey(GeoTiffGCSCodes.GeographicTypeGeoKey); // lookup the angular units used in this geotiff image Unit<?> angularUnit = null; try { angularUnit = createUnit(GeoTiffGCSCodes.GeogAngularUnitsGeoKey,GeoTiffGCSCodes.GeogAngularUnitSizeGeoKey, SI.RADIAN, NonSI.DEGREE_ANGLE, metadata); } catch (GeoTiffException e) { if(LOGGER.isLoggable(Level.FINE)){ LOGGER.log(Level.FINE,e.getLocalizedMessage(),e); } angularUnit = null; } if(angularUnit==null){ angularUnit=NonSI.DEGREE_ANGLE; } // linear unit Unit<?> linearUnit = null; try { linearUnit = createUnit(GeoTiffGCSCodes.GeogLinearUnitsGeoKey,GeoTiffGCSCodes.GeogLinearUnitSizeGeoKey, SI.METER, SI.METER, metadata); } catch (GeoTiffException e) { if(LOGGER.isLoggable(Level.FINE)){ LOGGER.log(Level.FINE,e.getLocalizedMessage(),e); } linearUnit = null; } if(linearUnit==null){ linearUnit=SI.METER; } // if it's user defined, there's a lot of work to do if (tempCode == null || tempCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { // // it is user-defined we have to parse a lot of information in order // to built it. // gcs = createUserDefinedGCS(metadata, linearUnit, angularUnit); } else { try { // //////////////////////////////////////////////////////////////////// // // If it's not user defined, just use the EPSG factory to create // the coordinate system but check if the user specified a // different angular unit. In this case we need to create a // user-defined GCRS. // // //////////////////////////////////////////////////////////////////// final StringBuilder geogCode = new StringBuilder(tempCode); if (!tempCode.startsWith("EPSG") && !tempCode.startsWith("epsg")) { geogCode.insert(0, "EPSG:"); } gcs = (GeographicCRS) allAuthoritiesFactory.createCoordinateReferenceSystem(geogCode.toString()); if (angularUnit != null && !angularUnit.equals(gcs.getCoordinateSystem() .getAxis(0).getUnit())) { // // // // Create a user-defined GCRS using the provided angular // unit. // // // gcs = new DefaultGeographicCRS(DefaultEllipsoidalCS .getName(gcs, new CitationImpl("EPSG")), (GeodeticDatum) gcs.getDatum(), DefaultEllipsoidalCS.GEODETIC_2D .usingUnit(angularUnit)); } } catch (FactoryException fe) { final IOException ex = new GeoTiffException(metadata, fe .getLocalizedMessage(), fe); throw ex; } } return gcs; } /** * Getting a specified geotiff geo key as a int. It is somehow tolerant in * the sense that in case such a key does not exist it retrieves 0. * * @param key * we want to get the value for. * @param metadata * containing the key we are looking for. * @return */ private static int getGeoKeyAsInt(final int key, final GeoTiffIIOMetadataDecoder metadata) { try { return Integer.parseInt(metadata.getGeoKey(key)); } catch (NumberFormatException ne) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, ne.getLocalizedMessage(), ne); return GeoTiffConstants.UNDEFINED; } } /** * Create the grid to world (or raster to model) transformation for this * source respecting ALWAYS the OGC {@link PixelInCell#CELL_CENTER} * convention for the {@link ImageDatum} of the underlying {@link ImageCRS}. * * @see <a * href="http://lists.maptools.org/pipermail/geotiff/2006-January/000213.html">this * email post</a> * @param metadata * containing the information to build the {@link MathTransform} * for going from grid to world. * * @throws GeoTiffException */ public static MathTransform getRasterToModel(final GeoTiffIIOMetadataDecoder metadata) throws GeoTiffException { // checks Utilities.ensureNonNull("metadata", metadata); // // Load initials // final boolean hasTiePoints = metadata.hasTiePoints(); final boolean hasPixelScales = metadata.hasPixelScales(); final boolean hasModelTransformation = metadata.hasModelTrasformation(); // decide raster type int rasterType = getGeoKeyAsInt(GeoTiffConstants.GTRasterTypeGeoKey,metadata); // geotiff spec says that PixelIsArea is the default if (rasterType == GeoTiffConstants.UNDEFINED){ rasterType = GeoTiffConstants.RasterPixelIsArea; } // prepare final transform MathTransform xform = null; if (hasTiePoints && hasPixelScales) { // // we use tie points and pixel scales to build the grid to world // // model space final TiePoint[] tiePoints = metadata.getModelTiePoints(); final PixelScale pixScales = metadata.getModelPixelScales(); // logging if(LOGGER.isLoggable(Level.FINE)){ final StringBuilder builder= new StringBuilder(); builder.append("Logging tiePoints:").append("\n"); for(TiePoint t: tiePoints){ builder.append( t.toString()).append("\n"); } builder.append("Logging pixScales:").append("\n"); builder.append(pixScales.toString()).append("\n"); LOGGER.log(Level.FINE, builder.toString()); } // here is the matrix we need to build final GeneralMatrix gm = new GeneralMatrix(3); final double scaleRaster2ModelLongitude = pixScales.getScaleX(); final double scaleRaster2ModelLatitude = -pixScales.getScaleY(); // "raster" space final double tiePointColumn = tiePoints[0].getValueAt(0)+ (rasterType == GeoTiffConstants.RasterPixelIsArea ? - 0.5: 0); // coordinates // (indicies) final double tiePointRow = tiePoints[0].getValueAt(1)+ (rasterType == GeoTiffConstants.RasterPixelIsArea ? - 0.5: 0); // compute an "offset and scale" matrix gm.setElement(0, 0, scaleRaster2ModelLongitude); gm.setElement(1, 1, scaleRaster2ModelLatitude); gm.setElement(0, 1, 0); gm.setElement(1, 0, 0); gm.setElement(0, 2, tiePoints[0].getValueAt(3) - (scaleRaster2ModelLongitude * tiePointColumn)); gm.setElement(1, 2, tiePoints[0].getValueAt(4) - (scaleRaster2ModelLatitude * tiePointRow)); // make it a LinearTransform xform = ProjectiveTransform.create(gm); } else if (hasModelTransformation) { AffineTransform modelTransformation = metadata.getModelTransformation(); if(modelTransformation==null){ throw new GeoTiffException(metadata, "Null modelTransformation", null); } // logging if(LOGGER.isLoggable(Level.FINE)){ final StringBuilder builder= new StringBuilder(); builder.append("Logging ModelTransformation:").append("\n"); builder.append( modelTransformation.toString()).append("\n"); LOGGER.log(Level.FINE, builder.toString()); } if (rasterType == GeoTiffConstants.RasterPixelIsArea) { final AffineTransform tempTransform = new AffineTransform(modelTransformation); tempTransform.concatenate(PixelIsArea2PixelIsPoint); xform = ProjectiveTransform.create(tempTransform); } else { assert rasterType == GeoTiffConstants.RasterPixelIsPoint; xform = ProjectiveTransform.create(modelTransformation); } } else{ throw new GeoTiffException(metadata,"Unknown Raster to Model configuration.", null); } // final check, is this invertible at all? try{ xform.inverse(); }catch(Exception e){ if(LOGGER.isLoggable(Level.FINE)){ LOGGER.log(Level.FINE,"Invalid model transformation found!\n"+e.getLocalizedMessage(),e); } return null; } return xform; } /** * Maps the RasterType GeoKey for this geotiff the {@link PixelInCell} * * @param metadata the metadata instance to extract the information from. * * @return an instance of the {@link PixelInCell} enum that capture the raster type. */ public static PixelInCell getRasterType(GeoTiffIIOMetadataDecoder metadata){ int rasterType = getGeoKeyAsInt(GeoTiffConstants.GTRasterTypeGeoKey,metadata); // geotiff spec says that PixelIsArea is the default if (rasterType == GeoTiffConstants.UNDEFINED){ rasterType = GeoTiffConstants.RasterPixelIsArea; } if (rasterType == GeoTiffConstants.RasterPixelIsArea){ return PixelInCell.CELL_CORNER; } else if (rasterType == GeoTiffConstants.RasterPixelIsPoint){ return PixelInCell.CELL_CENTER; } throw new IllegalArgumentException ("Unsupported rasterType"); } /** * Getting a specified geotiff geo key as a double. It is somehow tolerant * in the sense that in case such a key does not exist it returns <code>Double.NaN</code>. * * @param key * we want to get the value for. * @param metadata * containing the key we are looking for. * @return the value for the provided key. * @throws IOException */ private static double getGeoKeyAsDouble(final int key, final GeoTiffIIOMetadataDecoder metadata) { try { final String geoKey = metadata.getGeoKey(key); if (geoKey != null) return Double.parseDouble(geoKey); else return Double.NaN; } catch (NumberFormatException ne) { if (LOGGER.isLoggable(Level.WARNING)) LOGGER.log(Level.WARNING, ne.getLocalizedMessage(), ne); return Double.NaN; } catch (Exception e) { LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); return Double.NaN; } } /** * We have a user defined {@link ProjectedCRS}, let's try to parse it. * * @param linearUnit * is the UoM that this {@link ProjectedCRS} will use. It could * be null. * * @return a user-defined {@link ProjectedCRS}. * @throws IOException * @throws FactoryException */ private ProjectedCRS createUserDefinedPCS( final GeoTiffIIOMetadataDecoder metadata, Unit<?> linearUnit) throws Exception { // // At the top level a user-defined PCRS is made by // <ol> // <li>PCSCitationGeoKey (NAME) // <li>ProjectionGeoKey // <li>GeographicTypeGeoKey // </ol> // // // // GEOGRAPHIC CRS is the baseCRS for the projection // final GeographicCRS baseCRS = createGeographicCoordinateReferenceSystem(metadata); // // NAME of the user defined projected coordinate reference system. // String pcsCitationGeoKey = metadata.getGeoKey(GeoTiffPCSCodes.PCSCitationGeoKey); String projectedCrsName = pcsCitationGeoKey; if (projectedCrsName == null){ projectedCrsName = "unnamed".intern(); } else { projectedCrsName = cleanName(projectedCrsName); } // // PROJECTION geo key for this projected coordinate reference system. // get the projection code for this PCRS to build it from the GCS. // // In case i is user defined it requires: // PCSCitationGeoKey // ProjCoordTransGeoKey // ProjLinearUnitsGeoKey // final String projCode = metadata.getGeoKey(GeoTiffPCSCodes.ProjectionGeoKey); boolean projUserDefined = false; if (projCode == null|| projCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)){ projUserDefined = true; } // is it user defined? final Conversion conversionFromBase; String projectionName=null; final ParameterValueGroup parameters; final MathTransform transform; if (projUserDefined) { String citationName = metadata.getGeoKey(GeoTiffGCSCodes.GTCitationGeoKey); if ((projectedCrsName == null || "unnamed".equalsIgnoreCase(projectedCrsName)) && citationName != null) { // Fallback on GTCitation projectedCrsName = citationName; } // A user defined projection is made up by // <ol> // <li>PCSCitationGeoKey (NAME) // <li>ProjCoordTransGeoKey // <li>ProjLinearUnitsGeoKey // </ol> // NAME of this projection coordinate transformation // getting user defined parameters projectionName = pcsCitationGeoKey; if (projectionName == null){ //Fall back on citation projectionName = citationName != null? citationName : "unnamed"; } // // // // getting default parameters for this projection and filling them // with the values found // inside the geokeys list. // // // parameters = createUserDefinedProjectionParameter(projectionName,metadata); if (parameters == null){ throw new GeoTiffException( metadata, "GeoTiffMetadata2CRSAdapter::createUserDefinedPCS:Projection is not supported.", null); } // set the remaining parameters. refineParameters(baseCRS, parameters); // create math transform transform = mtFactory.createParameterizedTransform(parameters); // create the conversion from base, aka the projection transform conversionFromBase= new DefiningConversion( Collections.singletonMap("name", cleanName(projectionName)), new DefaultOperationMethod(transform), transform); } else { // create the conversion from EPSG code conversionFromBase = (Conversion) this.allAuthoritiesFactory.createCoordinateOperation(new StringBuilder("EPSG:").append(projCode).toString()); // prepare the parameters parameters=conversionFromBase.getParameterValues(); refineParameters(baseCRS, parameters); // create math transform transform = mtFactory.createParameterizedTransform(parameters); } // // PROJECTED CRS // // // // // I am putting particular attention on the management of the unit // of measure since it seems that very often people change the unit // of measure to feet even if the standard UoM for the request // projection is M. // // /// if (projUserDefined) { // user defined projection // standard unit of measure if (linearUnit != null && linearUnit.equals(SI.METER)){ return new DefaultProjectedCRS( java.util.Collections.singletonMap("name",projectedCrsName), conversionFromBase, baseCRS, transform, DefaultCartesianCS.PROJECTED); } // unit of measure is not standard return new DefaultProjectedCRS( java.util.Collections.singletonMap("name",projectedCrsName), conversionFromBase, baseCRS, transform, DefaultCartesianCS.PROJECTED.usingUnit(linearUnit)); } // standard projection if (linearUnit != null && !linearUnit.equals(SI.METER)){ return new DefaultProjectedCRS( Collections.singletonMap("name",projectedCrsName), conversionFromBase, baseCRS, transform, DefaultCartesianCS.PROJECTED.usingUnit(linearUnit)); } return new DefaultProjectedCRS( Collections.singletonMap("name",projectedCrsName), conversionFromBase, baseCRS, transform, DefaultCartesianCS.PROJECTED); } /** * @param baseCRS * @param parameters * @throws InvalidParameterValueException * @throws ParameterNotFoundException */ private void refineParameters(final GeographicCRS baseCRS, final ParameterValueGroup parameters) throws InvalidParameterValueException, ParameterNotFoundException { // set the remaining parameters. final GeodeticDatum tempDatum = baseCRS.getDatum(); final DefaultEllipsoid tempEll = (DefaultEllipsoid) tempDatum.getEllipsoid(); double inverseFlattening = tempEll.getInverseFlattening(); double semiMajorAxis = tempEll.getSemiMajorAxis(); // setting missing parameters parameters.parameter("semi_minor").setValue(semiMajorAxis * (1 - (1 / inverseFlattening))); parameters.parameter("semi_major").setValue(semiMajorAxis); } /** * Clean the provided parameters <code>tiffName</code> from strange * strings like it happens with erdas imagine. * * @param tiffName * is the {@link String} to clean up. * @return a cleaned up {@link String}. */ private final static String cleanName(String tiffName) { // look for strange chars // $ int index = tiffName.lastIndexOf('$'); if (index != -1) tiffName = tiffName.substring(index + 1); // \n index = tiffName.lastIndexOf('\n'); if (index != -1) tiffName = tiffName.substring(index + 1); // \r index = tiffName.lastIndexOf('\r'); if (index != -1) tiffName = tiffName.substring(index + 1); return tiffName; } /** * Creates a {@link CartesianCS} for a {@link ProjectedCRS} given the * provided {@link Unit}. * * @todo consider caching this items * @param linearUnit * to be used for building this {@link CartesianCS}. * @return an instance of {@link CartesianCS} using the provided * {@link Unit}, */ private DefaultCartesianCS createProjectedCS(Unit<?> linearUnit) { if (linearUnit == null) throw new NullPointerException( "Error when trying to create a PCS using this linear UoM "); if (!linearUnit.isCompatible(SI.METER)) throw new IllegalArgumentException( "Error when trying to create a PCS using this linear UoM " + linearUnit.toString()); final Map<String,String> props= new HashMap<String, String>(); props.put("name", Vocabulary.formatInternational(VocabularyKeys.PROJECTED).toString()); props.put("alias", Vocabulary.formatInternational(VocabularyKeys.PROJECTED).toString()); return new DefaultCartesianCS( props, new DefaultCoordinateSystemAxis(Vocabulary .formatInternational(VocabularyKeys.EASTING), "E", AxisDirection.EAST, linearUnit), new DefaultCoordinateSystemAxis(Vocabulary .formatInternational(VocabularyKeys.NORTHING), "N", AxisDirection.NORTH, linearUnit)); } /** * Creating a prime meridian for the gcs we are creating at an higher level. * As usual this method tries to follow the geotiff specification. * * @param linearUnit * to use for building this {@link PrimeMeridian}. * * @return a {@link PrimeMeridian} built using the provided {@link Unit} and * the provided metadata. * @throws IOException */ private PrimeMeridian createPrimeMeridian( final GeoTiffIIOMetadataDecoder metadata, Unit linearUnit) throws IOException { // look up the prime meridian: // + could be an EPSG code // + could be user defined // + not defined = greenwich final String pmCode = metadata .getGeoKey(GeoTiffGCSCodes.GeogPrimeMeridianGeoKey); PrimeMeridian pm = null; try { if (pmCode != null) { if (pmCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { try { String name = metadata.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey); if(name==null){ name="unnamed"; } else { String[] values = name.split("\\|"); if(values!=null&&values.length>=4){ name=values[3]; } } final String pmValue = metadata.getGeoKey(GeoTiffGCSCodes.GeogPrimeMeridianLongGeoKey); final double pmNumeric = Double.parseDouble(pmValue); // is it Greenwich? if (pmNumeric == 0){ return DefaultPrimeMeridian.GREENWICH; } final Map<String,String> props = new HashMap<String,String>(); props.put("name", name ); pm = datumObjFactory.createPrimeMeridian(props,pmNumeric, linearUnit); } catch (NumberFormatException nfe) { final IOException io = new GeoTiffException(metadata, "Invalid user-defined prime meridian spec.", nfe); throw io; } } else { pm = this.allAuthoritiesFactory.createPrimeMeridian("EPSG:" + pmCode); } } else { pm = DefaultPrimeMeridian.GREENWICH; } } catch (FactoryException fe) { final IOException io = new GeoTiffException(metadata, fe .getLocalizedMessage(), fe); throw io; } return pm; } /** * Looks up the Geodetic Datum as specified in the GeoTIFFWritingUtilities * file. The geotools definition of the geodetic datum includes both an * ellipsoid and a prime meridian, but the code in the * GeoTIFFWritingUtilities file does NOT include the prime meridian, as it * is specified separately. This code currently does not support user * defined datum. * * @param unit * to use for building this {@link GeodeticDatum}. * * @return a {@link GeodeticDatum}. * * @throws IOException * * @throws GeoTiffException * */ private GeodeticDatum createGeodeticDatum(final Unit<?> unit, final GeoTiffIIOMetadataDecoder metadata) throws IOException { // lookup the datum (w/o PrimeMeridian), error if "user defined" GeodeticDatum datum = null; final String datumCode = metadata.getGeoKey(GeoTiffGCSCodes.GeogGeodeticDatumGeoKey); if (datumCode == null) { throw new GeoTiffException( metadata, "GeoTiffMetadata2CRSAdapter::createGeodeticDatum(Unit unit):A user defined Geographic Coordinate system must include a predefined datum!", null); } if (datumCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { /** * * USER DEFINED DATUM * * From the spec: * * GeoKey Requirements for User-Defined Horizontal Datum: * GeogCitationGeoKey * GeogEllipsoidGeoKey * */ // datum name String name = metadata.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey); if(name==null){ name="unnamed"; } else { String[] values = name.split("\\|"); if(values!=null&&values.length>=2){ name=values[1]; } } // is it WGS84? if (name.trim().equalsIgnoreCase("WGS84")){ return DefaultGeodeticDatum.WGS84; } // ELLIPSOID final Ellipsoid ellipsoid = createEllipsoid(unit, metadata); // PRIME MERIDIAN // lookup the Prime Meridian. final PrimeMeridian primeMeridian = createPrimeMeridian(metadata,unit); // DATUM datum = new DefaultGeodeticDatum(cleanName(name), ellipsoid,primeMeridian); } else { /** * NOT USER DEFINED DATUM */ // we are going to use the provided EPSG code try { datum = (GeodeticDatum) (this.allAuthoritiesFactory .createDatum("EPSG:"+datumCode)); } catch (Exception cce) { final GeoTiffException ex = new GeoTiffException(metadata, cce .getLocalizedMessage(), cce); throw ex; } } return datum; } /** * Creating an ellipsoid following the GeoTiff spec. * * @param unit * to build this {@link Ellipsoid}.. * * @return an {@link Ellipsoid}. * * @throws GeoTiffException */ private Ellipsoid createEllipsoid(final Unit unit, final GeoTiffIIOMetadataDecoder metadata) throws GeoTiffException { // ///////////////////////////////////////////////////////////////////// // // Getting the ellipsoid key in order to understand if we are working // against a common ellipsoid or a user defined one. // // ///////////////////////////////////////////////////////////////////// // ellipsoid key final String ellipsoidKey = metadata.getGeoKey(GeoTiffGCSCodes.GeogEllipsoidGeoKey); String temp = null; // is the ellipsoid user defined? if (ellipsoidKey.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { // // USER DEFINED ELLIPSOID // String name = metadata.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey); if (name == null){ name = "unnamed"; } else { name=cleanName(name); String[] values = name.split("\\|"); if(values!=null&&values.length>=3){ name=values[2]; } } // is it the default for WGS84? if (name.trim().equalsIgnoreCase("WGS84")){ return DefaultEllipsoid.WGS84; } // // // // It is worth to point out that I ALWAYS use the inverse flattening // along with the semi-major axis to builde the Flattened Sphere. // This // has to be done in order to comply with the opposite process of // going from CRS to metadata where this coupls is always used. // // // // getting temporary parameters temp = metadata.getGeoKey(GeoTiffGCSCodes.GeogSemiMajorAxisGeoKey); final double semiMajorAxis = (temp != null ? Double.parseDouble(temp) : Double.NaN); temp = metadata.getGeoKey(GeoTiffGCSCodes.GeogInvFlatteningGeoKey); final double inverseFlattening; if (temp != null) { inverseFlattening = (temp != null ? Double.parseDouble(temp): Double.NaN); } else { temp = metadata.getGeoKey(GeoTiffGCSCodes.GeogSemiMinorAxisGeoKey); final double semiMinorAxis = (temp != null ? Double.parseDouble(temp) : Double.NaN); inverseFlattening = semiMajorAxis / (semiMajorAxis - semiMinorAxis); } // look for the Ellipsoid first then build the datum return DefaultEllipsoid.createFlattenedSphere(name,semiMajorAxis, inverseFlattening, unit); } try { // // EPSG STANDARD ELLIPSOID // return this.allAuthoritiesFactory.createEllipsoid(new StringBuilder("EPSG:").append(ellipsoidKey).toString()); } catch (FactoryException fe) { final GeoTiffException ex = new GeoTiffException(metadata, fe.getLocalizedMessage(), fe); throw ex; } } /** * The GeoTIFFWritingUtilities spec requires that a user defined GCS be * comprised of the following: * * <ul> * <li> a citation </li> * <li> a datum definition </li> * <li> a prime meridian definition (if not Greenwich) </li> * <li> an angular unit definition (if not degrees) </li> * </ul> * * @param metadata * to use for building this {@link GeographicCRS}. * @param linearUnit * @param angularUnit * @return a {@link GeographicCRS}. * * @throws IOException */ private GeographicCRS createUserDefinedGCS( final GeoTiffIIOMetadataDecoder metadata, Unit<?> linearUnit, Unit<?> angularUnit) throws IOException { // // // // coordinate reference system name (GeogCitationGeoKey) // // // String name = metadata.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey); if (name == null){ name = "unnamed"; } else { name=cleanName(name); String[] values = name.split("\\|"); if(values!=null&&values.length>=1){ name=values[0]; } } // lookup the Geodetic datum final GeodeticDatum datum = createGeodeticDatum(linearUnit, metadata); // coordinate reference system // property map is reused final Map<String, String> props = new HashMap<String, String>(); // make the user defined GCS from all the components... props.put("name", name); return new DefaultGeographicCRS( props, datum, DefaultEllipsoidalCS.GEODETIC_2D.usingUnit(angularUnit)); } /** * * @todo we should somehow try to to support user defined coordinate * transformation even if for the moment is not so clear to me how we * could achieve that since if we have no clue about the coordinate * transform what we are supposed to do in order to build a * conversion, guess it? How could we pick up the parameters, should * look for all and then guess the right transformation? * * @param name * indicates the name for the projection. * @param metadata * to use for building this {@link ParameterValueGroup}. * @return a {@link ParameterValueGroup} that can be used to trigger this * projection. * @throws IOException * @throws FactoryException */ private ParameterValueGroup createUserDefinedProjectionParameter( String name, final GeoTiffIIOMetadataDecoder metadata) throws IOException, FactoryException { // // // // Trying to get the name for the coordinate transformation involved. // // /// final String coordTrans = metadata .getGeoKey(GeoTiffPCSCodes.ProjCoordTransGeoKey); // throw descriptive exception if ProjCoordTransGeoKey not defined if ((coordTrans == null) || coordTrans.equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) { throw new GeoTiffException( metadata, "GeoTiffMetadata2CRSAdapter::createUserDefinedProjectionParameter(String name):User defined projections must specify" + " coordinate transformation code in ProjCoordTransGeoKey", null); } // getting math transform factory return setParametersForProjection(name, coordTrans, metadata); } /** * Set the projection parameters basing its decision on the projection name. * I found a complete list of projections on the geotiff website at address * http://www.remotesensing.org/geotiff/proj_list. * * I had no time to implement support for all of them therefore you will not * find all of them. If you want go ahead and add support for the missing * ones. I have tested this code against some geotiff files you can find on * the geotiff website under the ftp sample directory but I can say that * they are a real mess! I am respecting the specification strictly while * many of those fields do not! I could make this method trickier and use * workarounds in order to be less strict but I will not do this, since I * believe it is may lead us just on a very dangerous path. * * * @param name * @param metadata * to use fo building this {@link ParameterValueGroup}. * @param coordTrans * a {@link ParameterValueGroup} that can be used to trigger this * projection. * * @return * @throws GeoTiffException */ private ParameterValueGroup setParametersForProjection(String name, final String coordTransCode, final GeoTiffIIOMetadataDecoder metadata) throws GeoTiffException { ParameterValueGroup parameters = null; try { int code = 0; if (coordTransCode != null) code = Integer.parseInt(coordTransCode); if (name == null) name = "unnamed"; /** * * Transverse Mercator * */ if (name.equalsIgnoreCase("transverse_mercator") || code == GeoTiffCoordinateTransformationsCodes.CT_TransverseMercator) { parameters = mtFactory .getDefaultParameters("transverse_mercator"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("scale_factor").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Equidistant Cylindrical - Plate Caree - Equirectangular * */ if (name.equalsIgnoreCase("Equidistant_Cylindrical") || name.equalsIgnoreCase("Plate_Carree") || name.equalsIgnoreCase("Equidistant_Cylindrical") || code == GeoTiffCoordinateTransformationsCodes.CT_Equirectangular) { parameters = mtFactory .getDefaultParameters("Equidistant_Cylindrical"); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Mercator_1SP * Mercator_2SP * */ if (name.equalsIgnoreCase("mercator_1SP") || name.equalsIgnoreCase("Mercator_2SP") || code == GeoTiffCoordinateTransformationsCodes.CT_Mercator) { final double standard_parallel_1 = getGeoKeyAsDouble(GeoTiffPCSCodes.ProjStdParallel1GeoKey, metadata); boolean isMercator2SP = false; if (!Double.isNaN(standard_parallel_1)){ parameters = mtFactory.getDefaultParameters("Mercator_2SP"); isMercator2SP = true; } else parameters = mtFactory.getDefaultParameters("Mercator_1SP"); parameters.parameter("central_meridian").setValue(getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue(getOriginLat(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); if (isMercator2SP) parameters.parameter("standard_parallel_1").setValue(standard_parallel_1); else parameters.parameter("scale_factor").setValue(getScaleFactor(metadata)); return parameters; } /** * * Lambert_conformal_conic_1SP * */ if (name.equalsIgnoreCase("lambert_conformal_conic_1SP") || code == GeoTiffCoordinateTransformationsCodes.CT_LambertConfConic_Helmert) { parameters = mtFactory .getDefaultParameters("lambert_conformal_conic_1SP"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("scale_factor").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Lambert_conformal_conic_2SP * */ if (name.equalsIgnoreCase("lambert_conformal_conic_2SP") || name .equalsIgnoreCase("lambert_conformal_conic_2SP_Belgium") || code == GeoTiffCoordinateTransformationsCodes.CT_LambertConfConic_2SP) { parameters = mtFactory .getDefaultParameters("lambert_conformal_conic_2SP"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("standard_parallel_1").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel1GeoKey, metadata)); parameters.parameter("standard_parallel_2").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel2GeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Krovak * */ if (name.equalsIgnoreCase("Krovak")) { parameters = mtFactory.getDefaultParameters("Krovak"); parameters.parameter("longitude_of_center").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_center").setValue( getOriginLat(metadata)); parameters.parameter("azimuth").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel1GeoKey, metadata)); parameters.parameter("pseudo_standard_parallel_1").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel2GeoKey, metadata)); parameters.parameter("scale_factor").setValue( getFalseEasting(metadata)); return parameters; } // if (name.equalsIgnoreCase("equidistant_conic") // || code == GeoTiffMetadata2CRSAdapter.CT_EquidistantConic) { // parameters = mtFactory // .getDefaultParameters("equidistant_conic"); // parameters.parameter("central_meridian").setValue( // getOriginLong()); // parameters.parameter("latitude_of_origin").setValue( // getOriginLat()); // parameters // .parameter("standard_parallel_1") // .setValue( // this // .getGeoKeyAsDouble(GeoTiffIIOMetadataDecoder.ProjStdParallel1GeoKey)); // parameters // .parameter("standard_parallel_2") // .setValue( // this // .getGeoKeyAsDouble(GeoTiffIIOMetadataDecoder.ProjStdParallel2GeoKey)); // parameters.parameter("false_easting").setValue( // getFalseEasting()); // parameters.parameter("false_northing").setValue( // getFalseNorthing()); // // return parameters; // } /** * * STEREOGRAPHIC * */ if (name.equalsIgnoreCase("stereographic") || code == GeoTiffCoordinateTransformationsCodes.CT_Stereographic) { parameters = mtFactory.getDefaultParameters("stereographic"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("scale_factor").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * POLAR_STEREOGRAPHIC. * */ if (name.equalsIgnoreCase("polar_stereographic") || code == GeoTiffCoordinateTransformationsCodes.CT_PolarStereographic) { parameters = mtFactory .getDefaultParameters("polar_stereographic"); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("scale_factor").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); return parameters; } /** * Oblique Stereographic */ if(name.equalsIgnoreCase("oblique_stereographic") || code == GeoTiffCoordinateTransformationsCodes.CT_ObliqueStereographic) { parameters = mtFactory.getDefaultParameters("Oblique_Stereographic"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("scale_factor").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * OBLIQUE_MERCATOR. * */ if (name.equalsIgnoreCase("oblique_mercator") || name.equalsIgnoreCase("hotine_oblique_mercator") || code == GeoTiffCoordinateTransformationsCodes.CT_ObliqueMercator) { parameters = mtFactory.getDefaultParameters("oblique_mercator"); parameters.parameter("scale_factor").setValue( getScaleFactor(metadata)); parameters.parameter("azimuth").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjAzimuthAngleGeoKey, metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); parameters.parameter("longitude_of_center").setValue( getOriginLong(metadata)); parameters.parameter("latitude_of_center").setValue( getOriginLat(metadata)); return parameters; } /** * * albers_Conic_Equal_Area * */ if (name.equalsIgnoreCase("albers_Conic_Equal_Area") || code == GeoTiffCoordinateTransformationsCodes.CT_AlbersEqualArea) { parameters = mtFactory .getDefaultParameters("Albers_Conic_Equal_Area"); parameters.parameter("standard_parallel_1").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel1GeoKey, metadata)); parameters.parameter("standard_parallel_2").setValue( getGeoKeyAsDouble( GeoTiffPCSCodes.ProjStdParallel2GeoKey, metadata)); parameters.parameter("latitude_of_center").setValue( getOriginLat(metadata)); parameters.parameter("longitude_of_center").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Orthographic * */ if (name.equalsIgnoreCase("Orthographic") || code == GeoTiffCoordinateTransformationsCodes.CT_Orthographic) { parameters = mtFactory.getDefaultParameters("orthographic"); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("longitude_of_origin").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Lambert Azimuthal Equal Area * */ if (name.equalsIgnoreCase("Lambert_Azimuthal_Equal_Area") || code == GeoTiffCoordinateTransformationsCodes.CT_LambertAzimEqualArea) { parameters = mtFactory.getDefaultParameters("Lambert_Azimuthal_Equal_Area"); parameters.parameter("latitude_of_center").setValue( getOriginLat(metadata)); parameters.parameter("longitude_of_center").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Azimuthal Equidistant * */ if (name.equalsIgnoreCase("Azimuthal_Equidistant") || code == GeoTiffCoordinateTransformationsCodes.CT_AzimuthalEquidistant) { parameters = mtFactory.getDefaultParameters("Azimuthal_Equidistant"); parameters.parameter("longitude_of_center").setValue(getOriginLong(metadata)); parameters.parameter("latitude_of_center").setValue(getOriginLat(metadata)); parameters.parameter("false_easting").setValue(getFalseEasting(metadata)); parameters.parameter("false_northing").setValue(getFalseNorthing(metadata)); return parameters; } /** * * New Zealand Map Grid * */ if (name.equalsIgnoreCase("New_Zealand_Map_Grid") || code == GeoTiffCoordinateTransformationsCodes.CT_NewZealandMapGrid) { parameters = mtFactory .getDefaultParameters("New_Zealand_Map_Grid"); parameters.parameter("latitude_of_origin").setValue( getOriginLat(metadata)); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * World Van der Grinten I * */ if (name.equalsIgnoreCase("World_Van_der_Grinten_I") || code == GeoTiffCoordinateTransformationsCodes.CT_VanDerGrinten) { parameters = mtFactory .getDefaultParameters("World_Van_der_Grinten_I"); parameters.parameter("central_meridian").setValue( getOriginLong(metadata)); parameters.parameter("false_easting").setValue( getFalseEasting(metadata)); parameters.parameter("false_northing").setValue( getFalseNorthing(metadata)); return parameters; } /** * * Sinusoidal * */ if (name.equalsIgnoreCase("Sinusoidal") || code == GeoTiffCoordinateTransformationsCodes.CT_Sinusoidal) { parameters = mtFactory.getDefaultParameters("Sinusoidal"); parameters.parameter("central_meridian").setValue(getOriginLong(metadata)); parameters.parameter("false_easting").setValue(getFalseEasting(metadata)); parameters.parameter("false_northing").setValue(getFalseNorthing(metadata)); return parameters; } } catch (NoSuchIdentifierException e) { throw new GeoTiffException(metadata, e.getLocalizedMessage(), e); } return parameters; } /** * Retrieve the scale factor parameter as defined by the geotiff * specification. * * @param metadata * to use for searching the scale factor. * @return the scale factor */ private static double getScaleFactor(final GeoTiffIIOMetadataDecoder metadata) { String scale = metadata.getGeoKey(GeoTiffPCSCodes.ProjScaleAtCenterGeoKey); if (scale == null) scale = metadata.getGeoKey(GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey); if (scale == null) return 1.0; return Double.parseDouble(scale); } /** * Getting the false easting with a minimum of tolerance with respect to the * parameters name. I saw that often people use the wrong geokey to store * the false easting, we cannot be too picky we need to get going pretty * smoothly. * * @param metadata * to use for searching the false easting. * @return double False easting. */ private static double getFalseEasting(final GeoTiffIIOMetadataDecoder metadata) { String easting = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseEastingGeoKey); if (easting == null) easting = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseOriginEastingGeoKey); if (easting == null) return 0.0; return Double.parseDouble(easting); } /** * Getting the false northing with a minimum of tolerance with respect to * the parameters name. I saw that often people use the wrong geokey to * store the false easting, we cannot be too picky we need to get going * pretty smoothly. * * @param metadata * to use for searching the false northing. * @return double False northing. */ private static double getFalseNorthing(final GeoTiffIIOMetadataDecoder metadata) { String northing = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseNorthingGeoKey); if (northing == null) northing = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseOriginNorthingGeoKey); if (northing == null) return 0.0; return Double.parseDouble(northing); } /** * Getting the origin long with a minimum of tolerance with respect to the * parameters name. I saw that often people use the wrong geokey to store * the false easting, we cannot be too picky we need to get going pretty * smoothly. * * @param metadata * to use for searching the originating longitude. * @return double origin longitude. */ private static double getOriginLong(final GeoTiffIIOMetadataDecoder metadata) { String origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjCenterLongGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjNatOriginLongGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseOriginLongGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjStraightVertPoleLongGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseNorthingGeoKey); if (origin == null) return 0.0; return Double.parseDouble(origin); } /** * Getting the origin lat with a minimum of tolerance with respect to the * parameters name. I saw that often people use the wrong geokey to store * the false easting, we cannot be too picky we need to get going pretty * smoothly. * * @param metadata * to use for searching the origin latitude. * @return double origin latitude. */ private static double getOriginLat(final GeoTiffIIOMetadataDecoder metadata) { String origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjCenterLatGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjNatOriginLatGeoKey); if (origin == null) origin = metadata.getGeoKey(GeoTiffPCSCodes.ProjFalseOriginLatGeoKey); if (origin == null) return 0.0; return Double.parseDouble(origin); } /** * This code creates an <code>javax.Units.Unit</code> object out of the * <code>ProjLinearUnitsGeoKey</code> and the * <code>ProjLinearUnitSizeGeoKey</code>. The unit may either be * specified as a standard EPSG recognized unit, or may be user defined. * * @param key * * @param userDefinedKey * * @param base * * @param def * * * @return <code>Unit</code> object representative of the tags in the * file. * * @throws IOException * if the<code>ProjLinearUnitsGeoKey</code> is not specified * or if unit is user defined and * <code>ProjLinearUnitSizeGeoKey</code> is either not defined * or does not contain a number. */ private Unit<?> createUnit(int key, int userDefinedKey, Unit<?> base, Unit<?> def, final GeoTiffIIOMetadataDecoder metadata) throws IOException { final String unitCode = metadata.getGeoKey(key); // // // // if not defined, return the default unit of measure // // // if (unitCode == null) { return def; } // // // // if specified, retrieve the appropriate unit code. There are two case // to keep into account, first case is when the unit of measure has an // EPSG code, alternatively it can be instantiated as a conversion from // meter. // // // if (unitCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) { try { final String unitSize = metadata.getGeoKey(userDefinedKey); // throw descriptive exception if required key is not there. if (unitSize == null) { throw new GeoTiffException( metadata, "GeoTiffMetadata2CRSAdapter::createUnit:Must define unit length when using a user defined unit", null); } double sz = Double.parseDouble(unitSize); return base.times(sz); } catch (NumberFormatException nfe) { final IOException ioe = new GeoTiffException(metadata, nfe .getLocalizedMessage(), nfe); throw ioe; } } else { try { // using epsg code for this unit return (Unit<?>) this.allAuthoritiesFactory.createUnit("EPSG:"+unitCode); } catch (FactoryException fe) { final IOException io = new GeoTiffException(metadata, fe.getLocalizedMessage(), fe); throw io; } } } }