/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 1999-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * This package contains formulas from the PROJ package of USGS. * USGS's work is fully acknowledged here. This derived work has * been relicensed under LGPL with Frank Warmerdam's permission. */ package org.geotools.referencing.operation.projection; import org.opengis.util.InternationalString; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.PlanarProjection; import org.geotools.metadata.iso.citation.Citations; import org.geotools.referencing.NamedIdentifier; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.Utilities; import static java.lang.Math.*; /** * Stereographic Projection. The directions starting from the central point are true, * but the areas and the lengths become increasingly deformed as one moves away from * the center. This projection is used to represent polar areas. It can be adapted * for other areas having a circular form. * <p> * This implementation, and its subclasses, provides transforms for six cases of the * stereographic projection: * <ul> * <li>{@code "Oblique_Stereographic"} (EPSG code 9809), alias {@code "Double_Stereographic"} * in ESRI software</li> * <li>{@code "Stereographic"} in ESRI software (<strong>NOT</strong> EPSG code 9809)</li> * <li>{@code "Polar_Stereographic"} (EPSG code 9810, uses a series calculation for the * inverse)</li> * <li>{@code "Polar_Stereographic (variant B)"} (EPSG code 9829, uses a series calculation * for the inverse)</li> * <li>{@code "Stereographic_North_Pole"} in ESRI software (uses iteration for the inverse)</li> * <li>{@code "Stereographic_South_Pole"} in ESRI software (uses iteration for the inverse)</li> * </ul> * <p> * Both the {@code "Oblique_Stereographic"} and {@code "Stereographic"} * projections are "double" projections involving two parts: 1) a conformal * transformation of the geographic coordinates to a sphere and 2) a spherical * Stereographic projection. The EPSG considers both methods to be valid, but * considers them to be a different coordinate operation methods. * <p> * The {@code "Stereographic"} case uses the USGS equations of Snyder. * This employs a simplified conversion to the conformal sphere that * computes the conformal latitude of each point on the sphere. * <p> * The {@code "Oblique_Stereographic"} case uses equations from the EPSG. * This uses a more generalized form of the conversion to the conformal sphere; using only * a single conformal sphere at the origin point. Since this is a "double" projection, * it is sometimes called the "Double Stereographic". The {@code "Oblique_Stereographic"} * is used in New Brunswick (Canada) and the Netherlands. * <p> * The {@code "Stereographic"} and {@code "Double_Stereographic"} names are * used in ESRI's ArcGIS 8.x product. The {@code "Oblique_Stereographic"} * name is the EPSG name for the later only. * <p> * <strong>WARNING:</strong> Tests points calculated with ArcGIS's {@code "Double_Stereographic"} * are not always equal to points calculated with the {@code "Oblique_Stereographic"}. * However, where there are differences, two different implementations of these equations * (EPSG guidence note 7 and {@code libproj}) calculate the same values as we do. Until these * differences are resolved, please be careful when using this projection. * <p> * If a {@link Stereographic.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} parameter is * supplied and is not consistent with the projection classification (for example a latitude * different from ±90° for the polar case), then the oblique or polar case will be * automatically inferred from the latitude. In other words, the latitude of origin has * precedence on the projection classification. If ommited, then the default value is 90°N * for {@code "Polar_Stereographic"} and 0° for {@code "Oblique_Stereographic"}. * <p> * Polar projections that use the series equations for the inverse calculation will * be little bit faster, but may be a little bit less accurate. If a polar * {@link Stereographic.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} is used for * the {@code "Oblique_Stereographic"} or {@code "Stereographic"}, the iterative * equations will be used for inverse polar calculations. * <p> * The {@code "Polar Stereographic (variant B)"}, {@code "Stereographic_North_Pole"}, * and {@code "Stereographic_South_Pole"} cases include a * {@link StereographicPole.ProviderB#STANDARD_PARALLEL "standard_parallel_1"} parameter. * This parameter sets the latitude with a scale factor equal to the supplied * scale factor. The {@code "Polar Stereographic (variant A)"} receives its * {@code "latitude_of_origin"} parameter value from the hemisphere of the * {@link StereographicPole.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} value * (i.e. the value is forced to ±90°). * <p> * <b>References:</b> * <ul> * <li>John P. Snyder (Map Projections - A Working Manual,<br> * U.S. Geological Survey Professional Paper 1395, 1987)</li> * <li>"Coordinate Conversions and Transformations including Formulas",<br> * EPSG Guidence Note Number 7, Version 19.</li> * <li>Gerald Evenden. <A HREF="http://members.bellatlantic.net/~vze2hc4d/proj4/sterea.pdf"> * "Supplementary PROJ.4 Notes - Oblique Stereographic Alternative"</A></li> * <li>Krakiwsky, E.J., D.B. Thomson, and R.R. Steeves. 1977. A Manual * For Geodetic Coordinate Transformations in the Maritimes. * Geodesy and Geomatics Engineering, UNB. Technical Report No. 48.</li> * <li>Thomson, D.B., M.P. Mepham and R.R. Steeves. 1977. * The Stereographic Double Projection. * Geodesy and Geomatics Engineereng, UNB. Technical Report No. 46.</li> * </ul> * * @see <A HREF="http://mathworld.wolfram.com/StereographicProjection.html">Stereographic projection on MathWorld</A> * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/polar_stereographic.html">Polar_Stereographic</A> * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/oblique_stereographic.html">Oblique_Stereographic</A> * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/stereographic.html">Stereographic</A> * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/random_issues.html#stereographic">Some Random Stereographic Issues</A> * * @since 2.1 * * @source $URL$ * @version $Id$ * @author André Gosselin * @author Martin Desruisseaux (PMO, IRD) * @author Rueben Schulz */ public abstract class Stereographic extends MapProjection { /** * For compatibility with different versions during deserialization. */ private static final long serialVersionUID = -176731870235252852L; /** * Maximum difference allowed when comparing real numbers. */ private static final double EPSILON = 1E-6; /** * The parameter descriptor group to be returned by {@link #getParameterDescriptors()}. */ private final ParameterDescriptorGroup descriptor; /** * Creates a transform from the specified group of parameter values. * * @param parameters The group of parameter values. * @param descriptor The expected parameter descriptor. * @throws ParameterNotFoundException if a required parameter was not found. */ Stereographic(final ParameterValueGroup parameters, final ParameterDescriptorGroup descriptor) throws ParameterNotFoundException { // Fetch parameters super(parameters, descriptor.descriptors()); this.descriptor = descriptor; } /** * {@inheritDoc} */ public ParameterDescriptorGroup getParameterDescriptors() { return descriptor; } /** * Compares the specified object with this map projection for equality. */ @Override public boolean equals(final Object object) { /* * Implementation note: usually, we define this method in the last subclass, which may * compare every fields. However, all fields in subclasses like StereographicUSGS are * fully determined by the parameters like "latitude_of_origin", which are already * compared by super.equals(object). Comparing those derived fields would be redundant. */ if (object == this) { // Slight optimization return true; } if (super.equals(object)) { final Stereographic that = (Stereographic) object; return Utilities.equals(this.descriptor, that.descriptor); } return false; } ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// //////// //////// //////// PROVIDERS //////// //////// //////// ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// /** * The {@linkplain org.geotools.referencing.operation.MathTransformProvider math transform * provider} for a {@linkplain Stereographic Stereographic} projections using USGS equations. * This is <strong>not</strong> the provider for EPSG 9809. For the later, use * {@link ObliqueStereographic.Provider} instead. * * @since 2.4 * @version $Id$ * @author Rueben Schulz * * @see org.geotools.referencing.operation.DefaultMathTransformFactory */ public static class Provider extends AbstractProvider { /** * For compatibility with different versions during deserialization. */ private static final long serialVersionUID = 1243300263948365065L; /** * The localized name for stereographic projection. */ static final InternationalString NAME = Vocabulary.formatInternational(VocabularyKeys.STEREOGRAPHIC_PROJECTION); /** * The parameters group. */ static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { new NamedIdentifier(Citations.ESRI, "Stereographic"), new NamedIdentifier(Citations.GEOTIFF, "CT_Stereographic"), new NamedIdentifier(Citations.GEOTOOLS, NAME) }, new ParameterDescriptor[] { SEMI_MAJOR, SEMI_MINOR, CENTRAL_MERIDIAN, LATITUDE_OF_ORIGIN, SCALE_FACTOR, FALSE_EASTING, FALSE_NORTHING }); /** * Constructs a new provider with default parameters for EPSG stereographic oblique. */ public Provider() { this(PARAMETERS); } /** * Constructs a math transform provider from a set of parameters. The provider * {@linkplain #getIdentifiers identifiers} will be the same than the parameter * ones. * * @param parameters The set of parameters (never {@code null}). */ protected Provider(final ParameterDescriptorGroup parameters) { super(parameters); } /** * Returns the operation type for this map projection. */ @Override public Class<PlanarProjection> getOperationType() { return PlanarProjection.class; } /** * Creates a transform from the specified group of parameter values. * * @param parameters The group of parameter values. * @return The created math transform. * @throws ParameterNotFoundException if a required parameter was not found. */ protected MathTransform createMathTransform(final ParameterValueGroup parameters) throws ParameterNotFoundException { // Values here are in radians (the standard units for the map projection package) final double latitudeOfOrigin = abs(AbstractProvider.doubleValue(LATITUDE_OF_ORIGIN, parameters)); final boolean isSpherical = isSpherical(parameters); final ParameterDescriptorGroup descriptor = getParameters(); // Polar case. if (abs(latitudeOfOrigin - PI/2) < EPSILON) { if (isSpherical) { return new PolarStereographic.Spherical(parameters, descriptor, null); } else { return new PolarStereographic(parameters, descriptor, null); } } else // Equatorial case. if (latitudeOfOrigin < EPSILON) { if (isSpherical) { return new EquatorialStereographic.Spherical(parameters, descriptor); } else { return createMathTransform(parameters, descriptor); } } else // Generic (oblique) case. if (isSpherical) { return new StereographicUSGS.Spherical(parameters, descriptor); } else { return createMathTransform(parameters, descriptor); } } /** * Creates the general case. To be overriden by the EPSG case only. */ MathTransform createMathTransform(final ParameterValueGroup parameters, final ParameterDescriptorGroup descriptor) throws ParameterNotFoundException { return new StereographicUSGS(parameters, descriptor); } } }