/* * 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. *

* This implementation, and its subclasses, provides transforms for six cases of the * stereographic projection: *

*

* 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. *

* 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. *

* 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. *

* 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. *

* WARNING: 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. *

* 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"}. *

* 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. *

* 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°). *

* References: *

* * @see Stereographic projection on MathWorld * @see Polar_Stereographic * @see Oblique_Stereographic * @see Stereographic * @see Some Random Stereographic Issues * * @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 not 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 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); } } }