root / org.gvsig.jcrs / libJCRS / src / org / geotools / referencing / operation / projection / IdrNewZealandMapGrid.java @ 38
History | View | Annotate | Download (17.6 KB)
1 |
/*
|
---|---|
2 |
* GeoTools - OpenSource mapping toolkit
|
3 |
* http://geotools.org
|
4 |
*
|
5 |
* (C) 2005-2006, Geotools Project Managment Committee (PMC)
|
6 |
*
|
7 |
* This library is free software; you can redistribute it and/or
|
8 |
* modify it under the terms of the GNU Lesser General Public
|
9 |
* License as published by the Free Software Foundation;
|
10 |
* version 2.1 of the License.
|
11 |
*
|
12 |
* This library is distributed in the hope that it will be useful,
|
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15 |
* Lesser General Public License for more details.
|
16 |
*/
|
17 |
package org.geotools.referencing.operation.projection; |
18 |
|
19 |
// J2SE dependencies
|
20 |
import java.awt.geom.Point2D; |
21 |
import java.util.Collection; |
22 |
import javax.units.NonSI; |
23 |
import javax.units.SI; |
24 |
import javax.units.Unit; |
25 |
|
26 |
import org.geotools.metadata.iso.citation.CitationImpl; |
27 |
import org.geotools.referencing.NamedIdentifier; |
28 |
import org.opengis.metadata.Identifier; |
29 |
import org.opengis.parameter.ParameterDescriptor; |
30 |
import org.opengis.parameter.ParameterDescriptorGroup; |
31 |
import org.opengis.parameter.ParameterNotFoundException; |
32 |
import org.opengis.parameter.ParameterValueGroup; |
33 |
import org.opengis.referencing.operation.MathTransform; |
34 |
import org.opengis.spatialschema.geometry.complex.Complex; |
35 |
|
36 |
|
37 |
/**
|
38 |
* Implementation of the NZMG (New Zealand Map Grid) projection.
|
39 |
* <p>
|
40 |
* This is an implementation of algorithm published by
|
41 |
* <a href="http://www.govt.nz/record?recordid=28">Land Information New Zealand</a>.
|
42 |
* The algorithm is documented <a href="http://www.linz.govt.nz/rcs/linz/6137/">here</a>.
|
43 |
*
|
44 |
* @since 2.2
|
45 |
* @source $URL: http://svn.geotools.org/geotools/tags/2.3.0/module/referencing/src/org/geotools/referencing/operation/projection/NewZealandMapGrid.java $
|
46 |
* @version $Id: IdrNewZealandMapGrid.java 12357 2007-06-27 09:05:41Z dguerrero $
|
47 |
* @author Justin Deoliveira
|
48 |
* @author Martin Desruisseaux
|
49 |
*
|
50 |
* @todo The algorithm uses complex numbers, which is not very well supported in Java. This
|
51 |
* implementation uses {@linkplain Complex} as a support class. Various instances of
|
52 |
* {@link Complex} are created once for ever at {@code NewZealandMapGrid} construction
|
53 |
* time, in order to avoid creating up to 6 objects for every point to be projected.
|
54 |
* The downside is that transformation methods must be synchronized. The cost should
|
55 |
* be small for simple applications, but may become important for multi-thread applications.
|
56 |
* Furthermore, those fields raise a slight serialization issue.
|
57 |
* <p>
|
58 |
* The most efficient fix in Java would be to expand inline all {@link Complex} operations
|
59 |
* like {@link Complex#add add} (easy), {@link Complex#multiply multiply} (more tedious),
|
60 |
* <cite>etc.</cite>, until we get a code using only {@code double} primitives on the stack
|
61 |
* and no {@link Complex} objects on the heap (except the {@code A} and {@code B} constants).
|
62 |
* But it would make the code significantly more complex and difficult to read.
|
63 |
* <p>
|
64 |
* An elegant fix would have been "lightweight objects" allocated on the stack (something
|
65 |
* similar to {@code struct} in C#), if such thing existed in the Java language.
|
66 |
*/
|
67 |
public class IdrNewZealandMapGrid extends MapProjection { |
68 |
/**
|
69 |
* For compatibility with different versions during deserialization.
|
70 |
*/
|
71 |
private static final long serialVersionUID = 8394817836243729133L; |
72 |
|
73 |
/**
|
74 |
* Coefficients for forward and inverse projection.
|
75 |
*/
|
76 |
/*private static final Complex[] A = {
|
77 |
new Complex( 0.7557853228, 0.0 ),
|
78 |
new Complex( 0.249204646, 0.003371507 ),
|
79 |
new Complex( -0.001541739, 0.041058560 ),
|
80 |
new Complex( -0.10162907, 0.01727609 ),
|
81 |
new Complex( -0.26623489, -0.36249218 ),
|
82 |
new Complex( -0.6870983, -1.1651967 )
|
83 |
};*/
|
84 |
|
85 |
/**
|
86 |
* Coefficients for inverse projection.
|
87 |
*/
|
88 |
/*private static final Complex[] B = {
|
89 |
new Complex( 1.3231270439, 0.0 ),
|
90 |
new Complex( -0.577245789, -0.007809598 ),
|
91 |
new Complex( 0.508307513, -0.112208952 ),
|
92 |
new Complex( -0.15094762, 0.18200602 ),
|
93 |
new Complex( 1.01418179, 1.64497696 ),
|
94 |
new Complex( 1.9660549, 2.5127645 )
|
95 |
};*/
|
96 |
|
97 |
/**
|
98 |
* Coefficients for inverse projection.
|
99 |
*/
|
100 |
private static final double[] TPHI = new double[] { |
101 |
1.5627014243, 0.5185406398, -0.03333098, -0.1052906, -0.0368594, 0.007317, |
102 |
0.01220, 0.00394, -0.0013 |
103 |
}; |
104 |
|
105 |
/**
|
106 |
* Coefficients for forward projection.
|
107 |
*/
|
108 |
private static final double[] TPSI = new double[] { |
109 |
0.6399175073, -0.1358797613, 0.063294409, -0.02526853, 0.0117879, |
110 |
-0.0055161, 0.0026906, -0.001333, 0.00067, -0.00034 |
111 |
}; |
112 |
|
113 |
/**
|
114 |
* A temporary complex number used during transform calculation. Created once for
|
115 |
* ever in order to avoid new object creation for every point to be transformed.
|
116 |
*/
|
117 |
/*private transient final Complex theta = new Complex();*/
|
118 |
|
119 |
/**
|
120 |
* An other temporary complex number created once for ever for the same reason than
|
121 |
* {@link #theta}. This number is usually equals to some other complex number raised
|
122 |
* to some power.
|
123 |
*/
|
124 |
/*private transient final Complex power = new Complex();*/
|
125 |
|
126 |
/**
|
127 |
* An other temporary complex number created once for ever for the same reason than
|
128 |
* {@link #theta}.
|
129 |
*
|
130 |
* @todo Need to reassign those fields on deserialization.
|
131 |
*/
|
132 |
|
133 |
/*private transient final Complex z=new Complex(), t=new Complex(),
|
134 |
num=new Complex(), denom=new Complex();*/
|
135 |
|
136 |
/**
|
137 |
* Constructs a new map projection with default parameter values.
|
138 |
*/
|
139 |
protected IdrNewZealandMapGrid() {
|
140 |
this((ParameterValueGroup) Provider.PARAMETERS.createValue()); |
141 |
} |
142 |
|
143 |
/**
|
144 |
* Constructs a new map projection from the supplied parameters.
|
145 |
*
|
146 |
* @param parameters The parameter values in standard units.
|
147 |
* @throws ParameterNotFoundException if a mandatory parameter is missing.
|
148 |
*/
|
149 |
protected IdrNewZealandMapGrid(final ParameterValueGroup parameters) |
150 |
throws ParameterNotFoundException
|
151 |
{ |
152 |
super(parameters);
|
153 |
} |
154 |
|
155 |
/**
|
156 |
* {@inheritDoc}
|
157 |
*/
|
158 |
public ParameterDescriptorGroup getParameterDescriptors() {
|
159 |
return Provider.PARAMETERS; |
160 |
} |
161 |
|
162 |
/**
|
163 |
* Must be overrided because {@link Provider} uses instances of
|
164 |
* {@link ModifiedParameterDescriptor}. This hack was needed because the New Zeland map
|
165 |
* projection uses particular default values for parameters like "False Easting", etc.
|
166 |
*/
|
167 |
/*final boolean isExpectedParameter(final Collection expected, final ParameterDescriptor param) {
|
168 |
return ModifiedParameterDescriptor.contains(expected, param);
|
169 |
}*/
|
170 |
|
171 |
/**
|
172 |
* Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians)
|
173 |
* and stores the result in {@code ptDst} (linear distance on a unit sphere).
|
174 |
*/
|
175 |
protected synchronized Point2D transformNormalized(final double x, final double y, |
176 |
final Point2D ptDst) |
177 |
throws ProjectionException
|
178 |
{ |
179 |
/*
|
180 |
final double dphi = (y - latitudeOfOrigin) * (180/Math.PI * 3600E-5);
|
181 |
double dphi_pow_i = dphi;
|
182 |
double dpsi = 0;
|
183 |
for (int i=0; i<TPSI.length; i++) {
|
184 |
dpsi += (TPSI[i] * dphi_pow_i);
|
185 |
dphi_pow_i *= dphi;
|
186 |
}
|
187 |
power.real = theta.real = dpsi;
|
188 |
power.imag = theta.imag = x;
|
189 |
z.multiply(A[0], power);
|
190 |
for (int i=1; i<A.length; i++) {
|
191 |
power.multiply(power, theta);
|
192 |
z.addMultiply(z, A[i], power);
|
193 |
}
|
194 |
if (ptDst != null) {
|
195 |
ptDst.setLocation(z.imag, z.real);
|
196 |
return ptDst;
|
197 |
}
|
198 |
return new Point2D.Double(z.imag, z.real);
|
199 |
*/
|
200 |
return new Point2D.Double(0.0, 0.0); |
201 |
} |
202 |
|
203 |
/**
|
204 |
* Transforms the specified (<var>x</var>,<var>y</var>) coordinate
|
205 |
* and stores the result in {@code ptDst}.
|
206 |
*/
|
207 |
protected synchronized Point2D inverseTransformNormalized(final double x, final double y, |
208 |
final Point2D ptDst) |
209 |
throws ProjectionException
|
210 |
{ |
211 |
/*
|
212 |
power.real = z.real = y;
|
213 |
power.imag = z.imag = x;
|
214 |
theta.multiply(B[0], z);
|
215 |
for (int j=1; j<B.length; j++) {
|
216 |
power.multiply(power, z);
|
217 |
theta.addMultiply(theta, B[j], power);
|
218 |
}
|
219 |
// increasing the number of iterations through this loop decreases
|
220 |
// the error in the calculation, but 3 iterations gives 10-3 accuracy
|
221 |
for (int j=0; j<3; j++) {
|
222 |
power.power(theta, 2);
|
223 |
num.addMultiply(z, A[1], power);
|
224 |
for (int k=2; k<A.length; k++) {
|
225 |
power.multiply(power, theta);
|
226 |
t.multiply(A[k], power);
|
227 |
t.multiply(t, k);
|
228 |
num.add(num, t);
|
229 |
}
|
230 |
|
231 |
power.real = 1;
|
232 |
power.imag = 0;
|
233 |
denom.copy(A[0]);
|
234 |
for (int k=1; k<A.length; k++) {
|
235 |
power.multiply(power, theta);
|
236 |
t.multiply(A[k], power);
|
237 |
t.multiply(t, k+1);
|
238 |
denom.add(denom, t);
|
239 |
}
|
240 |
theta.divide(num, denom);
|
241 |
}
|
242 |
|
243 |
final double dpsi = theta.real;
|
244 |
double dpsi_pow_i = dpsi;
|
245 |
double dphi = TPHI[0] * dpsi;
|
246 |
for (int i=1; i<TPHI.length; i++) {
|
247 |
dpsi_pow_i *= dpsi;
|
248 |
dphi += (TPHI[i] * dpsi_pow_i);
|
249 |
}
|
250 |
|
251 |
dphi = dphi / (180/Math.PI * 3600E-5) + latitudeOfOrigin;
|
252 |
if (ptDst != null) {
|
253 |
ptDst.setLocation(theta.imag, dphi);
|
254 |
return ptDst;
|
255 |
}
|
256 |
return new Point2D.Double(theta.imag, dphi);
|
257 |
*/
|
258 |
return new Point2D.Double(0.0,0.0); |
259 |
} |
260 |
|
261 |
/**
|
262 |
* The {@link org.geotools.referencing.operation.MathTransformProvider} for
|
263 |
* {@linkplain IdrNewZealandMapGrid New Zealand Map Grid}.
|
264 |
*
|
265 |
* @since 2.2
|
266 |
* @version $Id: IdrNewZealandMapGrid.java 12357 2007-06-27 09:05:41Z dguerrero $
|
267 |
* @author Justin Deoliveira
|
268 |
*/
|
269 |
public static class Provider extends AbstractProvider { |
270 |
/**
|
271 |
* For compatibility with different versions during deserialization.
|
272 |
*/
|
273 |
private static final long serialVersionUID = -7716733400419275656L; |
274 |
|
275 |
|
276 |
/**
|
277 |
* The operation parameter descriptor for the {@link #semiMajor semiMajor} parameter value.
|
278 |
* Valid values range is from 0 to infinity. This parameter is mandatory.
|
279 |
*
|
280 |
* @todo Would like to start range from 0 <u>exclusive</u>.
|
281 |
*/
|
282 |
public static final ParameterDescriptor SEMI_MAJOR_LOCAL = createDescriptor( |
283 |
new NamedIdentifier[] { |
284 |
new NamedIdentifier(CitationImpl.OGC, "semi_major"), |
285 |
new NamedIdentifier(CitationImpl.EPSG, "semi-major axis") //epsg does not specifically define this parameter |
286 |
}, |
287 |
6378388.0, 6378388.0, 6378388.0, SI.METER); |
288 |
|
289 |
/**
|
290 |
* The operation parameter descriptor for the {@link #semiMinor semiMinor} parameter value.
|
291 |
* Valid values range is from 0 to infinity. This parameter is mandatory.
|
292 |
*
|
293 |
* @todo Would like to start range from 0 <u>exclusive</u>.
|
294 |
*/
|
295 |
public static final ParameterDescriptor SEMI_MINOR_LOCAL = createDescriptor( |
296 |
new NamedIdentifier[] { |
297 |
new NamedIdentifier(CitationImpl.OGC, "semi_minor"), |
298 |
new NamedIdentifier(CitationImpl.EPSG, "semi-minor axis") //epsg does not specifically define this parameter |
299 |
}, |
300 |
6378388.0*(1-1/297.0), 6378388.0*(1-1/297.0), 6378388.0*(1-1/297.0), SI.METER); |
301 |
|
302 |
/**
|
303 |
* The operation parameter descriptor for the {@link #centralMeridian centralMeridian}
|
304 |
* parameter value. Valid values range is from -180 to 180. Default value is 0.
|
305 |
*/
|
306 |
public static final ParameterDescriptor CENTRAL_MERIDIAN_LOCAL = createDescriptor( |
307 |
new NamedIdentifier[] { |
308 |
new NamedIdentifier(CitationImpl.OGC, "central_meridian"), |
309 |
new NamedIdentifier(CitationImpl.EPSG, "Longitude of natural origin"), |
310 |
new NamedIdentifier(CitationImpl.EPSG, "Longitude of false origin"), |
311 |
new NamedIdentifier(CitationImpl.ESRI, "Longitude_Of_Origin"), |
312 |
new NamedIdentifier(CitationImpl.ESRI, "Longitude_Of_Center"), //ESRI uses this in orthographic (not to be confused with Longitude_Of_Center in oblique mercator) |
313 |
new NamedIdentifier(CitationImpl.GEOTIFF, "NatOriginLong") |
314 |
}, |
315 |
173.0, 173.0, 173.0, NonSI.DEGREE_ANGLE); |
316 |
|
317 |
/**
|
318 |
* The operation parameter descriptor for the {@link #latitudeOfOrigin latitudeOfOrigin}
|
319 |
* parameter value. Valid values range is from -90 to 90. Default value is 0.
|
320 |
*/
|
321 |
public static final ParameterDescriptor LATITUDE_OF_ORIGIN_LOCAL = createDescriptor( |
322 |
new NamedIdentifier[] { |
323 |
new NamedIdentifier(CitationImpl.OGC, "latitude_of_origin"), |
324 |
new NamedIdentifier(CitationImpl.EPSG, "Latitude of false origin"), |
325 |
new NamedIdentifier(CitationImpl.EPSG, "Latitude of natural origin"), |
326 |
new NamedIdentifier(CitationImpl.ESRI, "Latitude_Of_Center"), //ESRI uses this in orthographic |
327 |
new NamedIdentifier(CitationImpl.GEOTIFF, "NatOriginLat") |
328 |
}, |
329 |
-41.0, -41.0, -41.0, NonSI.DEGREE_ANGLE); |
330 |
|
331 |
/**
|
332 |
* The operation parameter descriptor for the {@link #falseEasting falseEasting}
|
333 |
* parameter value. Valid values range is unrestricted. Default value is 0.
|
334 |
*/
|
335 |
public static final ParameterDescriptor FALSE_EASTING_LOCAL = createDescriptor( |
336 |
new NamedIdentifier[] { |
337 |
new NamedIdentifier(CitationImpl.OGC, "false_easting"), |
338 |
new NamedIdentifier(CitationImpl.EPSG, "False easting"), |
339 |
new NamedIdentifier(CitationImpl.EPSG, "Easting at false origin"), |
340 |
new NamedIdentifier(CitationImpl.GEOTIFF, "FalseEasting") |
341 |
}, |
342 |
2510000.0, 2510000.0, 2510000.0, SI.METER); |
343 |
|
344 |
/**
|
345 |
* The operation parameter descriptor for the {@link #falseNorthing falseNorthing}
|
346 |
* parameter value. Valid values range is unrestricted. Default value is 0.
|
347 |
*/
|
348 |
public static final ParameterDescriptor FALSE_NORTHING_LOCAL = createDescriptor( |
349 |
new NamedIdentifier[] { |
350 |
new NamedIdentifier(CitationImpl.OGC, "false_northing"), |
351 |
new NamedIdentifier(CitationImpl.EPSG, "False northing"), |
352 |
new NamedIdentifier(CitationImpl.EPSG, "Northing at false origin"), |
353 |
new NamedIdentifier(CitationImpl.GEOTIFF, "FalseNorthing") |
354 |
}, |
355 |
6023150.0, 6023150.0, 6023150.0, SI.METER); |
356 |
|
357 |
/**
|
358 |
* The parameters group.
|
359 |
*/
|
360 |
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new Identifier[] { |
361 |
new NamedIdentifier(CitationImpl.OGC, "New_Zealand_Map_Grid"), |
362 |
new NamedIdentifier(CitationImpl.EPSG, "New Zealand Map Grid"), |
363 |
new NamedIdentifier(CitationImpl.EPSG, "27200"), |
364 |
new NamedIdentifier(new CitationImpl("IDR"), "IDR") |
365 |
}, |
366 |
new ParameterDescriptor[] { |
367 |
SEMI_MAJOR_LOCAL, SEMI_MINOR_LOCAL, |
368 |
LATITUDE_OF_ORIGIN_LOCAL, CENTRAL_MERIDIAN_LOCAL, |
369 |
FALSE_EASTING_LOCAL, FALSE_NORTHING_LOCAL |
370 |
}); |
371 |
/*new ParameterDescriptor[] {
|
372 |
new ModifiedParameterDescriptor(SEMI_MAJOR_L, 6378388.0),
|
373 |
new ModifiedParameterDescriptor(SEMI_MINOR, 6378388.0*(1-1/297.0)),
|
374 |
new ModifiedParameterDescriptor(LATITUDE_OF_ORIGIN, -41.0),
|
375 |
new ModifiedParameterDescriptor(CENTRAL_MERIDIAN, 173.0),
|
376 |
new ModifiedParameterDescriptor(FALSE_EASTING, 2510000.0),
|
377 |
new ModifiedParameterDescriptor(FALSE_NORTHING, 6023150.0)*/
|
378 |
|
379 |
|
380 |
/**
|
381 |
* Constructs a new provider.
|
382 |
*/
|
383 |
public Provider() { |
384 |
super(PARAMETERS);
|
385 |
} |
386 |
|
387 |
/**
|
388 |
* Creates a transform from the specified group of parameter values. This method doesn't
|
389 |
* check for the spherical case, since the New Zealand Map Grid projection is used with
|
390 |
* the International 1924 ellipsoid.
|
391 |
*
|
392 |
* @param parameters The group of parameter values.
|
393 |
* @return The created math transform.
|
394 |
* @throws ParameterNotFoundException if a required parameter was not found.
|
395 |
*/
|
396 |
public MathTransform createMathTransform(final ParameterValueGroup parameters) |
397 |
throws ParameterNotFoundException
|
398 |
{ |
399 |
return new IdrNewZealandMapGrid(parameters); |
400 |
} |
401 |
} |
402 |
} |