/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2016, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
/*
* 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.geotoolkit.metadata.geotiff;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.measure.Unit;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.DatumFactory;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.operation.Conversion;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.crs.GeodeticCRS;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.util.FactoryException;
import org.apache.sis.measure.Units;
import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.referencing.provider.AlbersEqualArea;
import org.apache.sis.internal.referencing.provider.Equirectangular;
import org.apache.sis.internal.referencing.provider.Mercator1SP;
import org.apache.sis.internal.referencing.provider.Mercator2SP;
import org.apache.sis.internal.referencing.provider.LambertConformal1SP;
import org.apache.sis.internal.referencing.provider.LambertConformal2SP;
import org.apache.sis.internal.referencing.provider.ObliqueStereographic;
import org.apache.sis.internal.referencing.provider.PolarStereographicA;
import org.apache.sis.internal.referencing.provider.PolarStereographicB;
import org.apache.sis.internal.referencing.provider.PolarStereographicC;
import org.apache.sis.internal.referencing.provider.TransverseMercator;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.measure.Latitude;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.cs.DefaultCartesianCS;
import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.crs.DefaultGeographicCRS;
import org.apache.sis.referencing.datum.DefaultEllipsoid;
import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.referencing.factory.ReferencingFactoryContainer;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.image.io.metadata.ReferencingBuilder;
import org.geotoolkit.image.io.metadata.SpatialMetadata;
import org.geotoolkit.referencing.cs.PredefinedCS;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.referencing.operation.DefiningConversion;
import org.geotoolkit.referencing.operation.provider.Krovak;
import org.geotoolkit.referencing.operation.provider.LambertAzimuthalEqualArea;
import org.geotoolkit.referencing.operation.provider.NewZealandMapGrid;
import org.geotoolkit.referencing.operation.provider.ObliqueMercator;
import org.geotoolkit.referencing.operation.provider.Orthographic;
import org.geotoolkit.referencing.operation.provider.Stereographic;
import org.geotoolkit.resources.Vocabulary;
import org.apache.sis.referencing.crs.AbstractCRS;
import org.apache.sis.referencing.cs.AxesConvention;
import static org.geotoolkit.metadata.geotiff.GeoTiffConstants.*;
import static org.geotoolkit.metadata.geotiff.GeoTiffMetaDataReader.*;
/**
* TODO this class must be rewritten, redundant code is used here and all geotiff
* crs codes are not supported.
*
* @author Bryce Nordgren / USDA Forest Service
* @author Simone Giannecchini
* @author Daniele Romagnoli
* @author Johann Sorel (Geomatys)
* @module
*/
final class GeoTiffCRSReader {
/**
* Cached {@link MathTransformFactory} for building {@link MathTransform}
* objects.
*/
private final static MathTransformFactory mtFactory = FactoryFinder.getMathTransformFactory(null);
/**
* Logger to diffuse no blocking error message.
*/
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.metadata.geotiff");
/** EPSG factories for various purposes. */
private final GeodeticAuthorityFactory epsgFactory;
/** EPSG Factory for creating {@link GeodeticDatum}objects. */
private final DatumFactory datumObjFactory;
/** CRS Factory for creating CRS objects. */
private final CRSFactory crsFactory;
/** Group Factory for creating {@link ProjectedCRS} objects. */
private final ReferencingFactoryContainer factories;
public GeoTiffCRSReader() {
try {
epsgFactory = (GeodeticAuthorityFactory) CRS.getAuthorityFactory("EPSG");
} catch (FactoryException e) {
throw new IllegalStateException(e);
}
// factory = new ThreadedEpsgFactory(hints);
datumObjFactory = FactoryFinder.getDatumFactory(null);
crsFactory = FactoryFinder.getCRSFactory(null);
factories = ReferencingFactoryContainer.instance(null);
}
/**
* Fill the CRS metadatas with the values available in the geotiff tags.
*/
public void fillCRSMetaDatas(final SpatialMetadata metadatas, final ValueMap entries) throws IOException, FactoryException{
final Object type = entries.get(GTModelTypeGeoKey);
if (type == null) {
LOGGER.log(Level.FINE, "GTModelTypeGeoKey (Tiff CRS metadatas information) is not defined in tags.");
return;
}
switch( (Integer)type ){
case ModelTypeProjected: fillProjectedCRSMetaDatas(metadatas,entries);break;
case ModelTypeGeographic: fillGeographicCRSMetaDatas(metadatas,entries);break;
case ModelTypeGeocentric: fillGeocentricCRSMetaDatas(metadatas,entries);break;
default: throw new IOException("Unexpected crs model type : "+(Integer)type);
}
}
/**
* Fill a projected CRS metadatas with the values available in the geotiff tags.
*/
private void fillProjectedCRSMetaDatas(final SpatialMetadata metadatas, final ValueMap entries) throws IOException, FactoryException {
final ReferencingBuilder rb = new ReferencingBuilder(metadatas);
final CoordinateReferenceSystem crs;
// //
// Get the projection reference system code in case we have one by
// lookig for the ProjectedCSTypeGeoKey key
// //
String tempCode = entries.getAsString(ProjectedCSTypeGeoKey);
if (tempCode == null) {
tempCode = "unnamed";
}
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(ProjLinearUnitsGeoKey,
ProjLinearUnitSizeGeoKey, Units.METRE,
Units.METRE, entries);
} catch (IOException 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(GTUserDefinedGeoKey_String)) {
crs = createUserDefinedPCS(entries, linearUnit);
}else{
// //
// 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.
//TODO : jsorel : are we sure of this ? always long/lat order ?
final ProjectedCRS pcrs = (ProjectedCRS) AbstractCRS.castOrCopy(CRS.forCode(projCode.toString())).forConvention(AxesConvention.RIGHT_HANDED);
// //
// We have nothing to do with the unit of measure
// //
if (linearUnit == null || linearUnit.equals(pcrs.getCoordinateSystem().getAxis(0).getUnit())) {
crs = pcrs;
}else{
// //
// Creating anew projected CRS
// //
crs = new DefaultProjectedCRS(
java.util.Collections.singletonMap("name", IdentifiedObjects.getName(pcrs, new DefaultCitation("EPSG"))),
(GeographicCRS) pcrs.getBaseCRS(),
pcrs.getConversionFromBase(),
createProjectedCS(linearUnit));
}
} catch (FactoryException fe) {
throw new IOException(fe);
}
}
rb.setCoordinateReferenceSystem(crs);
}
/**
* Fill a geographic CRS metadatas with the values available in the geotiff tags.
*/
private void fillGeographicCRSMetaDatas(final SpatialMetadata metadatas, final ValueMap entries) throws IOException, FactoryException{
GeographicCRS gcs = null;
// ////////////////////////////////////////////////////////////////////
// Get the crs code
// ////////////////////////////////////////////////////////////////////
final String tempCode = entries.getAsString(GeographicTypeGeoKey);
// lookup the angular units used in this geotiff image
Unit angularUnit = null;
try {
angularUnit = createUnit(GeogAngularUnitsGeoKey,
GeogAngularUnitSizeGeoKey, Units.RADIAN,
Units.DEGREE, entries);
} catch (IOException e) {
angularUnit = null;
}
// linear unit
Unit linearUnit = null;
try {
linearUnit = createUnit(GeogLinearUnitsGeoKey,
GeogLinearUnitSizeGeoKey, Units.METRE,
Units.METRE, entries);
} catch (IOException 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(entries, 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:");
}
//TODO : jsorel : are we sure of this ? always long/lat order ?
gcs = (GeographicCRS) AbstractCRS.castOrCopy(CRS.forCode(geogCode.toString())).forConvention(AxesConvention.RIGHT_HANDED);
if (angularUnit != null
&& !angularUnit.equals(gcs.getCoordinateSystem().getAxis(0).getUnit())) {
// //
// Create a user-defined GCRS using the provided angular
// unit.
// //
gcs = new DefaultGeographicCRS(name(IdentifiedObjects.getName(gcs, new DefaultCitation("EPSG"))),
(GeodeticDatum) gcs.getDatum(),
PredefinedCS.usingUnit(CommonCRS.defaultGeographic().getCoordinateSystem(), angularUnit));
}
} catch (FactoryException ex) {
throw new IOException(ex);
}
}
ReferencingBuilder rb = new ReferencingBuilder(metadatas);
rb.setCoordinateReferenceSystem(gcs);
}
/**
* Fill a geocentric CRS metadatas with the values available in the geotiff tags.
*/
private void fillGeocentricCRSMetaDatas(final SpatialMetadata metadatas, final ValueMap entries) throws IOException{
throw new IOException("Not done yet.");
}
/**
* 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 ValueMap metadata, final Unit linearUnit,
final Unit angularUnit) throws IOException, FactoryException {
// //
// coordinate reference system name (GeogCitationGeoKey)
// //
String name = metadata.getAsString(GeogCitationGeoKey);
if (name == null) {
name = "unnamed";
}
// lookup the Geodetic datum
final GeodeticDatum datum = createGeodeticDatum(linearUnit, metadata);
// coordinate reference system
GeographicCRS gcs = null;
// property map is reused
final Map props = new HashMap();
// make the user defined GCS from all the components...
props.put("name", name);
return crsFactory.createGeographicCRS(props, datum,
PredefinedCS.usingUnit(CommonCRS.defaultGeographic().getCoordinateSystem(), angularUnit));
}
/**
* 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 ValueMap metadata, final 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.getAsString(PCSCitationGeoKey);
if (projectedCrsName == null) {
projectedCrsName = "unnamed".intern();
}
// /////////////////////////////////////////////////////////////////////
// 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.getAsString(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.getAsString(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 IOException("Projection is not supported.");
}
projection = new DefiningConversion(projectionName, parameters);
} else {
parameters = null;
projection = (Conversion) epsgFactory.createCoordinateOperation(String.valueOf(projCode));
}
// /////////////////////////////////////////////////////////////////////
// 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) {
CartesianCS cs = PredefinedCS.PROJECTED;
if(linearUnit != null && !linearUnit.equals(Units.METRE)){
cs = PredefinedCS.usingUnit(cs, linearUnit);
}
return this.factories.getCRSFactory().createProjectedCRS(
Collections.singletonMap("name", projectedCrsName),
gcs, projection, cs);
}
// standard projection
if (linearUnit != null && !linearUnit.equals(Units.METRE)) {
return factories.getCRSFactory().createProjectedCRS(Collections.singletonMap(
"name", projectedCrsName), gcs, projection,
PredefinedCS.usingUnit(PredefinedCS.PROJECTED, linearUnit));
}
return factories.getCRSFactory().createProjectedCRS(Collections.singletonMap("name",
projectedCrsName), gcs, projection,
PredefinedCS.PROJECTED);
}
////////////////////////////////////////////////////////////////////////////
// UTILS ///////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* 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 ValueMap metadata) {
String scale = metadata.getAsString(ProjScaleAtCenterGeoKey);
if (isZero(scale)) {
scale = metadata.getAsString(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 ValueMap metadata) {
String easting = metadata.getAsString(ProjFalseEastingGeoKey);
if (isZero(easting)) {
easting = metadata.getAsString(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 ValueMap metadata) {
String northing = metadata.getAsString(ProjFalseNorthingGeoKey);
if (isZero(northing)) {
northing = metadata.getAsString(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 ValueMap metadata) {
String origin = metadata.getAsString(ProjCenterLongGeoKey);
if (isZero(origin)) {
origin = metadata.getAsString(ProjNatOriginLongGeoKey);
}
if (isZero(origin)) {
origin = metadata.getAsString(ProjFalseOriginLongGeoKey);
}
if (isZero(origin)) {
origin = metadata.getAsString(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 ValueMap metadata) {
String origin = metadata.getAsString(ProjCenterLatGeoKey);
if (isZero(origin)) {
origin = metadata.getAsString(ProjNatOriginLatGeoKey);
}
if (isZero(origin)) {
origin = metadata.getAsString(ProjFalseOriginLatGeoKey);
}
if (origin == null) {
return 0.0;
}
return Double.parseDouble(origin);
}
/**
* Check if given string is null, empty or equals to zero.
* Geotiff tags are often badly defined, this ensure we skip "0.0" tags in the
* hope another tag will define a proper value.
* In the worst case if no valid tags are found the 0.0 value will be used anyway.
*
* @param code
* @return true if value is zero
*/
private static boolean isZero(String code){
if(code==null ||code.isEmpty()) return true;
try{
final double d = Double.parseDouble(code);
return d==0.0;
}catch(NumberFormatException ex){
return true;
}
}
/**
* @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(
final String name, final ValueMap metadata)
throws IOException, FactoryException {
// //
//
// Trying to get the name for the coordinate transformation involved.
//
// ///
final String coordTrans = metadata.getAsString(ProjCoordTransGeoKey);
// throw descriptive exception if ProjCoordTransGeoKey not defined
if ((coordTrans == null)
|| coordTrans.equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
throw new IOException(
"User defined projections must specify coordinate transformation code in ProjCoordTransGeoKey");
}
// 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 ValueMap metadata) throws IOException {
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 == CT_TransverseMercator) {
parameters = mtFactory.getDefaultParameters(code(new TransverseMercator().getParameters())); // TODO: avoid creation of temporary object.
parameters.parameter(code(TransverseMercator.LONGITUDE_OF_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(TransverseMercator.LATITUDE_OF_ORIGIN)).setValue(getOriginLat(metadata));
parameters.parameter(code(TransverseMercator.SCALE_FACTOR)).setValue(metadata.getAsDouble(ProjScaleAtNatOriginGeoKey));
parameters.parameter(code(TransverseMercator.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(TransverseMercator.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 == CT_Equirectangular) {
parameters = mtFactory.getDefaultParameters("Equirectangular");
parameters.parameter(code(Equirectangular.STANDARD_PARALLEL)).setValue(getOriginLat(metadata));
parameters.parameter(code(Equirectangular.LONGITUDE_OF_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(Equirectangular.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(Equirectangular.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* Mercator_1SP
* Mercator_2SP
*/
if (name.equalsIgnoreCase("mercator_1SP")
|| name.equalsIgnoreCase("Mercator_2SP")
|| code == CT_Mercator) {
final double standard_parallel_1 = metadata.getAsDouble(ProjStdParallel1GeoKey);
boolean isMercator2SP = false;
if (!Double.isNaN(standard_parallel_1)) {
parameters = mtFactory.getDefaultParameters("Mercator_2SP");
isMercator2SP = true;
} else {
parameters = mtFactory.getDefaultParameters("Mercator_1SP");
}
parameters.parameter(code(Mercator1SP.LONGITUDE_OF_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(Mercator1SP.LATITUDE_OF_ORIGIN)).setValue(getOriginLat(metadata));
parameters.parameter(code(Mercator2SP.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(Mercator2SP.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
if (isMercator2SP) {
parameters.parameter(code(Mercator2SP.STANDARD_PARALLEL)).setValue(standard_parallel_1);
} else {
parameters.parameter(code(Mercator1SP.SCALE_FACTOR)).setValue(getScaleFactor(metadata));
}
return parameters;
}
/**
* Lambert_conformal_conic_1SP
*/
if (name.equalsIgnoreCase("lambert_conformal_conic_1SP")
|| code == CT_LambertConfConic_Helmert) {
parameters = mtFactory.getDefaultParameters("Lambert_Conformal_Conic_1SP");
parameters.parameter(code(LambertConformal1SP.LONGITUDE_OF_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(LambertConformal1SP.LATITUDE_OF_ORIGIN)).setValue(getOriginLat(metadata));
parameters.parameter(code(LambertConformal1SP.SCALE_FACTOR)).setValue(metadata.getAsDouble(ProjScaleAtNatOriginGeoKey));
parameters.parameter(code(LambertConformal1SP.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(LambertConformal1SP.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* Lambert_conformal_conic_2SP
*/
if (name.equalsIgnoreCase("lambert_conformal_conic_2SP")
|| code == CT_LambertConfConic_2SP) {
parameters = mtFactory.getDefaultParameters("Lambert_Conformal_Conic_2SP");
parameters.parameter(code(LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN)).setValue(getOriginLat(metadata));
parameters.parameter(code(LambertConformal2SP.STANDARD_PARALLEL_1)).setValue(metadata.getAsDouble(ProjStdParallel1GeoKey));
parameters.parameter(code(LambertConformal2SP.STANDARD_PARALLEL_2)).setValue(metadata.getAsDouble(ProjStdParallel2GeoKey));
parameters.parameter(code(LambertConformal2SP.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(LambertConformal2SP.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* Krovak
*/
if (name.equalsIgnoreCase("Krovak")) {
parameters = mtFactory.getDefaultParameters(code(Krovak.PARAMETERS));
parameters.parameter(code(Krovak.LONGITUDE_OF_CENTRE)).setValue(getOriginLong(metadata));
parameters.parameter(code(Krovak.LATITUDE_OF_CENTRE)).setValue(getOriginLat(metadata));
parameters.parameter(code(Krovak.AZIMUTH)).setValue(metadata.getAsDouble(ProjStdParallel1GeoKey));
parameters.parameter(code(Krovak.PSEUDO_STANDARD_PARALLEL)).setValue(metadata.getAsDouble(ProjStdParallel2GeoKey));
parameters.parameter(code(Krovak.SCALE_FACTOR)).setValue(getFalseEasting(metadata));
return parameters;
}
/**
* STEREOGRAPHIC
*/
if (name.equalsIgnoreCase("stereographic")
|| code == CT_Stereographic) {
parameters = mtFactory.getDefaultParameters(code(Stereographic.PARAMETERS));
parameters.parameter(code(Stereographic.CENTRAL_MERIDIAN)).setValue(this.getOriginLong(metadata));
parameters.parameter(code(Stereographic.LATITUDE_OF_ORIGIN)).setValue(this.getOriginLat(metadata));
parameters.parameter(code(Stereographic.SCALE_FACTOR)).setValue(metadata.getAsDouble(ProjScaleAtNatOriginGeoKey));
parameters.parameter(code(Stereographic.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(Stereographic.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* POLAR_STEREOGRAPHIC variant A B and C
*/
if (code == CT_PolarStereographic) {
/**
* They exist 3 kind of polar StereoGraphic projections,define the case
* relative to existing needed attributs
*/
//-- set the mutual projection attributs
//-- all polar stereographic formulas share LONGITUDE_OF_ORIGIN
final double longitudeOfOrigin = metadata.getAsDouble(ProjStraightVertPoleLongGeoKey);
/*
* For polar Stereographic variant A only latitudeOfNaturalOrigin expected values are {-90; +90}.
* In some case, standard parallele is stipulate into latitudeOfNaturalOrigin tiff tag by error.
* To avoid CRS problem creation, try to anticipe this comportement by switch latitudeOfNaturalOrigin into standard parallele.
* HACK FOR USGS LANDSAT 8 difference between geotiff tag and Landsat 8 metadata MTL.txt file.
*/
double standardParallel = metadata.getAsDouble(ProjStdParallel1GeoKey);
final double latitudeOfNaturalOrigin = metadata.getAsDouble(ProjNatOriginLatGeoKey);
final boolean isVariantALatitudeConform = (Math.abs(Latitude.MAX_VALUE - Math.abs(latitudeOfNaturalOrigin)) < Formulas.ANGULAR_TOLERANCE);
if (!isVariantALatitudeConform && Double.isNaN(standardParallel)) {
LOGGER.log(Level.WARNING, "The latitudeOfNaturalOrigin for Polar Stereographic variant A is not conform.\n"
+ "Expected values are {-90; +90}, found : "+latitudeOfNaturalOrigin+"\n"
+ "Switch latitudeOfNaturalOrigin by Latitude of standard parallel to try building of Polar Stereographic Variant B or C.");
standardParallel = latitudeOfNaturalOrigin;
}
if (Double.isNaN(standardParallel)) {
//-- no standard parallele : PolarStereoGraphic VARIANT A
final OperationMethod method = DefaultFactories.forBuildin(CoordinateOperationFactory.class)
.getOperationMethod("Polar Stereographic (variant A)");
parameters = method.getParameters().createValue();
parameters.parameter(code(PolarStereographicA.LONGITUDE_OF_ORIGIN)).setValue(longitudeOfOrigin);
parameters.parameter(code(PolarStereographicA.LATITUDE_OF_ORIGIN)).setValue(latitudeOfNaturalOrigin);
parameters.parameter(code(PolarStereographicA.SCALE_FACTOR)).setValue(metadata.getAsDouble(ProjScaleAtNatOriginGeoKey));
parameters.parameter(code(PolarStereographicA.FALSE_EASTING)).setValue(metadata.getAsDouble(ProjFalseEastingGeoKey));
parameters.parameter(code(PolarStereographicA.FALSE_NORTHING)).setValue(metadata.getAsDouble(ProjFalseNorthingGeoKey));
} else {
//-- Variant B and C share STANDARD_PARALLEL
final double falseOriginEasting = metadata.getAsDouble(ProjFalseOriginEastingGeoKey);
if (Double.isNaN(falseOriginEasting)) {
//-- no false Origin Easting : PolarStereoGraphic VARIANT B
final OperationMethod method = DefaultFactories.forBuildin(CoordinateOperationFactory.class)
.getOperationMethod("Polar Stereographic (variant B)");
parameters = method.getParameters().createValue();
parameters.parameter(code(PolarStereographicB.STANDARD_PARALLEL)).setValue(standardParallel);
parameters.parameter(code(PolarStereographicB.LONGITUDE_OF_ORIGIN)).setValue(longitudeOfOrigin);
parameters.parameter(code(PolarStereographicB.FALSE_EASTING)).setValue(metadata.getAsDouble(ProjFalseEastingGeoKey));
parameters.parameter(code(PolarStereographicB.FALSE_NORTHING)).setValue(metadata.getAsDouble(ProjFalseNorthingGeoKey));
} else {
//-- PolarStereoGraphic VARIANT C
final OperationMethod method = DefaultFactories.forBuildin(CoordinateOperationFactory.class)
.getOperationMethod("Polar Stereographic (variant C)");
parameters = method.getParameters().createValue();
parameters.parameter(code(PolarStereographicB.STANDARD_PARALLEL)).setValue(standardParallel);
parameters.parameter(code(PolarStereographicB.LONGITUDE_OF_ORIGIN)).setValue(longitudeOfOrigin);
parameters.parameter(code(PolarStereographicC.EASTING_AT_FALSE_ORIGIN)).setValue(metadata.getAsDouble(ProjFalseOriginEastingGeoKey));
parameters.parameter(code(PolarStereographicC.NORTHING_AT_FALSE_ORIGIN)).setValue(metadata.getAsDouble(ProjFalseNorthingGeoKey));
}
}
}
/**
* Oblique Stereographic
*/
if (name.equalsIgnoreCase("oblique_stereographic")
|| code == CT_ObliqueStereographic) {
parameters = mtFactory.getDefaultParameters(code(new ObliqueStereographic().getParameters())); // TODO: use a more efficient way.
parameters.parameter(code(ObliqueStereographic.LONGITUDE_OF_ORIGIN)).setValue(getOriginLong(metadata));
parameters.parameter(code(ObliqueStereographic.LATITUDE_OF_ORIGIN)).setValue(getOriginLat(metadata));
parameters.parameter(code(ObliqueStereographic.SCALE_FACTOR)).setValue(metadata.getAsDouble(ProjScaleAtNatOriginGeoKey));
parameters.parameter(code(ObliqueStereographic.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(ObliqueStereographic.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* OBLIQUE_MERCATOR.
*/
if (name.equalsIgnoreCase("oblique_mercator")
|| name.equalsIgnoreCase("hotine_oblique_mercator")
|| code == CT_ObliqueMercator) {
parameters = mtFactory.getDefaultParameters(code(ObliqueMercator.PARAMETERS));
parameters.parameter(code(ObliqueMercator.SCALE_FACTOR)).setValue(getScaleFactor(metadata));
parameters.parameter(code(ObliqueMercator.AZIMUTH)).setValue(metadata.getAsDouble(ProjAzimuthAngleGeoKey));
parameters.parameter(code(ObliqueMercator.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(ObliqueMercator.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
parameters.parameter(code(ObliqueMercator.LONGITUDE_OF_CENTRE)).setValue(getOriginLong(metadata));
parameters.parameter(code(ObliqueMercator.LATITUDE_OF_CENTRE)).setValue(getOriginLat(metadata));
return parameters;
}
/**
* albers_Conic_Equal_Area
*/
if (name.equalsIgnoreCase("albers_Conic_Equal_Area")
|| code == CT_AlbersEqualArea) {
parameters = mtFactory.getDefaultParameters("Albers Equal Area");
parameters.parameter(code(AlbersEqualArea.STANDARD_PARALLEL_1)).setValue(metadata.getAsDouble(ProjStdParallel1GeoKey));
parameters.parameter(code(AlbersEqualArea.STANDARD_PARALLEL_2)).setValue(metadata.getAsDouble(ProjStdParallel2GeoKey));
parameters.parameter(code(AlbersEqualArea.LATITUDE_OF_FALSE_ORIGIN)).setValue(getOriginLat(metadata)); //TODO what is the correct match ?
parameters.parameter(code(AlbersEqualArea.LONGITUDE_OF_FALSE_ORIGIN)).setValue(getOriginLong(metadata)); //TODO what is the correct match ?
parameters.parameter(code(AlbersEqualArea.EASTING_AT_FALSE_ORIGIN)).setValue(getFalseEasting(metadata));
parameters.parameter(code(AlbersEqualArea.NORTHING_AT_FALSE_ORIGIN)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* Orthographic
*/
if (name.equalsIgnoreCase("Orthographic")
|| code == CT_Orthographic) {
parameters = mtFactory.getDefaultParameters(code(Orthographic.PARAMETERS));
parameters.parameter(code(Orthographic.LATITUDE_OF_CENTRE)).setValue(getOriginLat(metadata));
parameters.parameter(code(Orthographic.LONGITUDE_OF_CENTRE)).setValue(getOriginLong(metadata));
parameters.parameter(code(Orthographic.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(Orthographic.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* Lambert Azimuthal Equal Area
*/
if (name.equalsIgnoreCase("Lambert_Azimuthal_Equal_Area")
|| code == CT_LambertAzimEqualArea) {
parameters = mtFactory.getDefaultParameters(code(LambertAzimuthalEqualArea.PARAMETERS));
parameters.parameter(code(LambertAzimuthalEqualArea.LATITUDE_OF_CENTRE)).setValue(getOriginLat(metadata));
parameters.parameter(code(LambertAzimuthalEqualArea.LONGITUDE_OF_CENTRE)).setValue(getOriginLong(metadata));
parameters.parameter(code(LambertAzimuthalEqualArea.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(LambertAzimuthalEqualArea.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
/**
* New Zealand Map Grid
*/
if (name.equalsIgnoreCase("New_Zealand_Map_Grid")
|| code == CT_NewZealandMapGrid) {
parameters = mtFactory.getDefaultParameters(code(NewZealandMapGrid.PARAMETERS));
parameters.parameter(code(NewZealandMapGrid.LATITUDE_OF_ORIGIN)).setValue(this.getOriginLat(metadata));
parameters.parameter(code(NewZealandMapGrid.CENTRAL_MERIDIAN)).setValue(getOriginLong(metadata));
parameters.parameter(code(NewZealandMapGrid.FALSE_EASTING)).setValue(getFalseEasting(metadata));
parameters.parameter(code(NewZealandMapGrid.FALSE_NORTHING)).setValue(getFalseNorthing(metadata));
return parameters;
}
} catch (FactoryException e) {
throw new IOException(e.getLocalizedMessage(), e);
}
return parameters;
}
/**
* 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 ValueMap metadata) throws IOException, FactoryException {
GeographicCRS gcs = null;
// ////////////////////////////////////////////////////////////////////
// Get the crs code
// ////////////////////////////////////////////////////////////////////
final String tempCode = metadata.getAsString(GeographicTypeGeoKey);
// lookup the angular units used in this geotiff image
Unit angularUnit = null;
try {
angularUnit = createUnit(GeogAngularUnitsGeoKey,
GeogAngularUnitSizeGeoKey, Units.RADIAN,
Units.DEGREE, metadata);
} catch (IOException e) {
angularUnit = null;
}
// linear unit
Unit linearUnit = null;
try {
linearUnit = createUnit(GeogLinearUnitsGeoKey,
GeogLinearUnitSizeGeoKey, Units.METRE,
Units.METRE, metadata);
} catch (IOException 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:");
}
final CoordinateReferenceSystem decCRS = AbstractCRS.castOrCopy(CRS.forCode(geogCode.toString())).forConvention(AxesConvention.RIGHT_HANDED);
//-- all CRS must be Geodetic
if (!(decCRS instanceof GeodeticCRS))
throw new IllegalArgumentException("Impossible to define CRS from none Geodetic base. found : "+decCRS.toWKT());
if (decCRS instanceof GeographicCRS) {
gcs = (GeographicCRS) AbstractCRS.castOrCopy(CRS.forCode(geogCode.toString())).forConvention(AxesConvention.RIGHT_HANDED);
} else {
//-- Try to build it from datum and re-create Geographic CRS.
LOGGER.log(Level.WARNING, "Impossible to build Projected CRS from none Geographic base CRS, replaced by Geographic CRS.");
final GeodeticCRS geodeticCrs = (GeodeticCRS) decCRS;
final GeodeticDatum datum = geodeticCrs.getDatum();
final HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put(GeographicCRS.NAME_KEY, decCRS.getName());
gcs = new DefaultGeographicCRS(properties, datum, org.apache.sis.referencing.CommonCRS.defaultGeographic().getCoordinateSystem());
}
if (angularUnit != null
&& !angularUnit.equals(gcs.getCoordinateSystem().getAxis(0).getUnit())) {
// //
// Create a user-defined GCRS using the provided angular
// unit.
// //
gcs = new DefaultGeographicCRS(name(IdentifiedObjects.getName(gcs, new DefaultCitation("EPSG"))),
(GeodeticDatum) gcs.getDatum(),
PredefinedCS.usingUnit(CommonCRS.defaultGeographic().getCoordinateSystem(), angularUnit));
}
} catch (FactoryException fe) {
throw new IOException(fe);
}
}
return gcs;
}
/**
* 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(final Unit linearUnit) {
if (linearUnit == null) {
throw new NullPointerException(
"Error when trying to create a PCS using this linear UoM ");
}
if (!linearUnit.isCompatible(Units.METRE)) {
throw new IllegalArgumentException(
"Error when trying to create a PCS using this linear UoM "
+ linearUnit.toString());
}
return new DefaultCartesianCS(name(Vocabulary.formatInternational(Vocabulary.Keys.Projected).toString()),
new DefaultCoordinateSystemAxis(name(Vocabulary.formatInternational(Vocabulary.Keys.Easting).toString()), "E",
AxisDirection.EAST, linearUnit),
new DefaultCoordinateSystemAxis(name(Vocabulary.formatInternational(Vocabulary.Keys.Northing).toString()), "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 ValueMap metadata, final 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.getAsString(GeogPrimeMeridianGeoKey);
PrimeMeridian pm = null;
try {
if (pmCode != null) {
if (pmCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
try {
final String name = metadata.getAsString(GeogCitationGeoKey);
final String pmValue = metadata.getAsString(GeogPrimeMeridianLongGeoKey);
final double pmNumeric = Double.parseDouble(pmValue);
// is it Greenwich?
if (pmNumeric == 0) {
return CommonCRS.WGS84.primeMeridian();
}
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) {
throw new IOException("Invalid user-defined prime meridian spec.",nfe);
}
} else {
pm = epsgFactory.createPrimeMeridian(String.valueOf(pmCode));
}
} else {
pm = CommonCRS.WGS84.primeMeridian();
}
} catch (FactoryException fe) {
throw new IOException(fe);
}
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 ValueMap metadata) throws IOException {
// lookup the datum (w/o PrimeMeridian), error if "user defined"
GeodeticDatum datum = null;
final String datumCode = metadata.getAsString(GeogGeodeticDatumGeoKey);
if (datumCode == null) {
throw new IOException("A user defined Geographic Coordinate system must include a predefined datum!");
}
if (datumCode.equals(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
/**
* USER DEFINED DATUM
*/
// datum name
final String datumName = (metadata.getAsString(GeogCitationGeoKey) != null ? metadata.getAsString(GeogCitationGeoKey)
: "unnamed");
// is it WGS84?
if (datumName.trim().equalsIgnoreCase("WGS84")) {
return CommonCRS.WGS84.datum();
}
// ELLIPSOID
final Ellipsoid ellipsoid = createEllipsoid(unit, metadata);
// PRIME MERIDIAN
// lookup the Prime Meridian.
final PrimeMeridian primeMeridian = createPrimeMeridian(metadata,
unit);
// DATUM
datum = new DefaultGeodeticDatum(Collections.singletonMap(GeodeticDatum.NAME_KEY, datumName), ellipsoid,
primeMeridian);
} else {
/**
* NOT USER DEFINED DATUM
*/
// we are going to use the provided EPSG code
try {
datum = (GeodeticDatum) (epsgFactory.createDatum(String.valueOf(datumCode)));
} catch (FactoryException fe) {
throw new IOException(fe.getLocalizedMessage(), fe);
} catch (ClassCastException cce) {
throw new IOException(cce.getLocalizedMessage(), cce);
}
}
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 ValueMap metadata) throws IOException {
// /////////////////////////////////////////////////////////////////////
// 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.getAsString(GeogEllipsoidGeoKey);
String temp = null;
// is the ellipsoid user defined?
if (ellipsoidKey.equalsIgnoreCase(GeoTiffConstants.GTUserDefinedGeoKey_String)) {
// /////////////////////////////////////////////////////////////////////
// USER DEFINED ELLIPSOID
// /////////////////////////////////////////////////////////////////////
String nameEllipsoid = metadata.getAsString(GeogCitationGeoKey);
if (nameEllipsoid == null) {
nameEllipsoid = "unnamed";
}
// is it the default for WGS84?
if (nameEllipsoid.trim().equalsIgnoreCase("WGS84")) {
return CommonCRS.WGS84.ellipsoid();
}
// //
// 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.getAsString(GeogSemiMajorAxisGeoKey);
final double semiMajorAxis = (temp != null ? Double.parseDouble(temp) : Double.NaN);
temp = metadata.getAsString(GeogInvFlatteningGeoKey);
final double inverseFlattening;
if (temp != null) {
inverseFlattening = (temp != null ? Double.parseDouble(temp)
: Double.NaN);
} else {
temp = metadata.getAsString(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(
Collections.singletonMap(DefaultEllipsoid.NAME_KEY, nameEllipsoid),
semiMajorAxis, inverseFlattening, unit);
}
try {
// /////////////////////////////////////////////////////////////////////
// EPSG STANDARD ELLIPSOID
// /////////////////////////////////////////////////////////////////////
return epsgFactory.createEllipsoid(String.valueOf(ellipsoidKey));
} catch (FactoryException fe) {
throw new IOException(fe.getLocalizedMessage(), fe);
}
}
/**
* 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(final int key, final int userDefinedKey, final Unit base, final Unit def,
final ValueMap metadata) throws IOException {
final String unitCode = metadata.getAsString(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.getAsString(userDefinedKey);
// throw descriptive exception if required key is not there.
if (unitSize == null) {
throw new IOException("Must define unit length when using a user defined unit");
}
double sz = Double.parseDouble(unitSize);
return base.multiply(sz);
} catch (NumberFormatException nfe) {
throw new IOException(nfe);
}
} else {
try {
// using epsg code for this unit
return epsgFactory.createUnit(String.valueOf(unitCode));
} catch (FactoryException fe) {
throw new IOException(fe);
}
}
}
private static String code(final GeneralParameterDescriptor desc){
return desc.getName().getCode();
}
private static Map<String,?> name(final String name) {
return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
}
}