root / branches / F2 / libraries / libJCRS / src / org / geotools / referencing / operation / projection / TransverseMercator.java @ 12205
History | View | Annotate | Download (34.5 KB)
1 |
/*
|
---|---|
2 |
* GeoTools - OpenSource mapping toolkit
|
3 |
* http://geotools.org
|
4 |
*
|
5 |
* (C) 2003-2006, Geotools Project Managment Committee (PMC)
|
6 |
* (C) 2002, Centre for Computational Geography
|
7 |
* (C) 2001, Institut de Recherche pour le Dveloppement
|
8 |
* (C) 2000, Frank Warmerdam
|
9 |
* (C) 1999, Fisheries and Oceans Canada
|
10 |
*
|
11 |
* This library is free software; you can redistribute it and/or
|
12 |
* modify it under the terms of the GNU Lesser General Public
|
13 |
* License as published by the Free Software Foundation; either
|
14 |
* version 2.1 of the License, or (at your option) any later version.
|
15 |
*
|
16 |
* This library is distributed in the hope that it will be useful,
|
17 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
19 |
* Lesser General Public License for more details.
|
20 |
*
|
21 |
* This package contains formulas from the PROJ package of USGS.
|
22 |
* USGS's work is fully acknowledged here. This derived work has
|
23 |
* been relicensed under LGPL with Frank Warmerdam's permission.
|
24 |
*/
|
25 |
package org.geotools.referencing.operation.projection; |
26 |
|
27 |
// J2SE dependencies and extensions
|
28 |
import java.awt.geom.AffineTransform; |
29 |
import java.awt.geom.Point2D; |
30 |
import java.util.Collection; |
31 |
import javax.units.Unit; |
32 |
|
33 |
import javax.units.NonSI; |
34 |
|
35 |
// OpenGIS dependencies
|
36 |
import org.opengis.metadata.Identifier; |
37 |
import org.opengis.parameter.ParameterDescriptor; |
38 |
import org.opengis.parameter.ParameterDescriptorGroup; |
39 |
import org.opengis.parameter.ParameterNotFoundException; |
40 |
import org.opengis.parameter.ParameterValueGroup; |
41 |
import org.opengis.referencing.operation.CylindricalProjection; |
42 |
import org.opengis.referencing.operation.MathTransform; |
43 |
|
44 |
// Geotools dependencies
|
45 |
import org.geotools.metadata.iso.citation.CitationImpl; |
46 |
import org.geotools.referencing.NamedIdentifier; |
47 |
import org.geotools.referencing.operation.projection.CassiniSoldner.Provider; |
48 |
import org.geotools.referencing.operation.transform.ConcatenatedTransform; |
49 |
import org.geotools.referencing.operation.transform.ProjectiveTransform; |
50 |
//import org.geotools.resources.i18n.VocabularyKeys;
|
51 |
//import org.geotools.resources.i18n.Vocabulary;
|
52 |
import org.geotools.resources.cts.ResourceKeys; |
53 |
import org.geotools.resources.cts.Resources; |
54 |
|
55 |
|
56 |
/**
|
57 |
* Transverse Mercator Projection (EPSG code 9807). This
|
58 |
* is a cylindrical projection, in which the cylinder has been rotated 90.
|
59 |
* Instead of being tangent to the equator (or to an other standard latitude),
|
60 |
* it is tangent to a central meridian. Deformation are more important as we
|
61 |
* are going futher from the central meridian. The Transverse Mercator
|
62 |
* projection is appropriate for region wich have a greater extent north-south
|
63 |
* than east-west.
|
64 |
* <p>
|
65 |
*
|
66 |
* The elliptical equations used here are series approximations, and their accuracy
|
67 |
* decreases as points move farther from the central meridian of the projection.
|
68 |
* The forward equations here are accurate to a less than a mm ±10 degrees from
|
69 |
* the central meridian, a few mm ±15 degrees from the
|
70 |
* central meridian and a few cm ±20 degrees from the central meridian.
|
71 |
* The spherical equations are not approximations and should always give the
|
72 |
* correct values.
|
73 |
* <p>
|
74 |
*
|
75 |
* There are a number of versions of the transverse mercator projection
|
76 |
* including the Universal (UTM) and Modified (MTM) Transverses Mercator
|
77 |
* projections. In these cases the earth is divided into zones. For the UTM
|
78 |
* the zones are 6 degrees wide, numbered from 1 to 60 proceeding east from
|
79 |
* 180 degrees longitude, and between lats 84 degrees North and 80
|
80 |
* degrees South. The central meridian is taken as the center of the zone
|
81 |
* and the latitude of origin is the equator. A scale factor of 0.9996 and
|
82 |
* false easting of 500000m is used for all zones and a false northing of 10000000m
|
83 |
* is used for zones in the southern hemisphere.
|
84 |
* <p>
|
85 |
*
|
86 |
* NOTE: formulas used below are not those of Snyder, but rather those
|
87 |
* from the {@code proj4} package of the USGS survey, which
|
88 |
* have been reproduced verbatim. USGS work is acknowledged here.
|
89 |
* <p>
|
90 |
*
|
91 |
* <strong>References:</strong><ul>
|
92 |
* <li> Proj-4.4.6 available at <A HREF="http://www.remotesensing.org/proj">www.remotesensing.org/proj</A><br>
|
93 |
* Relevent files are: PJ_tmerc.c, pj_mlfn.c, pj_fwd.c and pj_inv.c </li>
|
94 |
* <li> John P. Snyder (Map Projections - A Working Manual,
|
95 |
* U.S. Geological Survey Professional Paper 1395, 1987)</li>
|
96 |
* <li> "Coordinate Conversions and Transformations including Formulas",
|
97 |
* EPSG Guidence Note Number 7, Version 19.</li>
|
98 |
* </ul>
|
99 |
*
|
100 |
* @see <A HREF="http://mathworld.wolfram.com/MercatorProjection.html">Transverse Mercator projection on MathWorld</A>
|
101 |
* @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/transverse_mercator.html">"Transverse_Mercator" on Remote Sensing</A>
|
102 |
*
|
103 |
* @since 2.1
|
104 |
* @source $URL: http://svn.geotools.org/geotools/tags/2.3.0/module/referencing/src/org/geotools/referencing/operation/projection/TransverseMercator.java $
|
105 |
* @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
|
106 |
* @author Andr Gosselin
|
107 |
* @author Martin Desruisseaux
|
108 |
* @author Rueben Schulz
|
109 |
*/
|
110 |
public class TransverseMercator extends MapProjection { |
111 |
|
112 |
private final double scaleFactorLocal; |
113 |
private final double initialLongitude; |
114 |
private final double zoneWidth; |
115 |
|
116 |
/**
|
117 |
* A derived quantity of excentricity, computed by <code>e' = (a鲲-b)/b = es/(1-es)</code>
|
118 |
* where <var>a</var> is the semi-major axis length and <var>b</bar> is the semi-minor axis
|
119 |
* length.
|
120 |
*/
|
121 |
private final double esp; |
122 |
|
123 |
/**
|
124 |
* Meridian distance at the {@code latitudeOfOrigin}.
|
125 |
* Used for calculations for the ellipsoid.
|
126 |
*/
|
127 |
private final double ml0; |
128 |
|
129 |
/**
|
130 |
* Constant needed for the <code>mlfn<code> method.
|
131 |
* Setup at construction time.
|
132 |
*/
|
133 |
private final double en0,en1,en2,en3,en4; |
134 |
|
135 |
/**
|
136 |
* Constants used to calculate {@link #en0}, {@link #en1},
|
137 |
* {@link #en2}, {@link #en3}, {@link #en4}.
|
138 |
*/
|
139 |
private static final double C00= 1.0, |
140 |
C02= 0.25,
|
141 |
C04= 0.046875,
|
142 |
C06= 0.01953125,
|
143 |
C08= 0.01068115234375,
|
144 |
C22= 0.75,
|
145 |
C44= 0.46875,
|
146 |
C46= 0.01302083333333333333,
|
147 |
C48= 0.00712076822916666666,
|
148 |
C66= 0.36458333333333333333,
|
149 |
C68= 0.00569661458333333333,
|
150 |
C88= 0.3076171875;
|
151 |
|
152 |
/**
|
153 |
* Contants used for the forward and inverse transform for the eliptical
|
154 |
* case of the Transverse Mercator.
|
155 |
*/
|
156 |
private static final double FC1= 1.00000000000000000000000, // 1/1 |
157 |
FC2= 0.50000000000000000000000, // 1/2 |
158 |
FC3= 0.16666666666666666666666, // 1/6 |
159 |
FC4= 0.08333333333333333333333, // 1/12 |
160 |
FC5= 0.05000000000000000000000, // 1/20 |
161 |
FC6= 0.03333333333333333333333, // 1/30 |
162 |
FC7= 0.02380952380952380952380, // 1/42 |
163 |
FC8= 0.01785714285714285714285; // 1/56 |
164 |
|
165 |
/**
|
166 |
* Relative iteration precision used in the <code>mlfn<code> method. This
|
167 |
* overrides the value in the MapProjection class.
|
168 |
*/
|
169 |
private static final double TOL = 1E-11; |
170 |
|
171 |
/**
|
172 |
* Constructs a new map projection from the supplied parameters.
|
173 |
*
|
174 |
* @param parameters The parameter values in standard units.
|
175 |
* @throws ParameterNotFoundException if a mandatory parameter is missing.
|
176 |
*/
|
177 |
protected TransverseMercator(final ParameterValueGroup parameters) |
178 |
throws ParameterNotFoundException
|
179 |
{ |
180 |
//Fetch parameters
|
181 |
super(parameters);
|
182 |
|
183 |
// Compute constants
|
184 |
esp = excentricitySquared / (1.0 - excentricitySquared);
|
185 |
|
186 |
double t;
|
187 |
en0 = C00 - excentricitySquared * (C02 + excentricitySquared * |
188 |
(C04 + excentricitySquared * (C06 + excentricitySquared * C08))); |
189 |
en1 = excentricitySquared * (C22 - excentricitySquared * |
190 |
(C04 + excentricitySquared * (C06 + excentricitySquared * C08))); |
191 |
en2 = (t = excentricitySquared * excentricitySquared) * |
192 |
(C44 - excentricitySquared * (C46 + excentricitySquared * C48)); |
193 |
en3 = (t *= excentricitySquared) * (C66 - excentricitySquared * C68); |
194 |
en4 = t * excentricitySquared * C88; |
195 |
ml0 = mlfn(latitudeOfOrigin, Math.sin(latitudeOfOrigin), Math.cos(latitudeOfOrigin)); |
196 |
|
197 |
final Collection expected = getParameterDescriptors().descriptors(); |
198 |
if (expected.contains(Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE)) {
|
199 |
initialLongitude = Math.abs(doubleValue(expected,
|
200 |
Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE, parameters)); |
201 |
ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE, initialLongitude, false);
|
202 |
} else {
|
203 |
// standard parallel is the equator (Plate Carree or Equirectangular)
|
204 |
initialLongitude = Double.NaN;
|
205 |
} |
206 |
if (expected.contains(Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH)) {
|
207 |
zoneWidth = Math.abs(doubleValue(expected,
|
208 |
Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH, parameters)); |
209 |
ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH, zoneWidth, false);
|
210 |
} else {
|
211 |
// standard parallel is the equator (Plate Carree or Equirectangular)
|
212 |
zoneWidth = Double.NaN;
|
213 |
} |
214 |
if (expected.contains(Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL)) {
|
215 |
scaleFactorLocal = Math.abs(doubleValue(expected,
|
216 |
Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL, parameters)); |
217 |
ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL, scaleFactorLocal, false);
|
218 |
} else {
|
219 |
// standard parallel is the equator (Plate Carree or Equirectangular)
|
220 |
scaleFactorLocal = Double.NaN;
|
221 |
} |
222 |
} |
223 |
|
224 |
/**
|
225 |
* {@inheritDoc}
|
226 |
*/
|
227 |
public ParameterDescriptorGroup getParameterDescriptors() {
|
228 |
return Provider.PARAMETERS; |
229 |
} |
230 |
|
231 |
/**
|
232 |
* Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians)
|
233 |
* and stores the result in {@code ptDst} (linear distance on a unit sphere).
|
234 |
*/
|
235 |
protected Point2D transformNormalized(double x, double y, Point2D ptDst) |
236 |
throws ProjectionException
|
237 |
{ |
238 |
double sinphi = Math.sin(y); |
239 |
double cosphi = Math.cos(y); |
240 |
|
241 |
double t = (Math.abs(cosphi)>EPS) ? sinphi/cosphi : 0; |
242 |
t *= t; |
243 |
double al = cosphi*x;
|
244 |
double als = al*al;
|
245 |
al /= Math.sqrt(1.0 - excentricitySquared * sinphi*sinphi); |
246 |
double n = esp * cosphi*cosphi;
|
247 |
|
248 |
/* NOTE: meridinal distance at latitudeOfOrigin is always 0 */
|
249 |
y = (mlfn(y, sinphi, cosphi) - ml0 + |
250 |
sinphi*al*x* |
251 |
FC2 * ( 1.0 +
|
252 |
FC4 * als * (5.0 - t + n*(9.0 + 4.0*n) + |
253 |
FC6 * als * (61.0 + t * (t - 58.0) + n*(270.0 - 330.0*t) + |
254 |
FC8 * als * (1385.0 + t * ( t*(543.0 - t) - 3111.0)))))); |
255 |
|
256 |
x = al*(FC1 + FC3 * als*(1.0 - t + n +
|
257 |
FC5 * als * (5.0 + t*(t - 18.0) + n*(14.0 - 58.0*t) + |
258 |
FC7 * als * (61.0+ t*(t*(179.0 - t) - 479.0 ))))); |
259 |
|
260 |
if (ptDst != null) { |
261 |
ptDst.setLocation(x,y); |
262 |
return ptDst;
|
263 |
} |
264 |
return new Point2D.Double(x,y); |
265 |
} |
266 |
|
267 |
/**
|
268 |
* Transforms the specified (<var>x</var>,<var>y</var>) coordinate
|
269 |
* and stores the result in {@code ptDst}.
|
270 |
*/
|
271 |
protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) |
272 |
throws ProjectionException
|
273 |
{ |
274 |
double phi = inv_mlfn(ml0 + y);
|
275 |
|
276 |
if (Math.abs(phi) >= (Math.PI/2)) { |
277 |
y = y<0.0 ? -(Math.PI/2) : (Math.PI/2); |
278 |
x = 0.0;
|
279 |
} else {
|
280 |
double sinphi = Math.sin(phi); |
281 |
double cosphi = Math.cos(phi); |
282 |
double t = (Math.abs(cosphi) > EPS) ? sinphi/cosphi : 0.0; |
283 |
double n = esp * cosphi*cosphi;
|
284 |
double con = 1.0 - excentricitySquared * sinphi*sinphi; |
285 |
double d = x*Math.sqrt(con); |
286 |
con *= t; |
287 |
t *= t; |
288 |
double ds = d*d;
|
289 |
|
290 |
y = phi - (con*ds / (1.0 - excentricitySquared)) *
|
291 |
FC2 * (1.0 - ds *
|
292 |
FC4 * (5.0 + t*(3.0 - 9.0*n) + n*(1.0 - 4*n) - ds * |
293 |
FC6 * (61.0 + t*(90.0 - 252.0*n + 45.0*t) + 46.0*n - ds * |
294 |
FC8 * (1385.0 + t*(3633.0 + t*(4095.0 + 1574.0*t)))))); |
295 |
|
296 |
x = d*(FC1 - ds * FC3 * (1.0 + 2.0*t + n - |
297 |
ds*FC5*(5.0 + t*(28.0 + 24* t + 8.0*n) + 6.0*n - |
298 |
ds*FC7*(61.0 + t*(662.0 + t*(1320.0 + 720.0*t))))))/cosphi; |
299 |
} |
300 |
|
301 |
if (ptDst != null) { |
302 |
ptDst.setLocation(x,y); |
303 |
return ptDst;
|
304 |
} |
305 |
return new Point2D.Double(x,y); |
306 |
} |
307 |
|
308 |
/**
|
309 |
* {@inheritDoc}
|
310 |
*/
|
311 |
protected double getToleranceForAssertions(final double longitude, final double latitude) { |
312 |
if (Math.abs(longitude - centralMeridian) > 0.26) { //15 degrees |
313 |
// When far from the valid area, use a larger tolerance.
|
314 |
return 2.5; |
315 |
} else if (Math.abs(longitude - centralMeridian) > 0.22) { //12.5 degrees |
316 |
return 1.0; |
317 |
} else if (Math.abs(longitude - centralMeridian) > 0.17) { //10 degrees |
318 |
return 0.5; |
319 |
} |
320 |
// a normal tolerance
|
321 |
return 1E-6; |
322 |
} |
323 |
|
324 |
|
325 |
/**
|
326 |
* Provides the transform equations for the spherical case of the
|
327 |
* TransverseMercator projection.
|
328 |
*
|
329 |
* @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
|
330 |
* @author Andr Gosselin
|
331 |
* @author Martin Desruisseaux
|
332 |
* @author Rueben Schulz
|
333 |
*/
|
334 |
private static final class Spherical extends TransverseMercator { |
335 |
/**
|
336 |
* Constructs a new map projection from the suplied parameters.
|
337 |
*
|
338 |
* @param parameters The parameter values in standard units.
|
339 |
* @throws ParameterNotFoundException if a mandatory parameter is missing.
|
340 |
*/
|
341 |
protected Spherical(final ParameterValueGroup parameters) |
342 |
throws ParameterNotFoundException
|
343 |
{ |
344 |
super(parameters);
|
345 |
//assert isSpherical;
|
346 |
} |
347 |
|
348 |
/**
|
349 |
* {@inheritDoc}
|
350 |
*/
|
351 |
protected Point2D transformNormalized(double x, double y, Point2D ptDst) |
352 |
throws ProjectionException
|
353 |
{ |
354 |
// Compute using ellipsoidal formulas, for comparaison later.
|
355 |
double normalizedLongitude = x;
|
356 |
//assert (ptDst = super.transformNormalized(x, y, ptDst)) != null;
|
357 |
|
358 |
double cosphi = Math.cos(y); |
359 |
double b = cosphi * Math.sin(x); |
360 |
if (Math.abs(Math.abs(b) - 1.0) <= EPS) { |
361 |
throw new ProjectionException(Resources.format(ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY)); |
362 |
} |
363 |
|
364 |
//Using Snyder's equation for calculating y, instead of the one used in Proj4
|
365 |
//poential problems when y and x = 90 degrees, but behaves ok in tests
|
366 |
y = Math.atan2(Math.tan(y),Math.cos(x)) - latitudeOfOrigin; /* Snyder 8-3 */ |
367 |
x = 0.5 * Math.log((1.0+b)/(1.0-b)); /* Snyder 8-1 */ |
368 |
|
369 |
//assert Math.abs(ptDst.getX()-x) <= getToleranceForSphereAssertions(normalizedLongitude,0) : ptDst.getX()-x;
|
370 |
//assert Math.abs(ptDst.getY()-y) <= getToleranceForSphereAssertions(normalizedLongitude,0) : ptDst.getY()-y;
|
371 |
if (ptDst != null) { |
372 |
ptDst.setLocation(x,y); |
373 |
return ptDst;
|
374 |
} |
375 |
return new Point2D.Double(x,y); |
376 |
} |
377 |
|
378 |
/**
|
379 |
* {@inheritDoc}
|
380 |
*/
|
381 |
protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) |
382 |
throws ProjectionException
|
383 |
{ |
384 |
// Compute using ellipsoidal formulas, for comparaison later.
|
385 |
//assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null;
|
386 |
|
387 |
double t = Math.exp(x); |
388 |
double sinhX = 0.5 * (t-1.0/t); //sinh(x) |
389 |
double cosD = Math.cos(latitudeOfOrigin + y); |
390 |
double phi = Math.asin(Math.sqrt((1.0-cosD*cosD)/(1.0+sinhX*sinhX))); |
391 |
// correct for the fact that we made everything positive using sqrt(x*x)
|
392 |
y = ((y + latitudeOfOrigin)<0.0) ? -phi : phi;
|
393 |
x = (Math.abs(sinhX)<=EPS && Math.abs(cosD)<=EPS) ? |
394 |
0.0 : Math.atan2(sinhX,cosD); |
395 |
|
396 |
//assert Math.abs(ptDst.getX()-x) <= getToleranceForSphereAssertions(x,0) : ptDst.getX()-x;
|
397 |
//assert Math.abs(ptDst.getY()-y) <= getToleranceForSphereAssertions(x,0) : ptDst.getY()-y;
|
398 |
if (ptDst != null) { |
399 |
ptDst.setLocation(x,y); |
400 |
return ptDst;
|
401 |
} |
402 |
return new Point2D.Double(x,y); |
403 |
} |
404 |
|
405 |
/*
|
406 |
* Allow a larger tolerance when comparing spherical to elliptical calculations
|
407 |
* when we are far from the central meridian (elliptical calculations are
|
408 |
* not valid here).
|
409 |
*
|
410 |
* longitude here is standardized (in radians) and centralMeridian has
|
411 |
* already been removed from it.
|
412 |
*/
|
413 |
protected double getToleranceForSphereAssertions(final double longitude, final double latitude) { |
414 |
if (Math.abs(Math.abs(longitude)- Math.PI/2.0) < TOL) { //90 degrees |
415 |
// elliptical equations are at their worst here
|
416 |
return 1E18; |
417 |
} |
418 |
if (Math.abs(longitude) > 0.26) { //15 degrees |
419 |
// When far from the valid area, use a very larger tolerance.
|
420 |
return 1000000; |
421 |
} |
422 |
// a normal tolerance
|
423 |
return 1E-6; |
424 |
} |
425 |
} |
426 |
|
427 |
|
428 |
/**
|
429 |
* Calculates the meridian distance. This is the distance along the central
|
430 |
* meridian from the equator to {@code phi}. Accurate to < 1e-5 meters
|
431 |
* when used in conjuction with typical major axis values.
|
432 |
*
|
433 |
* @param phi latitude to calculate meridian distance for.
|
434 |
* @param sphi sin(phi).
|
435 |
* @param cphi cos(phi).
|
436 |
* @return meridian distance for the given latitude.
|
437 |
*/
|
438 |
private final double mlfn(final double phi, double sphi, double cphi) { |
439 |
cphi *= sphi; |
440 |
sphi *= sphi; |
441 |
return en0 * phi - cphi *
|
442 |
(en1 + sphi * |
443 |
(en2 + sphi * |
444 |
(en3 + sphi * |
445 |
(en4)))); |
446 |
} |
447 |
|
448 |
/**
|
449 |
* Calculates the latitude ({@code phi}) from a meridian distance.
|
450 |
* Determines phi to TOL (1e-11) radians, about 1e-6 seconds.
|
451 |
*
|
452 |
* @param arg meridian distance to calulate latitude for.
|
453 |
* @return the latitude of the meridian distance.
|
454 |
* @throws ProjectionException if the itteration does not converge.
|
455 |
*/
|
456 |
private final double inv_mlfn(double arg) throws ProjectionException { |
457 |
double s, t, phi, k = 1.0/(1.0 - excentricitySquared); |
458 |
int i;
|
459 |
phi = arg; |
460 |
for (i=MAX_ITER; true;) { // rarely goes over 5 iterations |
461 |
if (--i < 0) { |
462 |
throw new ProjectionException(Resources.format(ResourceKeys.ERROR_NO_CONVERGENCE)); |
463 |
} |
464 |
s = Math.sin(phi);
|
465 |
t = 1.0 - excentricitySquared * s * s;
|
466 |
t = (mlfn(phi, s, Math.cos(phi)) - arg) * (t * Math.sqrt(t)) * k; |
467 |
phi -= t; |
468 |
if (Math.abs(t) < TOL) { |
469 |
return phi;
|
470 |
} |
471 |
} |
472 |
} |
473 |
|
474 |
/**
|
475 |
* Convenience method computing the zone code from the central meridian.
|
476 |
* Information about zones convention must be specified in argument. Two
|
477 |
* widely set of arguments are of Universal Transverse Mercator (UTM) and
|
478 |
* Modified Transverse Mercator (MTM) projections:<br>
|
479 |
* <p>
|
480 |
*
|
481 |
* UTM projection (zones numbered from 1 to 60):<br>
|
482 |
* <p>
|
483 |
* {@code getZone(-177, 6);}<br>
|
484 |
* <p>
|
485 |
* MTM projection (zones numbered from 1 to 120):<br>
|
486 |
* <p>
|
487 |
* {@code getZone(-52.5, -3);}<br>
|
488 |
*
|
489 |
* @param centralLongitudeZone1 Longitude in the middle of zone 1, in decimal degrees
|
490 |
* relative to Greenwich. Positive longitudes are toward east, and negative
|
491 |
* longitudes toward west.
|
492 |
* @param zoneWidth Number of degrees of longitudes in one zone. A positive value
|
493 |
* means that zones are numbered from west to east (i.e. in the direction of
|
494 |
* positive longitudes). A negative value means that zones are numbered from
|
495 |
* east to west.
|
496 |
* @return The zone number. First zone is numbered 1.
|
497 |
*/
|
498 |
private int getZone(final double centralLongitudeZone1, final double zoneWidth) { |
499 |
final double zoneCount = Math.abs(360/zoneWidth); |
500 |
double t;
|
501 |
t = centralLongitudeZone1 - 0.5*zoneWidth; // Longitude at the beginning of the first zone. |
502 |
t = Math.toDegrees(centralMeridian) - t; // Degrees of longitude between the central longitude and longitude 1. |
503 |
t = Math.floor(t/zoneWidth + EPS); // Number of zones between the central longitude and longitude 1. |
504 |
t -= zoneCount*Math.floor(t/zoneCount); // If negative, bring back to the interval 0 to (zoneCount-1). |
505 |
return ((int) t)+1; |
506 |
} |
507 |
|
508 |
/**
|
509 |
* Convenience method returning the meridian in the middle of current zone. This meridian is
|
510 |
* typically the central meridian. This method may be invoked to make sure that the central
|
511 |
* meridian is correctly set.
|
512 |
*
|
513 |
* @param centralLongitudeZone1 Longitude in the middle of zone 1, in decimal degrees
|
514 |
* relative to Greenwich. Positive longitudes are toward east, and negative
|
515 |
* longitudes toward west.
|
516 |
* @param zoneWidth Number of degrees of longitudes in one zone. A positive value
|
517 |
* means that zones are numbered from west to east (i.e. in the direction of
|
518 |
* positive longitudes). A negative value means that zones are numbered from
|
519 |
* east to west.
|
520 |
* @return The central meridian.
|
521 |
*/
|
522 |
private double getCentralMedirian(final double centralLongitudeZone1, final double zoneWidth) { |
523 |
double t;
|
524 |
t = centralLongitudeZone1 + (getZone(centralLongitudeZone1, zoneWidth)-1)*zoneWidth;
|
525 |
t -= 360*Math.floor((t+180)/360); // Bring back into [-180..+180] range. |
526 |
return t;
|
527 |
} |
528 |
|
529 |
/**
|
530 |
* Convenience method computing the zone code from the central meridian.
|
531 |
*
|
532 |
* @return The zone number, using the scalefactor and false easting
|
533 |
* to decide if this is a UTM or MTM case. Returns 0 if the
|
534 |
* case of the projection cannot be determined.
|
535 |
*/
|
536 |
public int getZone() { |
537 |
// UTM
|
538 |
if (scaleFactor == 0.9996 && falseEasting == 500000.0) { |
539 |
return getZone(-177, 6); |
540 |
} |
541 |
// MTM
|
542 |
if (scaleFactor == 0.9999 && falseEasting == 304800.0){ |
543 |
return getZone(-52.5, -3); |
544 |
} |
545 |
// unknown
|
546 |
throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_UNKNOW_TYPE_$1));//.UNKNOW_PROJECTION_TYPE)); |
547 |
} |
548 |
|
549 |
/**
|
550 |
* Convenience method returning the meridian in the middle of current zone. This meridian is
|
551 |
* typically the central meridian. This method may be invoked to make sure that the central
|
552 |
* meridian is correctly set.
|
553 |
*
|
554 |
* @return The central meridian, using the scalefactor and false easting
|
555 |
* to decide if this is a UTM or MTM case. Returns {@link Double#NaN}
|
556 |
* if the case of the projection cannot be determined.
|
557 |
*/
|
558 |
public double getCentralMeridian() { |
559 |
// UTM
|
560 |
if (scaleFactor == 0.9996 && falseEasting == 500000.0) { |
561 |
return getCentralMedirian(-177, 6); |
562 |
} |
563 |
// MTM
|
564 |
if (scaleFactor == 0.9999 && falseEasting == 304800.0){ |
565 |
return getCentralMedirian(-52.5, -3); |
566 |
} |
567 |
// unknown
|
568 |
throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_UNKNOW_TYPE_$1));//.UNKNOW_PROJECTION_TYPE)); |
569 |
} |
570 |
|
571 |
/**
|
572 |
* Returns a hash value for this projection.
|
573 |
*/
|
574 |
public int hashCode() { |
575 |
final long code = Double.doubleToLongBits(ml0); |
576 |
return ((int)code ^ (int)(code >>> 32)) + 37*super.hashCode(); |
577 |
} |
578 |
|
579 |
/**
|
580 |
* Compares the specified object with this map projection for equality.
|
581 |
*/
|
582 |
public boolean equals(final Object object) { |
583 |
if (object == this) { |
584 |
// Slight optimization
|
585 |
return true; |
586 |
} |
587 |
// Relevant parameters are already compared in MapProjection
|
588 |
return super.equals(object); |
589 |
} |
590 |
|
591 |
|
592 |
|
593 |
|
594 |
//////////////////////////////////////////////////////////////////////////////////////////
|
595 |
//////////////////////////////////////////////////////////////////////////////////////////
|
596 |
//////// ////////
|
597 |
//////// PROVIDER ////////
|
598 |
//////// ////////
|
599 |
//////////////////////////////////////////////////////////////////////////////////////////
|
600 |
//////////////////////////////////////////////////////////////////////////////////////////
|
601 |
|
602 |
/**
|
603 |
* The {@link org.geotools.referencing.operation.MathTransformProvider}
|
604 |
* for a {@link TransverseMercator} projection.
|
605 |
*
|
606 |
* @see org.geotools.referencing.operation.DefaultMathTransformFactory
|
607 |
*
|
608 |
* @since 2.1
|
609 |
* @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
|
610 |
* @author Martin Desruisseaux
|
611 |
* @author Rueben Schulz
|
612 |
*/
|
613 |
public static class Provider extends AbstractProvider { |
614 |
/**
|
615 |
* Returns a descriptor group for the specified parameters.
|
616 |
*/
|
617 |
static ParameterDescriptorGroup createDescriptorGroup(final Identifier[] identifiers) { |
618 |
return createDescriptorGroup(identifiers, new ParameterDescriptor[] { |
619 |
SEMI_MAJOR, SEMI_MINOR, |
620 |
CENTRAL_MERIDIAN, LATITUDE_OF_ORIGIN, |
621 |
SCALE_FACTOR, FALSE_EASTING, |
622 |
FALSE_NORTHING |
623 |
}); |
624 |
} |
625 |
|
626 |
/**
|
627 |
* The parameters group.
|
628 |
*/
|
629 |
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { |
630 |
new NamedIdentifier(CitationImpl.OGC, "Transverse_Mercator"), |
631 |
new NamedIdentifier(CitationImpl.EPSG, "Transverse Mercator"), |
632 |
new NamedIdentifier(CitationImpl.EPSG, "Gauss-Kruger"), |
633 |
new NamedIdentifier(CitationImpl.EPSG, "9807"), |
634 |
new NamedIdentifier(CitationImpl.GEOTIFF, "CT_TransverseMercator"), |
635 |
new NamedIdentifier(CitationImpl.ESRI, "Transverse_Mercator"), |
636 |
new NamedIdentifier(CitationImpl.ESRI, "Gauss_Kruger")//, |
637 |
//new NamedIdentifier(CitationImpl.GEOTOOLS, Vocabulary.formatInternational(
|
638 |
// VocabularyKeys.TRANSVERSE_MERCATOR_PROJECTION))
|
639 |
}); |
640 |
|
641 |
/**
|
642 |
* Constructs a new provider.
|
643 |
*/
|
644 |
public Provider() { |
645 |
super(PARAMETERS);
|
646 |
} |
647 |
|
648 |
/**
|
649 |
* Constructs a new provider with the specified parameters.
|
650 |
*/
|
651 |
Provider(final ParameterDescriptorGroup descriptor) { |
652 |
super(descriptor);
|
653 |
} |
654 |
|
655 |
/**
|
656 |
* Returns the operation type for this map projection.
|
657 |
*/
|
658 |
protected Class getOperationType() { |
659 |
return CylindricalProjection.class;
|
660 |
} |
661 |
|
662 |
/**
|
663 |
* Creates a transform from the specified group of parameter values.
|
664 |
*
|
665 |
* @param parameters The group of parameter values.
|
666 |
* @return The created math transform.
|
667 |
* @throws ParameterNotFoundException if a required parameter was not found.
|
668 |
*/
|
669 |
public MathTransform createMathTransform(final ParameterValueGroup parameters) |
670 |
throws ParameterNotFoundException
|
671 |
{ |
672 |
if (isSpherical(parameters)) {
|
673 |
return new Spherical(parameters); |
674 |
} else {
|
675 |
return new TransverseMercator(parameters); |
676 |
} |
677 |
} |
678 |
} |
679 |
|
680 |
/**
|
681 |
* The {@link org.geotools.referencing.operation.MathTransformProvider} for a South Orientated
|
682 |
* {@link TransverseMercator} projection (EPSG code 9808). Note that at the contrary of what
|
683 |
* this class name suggest, the projected coordinates are still increasing toward North. This
|
684 |
* is because all {@link MapProjection}s must complies with standard axis orientations. The
|
685 |
* real axis flip is performed by the CRS framework outside this package.
|
686 |
* See "<cite>Axis units and orientation</cite>" in
|
687 |
* {@linkplain org.geotools.referencing.operation.projection package description} for details.
|
688 |
* <p>
|
689 |
* The usual Transverse Mercator formulas are:
|
690 |
* <p>
|
691 |
* <ul>
|
692 |
* <li><var>easting</var> = <var>{@linkplain #falseEasting false easting}</var> + <var>px</var></li>
|
693 |
* <li><var>northing</var> = <var>{@linkplain #falseNorthing false northing}</var> + <var>py</var></li>
|
694 |
* </ul>
|
695 |
* <p>
|
696 |
* The Transverse Mercator South Orientated Projection formulas are:
|
697 |
* <p>
|
698 |
* <ul>
|
699 |
* <li><var>westing</var> = <var>{@linkplain #falseEasting false easting}</var> - <var>px</var></li>
|
700 |
* <li><var>southing</var> = <var>{@linkplain #falseNorthing false northing}</var> - <var>py</var></li>
|
701 |
* </ul>
|
702 |
* <p>
|
703 |
* Where the <var>px</var> and <var>py</var> terms are the same in both cases.
|
704 |
* Transforms created by this provider actually computes
|
705 |
* (<var>easting</var>,<var>northing</var>) = (-<var>westing</var>,-<var>southing</var>).
|
706 |
* This is equivalent to a {@link TransverseMercator} projection with
|
707 |
* {@link #falseEasting falseEasting} and {@link #falseNorthing falseNorthing} sign reverted.
|
708 |
* This operation is implemented as a concatenation of a North-orientated transverse mercator
|
709 |
* projection with an affine transform for (<var>false easting</var>,<var>false northing</var>)
|
710 |
* correction.
|
711 |
*
|
712 |
* @since 2.2
|
713 |
* @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
|
714 |
* @author Martin Desruisseaux
|
715 |
*/
|
716 |
public static class Provider_SouthOrientated extends Provider { |
717 |
/**
|
718 |
* Constructs a new provider.
|
719 |
*/
|
720 |
public Provider_SouthOrientated() {
|
721 |
super(createDescriptorGroup(new NamedIdentifier[] { |
722 |
new NamedIdentifier(CitationImpl.EPSG, "Transverse Mercator (South Orientated)"), |
723 |
new NamedIdentifier(CitationImpl.EPSG, "9808") |
724 |
})); |
725 |
} |
726 |
|
727 |
/**
|
728 |
* Creates a transform from the specified group of parameter values.
|
729 |
*
|
730 |
* @param parameters The group of parameter values.
|
731 |
* @return The created math transform.
|
732 |
* @throws ParameterNotFoundException if a required parameter was not found.
|
733 |
*/
|
734 |
public MathTransform createMathTransform(final ParameterValueGroup parameters) |
735 |
throws ParameterNotFoundException
|
736 |
{ |
737 |
final MapProjection projection = (MapProjection) super.createMathTransform(parameters); |
738 |
if (projection.falseEasting==0 && projection.falseNorthing==0) { |
739 |
return projection;
|
740 |
} |
741 |
final AffineTransform step = AffineTransform.getTranslateInstance( |
742 |
-2*projection.falseEasting, -2*projection.falseNorthing); |
743 |
return ConcatenatedTransform.create(ProjectiveTransform.create(step), projection);
|
744 |
} |
745 |
} |
746 |
public static class Provider_TransverseMercatorZonedGridSystem extends Provider { |
747 |
/**
|
748 |
* Constructs a new provider.
|
749 |
*/
|
750 |
public static final ParameterDescriptor INITIAL_LONGITUDE = createDescriptor( |
751 |
new NamedIdentifier[] { |
752 |
new NamedIdentifier(CitationImpl.OGC, "Initial longitude"), |
753 |
new NamedIdentifier(CitationImpl.EPSG, "Initial_longitude") |
754 |
}, 0.0, -180.0, 180.0, NonSI.DEGREE_ANGLE); |
755 |
|
756 |
public static final ParameterDescriptor ZONE_WIDTH = createDescriptor( |
757 |
new NamedIdentifier[] { |
758 |
new NamedIdentifier(CitationImpl.OGC, "Zone width"), |
759 |
new NamedIdentifier(CitationImpl.EPSG, "Zone_width") |
760 |
}, 0.0, 0.0, 12.0, NonSI.DEGREE_ANGLE); |
761 |
|
762 |
public static final ParameterDescriptor SCALE_FACTOR_LOCAL = createDescriptor( |
763 |
new NamedIdentifier[] { |
764 |
new NamedIdentifier(CitationImpl.OGC, "scale_factor"), |
765 |
new NamedIdentifier(CitationImpl.EPSG, "Scale factor on initial line"), |
766 |
new NamedIdentifier(CitationImpl.EPSG, "Scale factor at natural origin"), |
767 |
new NamedIdentifier(CitationImpl.GEOTIFF, "ScaleAtNatOrigin"), |
768 |
new NamedIdentifier(CitationImpl.GEOTIFF, "ScaleAtCenter") |
769 |
}, |
770 |
1, 0, Double.POSITIVE_INFINITY, Unit.ONE); |
771 |
|
772 |
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { |
773 |
new NamedIdentifier(CitationImpl.OGC, "Transverse_Mercator_Zoned_Grid_System"), |
774 |
new NamedIdentifier(CitationImpl.EPSG, "Transverse Mercator Zoned Grid System") |
775 |
// new NamedIdentifier(CitationImpl.GEOTOOLS, Vocabulary.formatInternational(
|
776 |
// VocabularyKeys.EQUIDISTANT_CYLINDRICAL_PROJECTION))
|
777 |
}, new ParameterDescriptor[] { |
778 |
SEMI_MAJOR, SEMI_MINOR, |
779 |
CENTRAL_MERIDIAN, |
780 |
INITIAL_LONGITUDE, ZONE_WIDTH, |
781 |
LATITUDE_OF_ORIGIN, SCALE_FACTOR_LOCAL, |
782 |
FALSE_EASTING, FALSE_NORTHING |
783 |
}); |
784 |
|
785 |
public Provider_TransverseMercatorZonedGridSystem() {
|
786 |
super(PARAMETERS);
|
787 |
} |
788 |
|
789 |
protected Class getOperationType() { |
790 |
return CylindricalProjection.class;
|
791 |
} |
792 |
|
793 |
/**
|
794 |
* Creates a transform from the specified group of parameter values.
|
795 |
*
|
796 |
* @param parameters The group of parameter values.
|
797 |
* @return The created math transform.
|
798 |
* @throws ParameterNotFoundException if a required parameter was not found.
|
799 |
*/
|
800 |
public MathTransform createMathTransform(final ParameterValueGroup parameters) |
801 |
throws ParameterNotFoundException
|
802 |
{ |
803 |
//return null;
|
804 |
return new CassiniSoldner(parameters); |
805 |
} |
806 |
} |
807 |
} |