0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation;
0009: * version 2.1 of the License.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: /*
0017: * NOTICE OF RELEASE TO THE PUBLIC DOMAIN
0018: *
0019: * This work was created by employees of the USDA Forest Service's
0020: * Fire Science Lab for internal use. It is therefore ineligible for
0021: * copyright under title 17, section 105 of the United States Code. You
0022: * may treat it as you would treat any public domain work: it may be used,
0023: * changed, copied, or redistributed, with or without permission of the
0024: * authors, for free or for compensation. You may not claim exclusive
0025: * ownership of this code because it is already owned by everyone. Use this
0026: * software entirely at your own risk. No warranty of any kind is given.
0027: *
0028: * A copy of 17-USC-105 should have accompanied this distribution in the file
0029: * 17USC105.html. If not, you may access the law via the US Government's
0030: * public websites:
0031: * - http://www.copyright.gov/title17/92chap1.html#105
0032: * - http://www.gpoaccess.gov/uscode/ (enter "17USC105" in the search box.)
0033: */
0034: package org.geotools.gce.geotiff.crs_adapters;
0035:
0036: import java.awt.geom.AffineTransform;
0037: import java.io.IOException;
0038: import java.lang.ref.Reference;
0039: import java.util.Collections;
0040: import java.util.HashMap;
0041: import java.util.Map;
0042: import java.util.logging.Level;
0043: import java.util.logging.Logger;
0044:
0045: import javax.units.NonSI;
0046: import javax.units.SI;
0047: import javax.units.Unit;
0048:
0049: import org.geotools.factory.Hints;
0050: import org.geotools.gce.geotiff.GeoTiffException;
0051: import org.geotools.gce.geotiff.IIOMetadataAdpaters.GeoTiffIIOMetadataDecoder;
0052: import org.geotools.gce.geotiff.IIOMetadataAdpaters.PixelScale;
0053: import org.geotools.gce.geotiff.IIOMetadataAdpaters.TiePoint;
0054: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.GeoTiffConstants;
0055: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffCoordinateTransformationsCodes;
0056: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffGCSCodes;
0057: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffPCSCodes;
0058: import org.geotools.metadata.iso.citation.CitationImpl;
0059: import org.geotools.referencing.CRS;
0060: import org.geotools.referencing.ReferencingFactoryFinder;
0061: import org.geotools.referencing.crs.DefaultGeographicCRS;
0062: import org.geotools.referencing.crs.DefaultProjectedCRS;
0063: import org.geotools.referencing.cs.DefaultCartesianCS;
0064: import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
0065: import org.geotools.referencing.cs.DefaultEllipsoidalCS;
0066: import org.geotools.referencing.datum.DefaultEllipsoid;
0067: import org.geotools.referencing.datum.DefaultGeodeticDatum;
0068: import org.geotools.referencing.datum.DefaultPrimeMeridian;
0069: import org.geotools.referencing.factory.AllAuthoritiesFactory;
0070: import org.geotools.referencing.factory.ReferencingFactoryContainer;
0071: import org.geotools.referencing.operation.DefaultMathTransformFactory;
0072: import org.geotools.referencing.operation.matrix.GeneralMatrix;
0073: import org.geotools.referencing.operation.transform.ProjectiveTransform;
0074: import org.geotools.resources.i18n.Vocabulary;
0075: import org.geotools.resources.i18n.VocabularyKeys;
0076: import org.geotools.util.SoftValueHashMap;
0077: import org.opengis.parameter.ParameterValueGroup;
0078: import org.opengis.referencing.FactoryException;
0079: import org.opengis.referencing.NoSuchIdentifierException;
0080: import org.opengis.referencing.crs.CRSFactory;
0081: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0082: import org.opengis.referencing.crs.GeographicCRS;
0083: import org.opengis.referencing.crs.ImageCRS;
0084: import org.opengis.referencing.crs.ProjectedCRS;
0085: import org.opengis.referencing.cs.AxisDirection;
0086: import org.opengis.referencing.cs.CSFactory;
0087: import org.opengis.referencing.cs.CartesianCS;
0088: import org.opengis.referencing.cs.CoordinateSystem;
0089: import org.opengis.referencing.datum.DatumFactory;
0090: import org.opengis.referencing.datum.Ellipsoid;
0091: import org.opengis.referencing.datum.GeodeticDatum;
0092: import org.opengis.referencing.datum.ImageDatum;
0093: import org.opengis.referencing.datum.PixelInCell;
0094: import org.opengis.referencing.datum.PrimeMeridian;
0095: import org.opengis.referencing.operation.Conversion;
0096: import org.opengis.referencing.operation.MathTransform;
0097: import org.opengis.referencing.operation.MathTransformFactory;
0098:
0099: /**
0100: * The <code>GeoTiffMetadata2CRSAdapter</code> is responsible for interpreting
0101: * the metadata provided by the <code>GeoTiffIIOMetadataDecoder</code> for the
0102: * purposes of constructing a CoordinateSystem object representative of the
0103: * information found in the tags.
0104: *
0105: * <p>
0106: * This class implements the flow indicated by the following diagram:
0107: * </p>
0108: *
0109: * <p align="center">
0110: * <img src="../../../../../../../doc-files/GeoTiffFlow.png">
0111: * </p>
0112: *
0113: * <p>
0114: * To use this class, the <CODE>GeoTiffReader</CODE> should create an instance
0115: * with the <code>CoordinateSystemAuthorityFactory</code> specified by the
0116: * <CODE>GeoTiffFormat</CODE> instance which created the reader. The image
0117: * specific metadata should then be set with the appropriate accessor methods.
0118: * Finally, the <code>createCoordinateSystem()</code> method is called to
0119: * produce the <code>CoordinateReferenceSystem</code> object specified by the
0120: * metadata.
0121: * </p>
0122: *
0123: * @author Bryce Nordgren / USDA Forest Service
0124: * @author Simone Giannecchini
0125: * @source $URL:
0126: * http://svn.geotools.org/geotools/trunk/gt/plugin/geotiff/src/org/geotools/gce/geotiff/crs_adapters/GeoTiffMetadata2CRSAdapter.java $
0127: */
0128: public final class GeoTiffMetadata2CRSAdapter {
0129:
0130: /** {@link Logger}. */
0131: private final static Logger LOGGER = org.geotools.util.logging.Logging
0132: .getLogger("org.geotools.gce.geotiff.crs_adapters");
0133:
0134: /**
0135: * This {@link AffineTransform} can be used when the underlying geotiff
0136: * declares to work with {@link GeoTiffConstants#RasterPixelIsArea} pixel
0137: * interpretation in order to convert the transformtion back to using the
0138: * {@link GeoTiffConstants#RasterPixelIsArea} convention which is the one
0139: * OGC requires for coverage.
0140: */
0141: private static final AffineTransform PixelIsArea2PixelIsPoint = AffineTransform
0142: .getTranslateInstance(0.5, 0.5);
0143:
0144: /** EPSG Factory for creating {@link GeodeticDatum}objects. */
0145: private final DatumFactory datumObjFactory;
0146:
0147: /** CRS Factory for creating CRS objects. */
0148: private final CRSFactory crsFactory;
0149:
0150: /**
0151: * {@link Hints} to control the creation of the factories for this
0152: * {@link GeoTiffMetadata2CRSAdapter} object.
0153: */
0154: private Hints hints;
0155:
0156: /**
0157: * Cached {@link MathTransformFactory} for building {@link MathTransform}
0158: * objects.
0159: */
0160: private final static MathTransformFactory mtFactory = new DefaultMathTransformFactory();
0161:
0162: /**
0163: * The default value for {@link #maxStrongReferences} .
0164: */
0165: public static final int DEFAULT_MAX = 20;
0166:
0167: /**
0168: * The pool of cached objects.
0169: */
0170: private final static Map pool = Collections
0171: .synchronizedMap(new SoftValueHashMap(DEFAULT_MAX));
0172:
0173: /** Group Factory for creating {@link ProjectedCRS} objects. */
0174: private final ReferencingFactoryContainer factories;
0175:
0176: /** CS Factory for creating {@link CoordinateSystem} objects. */
0177: private final CSFactory csFactory;
0178:
0179: /** EPSG factories for various purposes. */
0180: private final AllAuthoritiesFactory allAuthoritiesFactory;
0181:
0182: /**
0183: * Creates a new instance of GeoTiffMetadata2CRSAdapter
0184: *
0185: * @param hints
0186: * a map of hints to locate the authority and object factories.
0187: * (can be null)
0188: */
0189: public GeoTiffMetadata2CRSAdapter(Hints hints) {
0190:
0191: final Hints tempHints = hints != null ? new Hints(hints)
0192: : new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
0193: Boolean.TRUE);
0194:
0195: this .hints = (Hints) tempHints.clone();
0196: allAuthoritiesFactory = new AllAuthoritiesFactory(this .hints);
0197:
0198: // factory = new ThreadedEpsgFactory(hints);
0199: datumObjFactory = ReferencingFactoryFinder
0200: .getDatumFactory(this .hints);
0201: crsFactory = ReferencingFactoryFinder.getCRSFactory(this .hints);
0202: csFactory = ReferencingFactoryFinder.getCSFactory(this .hints);
0203: tempHints.put(Hints.DATUM_AUTHORITY_FACTORY,
0204: allAuthoritiesFactory);
0205: tempHints.put(Hints.CS_FACTORY, csFactory);
0206: tempHints.put(Hints.CRS_FACTORY, crsFactory);
0207: tempHints.put(Hints.MATH_TRANSFORM_FACTORY, mtFactory);
0208: factories = ReferencingFactoryContainer.instance(tempHints);
0209: }
0210:
0211: /**
0212: * This method creates a <code>CoordinateReferenceSystem</code> object
0213: * from the metadata which has been set earlier. If it cannot create the
0214: * <code>CoordinateReferenceSystem</code>, then one of three exceptions
0215: * is thrown to indicate the error.
0216: *
0217: * @return the <code>CoordinateReferenceSystem</code> object representing
0218: * the file data
0219: *
0220: * @throws IOException
0221: * if there is unexpected data in the GeoKey tags.
0222: * @throws FactoryException
0223: * @throws NullPointerException
0224: * if the <code>csAuthorityFactory</code>,
0225: * <code>datumFactory</code>, <code>crsFactory</code> or
0226: * <code>metadata</code> are uninitialized
0227: * @throws UnsupportedOperationException
0228: * if the coordinate system specified by the GeoTiff file is not
0229: * supported.
0230: */
0231: public CoordinateReferenceSystem createCoordinateSystem(
0232: final GeoTiffIIOMetadataDecoder metadata)
0233: throws IOException, FactoryException {
0234:
0235: // the first thing to check is the Model Type.
0236: // is it "Projected" or is it "Geographic"?
0237: // "Geocentric" is not supported.
0238: switch (getGeoKeyAsInt(GeoTiffConstants.GTModelTypeGeoKey,
0239: metadata)) {
0240: case GeoTiffPCSCodes.ModelTypeProjected:
0241: return createProjectedCoordinateSystem(metadata);
0242:
0243: case GeoTiffGCSCodes.ModelTypeGeographic:
0244: return createGeographicCoordinateSystem(metadata);
0245:
0246: default:
0247: throw new UnsupportedOperationException(
0248: "GeoTiffMetadata2CRSAdapter::createCoordinateSystem:Only Geographic & Projected Systems are supported. ");
0249: }
0250:
0251: }
0252:
0253: /**
0254: * This code is responsible for creating a projected coordinate reference
0255: * system as specified in the GeoTiff specification. User defined values are
0256: * supported throughout the evolution of this specification with except of
0257: * the coordinate transformation which must be one of the supported types.
0258: *
0259: * @param metadata
0260: * to use for building a {@link ProjectedCRS}.
0261: *
0262: * @return
0263: * @throws IOException
0264: * @throws FactoryException
0265: */
0266: private ProjectedCRS createProjectedCoordinateSystem(
0267: GeoTiffIIOMetadataDecoder metadata) throws IOException,
0268: FactoryException {
0269:
0270: // //
0271: //
0272: // Get the projection reference system code in case we have one by
0273: // lookig for the ProjectedCSTypeGeoKey key
0274: //
0275: // //
0276: String tempCode = metadata
0277: .getGeoKey(GeoTiffPCSCodes.ProjectedCSTypeGeoKey);
0278: if (tempCode == null)
0279: tempCode = "unnamed".intern();
0280: final StringBuffer projCode = new StringBuffer(tempCode.trim()
0281: .intern());
0282:
0283: // //
0284: //
0285: // getting the linear unit used by this coordinate reference system
0286: // since we will use it anyway.
0287: //
0288: // //
0289: Unit linearUnit;
0290: try {
0291: linearUnit = createUnit(
0292: GeoTiffPCSCodes.ProjLinearUnitsGeoKey,
0293: GeoTiffPCSCodes.ProjLinearUnitSizeGeoKey, SI.METER,
0294: SI.METER, metadata);
0295: } catch (GeoTiffException e) {
0296: linearUnit = null;
0297: }
0298: // //
0299: //
0300: // if it's user defined, there's a lot of work to do, we have to parse
0301: // many information.
0302: //
0303: // //
0304: if (tempCode.equalsIgnoreCase("unnamed")
0305: || tempCode
0306: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
0307: return createUserDefinedPCS(metadata, linearUnit);
0308:
0309: }
0310: // //
0311: //
0312: // if it's not user defined, just use the EPSG factory to create the
0313: // coordinate system
0314: //
0315: // //
0316: try {
0317: if (!tempCode.startsWith("EPSG")
0318: && !tempCode.startsWith("epsg")) {
0319: projCode.insert(0, "EPSG:");
0320: }
0321: // it is an EPSG crs let's create it.
0322: final ProjectedCRS pcrs = (ProjectedCRS) CRS.decode(
0323: projCode.toString(), true);
0324: // //
0325: //
0326: // We have nothing to do with the unit of measure
0327: //
0328: // //
0329: if (linearUnit == null
0330: || linearUnit.equals(pcrs.getCoordinateSystem()
0331: .getAxis(0).getUnit()))
0332: return pcrs;
0333: // //
0334: //
0335: // Creating anew projected CRS
0336: //
0337: // //
0338: return new DefaultProjectedCRS(DefaultEllipsoidalCS
0339: .getName(pcrs, new CitationImpl("EPSG")), pcrs
0340: .getConversionFromBase().getMethod(),
0341: (GeographicCRS) pcrs.getBaseCRS(),
0342: pcrs.getConversionFromBase().getMathTransform(),
0343: createProjectedCS(linearUnit));
0344: } catch (FactoryException fe) {
0345: final IOException ex = new GeoTiffException(metadata, fe
0346: .getLocalizedMessage(), fe);
0347: throw ex;
0348: }
0349: }
0350:
0351: /**
0352: * Creation of a geographic coordinate reference system as specified in the
0353: * GeoTiff specification. User defined values are supported for all the
0354: * possible levels of the above mentioned specification.
0355: *
0356: * @param metadata
0357: * to use for building a {@link GeographicCRS}.
0358: *
0359: * @return
0360: * @throws IOException
0361: */
0362: private GeographicCRS createGeographicCoordinateSystem(
0363: final GeoTiffIIOMetadataDecoder metadata)
0364: throws IOException {
0365: GeographicCRS gcs = null;
0366:
0367: // ////////////////////////////////////////////////////////////////////
0368: //
0369: // Get the crs code
0370: //
0371: // ////////////////////////////////////////////////////////////////////
0372: final String tempCode = metadata
0373: .getGeoKey(GeoTiffGCSCodes.GeographicTypeGeoKey);
0374: // lookup the angular units used in this geotiff image
0375: Unit angularUnit = null;
0376: try {
0377: angularUnit = createUnit(
0378: GeoTiffGCSCodes.GeogAngularUnitsGeoKey,
0379: GeoTiffGCSCodes.GeogAngularUnitSizeGeoKey,
0380: SI.RADIAN, NonSI.DEGREE_ANGLE, metadata);
0381: } catch (GeoTiffException e) {
0382: angularUnit = null;
0383: }
0384: // linear unit
0385: Unit linearUnit = null;
0386: try {
0387: linearUnit = createUnit(
0388: GeoTiffGCSCodes.GeogLinearUnitsGeoKey,
0389: GeoTiffGCSCodes.GeogLinearUnitSizeGeoKey, SI.METER,
0390: SI.METER, metadata);
0391: } catch (GeoTiffException e) {
0392: linearUnit = null;
0393: }
0394: // if it's user defined, there's a lot of work to do
0395: if (tempCode == null
0396: || tempCode
0397: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
0398: // ////////////////////////////////////////////////////////////////////
0399: //
0400: // it is user-defined we have to parse a lot of information in order
0401: // to built it.
0402: //
0403: // ////////////////////////////////////////////////////////////////////
0404: gcs = createUserDefinedGCS(metadata, linearUnit,
0405: angularUnit);
0406:
0407: } else {
0408: try {
0409:
0410: // ////////////////////////////////////////////////////////////////////
0411: //
0412: // If it's not user defined, just use the EPSG factory to create
0413: // the coordinate system but check if the user specified a
0414: // different angular unit. In this case we need to create a
0415: // user-defined GCRS.
0416: //
0417: // ////////////////////////////////////////////////////////////////////
0418: final StringBuffer geogCode = new StringBuffer(tempCode);
0419: if (!tempCode.startsWith("EPSG")
0420: && !tempCode.startsWith("epsg")) {
0421: geogCode.insert(0, "EPSG:");
0422: }
0423: gcs = (GeographicCRS) CRS.decode(geogCode.toString(),
0424: true);
0425: if (angularUnit != null
0426: && !angularUnit.equals(gcs
0427: .getCoordinateSystem().getAxis(0)
0428: .getUnit())) {
0429: // //
0430: //
0431: // Create a user-defined GCRS using the provided angular
0432: // unit.
0433: //
0434: // //
0435: gcs = new DefaultGeographicCRS(DefaultEllipsoidalCS
0436: .getName(gcs, new CitationImpl("EPSG")),
0437: (GeodeticDatum) gcs.getDatum(),
0438: DefaultEllipsoidalCS.GEODETIC_2D
0439: .usingUnit(angularUnit));
0440: }
0441: } catch (FactoryException fe) {
0442: final IOException ex = new GeoTiffException(metadata,
0443: fe.getLocalizedMessage(), fe);
0444:
0445: throw ex;
0446: }
0447: }
0448:
0449: return gcs;
0450: }
0451:
0452: /**
0453: * Getting a specified geotiff geo key as a int. It is somehow tolerant in
0454: * the sense that in case such a key does not exist it retrieves 0.
0455: *
0456: * @param key
0457: * we want to get the value for.
0458: * @param metadata
0459: * containing the key we are looking for.
0460: * @return
0461: */
0462: private int getGeoKeyAsInt(final int key,
0463: final GeoTiffIIOMetadataDecoder metadata) {
0464:
0465: try {
0466: return Integer.parseInt(metadata.getGeoKey(key));
0467: } catch (NumberFormatException ne) {
0468: if (LOGGER.isLoggable(Level.WARNING))
0469: LOGGER.log(Level.WARNING, ne.getLocalizedMessage(), ne);
0470: return Integer.MIN_VALUE;
0471: }
0472:
0473: }
0474:
0475: /**
0476: * Create the grid to world (or raster to model) transformation for this
0477: * source repsecting ALWAYS the OGC {@link PixelInCell#CELL_CENTER}
0478: * onvention for the {@link ImageDatum} of the underlying {@link ImageCRS}.
0479: *
0480: * @see <a
0481: * href="http://lists.maptools.org/pipermail/geotiff/2006-January/000213.html">this
0482: * email post</a>
0483: * @param metadata
0484: * containing the information to build the {@link MathTransform}
0485: * for going from grid to world.
0486: *
0487: * @throws GeoTiffException
0488: */
0489: public MathTransform getRasterToModel(
0490: final GeoTiffIIOMetadataDecoder metadata)
0491: throws GeoTiffException {
0492: // /////////////////////////////////////////////////////////////////////
0493: //
0494: // Load initials
0495: //
0496: // /////////////////////////////////////////////////////////////////////
0497: final boolean hasTiePoints = metadata.hasTiePoints();
0498: final boolean hasPixelScales = metadata.hasPixelScales();
0499: final boolean hasModelTransformation = metadata
0500: .hasModelTrasformation();
0501: int rasterType = getGeoKeyAsInt(
0502: GeoTiffConstants.GTRasterTypeGeoKey, metadata);
0503: // geotiff spec says that PixelIsArea is the default
0504: if (rasterType == GeoTiffConstants.UNDEFINED)
0505: rasterType = GeoTiffConstants.RasterPixelIsArea;
0506: MathTransform xform = null;
0507: if (hasTiePoints && hasPixelScales) {
0508: final TiePoint[] tiePoints = metadata.getModelTiePoints();
0509: final PixelScale pixScales = metadata.getModelPixelScales();
0510:
0511: // /////////////////////////////////////////////////////////////////////
0512: //
0513: // checking the directions of the axes.
0514: // we need to understand how the axes of this gridcoverage are
0515: // specified.
0516: // trying to understand the direction of the first axis in order to
0517: //
0518: // /////////////////////////////////////////////////////////////////////
0519: // latitude index
0520:
0521: final GeneralMatrix gm = new GeneralMatrix(3); // identity
0522: final double scaleRaster2ModelLongitude = pixScales
0523: .getScaleX();
0524: final double scaleRaster2ModelLatitude = -pixScales
0525: .getScaleY();
0526: final double tiePointColumn = tiePoints[0].getValueAt(0)
0527: + (rasterType == GeoTiffConstants.RasterPixelIsArea ? -0.5
0528: : 0); // "raster" space
0529: // coordinates
0530: // (indicies)
0531: final double tiePointRow = tiePoints[0].getValueAt(1)
0532: + (rasterType == GeoTiffConstants.RasterPixelIsArea ? -0.5
0533: : 0);
0534:
0535: // compute an "offset and scale" matrix
0536: gm.setElement(0, 0, scaleRaster2ModelLongitude);
0537: gm.setElement(1, 1, scaleRaster2ModelLatitude);
0538: gm.setElement(0, 1, 0);
0539: gm.setElement(1, 0, 0);
0540:
0541: gm.setElement(0, 2, tiePoints[0].getValueAt(3)
0542: - (scaleRaster2ModelLongitude * tiePointColumn));
0543: gm.setElement(1, 2, tiePoints[0].getValueAt(4)
0544: - (scaleRaster2ModelLatitude * tiePointRow));
0545:
0546: // make it a LinearTransform
0547: xform = ProjectiveTransform.create(gm);
0548:
0549: } else if (hasModelTransformation) {
0550: if (rasterType == GeoTiffConstants.RasterPixelIsArea)
0551: xform = ProjectiveTransform.create(metadata
0552: .getModelTransformation());
0553: else {
0554: assert rasterType == GeoTiffConstants.RasterPixelIsPoint;
0555: final AffineTransform tempTransform = new AffineTransform(
0556: metadata.getModelTransformation());
0557: tempTransform.concatenate(PixelIsArea2PixelIsPoint);
0558: xform = ProjectiveTransform.create(tempTransform);
0559:
0560: }
0561: } else
0562: throw new GeoTiffException(metadata,
0563: "Unknown Raster to Model configuration.", null);
0564:
0565: return xform;
0566: }
0567:
0568: /**
0569: * Getting a specified geotiff geo key as a double. It is somehow tolerant
0570: * in the sense that in case such a key does not exist it retrieves 0.
0571: *
0572: * @param key
0573: * we want to get the value for.
0574: * @param metadata
0575: * containing the key we are looking for.
0576: * @return the value for the provided key.
0577: * @throws IOException
0578: */
0579: private double getGeoKeyAsDouble(final int key,
0580: final GeoTiffIIOMetadataDecoder metadata) {
0581:
0582: try {
0583: return Double.parseDouble(metadata.getGeoKey(key));
0584: } catch (NumberFormatException ne) {
0585: if (LOGGER.isLoggable(Level.WARNING))
0586: LOGGER.log(Level.WARNING, ne.getLocalizedMessage(), ne);
0587: return Double.NaN;
0588: } catch (Exception e) {
0589: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
0590: return Double.NaN;
0591: }
0592: }
0593:
0594: /**
0595: * We have a user defined {@link ProjectedCRS}, let's try to parse it.
0596: *
0597: * @param linearUnit
0598: * is the UoM that this {@link ProjectedCRS} will use. It could
0599: * be null.
0600: *
0601: * @return a user-defined {@link ProjectedCRS}.
0602: * @throws IOException
0603: * @throws FactoryException
0604: */
0605: private ProjectedCRS createUserDefinedPCS(
0606: final GeoTiffIIOMetadataDecoder metadata, Unit linearUnit)
0607: throws IOException, FactoryException {
0608:
0609: // /////////////////////////////////////////////////////////////////
0610: //
0611: // At the top level a user-defined PCRS is made by
0612: // <ol>
0613: // <li>PCSCitationGeoKey (NAME)
0614: // <li>ProjectionGeoKey
0615: // <li>GeographicTypeGeoKey
0616: // </ol>
0617: //
0618: //
0619: // /////////////////////////////////////////////////////////////////
0620: // //
0621: //
0622: // NAME of the user defined projected coordinate reference system.
0623: //
0624: // //
0625: String projectedCrsName = metadata
0626: .getGeoKey(GeoTiffPCSCodes.PCSCitationGeoKey);
0627: if (projectedCrsName == null)
0628: projectedCrsName = "unnamed".intern();
0629: else
0630: projectedCrsName = cleanName(projectedCrsName);
0631:
0632: // /////////////////////////////////////////////////////////////////////
0633: //
0634: // PROJECTION geo key for this projected coordinate reference system.
0635: // get the projection code for this PCRS to build it from the GCS.
0636: //
0637: // In case i is user defined it requires:
0638: // PCSCitationGeoKey
0639: // ProjCoordTransGeoKey
0640: // ProjLinearUnitsGeoKey
0641: //
0642: // /////////////////////////////////////////////////////////////////////
0643: final String projCode = metadata
0644: .getGeoKey(GeoTiffPCSCodes.ProjectionGeoKey);
0645: boolean projUserDefined = false;
0646: if (projCode == null
0647: || projCode
0648: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String))
0649: projUserDefined = true;
0650:
0651: // is it user defined?
0652: Conversion projection = null;
0653: final ParameterValueGroup parameters;
0654: if (projUserDefined) {
0655: // /////////////////////////////////////////////////////////////////
0656: // A user defined projection is made up by
0657: // <ol>
0658: // <li>PCSCitationGeoKey (NAME)
0659: // <li>ProjCoordTransGeoKey
0660: // <li>ProjLinearUnitsGeoKey
0661: // </ol>
0662: // /////////////////////////////////////////////////////////////////
0663: // NAME of this projection coordinate transformation
0664: // getting user defined parameters
0665: String projectionName = metadata
0666: .getGeoKey(GeoTiffPCSCodes.PCSCitationGeoKey);
0667: if (projectionName == null)
0668: projectionName = "unnamed";
0669:
0670: // //
0671: //
0672: // getting default parameters for this projection and filling them
0673: // with the values found
0674: // inside the geokeys list.
0675: //
0676: // //
0677: parameters = createUserDefinedProjectionParameter(
0678: projectionName, metadata);
0679: if (parameters == null)
0680: throw new GeoTiffException(
0681: metadata,
0682: "GeoTiffMetadata2CRSAdapter::createUserDefinedPCS:Projection is not supported.",
0683: null);
0684:
0685: } else {
0686: parameters = null;
0687: projection = (Conversion) this .allAuthoritiesFactory
0688: .createCoordinateOperation(new StringBuffer("EPSG:")
0689: .append(projCode).toString());
0690: }
0691:
0692: // /////////////////////////////////////////////////////////////////////
0693: //
0694: // GEOGRAPHIC CRS
0695: //
0696: // /////////////////////////////////////////////////////////////////////
0697: final GeographicCRS gcs = createGeographicCoordinateSystem(metadata);
0698:
0699: // was the projection user defined?
0700: // in such case we need to set the remaining parameters.
0701: if (projUserDefined) {
0702: final GeodeticDatum tempDatum = ((GeodeticDatum) gcs
0703: .getDatum());
0704: final DefaultEllipsoid tempEll = (DefaultEllipsoid) tempDatum
0705: .getEllipsoid();
0706: double inverseFlattening = tempEll.getInverseFlattening();
0707: double semiMajorAxis = tempEll.getSemiMajorAxis();
0708: // setting missing parameters
0709: parameters.parameter("semi_minor").setValue(
0710: semiMajorAxis * (1 - (1 / inverseFlattening)));
0711: parameters.parameter("semi_major").setValue(semiMajorAxis);
0712:
0713: }
0714:
0715: // /////////////////////////////////////////////////////////////////////
0716: //
0717: // PROJECTED CRS
0718: //
0719: // /////////////////////////////////////////////////////////////////////
0720: // //
0721: //
0722: // I am putting particular attention on the management of the unit
0723: // of measure since it seems that very often people change the unit
0724: // of measure to feet even if the standard UoM for the request
0725: // projection is M.
0726: //
0727: // ///
0728: if (projUserDefined) {
0729: // user defined projection
0730: if (linearUnit != null && linearUnit.equals(SI.METER))
0731: return this .factories.createProjectedCRS(Collections
0732: .singletonMap("name", projectedCrsName), gcs,
0733: null, parameters, DefaultCartesianCS.PROJECTED);
0734: return factories.createProjectedCRS(Collections
0735: .singletonMap("name", projectedCrsName), gcs, null,
0736: parameters, DefaultCartesianCS.PROJECTED
0737: .usingUnit(linearUnit));
0738: }
0739: // standard projection
0740: if (linearUnit != null && !linearUnit.equals(SI.METER))
0741: return factories.createProjectedCRS(Collections
0742: .singletonMap("name", projectedCrsName), gcs,
0743: projection, DefaultCartesianCS.PROJECTED
0744: .usingUnit(linearUnit));
0745: return factories.createProjectedCRS(Collections.singletonMap(
0746: "name", projectedCrsName), gcs, projection,
0747: DefaultCartesianCS.PROJECTED);
0748: }
0749:
0750: /**
0751: * Clean the provided parameters <code>tiffName</code> from strange
0752: * strings like it happens with erdas imageine.
0753: *
0754: * @param tiffName
0755: * is the {@link String} to clean up.
0756: * @return a cleaned up {@link String}.
0757: */
0758: private final static String cleanName(String tiffName) {
0759: // look fofr strange chars
0760: // $
0761: int index = tiffName.lastIndexOf('$');
0762: if (index != -1)
0763: tiffName = tiffName.substring(index + 1);
0764: // \n
0765: index = tiffName.lastIndexOf('\n');
0766: if (index != -1)
0767: tiffName = tiffName.substring(index + 1);
0768: // \r
0769: index = tiffName.lastIndexOf('\r');
0770: if (index != -1)
0771: tiffName = tiffName.substring(index + 1);
0772: return tiffName;
0773: }
0774:
0775: /**
0776: * Creates a {@link CartesianCS} for a {@link ProjectedCRS} given the
0777: * provided {@link Unit}.
0778: *
0779: * @todo conside caching this items
0780: * @param linearUnit
0781: * to be used for building this {@link CartesianCS}.
0782: * @return an instance of {@link CartesianCS} using the provided
0783: * {@link Unit},
0784: */
0785: private DefaultCartesianCS createProjectedCS(Unit linearUnit) {
0786: if (linearUnit == null)
0787: throw new NullPointerException(
0788: "Error when trying to create a PCS using this linear UoM ");
0789: if (!linearUnit.isCompatible(SI.METER))
0790: throw new IllegalArgumentException(
0791: "Error when trying to create a PCS using this linear UoM "
0792: + linearUnit.toString());
0793: return new DefaultCartesianCS(Vocabulary.formatInternational(
0794: VocabularyKeys.PROJECTED).toString(),
0795: new DefaultCoordinateSystemAxis(Vocabulary
0796: .formatInternational(VocabularyKeys.EASTING),
0797: "E", AxisDirection.EAST, linearUnit),
0798: new DefaultCoordinateSystemAxis(Vocabulary
0799: .formatInternational(VocabularyKeys.NORTHING),
0800: "N", AxisDirection.NORTH, linearUnit));
0801: }
0802:
0803: /**
0804: * Creating a prime meridian for the gcs we are creating at an higher level.
0805: * As usua this method tries to follow tthe geotiff specification.
0806: *
0807: * @param linearUnit
0808: * to use for building this {@link PrimeMeridian}.
0809: *
0810: * @return a {@link PrimeMeridian} built using the provided {@link Unit} and
0811: * the provided metadata.
0812: * @throws IOException
0813: */
0814: private PrimeMeridian createPrimeMeridian(
0815: final GeoTiffIIOMetadataDecoder metadata, Unit linearUnit)
0816: throws IOException {
0817: // look up the prime meridian:
0818: // + could be an EPSG code
0819: // + could be user defined
0820: // + not defined = greenwich
0821: final String pmCode = metadata
0822: .getGeoKey(GeoTiffGCSCodes.GeogPrimeMeridianGeoKey);
0823: PrimeMeridian pm = null;
0824:
0825: try {
0826: if (pmCode != null) {
0827: if (pmCode
0828: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
0829: try {
0830: final String name = metadata
0831: .getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey);
0832: final String pmValue = metadata
0833: .getGeoKey(GeoTiffGCSCodes.GeogPrimeMeridianLongGeoKey);
0834: final double pmNumeric = Double
0835: .parseDouble(pmValue);
0836: // is it Greenwich?
0837: if (pmNumeric == 0)
0838: return DefaultPrimeMeridian.GREENWICH;
0839: final Map props = new HashMap();
0840: props
0841: .put(
0842: "name",
0843: (name != null) ? name
0844: : "User Defined GEOTIFF Prime Meridian");
0845: pm = datumObjFactory.createPrimeMeridian(props,
0846: pmNumeric, linearUnit);
0847: } catch (NumberFormatException nfe) {
0848: final IOException io = new GeoTiffException(
0849: metadata,
0850: "Invalid user-defined prime meridian spec.",
0851: nfe);
0852:
0853: throw io;
0854: }
0855: } else {
0856: pm = this .allAuthoritiesFactory
0857: .createPrimeMeridian("EPSG:" + pmCode);
0858: }
0859: } else {
0860: pm = DefaultPrimeMeridian.GREENWICH;
0861: }
0862: } catch (FactoryException fe) {
0863: final IOException io = new GeoTiffException(metadata, fe
0864: .getLocalizedMessage(), fe);
0865: throw io;
0866: }
0867:
0868: return pm;
0869: }
0870:
0871: /**
0872: * Looks up the Geodetic Datum as specified in the GeoTIFFWritingUtilities
0873: * file. The geotools definition of the geodetic datum includes both an
0874: * ellipsoid and a prime meridian, but the code in the
0875: * GeoTIFFWritingUtilities file does NOT include the prime meridian, as it
0876: * is specified separately. This code currently does not support user
0877: * defined datum.
0878: *
0879: * @param unit
0880: * to use for building this {@link GeodeticDatum}.
0881: *
0882: * @return a {@link GeodeticDatum}.
0883: *
0884: * @throws IOException
0885: *
0886: * @throws GeoTiffException
0887: *
0888: */
0889: private GeodeticDatum createGeodeticDatum(final Unit unit,
0890: final GeoTiffIIOMetadataDecoder metadata)
0891: throws IOException {
0892: // lookup the datum (w/o PrimeMeridian), error if "user defined"
0893: GeodeticDatum datum = null;
0894: final String datumCode = metadata
0895: .getGeoKey(GeoTiffGCSCodes.GeogGeodeticDatumGeoKey);
0896:
0897: if (datumCode == null) {
0898: throw new GeoTiffException(
0899: metadata,
0900: "GeoTiffMetadata2CRSAdapter::createGeodeticDatum(Unit unit):A user defined Geographic Coordinate system must include a predefined datum!",
0901: null);
0902: }
0903:
0904: if (datumCode
0905: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
0906: /**
0907: *
0908: *
0909: * USER DEFINED DATUM
0910: *
0911: *
0912: *
0913: */
0914: // datum name
0915: final String datumName = (metadata
0916: .getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey) != null ? metadata
0917: .getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey)
0918: : "unnamed");
0919:
0920: // is it WGS84?
0921: if (datumName.trim().equalsIgnoreCase("WGS84"))
0922: return DefaultGeodeticDatum.WGS84;
0923:
0924: // ELLIPSOID
0925: final Ellipsoid ellipsoid = createEllipsoid(unit, metadata);
0926:
0927: // PRIME MERIDIAN
0928: // lookup the Prime Meridian.
0929: final PrimeMeridian primeMeridian = createPrimeMeridian(
0930: metadata, unit);
0931:
0932: // DATUM
0933: datum = new DefaultGeodeticDatum(datumName, ellipsoid,
0934: primeMeridian);
0935: } else {
0936: /**
0937: * NOT USER DEFINED DATUM
0938: */
0939:
0940: // we are going to use the provided EPSG code
0941: try {
0942: datum = (GeodeticDatum) (this .allAuthoritiesFactory
0943: .createDatum(new StringBuffer("EPSG:").append(
0944: datumCode).toString()));
0945: } catch (FactoryException fe) {
0946: final GeoTiffException ex = new GeoTiffException(
0947: metadata, fe.getLocalizedMessage(), fe);
0948:
0949: throw ex;
0950: } catch (ClassCastException cce) {
0951: final GeoTiffException ex = new GeoTiffException(
0952: metadata, cce.getLocalizedMessage(), cce);
0953: throw ex;
0954: }
0955: }
0956:
0957: return datum;
0958: }
0959:
0960: /**
0961: * Creating an ellipsoid following the GeoTiff spec.
0962: *
0963: * @param unit
0964: * to build this {@link Ellipsoid}..
0965: *
0966: * @return an {@link Ellipsoid}.
0967: *
0968: * @throws GeoTiffException
0969: */
0970: private Ellipsoid createEllipsoid(final Unit unit,
0971: final GeoTiffIIOMetadataDecoder metadata)
0972: throws GeoTiffException {
0973: // /////////////////////////////////////////////////////////////////////
0974: //
0975: // Getting the ellipsoid key in order to understand if we are working
0976: // against a common ellipsoid or a user defined one.
0977: //
0978: // /////////////////////////////////////////////////////////////////////
0979: // ellipsoid key
0980: final String ellipsoidKey = metadata
0981: .getGeoKey(GeoTiffGCSCodes.GeogEllipsoidGeoKey);
0982: String temp = null;
0983: // is the ellipsoid user defined?
0984: if (ellipsoidKey
0985: .equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
0986: // /////////////////////////////////////////////////////////////////////
0987: //
0988: // USER DEFINED ELLIPSOID
0989: //
0990: // /////////////////////////////////////////////////////////////////////
0991: String nameEllipsoid = metadata
0992: .getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey);
0993: if (nameEllipsoid == null)
0994: nameEllipsoid = "unnamed";
0995: // is it the default for WGS84?
0996: if (nameEllipsoid.trim().equalsIgnoreCase("WGS84"))
0997: return DefaultEllipsoid.WGS84;
0998:
0999: // //
1000: //
1001: // It is worth to point out that I ALWAYS use the inverse flattening
1002: // along with the semi-major axis to builde the Flattened Sphere.
1003: // This
1004: // has to be done in order to comply with the opposite process of
1005: // goin from CRS to metadata where this coupls is always used.
1006: //
1007: // //
1008: // getting temporary parameters
1009: temp = metadata
1010: .getGeoKey(GeoTiffGCSCodes.GeogSemiMajorAxisGeoKey);
1011: final double semiMajorAxis = (temp != null ? Double
1012: .parseDouble(temp) : Double.NaN);
1013: temp = metadata
1014: .getGeoKey(GeoTiffGCSCodes.GeogInvFlatteningGeoKey);
1015: final double inverseFlattening;
1016: if (temp != null) {
1017: inverseFlattening = (temp != null ? Double
1018: .parseDouble(temp) : Double.NaN);
1019: } else {
1020: temp = metadata
1021: .getGeoKey(GeoTiffGCSCodes.GeogSemiMinorAxisGeoKey);
1022: final double semiMinorAxis = (temp != null ? Double
1023: .parseDouble(temp) : Double.NaN);
1024: inverseFlattening = semiMajorAxis
1025: / (semiMajorAxis - semiMinorAxis);
1026:
1027: }
1028: // look for the Ellipsoid first then build the datum
1029: return DefaultEllipsoid.createFlattenedSphere(
1030: nameEllipsoid, semiMajorAxis, inverseFlattening,
1031: unit);
1032: }
1033:
1034: try {
1035: // /////////////////////////////////////////////////////////////////////
1036: //
1037: // EPSG STANDARD ELLIPSOID
1038: //
1039: // /////////////////////////////////////////////////////////////////////
1040: return this .allAuthoritiesFactory
1041: .createEllipsoid(new StringBuffer("EPSG:").append(
1042: ellipsoidKey).toString());
1043: } catch (FactoryException fe) {
1044: final GeoTiffException ex = new GeoTiffException(metadata,
1045: fe.getLocalizedMessage(), fe);
1046:
1047: throw ex;
1048: }
1049: }
1050:
1051: /**
1052: * The GeoTIFFWritingUtilities spec requires that a user defined GCS be
1053: * comprised of the following:
1054: *
1055: * <ul>
1056: * <li> a citation </li>
1057: * <li> a datum definition </li>
1058: * <li> a prime meridian definition (if not Greenwich) </li>
1059: * <li> an angular unit definition (if not degrees) </li>
1060: * </ul>
1061: *
1062: * @param metadata
1063: * to use fo building this {@link GeographicCRS}.
1064: * @param linearUnit
1065: * @param angularUnit
1066: * @return a {@link GeographicCRS}.
1067: *
1068: * @throws IOException
1069: */
1070: private GeographicCRS createUserDefinedGCS(
1071: final GeoTiffIIOMetadataDecoder metadata, Unit linearUnit,
1072: Unit angularUnit) throws IOException {
1073: // //
1074: //
1075: // coordinate reference system name (GeogCitationGeoKey)
1076: //
1077: // //
1078: String name = metadata
1079: .getGeoKey(GeoTiffGCSCodes.GeogCitationGeoKey);
1080: if (name == null)
1081: name = "unnamed";
1082:
1083: // lookup the Geodetic datum
1084: final GeodeticDatum datum = createGeodeticDatum(linearUnit,
1085: metadata);
1086:
1087: // coordinate reference system
1088: GeographicCRS gcs = null;
1089:
1090: try {
1091: // property map is reused
1092: final Map props = new HashMap();
1093: // make the user defined GCS from all the components...
1094: props.put("name", name);
1095: gcs = crsFactory.createGeographicCRS(props, datum,
1096: DefaultEllipsoidalCS.GEODETIC_2D
1097: .usingUnit(angularUnit));
1098: } catch (FactoryException fe) {
1099: final GeoTiffException io = new GeoTiffException(metadata,
1100: fe.getLocalizedMessage(), fe);
1101: throw io;
1102: }
1103:
1104: return gcs;
1105: }
1106:
1107: /**
1108: *
1109: * @todo we should somehow try to to support user defined coordinate
1110: * transformation even if for the moment is not so clear to me how we
1111: * could achieve that since if we have no clue about the coordinate
1112: * transform what we are supposed to do in order to build a
1113: * conversion, guess it? How could we pick up the parameters, should
1114: * look for all and then guess the right transformation?
1115: *
1116: * @param name
1117: * indicates the name for the projection.
1118: * @param metadata
1119: * to use fo building this {@link ParameterValueGroup}.
1120: * @return a {@link ParameterValueGroup} that can be used to trigger this
1121: * projection.
1122: * @throws IOException
1123: * @throws FactoryException
1124: */
1125: private ParameterValueGroup createUserDefinedProjectionParameter(
1126: String name, final GeoTiffIIOMetadataDecoder metadata)
1127: throws IOException, FactoryException {
1128: // //
1129: //
1130: // Trying to get the name for the coordinate transformation involved.
1131: //
1132: // ///
1133: final String coordTrans = metadata
1134: .getGeoKey(GeoTiffPCSCodes.ProjCoordTransGeoKey);
1135:
1136: // throw descriptive exception if ProjCoordTransGeoKey not defined
1137: if ((coordTrans == null)
1138: || coordTrans
1139: .equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
1140: throw new GeoTiffException(
1141: metadata,
1142: "GeoTiffMetadata2CRSAdapter::createUserDefinedProjectionParameter(String name):User defined projections must specify"
1143: + " coordinate transformation code in ProjCoordTransGeoKey",
1144: null);
1145: }
1146:
1147: // getting math transform factory
1148: return setParametersForProjection(name, coordTrans, metadata);
1149: }
1150:
1151: /**
1152: * Set the projection parameters basing its decision on the projection name.
1153: * I found a complete list of projections on the geotiff website at address
1154: * http://www.remotesensing.org/geotiff/proj_list.
1155: *
1156: * I had no time to implement support for all of them therefore you will not
1157: * find all of them. If you want go ahead and add support for the missing
1158: * ones. I have tested this code against some geotiff files you can find on
1159: * the geotiff website under the ftp sample directory but I can say that
1160: * they are a real mess! I am respecting the specification strictly while
1161: * many of those fiels do not! I could make this method trickier and use
1162: * workarounds in order to be less strict but I will not do this, since I
1163: * believe it is may lead us just on a very dangerous path.
1164: *
1165: *
1166: * @param name
1167: * @param metadata
1168: * to use fo building this {@link ParameterValueGroup}.
1169: * @param coordTrans
1170: * a {@link ParameterValueGroup} that can be used to trigger this
1171: * projection.
1172: *
1173: * @return
1174: * @throws GeoTiffException
1175: */
1176: private ParameterValueGroup setParametersForProjection(String name,
1177: final String coordTransCode,
1178: final GeoTiffIIOMetadataDecoder metadata)
1179: throws GeoTiffException {
1180: ParameterValueGroup parameters = null;
1181: try {
1182: int code = 0;
1183: if (coordTransCode != null)
1184: code = Integer.parseInt(coordTransCode);
1185: if (name == null)
1186: name = "unnamed";
1187: /**
1188: *
1189: * Transverse Mercator
1190: *
1191: */
1192: if (name.equalsIgnoreCase("transverse_mercator")
1193: || code == GeoTiffCoordinateTransformationsCodes.CT_TransverseMercator) {
1194: parameters = mtFactory
1195: .getDefaultParameters("transverse_mercator");
1196: parameters.parameter("central_meridian").setValue(
1197: getOriginLong(metadata));
1198: parameters.parameter("latitude_of_origin").setValue(
1199: getOriginLat(metadata));
1200: parameters
1201: .parameter("scale_factor")
1202: .setValue(
1203: this
1204: .getGeoKeyAsDouble(
1205: GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey,
1206: metadata));
1207: parameters.parameter("false_easting").setValue(
1208: getFalseEasting(metadata));
1209: parameters.parameter("false_northing").setValue(
1210: getFalseNorthing(metadata));
1211:
1212: return parameters;
1213: }
1214:
1215: /**
1216: *
1217: * Equidistant Cylindrical - Plate Caree - Equirectangular
1218: *
1219: */
1220: if (name.equalsIgnoreCase("Equidistant_Cylindrical")
1221: || name.equalsIgnoreCase("Plate_Carree")
1222: || name.equalsIgnoreCase("Equidistant_Cylindrical")
1223: || code == GeoTiffCoordinateTransformationsCodes.CT_Equirectangular) {
1224: parameters = mtFactory
1225: .getDefaultParameters("Equidistant_Cylindrical");
1226: parameters.parameter("latitude_of_origin").setValue(
1227: getOriginLat(metadata));
1228: parameters.parameter("central_meridian").setValue(
1229: getOriginLong(metadata));
1230:
1231: parameters.parameter("false_easting").setValue(
1232: getFalseEasting(metadata));
1233: parameters.parameter("false_northing").setValue(
1234: getFalseNorthing(metadata));
1235:
1236: return parameters;
1237: }
1238: /**
1239: *
1240: * Mercator_1SP
1241: *
1242: */
1243: if (name.equalsIgnoreCase("mercator_1SP")
1244: || name.equalsIgnoreCase("Mercator_2SP")
1245: || code == GeoTiffCoordinateTransformationsCodes.CT_Mercator) {
1246: parameters = mtFactory
1247: .getDefaultParameters("Mercator_1SP");
1248: parameters.parameter("central_meridian").setValue(
1249: getOriginLong(metadata));
1250: // parameters
1251: // .parameter("latitude_of_origin")
1252: // .setValue(
1253: // this
1254: // .getGeoKeyAsDouble(GeoTiffPCSCodes.ProjNatOriginLatGeoKey));
1255: parameters
1256: .parameter("scale_factor")
1257: .setValue(
1258: this
1259: .getGeoKeyAsDouble(
1260: GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey,
1261: metadata));
1262: parameters.parameter("false_easting").setValue(
1263: getFalseEasting(metadata));
1264: parameters.parameter("false_northing").setValue(
1265: getFalseNorthing(metadata));
1266:
1267: return parameters;
1268: }
1269:
1270: /**
1271: *
1272: * Mercator_2Sp
1273: *
1274: */
1275: if (name.equalsIgnoreCase("lambert_conformal_conic_1SP")
1276: || code == GeoTiffCoordinateTransformationsCodes.CT_LambertConfConic_Helmert) {
1277: parameters = mtFactory
1278: .getDefaultParameters("lambert_conformal_conic_1SP");
1279: parameters.parameter("central_meridian").setValue(
1280: getOriginLong(metadata));
1281: parameters.parameter("latitude_of_origin").setValue(
1282: getOriginLat(metadata));
1283: parameters
1284: .parameter("scale_factor")
1285: .setValue(
1286: this
1287: .getGeoKeyAsDouble(
1288: GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey,
1289: metadata));
1290: parameters.parameter("false_easting").setValue(
1291: getFalseEasting(metadata));
1292: parameters.parameter("false_northing").setValue(
1293: getFalseNorthing(metadata));
1294:
1295: return parameters;
1296: }
1297:
1298: /**
1299: *
1300: * LAMBERT_CONFORMAT_CONIC_2SP
1301: *
1302: */
1303: if (name.equalsIgnoreCase("lambert_conformal_conic_2SP")
1304: || name
1305: .equalsIgnoreCase("lambert_conformal_conic_2SP_Belgium")
1306: || code == GeoTiffCoordinateTransformationsCodes.CT_LambertConfConic_2SP) {
1307: parameters = mtFactory
1308: .getDefaultParameters("lambert_conformal_conic_2SP");
1309: parameters.parameter("central_meridian").setValue(
1310: getOriginLong(metadata));
1311: parameters.parameter("latitude_of_origin").setValue(
1312: getOriginLat(metadata));
1313: parameters.parameter("standard_parallel_1").setValue(
1314: this .getGeoKeyAsDouble(
1315: GeoTiffPCSCodes.ProjStdParallel1GeoKey,
1316: metadata));
1317: parameters.parameter("standard_parallel_2").setValue(
1318: this .getGeoKeyAsDouble(
1319: GeoTiffPCSCodes.ProjStdParallel2GeoKey,
1320: metadata));
1321: parameters.parameter("false_easting").setValue(
1322: getFalseEasting(metadata));
1323: parameters.parameter("false_northing").setValue(
1324: getFalseNorthing(metadata));
1325:
1326: return parameters;
1327: }
1328:
1329: /**
1330: *
1331: * Krovak
1332: *
1333: */
1334: if (name.equalsIgnoreCase("Krovak")) {
1335: parameters = mtFactory.getDefaultParameters("Krovak");
1336: parameters.parameter("longitude_of_center").setValue(
1337: getOriginLong(metadata));
1338: parameters.parameter("latitude_of_center").setValue(
1339: getOriginLat(metadata));
1340: parameters.parameter("azimuth").setValue(
1341: this .getGeoKeyAsDouble(
1342: GeoTiffPCSCodes.ProjStdParallel1GeoKey,
1343: metadata));
1344: parameters
1345: .parameter("pseudo_standard_parallel_1")
1346: .setValue(
1347: this
1348: .getGeoKeyAsDouble(
1349: GeoTiffPCSCodes.ProjStdParallel2GeoKey,
1350: metadata));
1351: parameters.parameter("scale_factor").setValue(
1352: getFalseEasting(metadata));
1353:
1354: return parameters;
1355: }
1356:
1357: // if (name.equalsIgnoreCase("equidistant_conic")
1358: // || code == GeoTiffMetadata2CRSAdapter.CT_EquidistantConic) {
1359: // parameters = mtFactory
1360: // .getDefaultParameters("equidistant_conic");
1361: // parameters.parameter("central_meridian").setValue(
1362: // getOriginLong());
1363: // parameters.parameter("latitude_of_origin").setValue(
1364: // getOriginLat());
1365: // parameters
1366: // .parameter("standard_parallel_1")
1367: // .setValue(
1368: // this
1369: // .getGeoKeyAsDouble(GeoTiffIIOMetadataDecoder.ProjStdParallel1GeoKey));
1370: // parameters
1371: // .parameter("standard_parallel_2")
1372: // .setValue(
1373: // this
1374: // .getGeoKeyAsDouble(GeoTiffIIOMetadataDecoder.ProjStdParallel2GeoKey));
1375: // parameters.parameter("false_easting").setValue(
1376: // getFalseEasting());
1377: // parameters.parameter("false_northing").setValue(
1378: // getFalseNorthing());
1379: //
1380: // return parameters;
1381: // }
1382:
1383: /**
1384: *
1385: * STEREOGRAPHIC
1386: *
1387: */
1388: if (name.equalsIgnoreCase("stereographic")
1389: || code == GeoTiffCoordinateTransformationsCodes.CT_Stereographic) {
1390: parameters = mtFactory
1391: .getDefaultParameters("stereographic");
1392: parameters.parameter("central_meridian").setValue(
1393: this .getOriginLong(metadata));
1394:
1395: parameters.parameter("latitude_of_origin").setValue(
1396:
1397: this .getOriginLat(metadata));
1398: parameters
1399: .parameter("scale_factor")
1400: .setValue(
1401: this
1402: .getGeoKeyAsDouble(
1403: GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey,
1404: metadata));
1405: parameters.parameter("false_easting").setValue(
1406: getFalseEasting(metadata));
1407: parameters.parameter("false_northing").setValue(
1408: getFalseNorthing(metadata));
1409:
1410: return parameters;
1411: }
1412:
1413: /**
1414: *
1415: * POLAR_STEREOGRAPHIC.
1416: *
1417: */
1418: if (name.equalsIgnoreCase("polar_stereographic")
1419: || code == GeoTiffCoordinateTransformationsCodes.CT_PolarStereographic) {
1420: parameters = mtFactory
1421: .getDefaultParameters("polar_stereographic");
1422:
1423: parameters.parameter("latitude_of_origin").setValue(
1424: this .getOriginLat(metadata));
1425: parameters
1426: .parameter("scale_factor")
1427: .setValue(
1428: this
1429: .getGeoKeyAsDouble(
1430: GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey,
1431: metadata));
1432: parameters.parameter("false_easting").setValue(
1433: getFalseEasting(metadata));
1434: parameters.parameter("false_northing").setValue(
1435: getFalseNorthing(metadata));
1436: parameters.parameter("central_meridian").setValue(
1437: getOriginLong(metadata));
1438:
1439: return parameters;
1440: }
1441:
1442: /**
1443: *
1444: * OBLIQUE_MERCATOR.
1445: *
1446: */
1447: if (name.equalsIgnoreCase("oblique_mercator")
1448: || name.equalsIgnoreCase("hotine_oblique_mercator")
1449: || code == GeoTiffCoordinateTransformationsCodes.CT_ObliqueMercator) {
1450: parameters = mtFactory
1451: .getDefaultParameters("oblique_mercator");
1452:
1453: parameters.parameter("scale_factor").setValue(
1454: getScaleFactor(metadata));
1455: parameters.parameter("azimuth").setValue(
1456: this .getGeoKeyAsDouble(
1457: GeoTiffPCSCodes.ProjAzimuthAngleGeoKey,
1458: metadata));
1459: parameters.parameter("false_easting").setValue(
1460: getFalseEasting(metadata));
1461: parameters.parameter("false_northing").setValue(
1462: getFalseNorthing(metadata));
1463: parameters.parameter("longitude_of_center").setValue(
1464: getOriginLong(metadata));
1465: parameters.parameter("latitude_of_center").setValue(
1466: getOriginLat(metadata));
1467: return parameters;
1468: }
1469:
1470: /**
1471: *
1472: * albers_Conic_Equal_Area
1473: *
1474: */
1475: if (name.equalsIgnoreCase("albers_Conic_Equal_Area")
1476: || code == GeoTiffCoordinateTransformationsCodes.CT_AlbersEqualArea) {
1477: parameters = mtFactory
1478: .getDefaultParameters("Albers_Conic_Equal_Area");
1479: parameters.parameter("standard_parallel_1").setValue(
1480: this .getGeoKeyAsDouble(
1481: GeoTiffPCSCodes.ProjStdParallel1GeoKey,
1482: metadata));
1483: parameters.parameter("standard_parallel_2").setValue(
1484: this .getGeoKeyAsDouble(
1485: GeoTiffPCSCodes.ProjStdParallel2GeoKey,
1486: metadata));
1487: parameters.parameter("latitude_of_center").setValue(
1488: getOriginLat(metadata));
1489: parameters.parameter("longitude_of_center").setValue(
1490: getOriginLong(metadata));
1491: parameters.parameter("false_easting").setValue(
1492: getFalseEasting(metadata));
1493: parameters.parameter("false_northing").setValue(
1494: getFalseNorthing(metadata));
1495:
1496: return parameters;
1497: }
1498:
1499: /**
1500: *
1501: * Orthographic
1502: *
1503: */
1504: if (name.equalsIgnoreCase("Orthographic")
1505: || code == GeoTiffCoordinateTransformationsCodes.CT_Orthographic) {
1506: parameters = mtFactory
1507: .getDefaultParameters("orthographic");
1508:
1509: parameters.parameter("latitude_of_origin").setValue(
1510: getOriginLat(metadata));
1511: parameters.parameter("longitude_of_origin").setValue(
1512: getOriginLong(metadata));
1513: parameters.parameter("false_easting").setValue(
1514: getFalseEasting(metadata));
1515: parameters.parameter("false_northing").setValue(
1516: getFalseNorthing(metadata));
1517:
1518: return parameters;
1519: }
1520:
1521: /**
1522: *
1523: * New Zealand Map Grid
1524: *
1525: */
1526: if (name.equalsIgnoreCase("New_Zealand_Map_Grid")
1527: || code == GeoTiffCoordinateTransformationsCodes.CT_NewZealandMapGrid) {
1528: parameters = mtFactory
1529: .getDefaultParameters("New_Zealand_Map_Grid");
1530:
1531: parameters.parameter("semi_major").setValue(
1532: getOriginLat(metadata));
1533: parameters.parameter("semi_minor").setValue(
1534: getOriginLong(metadata));
1535: parameters.parameter("latitude_of_origin").setValue(
1536: this .getOriginLat(metadata));
1537: parameters.parameter("central_meridian").setValue(
1538: getOriginLong(metadata));
1539: parameters.parameter("false_easting").setValue(
1540: getFalseEasting(metadata));
1541: parameters.parameter("false_northing").setValue(
1542: getFalseNorthing(metadata));
1543:
1544: return parameters;
1545: }
1546:
1547: } catch (NoSuchIdentifierException e) {
1548: throw new GeoTiffException(metadata, e
1549: .getLocalizedMessage(), e);
1550: }
1551:
1552: return parameters;
1553: }
1554:
1555: /**
1556: * Retrieve the scale factor parameter as defined by the geotiff
1557: * specification.
1558: *
1559: * @param metadata
1560: * to use for searching the scale factor.
1561: * @return the scale factor
1562: */
1563: private double getScaleFactor(
1564: final GeoTiffIIOMetadataDecoder metadata) {
1565: String scale = metadata
1566: .getGeoKey(GeoTiffPCSCodes.ProjScaleAtCenterGeoKey);
1567: if (scale == null)
1568: scale = metadata
1569: .getGeoKey(GeoTiffPCSCodes.ProjScaleAtNatOriginGeoKey);
1570: if (scale == null)
1571: return 0.0;
1572: return Double.parseDouble(scale);
1573: }
1574:
1575: /**
1576: * Getting the false easting with a minimum of tolerance with respect to the
1577: * parameters name. I saw that ofetn people use the wrong geokey to store
1578: * the false eassting, we cannot be too picky we need to get going pretty
1579: * smoouthly.
1580: *
1581: * @param metadata
1582: * to use for searching the false easting.
1583: * @return double False easting.
1584: */
1585: private double getFalseEasting(
1586: final GeoTiffIIOMetadataDecoder metadata) {
1587: String easting = metadata
1588: .getGeoKey(GeoTiffPCSCodes.ProjFalseEastingGeoKey);
1589: if (easting == null)
1590: easting = metadata
1591: .getGeoKey(GeoTiffPCSCodes.ProjFalseOriginEastingGeoKey);
1592: if (easting == null)
1593: return 0.0;
1594: return Double.parseDouble(easting);
1595:
1596: }
1597:
1598: /**
1599: * Getting the false northing with a minimum of tolerance with respect to
1600: * the parameters name. I saw that ofetn people use the wrong geokey to
1601: * store the false eassting, we cannot be too picky we need to get going
1602: * pretty smoouthly.
1603: *
1604: * @param metadata
1605: * to use for searching the false northing.
1606: * @return double False northing.
1607: */
1608: private double getFalseNorthing(
1609: final GeoTiffIIOMetadataDecoder metadata) {
1610: String northing = metadata
1611: .getGeoKey(GeoTiffPCSCodes.ProjFalseNorthingGeoKey);
1612: if (northing == null)
1613: northing = metadata
1614: .getGeoKey(GeoTiffPCSCodes.ProjFalseOriginNorthingGeoKey);
1615: if (northing == null)
1616: return 0.0;
1617: return Double.parseDouble(northing);
1618:
1619: }
1620:
1621: /**
1622: * Getting the origin long with a minimum of tolerance with respect to the
1623: * parameters name. I saw that ofetn people use the wrong geokey to store
1624: * the false eassting, we cannot be too picky we need to get going pretty
1625: * smoouthly.
1626: *
1627: * @param metadata
1628: * to use for searching the originating longitude.
1629: * @return double origin longitude.
1630: */
1631: private double getOriginLong(
1632: final GeoTiffIIOMetadataDecoder metadata) {
1633: String origin = metadata
1634: .getGeoKey(GeoTiffPCSCodes.ProjCenterLongGeoKey);
1635: if (origin == null)
1636: origin = metadata
1637: .getGeoKey(GeoTiffPCSCodes.ProjNatOriginLongGeoKey);
1638: if (origin == null)
1639: origin = metadata
1640: .getGeoKey(GeoTiffPCSCodes.ProjFalseOriginLongGeoKey);
1641: if (origin == null)
1642: origin = metadata
1643: .getGeoKey(GeoTiffPCSCodes.ProjFalseNorthingGeoKey);
1644: if (origin == null)
1645: return 0.0;
1646: return Double.parseDouble(origin);
1647: }
1648:
1649: /**
1650: * Getting the origin lat with a minimum of tolerance with respect to the
1651: * parameters name. I saw that ofetn people use the wrong geokey to store
1652: * the false eassting, we cannot be too picky we need to get going pretty
1653: * smoouthly.
1654: *
1655: * @param metadata
1656: * to use for searching the origin latitude.
1657: * @return double origin latitude.
1658: */
1659: private double getOriginLat(final GeoTiffIIOMetadataDecoder metadata) {
1660: String origin = metadata
1661: .getGeoKey(GeoTiffPCSCodes.ProjCenterLatGeoKey);
1662: if (origin == null)
1663: origin = metadata
1664: .getGeoKey(GeoTiffPCSCodes.ProjNatOriginLatGeoKey);
1665: if (origin == null)
1666: origin = metadata
1667: .getGeoKey(GeoTiffPCSCodes.ProjFalseOriginLatGeoKey);
1668: if (origin == null)
1669: return 0.0;
1670:
1671: return Double.parseDouble(origin);
1672: }
1673:
1674: /**
1675: * This code creates an <code>javax.Units.Unit</code> object out of the
1676: * <code>ProjLinearUnitsGeoKey</code> and the
1677: * <code>ProjLinearUnitSizeGeoKey</code>. The unit may either be
1678: * specified as a standard EPSG recognized unit, or may be user defined.
1679: *
1680: * @param key
1681: *
1682: * @param userDefinedKey
1683: *
1684: * @param base
1685: *
1686: * @param def
1687: *
1688: *
1689: * @return <code>Unit</code> object representative of the tags in the
1690: * file.
1691: *
1692: * @throws IOException
1693: * if the<code>ProjLinearUnitsGeoKey</code> is not specified
1694: * or if unit is user defined and
1695: * <code>ProjLinearUnitSizeGeoKey</code> is either not defined
1696: * or does not contain a number.
1697: */
1698: private Unit createUnit(int key, int userDefinedKey, Unit base,
1699: Unit def, final GeoTiffIIOMetadataDecoder metadata)
1700: throws IOException {
1701: final String unitCode = metadata.getGeoKey(key);
1702:
1703: // //
1704: //
1705: // if not defined, return the default unit of measure
1706: //
1707: // //
1708: if (unitCode == null) {
1709: return def;
1710: }
1711: // //
1712: //
1713: // if specified, retrieve the appropriate unit code. There are two case
1714: // to keep into account, first case is when the unit of measure has an
1715: // EPSG code, alternatively it can be instantiated as a conversion from
1716: // meter.
1717: //
1718: // //
1719: if (unitCode
1720: .equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
1721: try {
1722: final String unitSize = metadata
1723: .getGeoKey(userDefinedKey);
1724:
1725: // throw descriptive exception if required key is not there.
1726: if (unitSize == null) {
1727: throw new GeoTiffException(
1728: metadata,
1729: new StringBuffer(
1730: "GeoTiffMetadata2CRSAdapter::createUnit:Must define unit length when using a user ")
1731: .append("defined unit").toString(),
1732: null);
1733: }
1734:
1735: double sz = Double.parseDouble(unitSize);
1736: return base.multiply(sz);
1737: } catch (NumberFormatException nfe) {
1738: final IOException ioe = new GeoTiffException(metadata,
1739: nfe.getLocalizedMessage(), nfe);
1740: throw ioe;
1741: }
1742: } else {
1743: try {
1744: // using epsg code for this unit
1745: return (Unit) this .allAuthoritiesFactory
1746: .createUnit(new StringBuffer("EPSG:").append(
1747: unitCode).toString());
1748: } catch (FactoryException fe) {
1749: final IOException io = new GeoTiffException(metadata,
1750: fe.getLocalizedMessage(), fe);
1751: throw io;
1752: }
1753: }
1754:
1755: }
1756:
1757: /**
1758: * Returns an object from the pool for the specified code. If the object was
1759: * retained as a {@linkplain Reference weak reference}, the
1760: * {@link Reference#get referent} is returned.
1761: *
1762: * @param key
1763: * to use for doing the lookup inside the pool.
1764: * @return a cached instance of a {@link GeoTiffMetadata2CRSAdapter}
1765: * suitable for the provided key.
1766: * @todo Consider logging a message here to the finer or finest level.
1767: */
1768: public static Object get(final Object key) {
1769: synchronized (pool) {
1770:
1771: Object object = pool.get(key);
1772: if (object == null) {
1773: object = new GeoTiffMetadata2CRSAdapter((Hints) key);
1774: put(key, object);
1775: }
1776: return object;
1777: }
1778: }
1779:
1780: /**
1781: * Put an element in the pool. This method is invoked everytime a
1782: * {@code createFoo(...)} method is invoked, even if an object was already
1783: * in the pool for the given code, for the following reasons: 1) Replaces
1784: * weak reference by strong reference (if applicable) and 2) Alters the
1785: * linked hash set order, so that this object is declared as the last one
1786: * used.
1787: */
1788: private static void put(final Object key, final Object object) {
1789: synchronized (pool) {
1790: pool.put(key, object);
1791:
1792: }
1793: }
1794:
1795: /**
1796: * Returns the {@link Hints} for this {@link GeoTiffMetadata2CRSAdapter}.
1797: *
1798: * @return {@link Hints} for this {@link GeoTiffMetadata2CRSAdapter}.
1799: */
1800: public Hints getHints() {
1801: return hints;
1802: }
1803: }
|