001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2001, Institut de Recherche pour le Développement
007: * (C) 2000, Frank Warmerdam
008: * (C) 1999, Fisheries and Oceans Canada
009: *
010: * This library is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU Lesser General Public
012: * License as published by the Free Software Foundation; either
013: * version 2.1 of the License, or (at your option) any later version.
014: *
015: * This library is distributed in the hope that it will be useful,
016: * but WITHOUT ANY WARRANTY; without even the implied warranty of
017: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018: * Lesser General Public License for more details.
019: *
020: * This package contains formulas from the PROJ package of USGS.
021: * USGS's work is fully acknowledged here. This derived work has
022: * been relicensed under LGPL with Frank Warmerdam's permission.
023: */
024: package org.geotools.referencing.operation.projection;
025:
026: // J2SE dependencies and extensions
027: import javax.units.NonSI;
028:
029: // OpenGIS dependencies
030: import org.opengis.util.InternationalString;
031: import org.opengis.parameter.ParameterDescriptor;
032: import org.opengis.parameter.ParameterDescriptorGroup;
033: import org.opengis.parameter.ParameterNotFoundException;
034: import org.opengis.parameter.ParameterValueGroup;
035: import org.opengis.referencing.operation.MathTransform;
036: import org.opengis.referencing.operation.PlanarProjection;
037:
038: // Geotools dependencies
039: import org.geotools.metadata.iso.citation.Citations;
040: import org.geotools.referencing.NamedIdentifier;
041: import org.geotools.resources.Utilities;
042: import org.geotools.resources.i18n.Vocabulary;
043: import org.geotools.resources.i18n.VocabularyKeys;
044:
045: /**
046: * Stereographic Projection. The directions starting from the central point are true,
047: * but the areas and the lengths become increasingly deformed as one moves away from
048: * the center. This projection is used to represent polar areas. It can be adapted
049: * for other areas having a circular form.
050: * <p>
051: *
052: * This implementation, and its subclasses, provides transforms for six cases of the
053: * stereographic projection:
054: * <ul>
055: * <li>{@code "Oblique_Stereographic"} (EPSG code 9809), alias {@code "Double_Stereographic"}
056: * in ESRI software</li>
057: * <li>{@code "Stereographic"} in ESRI software (<strong>NOT</strong> EPSG code 9809)</li>
058: * <li>{@code "Polar_Stereographic"} (EPSG code 9810, uses a series calculation for the
059: * inverse)</li>
060: * <li>{@code "Polar_Stereographic (variant B)"} (EPSG code 9829, uses a series calculation
061: * for the inverse)</li>
062: * <li>{@code "Stereographic_North_Pole"} in ESRI software (uses iteration for the inverse)</li>
063: * <li>{@code "Stereographic_South_Pole"} in ESRI software (uses iteration for the inverse)</li>
064: * </ul>
065: *
066: * Both the {@code "Oblique_Stereographic"} and {@code "Stereographic"}
067: * projections are "double" projections involving two parts: 1) a conformal
068: * transformation of the geographic coordinates to a sphere and 2) a spherical
069: * Stereographic projection. The EPSG considers both methods to be valid, but
070: * considers them to be a different coordinate operation methods.
071: * <p>
072: *
073: * The {@code "Stereographic"} case uses the USGS equations of Snyder.
074: * This employs a simplified conversion to the conformal sphere that
075: * computes the conformal latitude of each point on the sphere.
076: * <p>
077: *
078: * The {@code "Oblique_Stereographic"} case uses equations from the EPSG.
079: * This uses a more generalized form of the conversion to the conformal sphere; using only
080: * a single conformal sphere at the origin point. Since this is a "double" projection,
081: * it is sometimes called the "Double Stereographic". The {@code "Oblique_Stereographic"}
082: * is used in New Brunswick (Canada) and the Netherlands.
083: * <p>
084: *
085: * The {@code "Stereographic"} and {@code "Double_Stereographic"} names are
086: * used in ESRI's ArcGIS 8.x product. The {@code "Oblique_Stereographic"}
087: * name is the EPSG name for the later only.
088: * <p>
089: *
090: * <strong>WARNING:</strong> Tests points calculated with ArcGIS's {@code "Double_Stereographic"}
091: * are not always equal to points calculated with the {@code "Oblique_Stereographic"}.
092: * However, where there are differences, two different implementations of these equations
093: * (EPSG guidence note 7 and {@code libproj}) calculate the same values as we do. Until these
094: * differences are resolved, please be careful when using this projection.
095: * <p>
096: *
097: * If a {@link Stereographic.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} parameter is
098: * supplied and is not consistent with the projection classification (for example a latitude
099: * different from ±90° for the polar case), then the oblique or polar case will be
100: * automatically inferred from the latitude. In other words, the latitude of origin has
101: * precedence on the projection classification. If ommited, then the default value is 90°N
102: * for {@code "Polar_Stereographic"} and 0° for {@code "Oblique_Stereographic"}.
103: * <p>
104: *
105: * Polar projections that use the series equations for the inverse calculation will
106: * be little bit faster, but may be a little bit less accurate. If a polar
107: * {@link Stereographic.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} is used for
108: * the {@code "Oblique_Stereographic"} or {@code "Stereographic"}, the iterative
109: * equations will be used for inverse polar calculations.
110: * <p>
111: *
112: * The {@code "Polar Stereographic (variant B)"}, {@code "Stereographic_North_Pole"},
113: * and {@code "Stereographic_South_Pole"} cases include a
114: * {@link StereographicPole.ProviderB#STANDARD_PARALLEL "standard_parallel_1"} parameter.
115: * This parameter sets the latitude with a scale factor equal to the supplied
116: * scale factor. The {@code "Polar Stereographic (variant A)"} receives its
117: * {@code "latitude_of_origin"} parameter value from the hemisphere of the
118: * {@link StereographicPole.Provider#LATITUDE_OF_ORIGIN "latitude_of_origin"} value
119: * (i.e. the value is forced to ±90°).
120: * <p>
121: *
122: * <strong>References:</strong><ul>
123: * <li>John P. Snyder (Map Projections - A Working Manual,<br>
124: * U.S. Geological Survey Professional Paper 1395, 1987)</li>
125: * <li>"Coordinate Conversions and Transformations including Formulas",<br>
126: * EPSG Guidence Note Number 7, Version 19.</li>
127: * <li>Gerald Evenden. <A HREF="http://members.bellatlantic.net/~vze2hc4d/proj4/sterea.pdf">
128: * "Supplementary PROJ.4 Notes - Oblique Stereographic Alternative"</A></li>
129: * <li>Krakiwsky, E.J., D.B. Thomson, and R.R. Steeves. 1977. A Manual
130: * For Geodetic Coordinate Transformations in the Maritimes.
131: * Geodesy and Geomatics Engineering, UNB. Technical Report No. 48.</li>
132: * <li>Thomson, D.B., M.P. Mepham and R.R. Steeves. 1977.
133: * The Stereographic Double Projection.
134: * Geodesy and Geomatics Engineereng, UNB. Technical Report No. 46.</li>
135: * </ul>
136: *
137: * @see <A HREF="http://mathworld.wolfram.com/StereographicProjection.html">Stereographic projection on MathWorld</A>
138: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/polar_stereographic.html">Polar_Stereographic</A>
139: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/oblique_stereographic.html">Oblique_Stereographic</A>
140: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/stereographic.html">Stereographic</A>
141: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/random_issues.html#stereographic">Some Random Stereographic Issues</A>
142: *
143: * @since 2.1
144: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Stereographic.java $
145: * @version $Id: Stereographic.java 25697 2007-05-31 14:26:35Z desruisseaux $
146: * @author André Gosselin
147: * @author Martin Desruisseaux
148: * @author Rueben Schulz
149: */
150: public abstract class Stereographic extends MapProjection {
151: /**
152: * Maximum difference allowed when comparing real numbers.
153: */
154: private static final double EPSILON = 1E-6;
155:
156: /**
157: * The parameter descriptor group to be returned by {@link #getParameterDescriptors()}.
158: */
159: private final ParameterDescriptorGroup descriptor;
160:
161: /**
162: * Creates a transform from the specified group of parameter values.
163: *
164: * @param parameters The group of parameter values.
165: * @param descriptor The expected parameter descriptor.
166: * @throws ParameterNotFoundException if a required parameter was not found.
167: */
168: Stereographic(final ParameterValueGroup parameters,
169: final ParameterDescriptorGroup descriptor)
170: throws ParameterNotFoundException {
171: // Fetch parameters
172: super (parameters, descriptor.descriptors());
173: this .descriptor = descriptor;
174: }
175:
176: /**
177: * {@inheritDoc}
178: */
179: public ParameterDescriptorGroup getParameterDescriptors() {
180: return descriptor;
181: }
182:
183: /**
184: * Compares the specified object with this map projection for equality.
185: */
186: public boolean equals(final Object object) {
187: /*
188: * Implementation note: usually, we define this method in the last subclass, which may
189: * compare every fields. However, all fields in subclasses like StereographicUSGS are
190: * fully determined by the parameters like "latitude_of_origin", which are already
191: * compared by super.equals(object). Comparing those derived fields would be redundant.
192: */
193: if (object == this ) {
194: // Slight optimization
195: return true;
196: }
197: if (super .equals(object)) {
198: final Stereographic that = (Stereographic) object;
199: return Utilities.equals(this .descriptor, that.descriptor);
200: }
201: return false;
202: }
203:
204: //////////////////////////////////////////////////////////////////////////////////////////
205: //////////////////////////////////////////////////////////////////////////////////////////
206: //////// ////////
207: //////// PROVIDERS ////////
208: //////// ////////
209: //////////////////////////////////////////////////////////////////////////////////////////
210: //////////////////////////////////////////////////////////////////////////////////////////
211:
212: /**
213: * The {@linkplain org.geotools.referencing.operation.MathTransformProvider math transform
214: * provider} for a {@linkplain Stereographic Stereographic} projections using USGS equations.
215: * This is <strong>not</strong> the provider for EPSG 9809. For the later, use
216: * {@link ObliqueStereographic.Provider} instead.
217: *
218: * @since 2.4
219: * @version $Id: Stereographic.java 25697 2007-05-31 14:26:35Z desruisseaux $
220: * @author Rueben Schulz
221: *
222: * @see org.geotools.referencing.operation.DefaultMathTransformFactory
223: */
224: public static class Provider extends AbstractProvider {
225: /**
226: * The localized name for stereographic projection.
227: */
228: static final InternationalString NAME = Vocabulary
229: .formatInternational(VocabularyKeys.STEREOGRAPHIC_PROJECTION);
230:
231: /**
232: * The parameters group.
233: */
234: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
235: new NamedIdentifier[] {
236: new NamedIdentifier(Citations.ESRI,
237: "Stereographic"),
238: new NamedIdentifier(Citations.GEOTIFF,
239: "CT_Stereographic"),
240: new NamedIdentifier(Citations.GEOTOOLS, NAME) },
241: new ParameterDescriptor[] { SEMI_MAJOR, SEMI_MINOR,
242: CENTRAL_MERIDIAN, LATITUDE_OF_ORIGIN,
243: SCALE_FACTOR, FALSE_EASTING, FALSE_NORTHING });
244:
245: /**
246: * Constructs a new provider with default parameters for EPSG stereographic oblique.
247: */
248: public Provider() {
249: this (PARAMETERS);
250: }
251:
252: /**
253: * Constructs a math transform provider from a set of parameters. The provider
254: * {@linkplain #getIdentifiers identifiers} will be the same than the parameter
255: * ones.
256: *
257: * @param parameters The set of parameters (never {@code null}).
258: */
259: protected Provider(final ParameterDescriptorGroup parameters) {
260: super (parameters);
261: }
262:
263: /**
264: * Returns the operation type for this map projection.
265: */
266: public Class getOperationType() {
267: return PlanarProjection.class;
268: }
269:
270: /**
271: * Creates a transform from the specified group of parameter values.
272: *
273: * @param parameters The group of parameter values.
274: * @return The created math transform.
275: * @throws ParameterNotFoundException if a required parameter was not found.
276: */
277: protected MathTransform createMathTransform(
278: final ParameterValueGroup parameters)
279: throws ParameterNotFoundException {
280: // Values here are in radians (the standard units for the map projection package)
281: final double latitudeOfOrigin = Math.abs(doubleValue(
282: LATITUDE_OF_ORIGIN, parameters));
283: final boolean isSpherical = isSpherical(parameters);
284: final ParameterDescriptorGroup descriptor = getParameters();
285: // Polar case.
286: if (Math.abs(latitudeOfOrigin - Math.PI / 2) < EPSILON) {
287: if (isSpherical) {
288: return new PolarStereographic.Spherical(parameters,
289: descriptor, null);
290: } else {
291: return new PolarStereographic(parameters,
292: descriptor, null);
293: }
294: } else
295: // Equatorial case.
296: if (latitudeOfOrigin < EPSILON) {
297: if (isSpherical) {
298: return new EquatorialStereographic.Spherical(
299: parameters, descriptor);
300: } else {
301: // return new EquatorialStereographic(parameters, descriptor);
302: return createMathTransform(parameters, descriptor);
303: }
304: } else
305: // Generic (oblique) case.
306: if (isSpherical) {
307: return new StereographicUSGS.Spherical(parameters,
308: descriptor);
309: } else {
310: return createMathTransform(parameters, descriptor);
311: }
312: }
313:
314: /**
315: * Creates the general case. To be overriden by the EPSG case only.
316: */
317: MathTransform createMathTransform(
318: final ParameterValueGroup parameters,
319: final ParameterDescriptorGroup descriptor)
320: throws ParameterNotFoundException {
321: return new StereographicUSGS(parameters, descriptor);
322: }
323: }
324: }
|