/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.coverage.io.netcdf.crs; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import org.geotools.factory.GeoTools; import org.geotools.factory.Hints; import org.geotools.imageio.netcdf.utilities.NetCDFUtilities; import org.geotools.metadata.iso.citation.Citations; import org.geotools.referencing.NamedIdentifier; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.crs.DefaultProjectedCRS; import org.geotools.referencing.cs.DefaultCartesianCS; import org.geotools.referencing.cs.DefaultEllipsoidalCS; import org.geotools.referencing.datum.DefaultEllipsoid; import org.geotools.referencing.datum.DefaultGeodeticDatum; import org.geotools.referencing.datum.DefaultPrimeMeridian; import org.geotools.referencing.operation.DefaultOperationMethod; import org.geotools.referencing.operation.DefiningConversion; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.util.Utilities; import org.opengis.metadata.citation.Citation; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.NoSuchIdentifierException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.cs.EllipsoidalCS; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.OperationMethod; /** * Class used to create an OGC {@link ProjectedCRS} instance on top of Projection name, parameters and Ellipsoid. A default datum will be created on * top of that ellipsoid. */ public class ProjectionBuilder { private static final String NAME = "name"; private static final String DEFAULT_DATUM_NAME = NetCDFUtilities.UNKNOWN; /** * Cached {@link MathTransformFactory} for building {@link MathTransform} objects. */ private static final MathTransformFactory mtFactory; public static final EllipsoidalCS DEFAULT_ELLIPSOIDAL_CS = DefaultEllipsoidalCS.GEODETIC_2D .usingUnit(NonSI.DEGREE_ANGLE); static { Hints hints = GeoTools.getDefaultHints().clone(); mtFactory = ReferencingFactoryFinder.getMathTransformFactory(hints); } /** * Quick method to create a {@link CoordinateReferenceSystem} instance, given the OGC ProjectionName, such as "lambert_conformal_conic_2sp"), a * custom code number for it, the semiMajor, the inverseFlattening (when infinity, assuming the reference ellipsoid is a spheroid), and the * Projection Params through a <key,value> map (as an instance: <"central_meridian",-95>) * * @throws FactoryException * */ public static CoordinateReferenceSystem createProjection(String projectionName, String code, Double semiMajor, Double inverseFlattening, Map<String, Double> params) throws FactoryException { ParameterValueGroup parameters = getProjectionParameters(projectionName); Ellipsoid ellipsoid = createEllipsoid(semiMajor, inverseFlattening); // Datum Set<String> keys = params.keySet(); for (String key : keys) { parameters.parameter(key).setValue(params.get(key)); } return buildCRS(buildProperties(projectionName, Citations.EPSG, code), parameters, ellipsoid); } /** * Get Projection parameters from the specified projection name. * * @param projectionName * @return * @throws NoSuchIdentifierException */ public static ParameterValueGroup getProjectionParameters(String projectionName) throws NoSuchIdentifierException { return mtFactory.getDefaultParameters(projectionName); } /** * Make sure to set SEMI_MINOR and SEMI_MAJOR projection's parameters from the ellipsoid definition * * @param parameters * @param ellipsoid */ public static void updateEllipsoidParams(ParameterValueGroup parameters, Ellipsoid ellipsoid) { Utilities.ensureNonNull("ellipsoid", ellipsoid); Utilities.ensureNonNull("parameters", parameters); double semiMajor = ellipsoid.getSemiMajorAxis(); double inverseFlattening = ellipsoid.getInverseFlattening(); // setting missing parameters parameters.parameter(NetCDFUtilities.SEMI_MINOR).setValue( semiMajor * (1 - (1 / inverseFlattening))); parameters.parameter(NetCDFUtilities.SEMI_MAJOR).setValue(semiMajor); } /** * Create a {@link DefiningConversion} object from the input {@link MathTransform} * * @param name * @param transform * @return */ public static DefiningConversion createConversionFromBase(String name, MathTransform transform) { return new DefiningConversion(Collections.singletonMap(NAME, name), new DefaultOperationMethod(transform), transform); } static Map<String, Object> buildProperties(String name, Citation authority, String code) { Map<String, Object> props = new HashMap<String, Object>(); props.put(IdentifiedObject.NAME_KEY, name); props.put(IdentifiedObject.IDENTIFIERS_KEY, new NamedIdentifier(authority, code)); return props; } /** * Build an ellipsoid provided semiMajor and inverseFlattening. * * @param semiMajor the semiMajor axis length in meters * @param inverseFlattening the inverseFlattening (when infinity, the ellipsoid will be a spheroid) * @return */ private static Ellipsoid createEllipsoid(Double semiMajor, Double inverseFlattening) { Map<String, Number> ellipsoidParams = new HashMap<String, Number>(); ellipsoidParams.put(NetCDFUtilities.SEMI_MAJOR, semiMajor); if (!Double.isInfinite(inverseFlattening)) { ellipsoidParams.put(NetCDFUtilities.INVERSE_FLATTENING, inverseFlattening); } return createEllipsoid(NetCDFUtilities.UNKNOWN, ellipsoidParams); } /** * Build a Default {@link GeodeticDatum} on top of a specific {@link Ellipsoid} instance, using {@link DefaultPrimeMeridian#GREENWICH} as * primeMeridian. * * @param name * @param ellipsoid * @return */ public static GeodeticDatum createGeodeticDatum(String name, Ellipsoid ellipsoid) { return new DefaultGeodeticDatum(name, ellipsoid, DefaultPrimeMeridian.GREENWICH); } /** * Build a {@link GeographicCRS} given the name to be assigned and the {@link GeodeticDatum} to be used. {@link EllipsoidalCS} is * {@value #DEFAULT_ELLIPSOIDAL_CS} * * @param name * @param datum * @param ellipsoidalCS * @return */ public static GeographicCRS createGeographicCRS(String name, GeodeticDatum datum) { return createGeographicCRS(name, datum, DEFAULT_ELLIPSOIDAL_CS); } /** * Build a {@link GeographicCRS} given the name to be assigned, the {@link GeodeticDatum} to be used and the {@link EllipsoidalCS}. * * @param name * @param datum * @param ellipsoidalCS * @return */ public static GeographicCRS createGeographicCRS(String name, GeodeticDatum datum, EllipsoidalCS ellipsoidalCS) { final Map<String, String> props = new HashMap<String, String>(); props.put(NAME, name); return new DefaultGeographicCRS(props, datum, ellipsoidalCS); } /** * Build a {@link ProjectedCRS} given the base {@link GeographicCRS}, the {@link DefiningConversion} instance from Base as well as the * {@link MathTransform} from the base CRS to returned CRS. The derivedCS is {@link DefaultCartesianCS#PROJECTED} by default. * * @param props * @param baseCRS * @param conversionFromBase * @param transform * @return */ public static ProjectedCRS createProjectedCRS(Map<String, ?> props, GeographicCRS baseCRS, DefiningConversion conversionFromBase, MathTransform transform) { // Create the projected CRS return new DefaultProjectedCRS(props, conversionFromBase, baseCRS, transform, DefaultCartesianCS.PROJECTED); } /** * Build a custom {@link Ellipsoid} provided the name and a Map contains <key,number> parameters describing that ellipsoid. Supported params are * {@link #SEMI_MAJOR}, {@link #SEMI_MINOR}, {@link NetCDFUtilities#INVERSE_FLATTENING} * * @param name * @param ellipsoidParams * @return */ public static Ellipsoid createEllipsoid(String name, Map<String, Number> ellipsoidParams) { Number semiMajor = NetCDFUtilities.DEFAULT_EARTH_RADIUS; Number semiMinor = null; Number inverseFlattening = Double.NEGATIVE_INFINITY; if (ellipsoidParams != null && !ellipsoidParams.isEmpty()) { if (ellipsoidParams.containsKey(NetCDFUtilities.SEMI_MAJOR)) { semiMajor = ellipsoidParams.get(NetCDFUtilities.SEMI_MAJOR); } if (ellipsoidParams.containsKey(NetCDFUtilities.SEMI_MINOR)) { semiMinor = ellipsoidParams.get(NetCDFUtilities.SEMI_MINOR); } if (ellipsoidParams.containsKey(NetCDFUtilities.INVERSE_FLATTENING)) { inverseFlattening = ellipsoidParams.get(NetCDFUtilities.INVERSE_FLATTENING); } } if (semiMinor != null) { return DefaultEllipsoid.createEllipsoid(name, semiMajor.doubleValue(), semiMinor.doubleValue(), SI.METER); } else { return DefaultEllipsoid.createFlattenedSphere(name, semiMajor.doubleValue(), inverseFlattening.doubleValue(), SI.METER); } } /** * Build a Projected {@link CoordinateReferenceSystem} parsing Conversion parameters and Ellipsoid * * @param props * @param parameters * @param ellipsoid * @return * @throws NoSuchIdentifierException * @throws FactoryException */ public static CoordinateReferenceSystem buildCRS(Map<String, ?> props, ParameterValueGroup parameters, Ellipsoid ellipsoid) throws NoSuchIdentifierException, FactoryException { // Refine the parameters by adding the required ellipsoid's related params updateEllipsoidParams(parameters, ellipsoid); // Datum final GeodeticDatum datum = ProjectionBuilder.createGeodeticDatum(DEFAULT_DATUM_NAME, ellipsoid); // Base Geographic CRS GeographicCRS baseCRS = ProjectionBuilder.createGeographicCRS(NetCDFUtilities.UNKNOWN, datum); // create math transform MathTransform transform = ProjectionBuilder.createTransform(parameters); // create the projection transform String name = NetCDFUtilities.UNKNOWN; if (props != null && !props.isEmpty() && props.containsKey(NetCDFUtilities.NAME)) { name = (String) props.get(NetCDFUtilities.NAME); } DefiningConversion conversionFromBase = ProjectionBuilder.createConversionFromBase(name, transform); OperationMethod method = conversionFromBase.getMethod(); if (!(method instanceof MathTransformProvider)) { OperationMethod opMethod = mtFactory.getLastMethodUsed(); if (opMethod instanceof MathTransformProvider) { final Map<String, Object> copy = new HashMap<String, Object>(props); copy.put(DefaultProjectedCRS.CONVERSION_TYPE_KEY, ((MathTransformProvider) opMethod).getOperationType()); props = copy; } } return ProjectionBuilder.createProjectedCRS(props, baseCRS, conversionFromBase, transform); } public static MathTransform createTransform(ParameterValueGroup parameters) throws NoSuchIdentifierException, FactoryException { return mtFactory.createParameterizedTransform(parameters); } /** * Get a {@link ParameterValueGroup} parameters instance for the specified projectionName. * * @param projectionName * @return * @throws NoSuchIdentifierException */ public static ParameterValueGroup getDefaultparameters(String projectionName) throws NoSuchIdentifierException { Utilities.ensureNonNull("projectionName", projectionName); return mtFactory.getDefaultParameters(projectionName); } }