/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
/*
* 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.gce.geotiff.adapters;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.lang.ref.Reference;
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.factory.Hints;
import org.geotools.gce.geotiff.GeoTiffException;
import org.geotools.gce.geotiff.codes.GeoTiffCoordinateTransformationsCodes;
import org.geotools.gce.geotiff.codes.GeoTiffGCSCodes;
import org.geotools.gce.geotiff.codes.GeoTiffPCSCodes;
import org.geotools.metadata.iso.citation.CitationImpl;
import org.geotools.referencing.CRS;
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.factory.ReferencingFactoryContainer;
import org.geotools.referencing.operation.DefaultMathTransformFactory;
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.SoftValueHashMap;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchIdentifierException;
import org.opengis.referencing.crs.CRSFactory;
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.CSFactory;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.CoordinateSystem;
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:
* http://svn.geotools.org/geotools/trunk/gt/plugin/geotiff/src/org/geotools/gce/geotiff/crs_adapters/GeoTiffMetadata2CRSAdapter.java $
*/
public final class GeoTiffMetadata2CRSAdapter {
/** {@link Logger}. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.gce.geotiff.adapters.adapters.crs");
/**
* 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;
/** CRS Factory for creating CRS objects. */
private final CRSFactory crsFactory;
/**
* {@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 static MathTransformFactory mtFactory = new DefaultMathTransformFactory();
/**
* The default value for {@link #maxStrongReferences} .
*/
public static final int DEFAULT_MAX = 100;
/**
* The pool of cached objects.
*/
private final static Map pool = Collections
.synchronizedMap(new SoftValueHashMap(DEFAULT_MAX));
/** Group Factory for creating {@link ProjectedCRS} objects. */
private final ReferencingFactoryContainer factories;
/** CS Factory for creating {@link CoordinateSystem} objects. */
private final CSFactory csFactory;
/** 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) {
final Hints tempHints = hints != null ? new Hints(hints) : new Hints(
Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
this.hints = (Hints) tempHints.clone();
allAuthoritiesFactory = new AllAuthoritiesFactory(this.hints);
// factory = new ThreadedEpsgFactory(hints);
datumObjFactory = ReferencingFactoryFinder.getDatumFactory(this.hints);
crsFactory = ReferencingFactoryFinder.getCRSFactory(this.hints);
csFactory = ReferencingFactoryFinder.getCSFactory(this.hints);
tempHints.put(Hints.DATUM_AUTHORITY_FACTORY, allAuthoritiesFactory);
tempHints.put(Hints.CS_FACTORY, csFactory);
tempHints.put(Hints.CRS_FACTORY, crsFactory);
tempHints.put(Hints.MATH_TRANSFORM_FACTORY, mtFactory);
factories = ReferencingFactoryContainer.instance(tempHints);
}
/**
* 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 IOException,
FactoryException {
// 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 createProjectedCoordinateSystem(metadata);
case GeoTiffGCSCodes.ModelTypeGeographic:
return createGeographicCoordinateSystem(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 createProjectedCoordinateSystem(
GeoTiffIIOMetadataDecoder metadata) throws IOException,
FactoryException {
// //
//
// Get the projection reference system code in case we have one by
// lookig for the ProjectedCSTypeGeoKey key
//
// //
String tempCode = metadata
.getGeoKey(GeoTiffPCSCodes.ProjectedCSTypeGeoKey);
if (tempCode == null)
tempCode = "unnamed".intern();
final StringBuffer projCode = new StringBuffer(tempCode.trim().intern());
// //
//
// 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 (tempCode.equalsIgnoreCase("unnamed")
|| tempCode.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 (!tempCode.startsWith("EPSG") && !tempCode.startsWith("epsg")) {
projCode.insert(0, "EPSG:");
}
// it is an EPSG crs let's create it.
final ProjectedCRS pcrs = (ProjectedCRS) CRS.decode(projCode
.toString(), true);
// //
//
// We have nothing to do with the unit of measure
//
// //
if (linearUnit == null
|| linearUnit.equals(pcrs.getCoordinateSystem().getAxis(0)
.getUnit()))
return pcrs;
// //
//
// Creating anew projected CRS
//
// //
return new DefaultProjectedCRS(
java.util.Collections.singletonMap("name",
DefaultEllipsoidalCS.getName(pcrs,
new CitationImpl("EPSG"))), pcrs.getConversionFromBase()
.getMethod(), (GeographicCRS) 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 createGeographicCoordinateSystem(
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) {
angularUnit = null;
}
// linear unit
Unit linearUnit = null;
try {
linearUnit = createUnit(GeoTiffGCSCodes.GeogLinearUnitsGeoKey,
GeoTiffGCSCodes.GeogLinearUnitSizeGeoKey, SI.METER,
SI.METER, metadata);
} catch (GeoTiffException e) {
linearUnit = null;
}
// 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 StringBuffer geogCode = new StringBuffer(tempCode);
if (!tempCode.startsWith("EPSG")
&& !tempCode.startsWith("epsg")) {
geogCode.insert(0, "EPSG:");
}
gcs = (GeographicCRS) CRS.decode(geogCode.toString(), true);
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 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 MathTransform getRasterToModel(
final GeoTiffIIOMetadataDecoder metadata) throws GeoTiffException {
return getRasterToModel(metadata, true);
}
/**
* Create the grid to world (or raster to model) transformation for this
* source.
*
* @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.
* @param forceToCellCenter if <code>true</code>, force to create the raster to model
* transformation respecting the OGC {@link PixelInCell#CELL_CENTER}
* convention for the {@link ImageDatum} of the underlying {@link ImageCRS}.
*
* @throws GeoTiffException
*/
public MathTransform getRasterToModel(
final GeoTiffIIOMetadataDecoder metadata,
final boolean forceToCellCenter) throws GeoTiffException {
// /////////////////////////////////////////////////////////////////////
//
// Load initials
//
// /////////////////////////////////////////////////////////////////////
final boolean hasTiePoints = metadata.hasTiePoints();
final boolean hasPixelScales = metadata.hasPixelScales();
final boolean hasModelTransformation = metadata.hasModelTrasformation();
int rasterType = getGeoKeyAsInt(GeoTiffConstants.GTRasterTypeGeoKey,metadata);
// geotiff spec says that PixelIsArea is the default
if (rasterType == GeoTiffConstants.UNDEFINED)
rasterType = GeoTiffConstants.RasterPixelIsArea;
MathTransform xform = null;
if (hasTiePoints && hasPixelScales) {
final TiePoint[] tiePoints = metadata.getModelTiePoints();
final PixelScale pixScales = metadata.getModelPixelScales();
// /////////////////////////////////////////////////////////////////////
//
// checking the directions of the axes.
// we need to understand how the axes of this gridcoverage are
// specified.
// trying to understand the direction of the first axis in order to
//
// /////////////////////////////////////////////////////////////////////
// latitude index
final GeneralMatrix gm = new GeneralMatrix(3); // identity
final double scaleRaster2ModelLongitude = pixScales.getScaleX();
final double scaleRaster2ModelLatitude = -pixScales.getScaleY();
final double tiePointColumn = tiePoints[0].getValueAt(0)
+ ((forceToCellCenter && rasterType == GeoTiffConstants.RasterPixelIsArea) ? -0.5
: 0); // "raster" space
// coordinates
// (indicies)
final double tiePointRow = tiePoints[0].getValueAt(1)
+ ((forceToCellCenter && 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) {
if (rasterType == GeoTiffConstants.RasterPixelIsArea){
final AffineTransform tempTransform = new AffineTransform(
metadata.getModelTransformation());
if (forceToCellCenter)
tempTransform.concatenate(PixelIsArea2PixelIsPoint);
xform = ProjectiveTransform.create(tempTransform);
}
else {
assert rasterType == GeoTiffConstants.RasterPixelIsPoint;
xform = ProjectiveTransform.create(metadata
.getModelTransformation());
}
} else
throw new GeoTiffException(metadata,
"Unknown Raster to Model configuration.", null);
return xform;
}
public 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 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 IOException, FactoryException {
// /////////////////////////////////////////////////////////////////
//
// At the top level a user-defined PCRS is made by
// <ol>
// <li>PCSCitationGeoKey (NAME)
// <li>ProjectionGeoKey
// <li>GeographicTypeGeoKey
// </ol>
//
//
// /////////////////////////////////////////////////////////////////
// //
//
// NAME of the user defined projected coordinate reference system.
//
// //
String projectedCrsName = metadata
.getGeoKey(GeoTiffPCSCodes.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?
Conversion projection = null;
final ParameterValueGroup parameters;
if (projUserDefined) {
// /////////////////////////////////////////////////////////////////
// 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
String projectionName = metadata
.getGeoKey(GeoTiffPCSCodes.PCSCitationGeoKey);
if (projectionName == null)
projectionName = "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);
} else {
parameters = null;
projection = (Conversion) this.allAuthoritiesFactory
.createCoordinateOperation(new StringBuffer("EPSG:")
.append(projCode).toString());
}
// /////////////////////////////////////////////////////////////////////
//
// GEOGRAPHIC CRS
//
// /////////////////////////////////////////////////////////////////////
final GeographicCRS gcs = createGeographicCoordinateSystem(metadata);
// was the projection user defined?
// in such case we need to set the remaining parameters.
if (projUserDefined) {
final GeodeticDatum tempDatum = ((GeodeticDatum) gcs.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);
}
// /////////////////////////////////////////////////////////////////////
//
// 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
if (linearUnit != null && linearUnit.equals(SI.METER))
return this.factories.createProjectedCRS(Collections
.singletonMap("name", projectedCrsName), gcs, null,
parameters, DefaultCartesianCS.PROJECTED);
return factories.createProjectedCRS(Collections.singletonMap(
"name", projectedCrsName), gcs, null, parameters,
DefaultCartesianCS.PROJECTED.usingUnit(linearUnit));
}
// standard projection
if (linearUnit != null && !linearUnit.equals(SI.METER))
return factories.createProjectedCRS(Collections.singletonMap(
"name", projectedCrsName), gcs, projection,
DefaultCartesianCS.PROJECTED.usingUnit(linearUnit));
return factories.createProjectedCRS(Collections.singletonMap("name",
projectedCrsName), gcs, projection,
DefaultCartesianCS.PROJECTED);
}
/**
* 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());
return new DefaultCartesianCS(Vocabulary.formatInternational(
VocabularyKeys.PROJECTED).toString(),
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 {
final String name = metadata
.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey);
final String pmValue = metadata
.getGeoKey(GeoTiffGCSCodes.GeogPrimeMeridianLongGeoKey);
final double pmNumeric = Double.parseDouble(pmValue);
// is it Greenwich?
if (pmNumeric == 0)
return DefaultPrimeMeridian.GREENWICH;
final Map props = new HashMap();
props.put("name", (name != null) ? name
: "User Defined GEOTIFF Prime Meridian");
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
*
*
*
*/
// datum name
final String datumName = (metadata
.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey) != null ? metadata
.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey)
: "unnamed");
// is it WGS84?
if (datumName.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(datumName, ellipsoid,
primeMeridian);
} else {
/**
* NOT USER DEFINED DATUM
*/
// we are going to use the provided EPSG code
try {
datum = (GeodeticDatum) (this.allAuthoritiesFactory
.createDatum(new StringBuffer("EPSG:")
.append(datumCode).toString()));
} catch (FactoryException fe) {
final GeoTiffException ex = new GeoTiffException(metadata, fe
.getLocalizedMessage(), fe);
throw ex;
} catch (ClassCastException 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
.equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
// /////////////////////////////////////////////////////////////////////
//
// USER DEFINED ELLIPSOID
//
// /////////////////////////////////////////////////////////////////////
String nameEllipsoid = metadata
.getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey);
if (nameEllipsoid == null)
nameEllipsoid = "unnamed";
// is it the default for WGS84?
if (nameEllipsoid.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(nameEllipsoid,
semiMajorAxis, inverseFlattening, unit);
}
try {
// /////////////////////////////////////////////////////////////////////
//
// EPSG STANDARD ELLIPSOID
//
// /////////////////////////////////////////////////////////////////////
return this.allAuthoritiesFactory.createEllipsoid(new StringBuffer(
"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";
// lookup the Geodetic datum
final GeodeticDatum datum = createGeodeticDatum(linearUnit, metadata);
// coordinate reference system
GeographicCRS gcs = null;
try {
// property map is reused
final Map props = new HashMap();
// make the user defined GCS from all the components...
props.put("name", name);
gcs = crsFactory.createGeographicCRS(props, datum,
DefaultEllipsoidalCS.GEODETIC_2D.usingUnit(angularUnit));
} catch (FactoryException fe) {
final GeoTiffException io = new GeoTiffException(metadata, fe
.getLocalizedMessage(), fe);
throw io;
}
return gcs;
}
/**
*
* @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(
this.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 =
this.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(
this.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(
this.getGeoKeyAsDouble(
GeoTiffPCSCodes.ProjStdParallel1GeoKey,
metadata));
parameters.parameter("standard_parallel_2").setValue(
this.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(
this.getGeoKeyAsDouble(
GeoTiffPCSCodes.ProjStdParallel1GeoKey,
metadata));
parameters.parameter("pseudo_standard_parallel_1").setValue(
this.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(
this.getOriginLong(metadata));
parameters.parameter("latitude_of_origin").setValue(
this.getOriginLat(metadata));
parameters.parameter("scale_factor").setValue(
this.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(
this.getOriginLat(metadata));
parameters.parameter("scale_factor").setValue(
this.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(
this.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(
this.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(
this.getGeoKeyAsDouble(
GeoTiffPCSCodes.ProjStdParallel1GeoKey,
metadata));
parameters.parameter("standard_parallel_2").setValue(
this.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;
}
/**
*
* 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(
this.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;
}
} 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 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 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 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 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.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 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,
new StringBuffer(
"GeoTiffMetadata2CRSAdapter::createUnit:Must define unit length when using a user ")
.append("defined unit").toString(), 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(new StringBuffer("EPSG:").append(unitCode)
.toString());
} catch (FactoryException fe) {
final IOException io = new GeoTiffException(metadata, fe
.getLocalizedMessage(), fe);
throw io;
}
}
}
/**
* Returns an object from the pool for the specified code. If the object was
* retained as a {@linkplain Reference weak reference}, the
* {@link Reference#get referent} is returned.
*
* @param key
* to use for doing the lookup inside the pool.
* @return a cached instance of a {@link GeoTiffMetadata2CRSAdapter}
* suitable for the provided key.
* @todo Consider logging a message here to the finer or finest level.
*/
public static Object get(final Object key) {
synchronized (pool) {
Object object = pool.get(key);
if (object == null) {
object = new GeoTiffMetadata2CRSAdapter((Hints) key);
put(key, object);
}
return object;
}
}
/**
* Put an element in the pool. This method is invoked everytime a
* {@code createFoo(...)} method is invoked, even if an object was already
* in the pool for the given code, for the following reasons: 1) Replaces
* weak reference by strong reference (if applicable) and 2) Alters the
* linked hash set order, so that this object is declared as the last one
* used.
*/
private static void put(final Object key, final Object object) {
synchronized (pool) {
pool.put(key, object);
}
}
/**
* Returns the {@link Hints} for this {@link GeoTiffMetadata2CRSAdapter}.
*
* @return {@link Hints} for this {@link GeoTiffMetadata2CRSAdapter}.
*/
public Hints getHints() {
return hints;
}
}