All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.internal.referencing.CRSUtilities Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, 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.
 */
package org.geotoolkit.internal.referencing;

import java.util.Map;
import java.util.List;
import java.util.Collections;
import javax.measure.unit.Unit;
import javax.measure.unit.NonSI;
import javax.measure.quantity.Angle;

import org.opengis.referencing.*;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;

import org.geotoolkit.lang.Static;
import org.geotoolkit.referencing.CRS;
import org.geotoolkit.referencing.cs.AxisRangeType;
import org.geotoolkit.referencing.cs.DefaultEllipsoidalCS;
import org.geotoolkit.referencing.crs.DefaultCompoundCRS;
import org.geotoolkit.referencing.crs.DefaultGeographicCRS;
import org.geotoolkit.referencing.datum.DefaultGeodeticDatum;
import org.geotoolkit.measure.Measure;
import org.geotoolkit.resources.Errors;

import static java.lang.Math.*;
import static org.geotoolkit.math.XMath.atanh;


/**
 * A set of static methods working on OpenGIS®
 * {@linkplain CoordinateReferenceSystem coordinate reference system} objects.
 * Some of those methods are useful, but not really rigorous. This is why they
 * do not appear in the "official" package, but instead in this private one.
 * 

* Do not rely on this API! It may change in incompatible way * in any future release. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @since 2.0 * @module */ public final class CRSUtilities extends Static { /** * Version of the embedded database. This string must be updated when * the SQL scripts in the {@code geotk-epsg} module are updated. * * @see http://www.geotoolkit.org/build/tools/geotk-epsg-pack/index.html */ public static final String EPSG_VERSION = "7.09"; /** * The key for specifying explicitely the value to be returned by {@link #getParameterValues()}. * It is usually not necessary to specify those parameters because they are inferred either from * the {@link MathTransform}, or specified explicitely in a {@link DefiningConversion}. However * there is a few cases, for example the Molodenski transform, where none of the above can apply, * because Geotk implements those operations as a concatenation of math transforms, and such * concatenations don't have {@link ParameterValueGroup}. * * @since 3.20 */ public static final String PARAMETERS_KEY = "parameters"; /** * Number of {@link org.geotoolkit.referencing.cs.AxisRangeType} values. * This is defined in order to avoid creating a useless array of enumeration * just for determining its length. * * @since 3.20 */ public static final int AXIS_RANGE_COUNT = 2; /** * Mask to apply on the {@link org.geotoolkit.referencing.cs.AxisRangeType} ordinate * value in order to have the "opposite" enum. The opposite of {@code POSITIVE_LONGITUDE} * is {@code SPANNING_ZERO_LONGITUDE}, and conversely. * * @since 3.20 */ public static final int AXIS_RANGE_RECIPROCAL_MASK = 1; /** * Do not allow creation of instances of this class. */ private CRSUtilities() { } /** * Returns the radius of a hypothetical sphere having the same surface than the ellipsoid * specified by the given axis length. * * @param a The semi-major axis length. * @param b The semi-minor axis length. * @return The radius of a sphere having the same surface than the specified ellipsoid. * * @see org.geotoolkit.referencing.datum.DefaultEllipsoid#getAuthalicRadius() * * @since 3.20 */ public static double getAuthalicRadius(final double a, final double b) { if (a == b) { return a; } final double f = 1 - b/a; final double e = sqrt(2*f - f*f); return sqrt(0.5 * (a*a + b*b*atanh(e)/e)); } /** * Returns the index of the first dimension in {@code fullCS} where axes colinear with * the {@code subCS} axes are found. If no such dimension is found, returns -1. * * @param fullCS The coordinate system which contains all axes. * @param subCS The coordinate system to search into {@code fullCS}. * @return The first dimension of a sequence of axes colinear with {@code subCS} axes, * or {@code -1} if none. * * @since 3.10 */ public static int dimensionColinearWith(final CoordinateSystem fullCS, final CoordinateSystem subCS) { final int dim = AxisDirections.indexOf(fullCS, subCS.getAxis(0).getDirection()); if (dim >= 0) { int i = subCS.getDimension(); if (dim + i <= fullCS.getDimension()) { while (--i > 0) { // Intentionally exclude 0. if (!AxisDirections.absolute(subCS.getAxis(i).getDirection()).equals( AxisDirections.absolute(fullCS.getAxis(i + dim).getDirection()))) { return -1; } } return dim; } } return -1; } /** * Returns the unit used for all axis in the specified coordinate system. * If not all axis uses the same unit, then this method returns {@code null}. * This convenience method is often used for Well Know Text (WKT) formatting. * * @param cs The coordinate system for which to get the unit. * @return The unit for all axis in the given coordinate system, or {@code null}. * * @since 2.2 */ public static Unit getUnit(final CoordinateSystem cs) { Unit unit = null; for (int i=cs.getDimension(); --i>=0;) { final Unit candidate = cs.getAxis(i).getUnit(); if (candidate != null) { if (unit == null) { unit = candidate; } else if (!unit.equals(candidate)) { return null; } } } return unit; } /** * Returns the dimension of the first coordinate reference system of the given type. The * {@code type} argument must be a sub-interface of {@link CoordinateReferenceSystem}. * If no such dimension is found, then this method returns {@code -1}. * * @param crs The coordinate reference system (CRS) to examine. * @param type The CRS type to look for. * Must be a subclass of {@link CoordinateReferenceSystem}. * @return The dimension range of the specified CRS type, or {@code -1} if none. * @throws IllegalArgumentException if the {@code type} is not legal. */ public static int getDimensionOf(final CoordinateReferenceSystem crs, final Class type) throws IllegalArgumentException { if (type.isAssignableFrom(crs.getClass())) { return 0; } if (crs instanceof CompoundCRS) { int offset = 0; for (final CoordinateReferenceSystem ci : ((CompoundCRS) crs).getComponents()) { final int index = getDimensionOf(ci, type); if (index >= 0) { return index + offset; } offset += ci.getCoordinateSystem().getDimension(); } } return -1; } /** * Returns a two-dimensional coordinate reference system representing the two first dimensions * of the specified coordinate reference system. If {@code crs} is already a two-dimensional * CRS, then it is returned unchanged. Otherwise, if it is a {@link CompoundCRS}, then the * head coordinate reference system is examined. * * @param crs The coordinate system, or {@code null}. * @return A two-dimensional coordinate reference system that represents the two first * dimensions of {@code crs}, or {@code null} if {@code crs} was {@code null}. * @throws TransformException if {@code crs} can't be reduced to a two-coordinate system. * We use this exception class since this method is usually invoked in the context * of a transformation process. */ public static CoordinateReferenceSystem getCRS2D(CoordinateReferenceSystem crs) throws TransformException { if (crs != null) { while (crs.getCoordinateSystem().getDimension() != 2) { if (crs instanceof CompoundCRS) { crs = ((CompoundCRS) crs).getComponents().get(0); // Continue the loop, examining only the first component. } else { crs = CRS.getHorizontalCRS(crs); if (crs == null) { throw new TransformException(Errors.format( Errors.Keys.CANT_REDUCE_TO_TWO_DIMENSIONS_$1, crs.getName())); } } } } return crs; } /** * Changes the dimension declared in the name. For example if {@code name} is * "WGS 84 (geographic 3D)", {@code search} is "3D" and {@code replace} is "2D", * then this method returns "WGS 84 (geographic 2D)". If the string to search is * not found, then it is concatenated to the name. * * @param object The identified object having the original name. * @param search The dimension token to search in the {@code object} name. * @param replace The new token to substitute to the one we were looking for. * @return The name with the substitution performed. * * @since 2.6 */ public static Map changeDimensionInName(final IdentifiedObject object, final String search, final String replace) { final StringBuilder name = new StringBuilder(object.getName().getCode()); final int last = name.length() - search.length(); boolean append = true; for (int i=name.lastIndexOf(search); i>=0; i=name.lastIndexOf(search, i-1)) { if (i != 0 && Character.isLetterOrDigit(name.charAt(i-1))) { continue; } if (i != last && Character.isLetterOrDigit(i + search.length())) { continue; } name.replace(i, i+search.length(), replace); i = name.indexOf(". ", i); if (i >= 0) { /* * Stops the sentence after the dimension, since it may contains details that * are not applicable anymore. For example the EPSG name for 3D EllipsoidalCS is: * * Ellipsoidal 3D CS. Axes: latitude, longitude, ellipsoidal height. * Orientations: north, east, up. UoM: DMSH, DMSH, m. */ name.setLength(i+1); } append = false; break; } if (append) { if (name.indexOf(" ") >= 0) { name.append(" (").append(replace).append(')'); } else { name.append('_').append(replace); } } return Collections.singletonMap(IdentifiedObject.NAME_KEY, name.toString()); } /** * Returns the longitude value relative to the Greenwich Meridian, * expressed in the specified units. * * @param pm The prime meridian from which to get the Greenwich longitude. * @param unit The unit for the prime meridian to return. * @return The prime meridian in the given units, or 0 if the given prime meridian was null. * * @since 3.19 */ public static double getGreenwichLongitude(final PrimeMeridian pm, final Unit unit) { if (pm == null) { return 0; } return pm.getAngularUnit().getConverterTo(unit).convert(pm.getGreenwichLongitude()); } /** * Returns the longitude value relative to the Greenwich Meridian, expressed in decimal degrees. * * @param pm The prime meridian from which to get the Greenwich longitude. * @return The prime meridian in the given units, or 0 if the given prime meridian was null. * * @since 3.19 */ public static double getGreenwichLongitude(final PrimeMeridian pm) { return getGreenwichLongitude(pm, NonSI.DEGREE_ANGLE); } /** * Implementation of {@link CRS#getDatum(CoordinateReferenceSystem)}, defined here in order * to avoid a dependency of {@link org.geotoolkit.referencing.crs.AbstractDerivedCRS} to the * {@link CRS} class. * * @param crs The coordinate reference system for which to get the datum. May be {@code null}. * @return The datum in the given CRS, or {@code null} if none. * * @see CRS#getEllipsoid(CoordinateReferenceSystem) */ public static Datum getDatum(final CoordinateReferenceSystem crs) { Datum datum; if (crs instanceof SingleCRS) { datum = ((SingleCRS) crs).getDatum(); } else { datum = null; for (final SingleCRS component : DefaultCompoundCRS.getSingleCRS(crs)) { final Datum candidate = component.getDatum(); if (datum != null && !datum.equals(candidate)) { if (isGeodetic3D(datum, candidate)) { continue; // Keep the current datum unchanged. } if (!isGeodetic3D(candidate, datum)) { return null; // Can't build a 3D geodetic datum. } } datum = candidate; } } return datum; } /** * Returns {@code true} if the given datum can form a three-dimensional geodetic datum. * * @param geodetic The presumed geodetic datum. * @param vertical The presumed vertical datum. * @return If the given datum can form a 3D geodetic datum. */ private static boolean isGeodetic3D(final Datum geodetic, final Datum vertical) { return (geodetic instanceof GeodeticDatum) && (vertical instanceof VerticalDatum) && VerticalDatumTypes.ELLIPSOIDAL.equals(((VerticalDatum) vertical).getVerticalDatumType()); } /** * Returns the ellipsoid used by the specified coordinate reference system, providing that * the two first dimensions use an instance of {@link GeographicCRS}. Otherwise (i.e. if the * two first dimensions are not geographic), returns {@code null}. * * @param crs The coordinate reference system for which to get the ellipsoid. * @return The ellipsoid in the given CRS, or {@code null} if none. */ public static Ellipsoid getHeadGeoEllipsoid(CoordinateReferenceSystem crs) { while (!(crs instanceof GeographicCRS)) { if (crs instanceof CompoundCRS) { crs = ((CompoundCRS) crs).getComponents().get(0); } else { return null; } } return ((GeographicCRS) crs).getDatum().getEllipsoid(); } /** * Derives a geographic CRS with (longitude, latitude) axis order in * decimal degrees, relative to Greenwich. If no such CRS can be obtained of created, returns * {@link DefaultGeographicCRS#WGS84}. * * @param crs A source CRS. * @return A two-dimensional geographic CRS with standard axis. Never {@code null}. */ public static GeographicCRS getStandardGeographicCRS2D(CoordinateReferenceSystem crs) { while (crs instanceof GeneralDerivedCRS) { crs = ((GeneralDerivedCRS) crs).getBaseCRS(); } if (!(crs instanceof SingleCRS)) { return DefaultGeographicCRS.WGS84; } final Datum datum = ((SingleCRS) crs).getDatum(); if (!(datum instanceof GeodeticDatum)) { return DefaultGeographicCRS.WGS84; } GeodeticDatum geoDatum = (GeodeticDatum) datum; if (geoDatum.getPrimeMeridian().getGreenwichLongitude() != 0) { geoDatum = new DefaultGeodeticDatum(geoDatum.getName().getCode(), geoDatum.getEllipsoid()); } else if (crs instanceof GeographicCRS) { if (CRS.equalsIgnoreMetadata(DefaultEllipsoidalCS.GEODETIC_2D, crs.getCoordinateSystem())) { return (GeographicCRS) crs; } } return new DefaultGeographicCRS(crs.getName().getCode(), geoDatum, DefaultEllipsoidalCS.GEODETIC_2D); } /** * Computes the resolution of the horizontal component, or {@code null} if it can not be * computed. * * @param crs The full (potentially multi-dimensional) CRS. * @param resolution The resolution along each CRS dimension, or {@code null} if none. * The array length shall be equals to the CRS dimension. * @return The horizontal resolution, or {@code null}. * * @since 3.18 */ public static Measure getHorizontalResolution(final CoordinateReferenceSystem crs, double... resolution) { if (resolution != null) { final SingleCRS horizontalCRS = CRS.getHorizontalCRS(crs); if (horizontalCRS != null) { final CoordinateSystem hcs = horizontalCRS.getCoordinateSystem(); final Unit unit = getUnit(hcs); if (unit != null) { final int s = dimensionColinearWith(crs.getCoordinateSystem(), hcs); if (s >= 0) { final int dim = hcs.getDimension(); double min = Double.POSITIVE_INFINITY; for (int i=s; i 0 && r < min) min = r; } if (min != Double.POSITIVE_INFINITY) { return new Measure(min, unit); } } } } } return null; } /** * Delegates to the {@code shiftAxisRange} method of the appropriate class. This method is not * in public API because the call to {@code castOrCopy} may involve useless objects creation. * Developers are encouraged to invoke {@code castOrCopy} themselves, then replace their old * reference by the new one before to invoke {@code shiftAxisRange}, because they Geotk * implementations cache the result of {@code shiftAxisRange} calls. * * @param crs The CRS on which to apply the shift, or {@code null}. * @param type The desired range of ordinate values. * @return The shifted CRS, or {@code crs} if no range shift applied. * * @since 3.20 */ public static CoordinateReferenceSystem shiftAxisRange(final CoordinateReferenceSystem crs, final AxisRangeType type) { if (crs instanceof GeographicCRS) { final DefaultGeographicCRS impl = DefaultGeographicCRS.castOrCopy((GeographicCRS) crs); final DefaultGeographicCRS shifted = impl.shiftAxisRange(type); if (shifted != impl) return shifted; } else if (crs instanceof CompoundCRS) { final DefaultCompoundCRS impl = DefaultCompoundCRS.castOrCopy((CompoundCRS) crs); final DefaultCompoundCRS shifted = impl.shiftAxisRange(type); if (shifted != impl) return shifted; } return crs; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy