1 | /******************************************************************************* |
---|
2 | NAME NEW ZEALAND MAP GRID |
---|
3 | |
---|
4 | PURPOSE: Transforms input longitude and latitude to Easting and |
---|
5 | Northing for the New Zealand Map Grid projection. The |
---|
6 | longitude and latitude must be in radians. The Easting |
---|
7 | and Northing values will be returned in meters. |
---|
8 | |
---|
9 | |
---|
10 | ALGORITHM REFERENCES |
---|
11 | |
---|
12 | 1. Department of Land and Survey Technical Circular 1973/32 |
---|
13 | http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf |
---|
14 | |
---|
15 | 2. OSG Technical Report 4.1 |
---|
16 | http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf |
---|
17 | |
---|
18 | |
---|
19 | IMPLEMENTATION NOTES |
---|
20 | |
---|
21 | The two references use different symbols for the calculated values. This |
---|
22 | implementation uses the variable names similar to the symbols in reference [1]. |
---|
23 | |
---|
24 | The alogrithm uses different units for delta latitude and delta longitude. |
---|
25 | The delta latitude is assumed to be in units of seconds of arc x 10^-5. |
---|
26 | The delta longitude is the usual radians. Look out for these conversions. |
---|
27 | |
---|
28 | The algorithm is described using complex arithmetic. There were three |
---|
29 | options: |
---|
30 | * find and use a Javascript library for complex arithmetic |
---|
31 | * write my own complex library |
---|
32 | * expand the complex arithmetic by hand to simple arithmetic |
---|
33 | |
---|
34 | This implementation has expanded the complex multiplication operations |
---|
35 | into parallel simple arithmetic operations for the real and imaginary parts. |
---|
36 | The imaginary part is way over to the right of the display; this probably |
---|
37 | violates every coding standard in the world, but, to me, it makes it much |
---|
38 | more obvious what is going on. |
---|
39 | |
---|
40 | The following complex operations are used: |
---|
41 | - addition |
---|
42 | - multiplication |
---|
43 | - division |
---|
44 | - complex number raised to integer power |
---|
45 | - summation |
---|
46 | |
---|
47 | A summary of complex arithmetic operations: |
---|
48 | (from http://en.wikipedia.org/wiki/Complex_arithmetic) |
---|
49 | addition: (a + bi) + (c + di) = (a + c) + (b + d)i |
---|
50 | subtraction: (a + bi) - (c + di) = (a - c) + (b - d)i |
---|
51 | multiplication: (a + bi) x (c + di) = (ac - bd) + (bc + ad)i |
---|
52 | division: (a + bi) / (c + di) = [(ac + bd)/(cc + dd)] + [(bc - ad)/(cc + dd)]i |
---|
53 | |
---|
54 | The algorithm needs to calculate summations of simple and complex numbers. This is |
---|
55 | implemented using a for-loop, pre-loading the summed value to zero. |
---|
56 | |
---|
57 | The algorithm needs to calculate theta^2, theta^3, etc while doing a summation. |
---|
58 | There are three possible implementations: |
---|
59 | - use Math.pow in the summation loop - except for complex numbers |
---|
60 | - precalculate the values before running the loop |
---|
61 | - calculate theta^n = theta^(n-1) * theta during the loop |
---|
62 | This implementation uses the third option for both real and complex arithmetic. |
---|
63 | |
---|
64 | For example |
---|
65 | psi_n = 1; |
---|
66 | sum = 0; |
---|
67 | for (n = 1; n <=6; n++) { |
---|
68 | psi_n1 = psi_n * psi; // calculate psi^(n+1) |
---|
69 | psi_n = psi_n1; |
---|
70 | sum = sum + A[n] * psi_n; |
---|
71 | } |
---|
72 | |
---|
73 | |
---|
74 | TEST VECTORS |
---|
75 | |
---|
76 | NZMG E, N: 2487100.638 6751049.719 metres |
---|
77 | NZGD49 long, lat: 172.739194 -34.444066 degrees |
---|
78 | |
---|
79 | NZMG E, N: 2486533.395 6077263.661 metres |
---|
80 | NZGD49 long, lat: 172.723106 -40.512409 degrees |
---|
81 | |
---|
82 | NZMG E, N: 2216746.425 5388508.765 metres |
---|
83 | NZGD49 long, lat: 169.172062 -46.651295 degrees |
---|
84 | |
---|
85 | Note that these test vectors convert from NZMG metres to lat/long referenced |
---|
86 | to NZGD49, not the more usual WGS84. The difference is about 70m N/S and about |
---|
87 | 10m E/W. |
---|
88 | |
---|
89 | These test vectors are provided in reference [1]. Many more test |
---|
90 | vectors are available in |
---|
91 | http://www.linz.govt.nz/docs/topography/topographicdata/placenamesdatabase/nznamesmar08.zip |
---|
92 | which is a catalog of names on the 260-series maps. |
---|
93 | |
---|
94 | |
---|
95 | EPSG CODES |
---|
96 | |
---|
97 | NZMG EPSG:27200 |
---|
98 | NZGD49 EPSG:4272 |
---|
99 | |
---|
100 | http://spatialreference.org/ defines these as |
---|
101 | Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs "; |
---|
102 | Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs "; |
---|
103 | |
---|
104 | |
---|
105 | LICENSE |
---|
106 | Copyright: Stephen Irons 2008 |
---|
107 | Released under terms of the LGPL as per: http://www.gnu.org/copyleft/lesser.html |
---|
108 | |
---|
109 | *******************************************************************************/ |
---|
110 | |
---|
111 | |
---|
112 | /** |
---|
113 | Initialize New Zealand Map Grip projection |
---|
114 | */ |
---|
115 | |
---|
116 | Proj4js.Proj.nzmg = { |
---|
117 | |
---|
118 | /** |
---|
119 | * iterations: Number of iterations to refine inverse transform. |
---|
120 | * 0 -> km accuracy |
---|
121 | * 1 -> m accuracy -- suitable for most mapping applications |
---|
122 | * 2 -> mm accuracy |
---|
123 | */ |
---|
124 | iterations: 1, |
---|
125 | |
---|
126 | init : function() { |
---|
127 | this.A = new Array(); |
---|
128 | this.A[1] = +0.6399175073; |
---|
129 | this.A[2] = -0.1358797613; |
---|
130 | this.A[3] = +0.063294409; |
---|
131 | this.A[4] = -0.02526853; |
---|
132 | this.A[5] = +0.0117879; |
---|
133 | this.A[6] = -0.0055161; |
---|
134 | this.A[7] = +0.0026906; |
---|
135 | this.A[8] = -0.001333; |
---|
136 | this.A[9] = +0.00067; |
---|
137 | this.A[10] = -0.00034; |
---|
138 | |
---|
139 | this.B_re = new Array(); this.B_im = new Array(); |
---|
140 | this.B_re[1] = +0.7557853228; this.B_im[1] = 0.0; |
---|
141 | this.B_re[2] = +0.249204646; this.B_im[2] = +0.003371507; |
---|
142 | this.B_re[3] = -0.001541739; this.B_im[3] = +0.041058560; |
---|
143 | this.B_re[4] = -0.10162907; this.B_im[4] = +0.01727609; |
---|
144 | this.B_re[5] = -0.26623489; this.B_im[5] = -0.36249218; |
---|
145 | this.B_re[6] = -0.6870983; this.B_im[6] = -1.1651967; |
---|
146 | |
---|
147 | this.C_re = new Array(); this.C_im = new Array(); |
---|
148 | this.C_re[1] = +1.3231270439; this.C_im[1] = 0.0; |
---|
149 | this.C_re[2] = -0.577245789; this.C_im[2] = -0.007809598; |
---|
150 | this.C_re[3] = +0.508307513; this.C_im[3] = -0.112208952; |
---|
151 | this.C_re[4] = -0.15094762; this.C_im[4] = +0.18200602; |
---|
152 | this.C_re[5] = +1.01418179; this.C_im[5] = +1.64497696; |
---|
153 | this.C_re[6] = +1.9660549; this.C_im[6] = +2.5127645; |
---|
154 | |
---|
155 | this.D = new Array(); |
---|
156 | this.D[1] = +1.5627014243; |
---|
157 | this.D[2] = +0.5185406398; |
---|
158 | this.D[3] = -0.03333098; |
---|
159 | this.D[4] = -0.1052906; |
---|
160 | this.D[5] = -0.0368594; |
---|
161 | this.D[6] = +0.007317; |
---|
162 | this.D[7] = +0.01220; |
---|
163 | this.D[8] = +0.00394; |
---|
164 | this.D[9] = -0.0013; |
---|
165 | }, |
---|
166 | |
---|
167 | /** |
---|
168 | New Zealand Map Grid Forward - long/lat to x/y |
---|
169 | long/lat in radians |
---|
170 | */ |
---|
171 | forward : function(p) { |
---|
172 | var lon = p.x; |
---|
173 | var lat = p.y; |
---|
174 | |
---|
175 | var delta_lat = lat - this.lat0; |
---|
176 | var delta_lon = lon - this.long0; |
---|
177 | |
---|
178 | // 1. Calculate d_phi and d_psi ... // and d_lambda |
---|
179 | // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians. |
---|
180 | var d_phi = delta_lat / Proj4js.common.SEC_TO_RAD * 1E-5; var d_lambda = delta_lon; |
---|
181 | var d_phi_n = 1; // d_phi^0 |
---|
182 | |
---|
183 | var d_psi = 0; |
---|
184 | for (n = 1; n <= 10; n++) { |
---|
185 | d_phi_n = d_phi_n * d_phi; |
---|
186 | d_psi = d_psi + this.A[n] * d_phi_n; |
---|
187 | } |
---|
188 | |
---|
189 | // 2. Calculate theta |
---|
190 | var th_re = d_psi; var th_im = d_lambda; |
---|
191 | |
---|
192 | // 3. Calculate z |
---|
193 | var th_n_re = 1; var th_n_im = 0; // theta^0 |
---|
194 | var th_n_re1; var th_n_im1; |
---|
195 | |
---|
196 | var z_re = 0; var z_im = 0; |
---|
197 | for (n = 1; n <= 6; n++) { |
---|
198 | th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; |
---|
199 | th_n_re = th_n_re1; th_n_im = th_n_im1; |
---|
200 | z_re = z_re + this.B_re[n]*th_n_re - this.B_im[n]*th_n_im; z_im = z_im + this.B_im[n]*th_n_re + this.B_re[n]*th_n_im; |
---|
201 | } |
---|
202 | |
---|
203 | // 4. Calculate easting and northing |
---|
204 | x = (z_im * this.a) + this.x0; |
---|
205 | y = (z_re * this.a) + this.y0; |
---|
206 | |
---|
207 | p.x = x; p.y = y; |
---|
208 | |
---|
209 | return p; |
---|
210 | }, |
---|
211 | |
---|
212 | |
---|
213 | /** |
---|
214 | New Zealand Map Grid Inverse - x/y to long/lat |
---|
215 | */ |
---|
216 | inverse : function(p) { |
---|
217 | |
---|
218 | var x = p.x; |
---|
219 | var y = p.y; |
---|
220 | |
---|
221 | var delta_x = x - this.x0; |
---|
222 | var delta_y = y - this.y0; |
---|
223 | |
---|
224 | // 1. Calculate z |
---|
225 | var z_re = delta_y / this.a; var z_im = delta_x / this.a; |
---|
226 | |
---|
227 | // 2a. Calculate theta - first approximation gives km accuracy |
---|
228 | var z_n_re = 1; var z_n_im = 0; // z^0 |
---|
229 | var z_n_re1; var z_n_im1; |
---|
230 | |
---|
231 | var th_re = 0; var th_im = 0; |
---|
232 | for (n = 1; n <= 6; n++) { |
---|
233 | z_n_re1 = z_n_re*z_re - z_n_im*z_im; z_n_im1 = z_n_im*z_re + z_n_re*z_im; |
---|
234 | z_n_re = z_n_re1; z_n_im = z_n_im1; |
---|
235 | th_re = th_re + this.C_re[n]*z_n_re - this.C_im[n]*z_n_im; th_im = th_im + this.C_im[n]*z_n_re + this.C_re[n]*z_n_im; |
---|
236 | } |
---|
237 | |
---|
238 | // 2b. Iterate to refine the accuracy of the calculation |
---|
239 | // 0 iterations gives km accuracy |
---|
240 | // 1 iteration gives m accuracy -- good enough for most mapping applications |
---|
241 | // 2 iterations bives mm accuracy |
---|
242 | for (i = 0; i < this.iterations; i++) { |
---|
243 | var th_n_re = th_re; var th_n_im = th_im; |
---|
244 | var th_n_re1; var th_n_im1; |
---|
245 | |
---|
246 | var num_re = z_re; var num_im = z_im; |
---|
247 | for (n = 2; n <= 6; n++) { |
---|
248 | th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; |
---|
249 | th_n_re = th_n_re1; th_n_im = th_n_im1; |
---|
250 | num_re = num_re + (n-1)*(this.B_re[n]*th_n_re - this.B_im[n]*th_n_im); num_im = num_im + (n-1)*(this.B_im[n]*th_n_re + this.B_re[n]*th_n_im); |
---|
251 | } |
---|
252 | |
---|
253 | th_n_re = 1; th_n_im = 0; |
---|
254 | var den_re = this.B_re[1]; var den_im = this.B_im[1]; |
---|
255 | for (n = 2; n <= 6; n++) { |
---|
256 | th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; |
---|
257 | th_n_re = th_n_re1; th_n_im = th_n_im1; |
---|
258 | den_re = den_re + n * (this.B_re[n]*th_n_re - this.B_im[n]*th_n_im); den_im = den_im + n * (this.B_im[n]*th_n_re + this.B_re[n]*th_n_im); |
---|
259 | } |
---|
260 | |
---|
261 | // Complex division |
---|
262 | var den2 = den_re*den_re + den_im*den_im; |
---|
263 | th_re = (num_re*den_re + num_im*den_im) / den2; th_im = (num_im*den_re - num_re*den_im) / den2; |
---|
264 | } |
---|
265 | |
---|
266 | // 3. Calculate d_phi ... // and d_lambda |
---|
267 | var d_psi = th_re; var d_lambda = th_im; |
---|
268 | var d_psi_n = 1; // d_psi^0 |
---|
269 | |
---|
270 | var d_phi = 0; |
---|
271 | for (n = 1; n <= 9; n++) { |
---|
272 | d_psi_n = d_psi_n * d_psi; |
---|
273 | d_phi = d_phi + this.D[n] * d_psi_n; |
---|
274 | } |
---|
275 | |
---|
276 | // 4. Calculate latitude and longitude |
---|
277 | // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians. |
---|
278 | var lat = this.lat0 + (d_phi * Proj4js.common.SEC_TO_RAD * 1E5); |
---|
279 | var lon = this.long0 + d_lambda; |
---|
280 | |
---|
281 | p.x = lon; |
---|
282 | p.y = lat; |
---|
283 | |
---|
284 | return p; |
---|
285 | } |
---|
286 | }; |
---|