1
|
/*
|
2
|
Author: Mike Adair mike.adairATccrs.nrcan.gc.ca
|
3
|
Richard Greenwood rich@greenwoodmap.com
|
4
|
License: LGPL as per: http://www.gnu.org/copyleft/lesser.html
|
5
|
$Id$
|
6
|
*/
|
7
|
|
8
|
/**
|
9
|
* Provides latitude/longitude to map projection (and vice versa) transformation methods.
|
10
|
* Initialized with EPSG codes. Has properties for units and title strings.
|
11
|
* All coordinates are handled as points which is a 2 element array where x is
|
12
|
* the first element and y is the second.
|
13
|
* For the Forward() method pass in lat/lon and it returns map XY.
|
14
|
* For the Inverse() method pass in map XY and it returns lat/long.
|
15
|
*
|
16
|
* TBD: retrieve initialization params (and conversion code?) from a web service
|
17
|
*
|
18
|
* @constructor
|
19
|
*
|
20
|
* @param srs The SRS (EPSG code) of the projection
|
21
|
*/
|
22
|
|
23
|
function Proj(srs) {
|
24
|
this.srs = srs.toUpperCase();
|
25
|
switch(this.srs) {
|
26
|
case "EPSG:4326": //lat/lon projection WGS_84
|
27
|
case "EPSG:4269": //lat/lon projection WGS_84
|
28
|
case "CRS:84": //lat/lon projection WGS_84
|
29
|
case "EPSG:4965": //lat/lon projection RGF93G IGN-F FD 2005
|
30
|
case new String("http://www.opengis.net/gml/srs/epsg.xml#4326").toUpperCase():
|
31
|
this.Forward = identity;
|
32
|
this.Inverse = identity;
|
33
|
this.units = "degrees";
|
34
|
this.title = "Lat/Long";
|
35
|
break;
|
36
|
case "EPSG:42101": //North American LCC WGS_84
|
37
|
this.Init = lccinit;
|
38
|
this.Forward = ll2lcc;
|
39
|
this.Inverse = lcc2ll;
|
40
|
this.Init(new Array(6378137.0,6356752.314245,49.0,77.0,-95.0, 0.0, 0.0, -8000000));
|
41
|
this.units = "meters";
|
42
|
this.title = "Lambert Conformal Conic";
|
43
|
break;
|
44
|
case "EPSG:42304": //North American LCC NAD_83
|
45
|
this.Init = lccinit;
|
46
|
this.Forward = ll2lcc;
|
47
|
this.Inverse = lcc2ll;
|
48
|
this.Init(new Array(6378137.0,6356752.314,49.0,77.0,-95.0, 49.0, 0.0, 0));
|
49
|
this.units = "meters";
|
50
|
this.title = "Lambert Conformal Conic";
|
51
|
break;
|
52
|
case "EPSG:26986": //NAD83 / Massachusetts Mainland
|
53
|
this.Init = lccinit;
|
54
|
this.Forward = ll2lcc;
|
55
|
this.Inverse = lcc2ll;
|
56
|
this.Init(new Array(6378137.0,6356752.314,42.68333333333333,41.71666666666667,-71.5, 41.0, 200000, 750000));
|
57
|
this.units = "meters";
|
58
|
this.title = "Massachusetts Mainland (LCC)";
|
59
|
break;
|
60
|
case "EPSG:32761": //Polar Stereographic
|
61
|
case "EPSG:32661":
|
62
|
this.Init = psinit;
|
63
|
this.Forward = ll2ps;
|
64
|
this.Inverse = ps2ll;
|
65
|
this.Init(new Array(6378137.0, 6356752.314245, 0.0, -90.0, 2000000, 2000000));
|
66
|
this.units = "meters";
|
67
|
this.title = "Polar Stereographic";
|
68
|
break;
|
69
|
case "EPSG:27561"://LAMB1 IGN-F modification FD 2005
|
70
|
this.Init = lccinit;
|
71
|
this.Forward = ll2lcc;
|
72
|
this.Inverse = lcc2ll;
|
73
|
this.Init(new Array(6378249.2, 6356515.0, 49.50, 49.50, 2.33722916655, 49.50, 600000.0, 200000.0));
|
74
|
this.units = "meters";
|
75
|
this.title = "Lambert Conformal Conic";
|
76
|
break;
|
77
|
case "EPSG:27562"://LAMB2 IGN-F modification FD 2005
|
78
|
this.Init = lccinit;
|
79
|
this.Forward = ll2lcc;
|
80
|
this.Inverse = lcc2ll;
|
81
|
this.Init(new Array(6378249.2, 6356515.0, 46.80, 46.80, 2.33722916655, 46.8, 600000.0, 200000.0));
|
82
|
this.units = "meters";
|
83
|
this.title = "Lambert Conformal Conic";
|
84
|
break;
|
85
|
case "EPSG:27572"://LAMBE IGN-F modification FD 2005
|
86
|
case "EPSG:27582"://LAMB2 IGN-F modification (deprecated) FD 2005
|
87
|
this.Init = lccinit;
|
88
|
this.Forward = ll2lcc;
|
89
|
this.Inverse = lcc2ll;
|
90
|
this.Init(new Array(6378249.2, 6356515.0, 46.80, 46.80, 2.33722916655, 46.8, 600000.0, 2200000.0));
|
91
|
this.units = "meters";
|
92
|
this.title = "Lambert Conformal Conic";
|
93
|
break;
|
94
|
case "EPSG:27563"://LAMB3 IGN-F modification FD 2005
|
95
|
this.Init = lccinit;
|
96
|
this.Forward = ll2lcc;
|
97
|
this.Inverse = lcc2ll;
|
98
|
this.Init(new Array(6378249.2, 6356515.0, 44.10, 44.10, 2.33722916655, 44.10, 600000.0, 200000.0));
|
99
|
this.units = "meters";
|
100
|
this.title = "Lambert Conformal Conic";
|
101
|
break;
|
102
|
case "EPSG:27564"://LAMB4 IGN-F modification FD 2005
|
103
|
this.Init = lccinit;
|
104
|
this.Forward = ll2lcc;
|
105
|
this.Inverse = lcc2ll;
|
106
|
this.Init(new Array(6378249.2, 6356515.0, 42.17, 42.17, 2.33722916655, 42.17, 234.358, 185861.369));
|
107
|
this.units = "meters";
|
108
|
this.title = "Lambert Conformal Conic";
|
109
|
break;
|
110
|
case "EPSG:2154" ://LAMB93 IGN-F modification FD 2005
|
111
|
this.Init = lccinit;
|
112
|
this.Forward = ll2lcc;
|
113
|
this.Inverse = lcc2ll;
|
114
|
this.Init(new Array(6378137.0, 6356752.3141, 44.00, 49.00, 3.00000000001, 46.50, 700000.0, 6600000.0));
|
115
|
this.units = "meters";
|
116
|
this.title = "Lambert Conformal Conic";
|
117
|
break;
|
118
|
case "EPSG:4326":case "EPSG:4269":case "CRS:84":case "EPSG:4965":case new String("http://www.opengis.net/gml/srs/epsg.xml#4326").toUpperCase():
|
119
|
this.Forward=identity;
|
120
|
this.Inverse=identity;
|
121
|
this.units="degrees";
|
122
|
this.title="Lat/Long";
|
123
|
break;
|
124
|
case "EPSG:102758":this.title="NAD 1983 StatePlane Wyoming West FIPS 4904 US Survey Feet";
|
125
|
this.Init=tminit;
|
126
|
this.Forward=ll2tm
|
127
|
this.Inverse=tm2ll;
|
128
|
this.Init(new Array(grs80[0], grs80[1], 0.9999375, -110.0833333333333, 40.5, 800000, 100000));
|
129
|
this.units="usfeet";
|
130
|
break;
|
131
|
case "EPSG:32158":this.title="NAD 1983 StatePlane Wyoming West meters";
|
132
|
this.Init=tminit;
|
133
|
this.Forward=ll2tm
|
134
|
this.Inverse=tm2ll;
|
135
|
this.Init(new Array(grs80[0], grs80[1], 0.9999375, -110.0833333333333, 40.5, 800000, 100000));
|
136
|
this.units="meters";
|
137
|
break;
|
138
|
// UTM NAD83 Zones 3 thru 23
|
139
|
case"EPSG:26903":case"EPSG:26904":case"EPSG:26905":case"EPSG:26906":case"EPSG:26907":case"EPSG:26908":case"EPSG:26909":
|
140
|
case"EPSG:26910":case"EPSG:26911":case"EPSG:26912":case"EPSG:26913":case"EPSG:26914":case"EPSG:26915":case"EPSG:26916":
|
141
|
case"EPSG:26917":case"EPSG:26918":case"EPSG:26919":case"EPSG:26920":case"EPSG:26921":case"EPSG:26922":case"EPSG:26923":
|
142
|
this.title="NAD83 / UTM zone "+ srs.substr(8,2)+"N";
|
143
|
this.Init=utminit;
|
144
|
this.Forward=ll2tm;
|
145
|
this.Inverse=tm2ll;
|
146
|
this.Init(new Array(grs80[0], grs80[1], 0.9996, srs.substr(8,2)));
|
147
|
this.units="meters";
|
148
|
break;
|
149
|
// UTM WGS84 Zones 1 thru 60 North
|
150
|
case"EPSG:32601":case"EPSG:32602":
|
151
|
case"EPSG:32603":case"EPSG:32604":case"EPSG:32605":case"EPSG:32606":case"EPSG:32607":case"EPSG:32608":case"EPSG:32609":
|
152
|
case"EPSG:32610":case"EPSG:32611":case"EPSG:32612":case"EPSG:32613":case"EPSG:32614":case"EPSG:32615":case"EPSG:32616":
|
153
|
case"EPSG:32617":case"EPSG:32618":case"EPSG:32619":case"EPSG:32620":case"EPSG:32621":case"EPSG:32622":case"EPSG:32623":
|
154
|
case"EPSG:32624":case"EPSG:32625":case"EPSG:32626":case"EPSG:32627":case"EPSG:32628":case"EPSG:32629":
|
155
|
case"EPSG:32630":case"EPSG:32631":case"EPSG:32632":case"EPSG:32633":case"EPSG:32634":case"EPSG:32635":case"EPSG:32636":
|
156
|
case"EPSG:32637":case"EPSG:32638":case"EPSG:32639":case"EPSG:32640":case"EPSG:32641":case"EPSG:32642":
|
157
|
case"EPSG:32643":case"EPSG:32644":case"EPSG:32645":case"EPSG:32646":case"EPSG:32647":case"EPSG:32648":case"EPSG:32649":
|
158
|
case"EPSG:32650":case"EPSG:32651":case"EPSG:32652":case"EPSG:32653":case"EPSG:32654":case"EPSG:32655":case"EPSG:32656":
|
159
|
case"EPSG:32657":case"EPSG:32658":case"EPSG:32659":case"EPSG:32660":
|
160
|
this.title="WGS84 / UTM zone " + srs.substr(8,2) + "N";
|
161
|
this.Init=utminit;
|
162
|
this.Forward=ll2tm;
|
163
|
this.Inverse=tm2ll;
|
164
|
this.Init(new Array(wgs84[0], wgs84[1], 0.9996, srs.substr(8,2)));
|
165
|
this.units="meters";
|
166
|
break;
|
167
|
// UTM WGS84 Zones 1 thru 60 South
|
168
|
case"EPSG:32701":case"EPSG:32702":
|
169
|
case"EPSG:32703":case"EPSG:32704":case"EPSG:32705":case"EPSG:32706":case"EPSG:32707":case"EPSG:32708":case"EPSG:32709":
|
170
|
case"EPSG:32710":case"EPSG:32711":case"EPSG:32712":case"EPSG:32713":case"EPSG:32714":case"EPSG:32715":case"EPSG:32716":
|
171
|
case"EPSG:32717":case"EPSG:32718":case"EPSG:32719":case"EPSG:32720":case"EPSG:32721":case"EPSG:32722":case"EPSG:32723":
|
172
|
case"EPSG:32724":case"EPSG:32725":case"EPSG:32726":case"EPSG:32727":case"EPSG:32728":case"EPSG:32729":
|
173
|
case"EPSG:32730":case"EPSG:32731":case"EPSG:32732":case"EPSG:32733":case"EPSG:32734":case"EPSG:32735":case"EPSG:32736":
|
174
|
case"EPSG:32737":case"EPSG:32738":case"EPSG:32739":case"EPSG:32740":case"EPSG:32741":case"EPSG:32742":
|
175
|
case"EPSG:32743":case"EPSG:32744":case"EPSG:32745":case"EPSG:32746":case"EPSG:32747":case"EPSG:32748":case"EPSG:32749":
|
176
|
case"EPSG:32750":case"EPSG:32751":case"EPSG:32752":case"EPSG:32753":case"EPSG:32754":case"EPSG:32755":case"EPSG:32756":
|
177
|
case"EPSG:32757":case"EPSG:32758":case"EPSG:32759":case"EPSG:32760":
|
178
|
this.title="WGS84 / UTM zone " + srs.substr(8,2) + "S";
|
179
|
this.Init=utminit;
|
180
|
this.Forward=ll2tm;
|
181
|
this.Inverse=tm2ll;
|
182
|
this.Init(new Array(wgs84[0], wgs84[1], 0.9996, "-"+srs.substr(8,2)));
|
183
|
this.units="meters";
|
184
|
break;
|
185
|
case "EPSG:26591":this.title="Monte Mario (Rome) / Italy zone 1";
|
186
|
this.Init=tminit;
|
187
|
this.Forward=ll2tm
|
188
|
this.Inverse=tm2ll;
|
189
|
this.Init(new Array(6378388.0, 6356911.94612795,0.9996, 9, 0.0, 1500000.0, 0.0));
|
190
|
this.units="meters";
|
191
|
break;
|
192
|
case "SCENE": //this is really a pixel projection with bilinear interpolation of the corners to get ll
|
193
|
this.Init = sceneInit;
|
194
|
this.Forward = ll2scene;
|
195
|
this.Inverse = scene2ll;
|
196
|
this.GetXYCoords = identity; //override to get line 0 at top left
|
197
|
this.GetPLCoords = identity; //
|
198
|
break;
|
199
|
case "PIXEL":
|
200
|
this.Forward = ll2pixel;
|
201
|
this.Inverse = pixel2ll;
|
202
|
this.units = "pixels";
|
203
|
this.GetXYCoords = identity; //override to get line 0 at top left
|
204
|
this.GetPLCoords = identity; //
|
205
|
break;
|
206
|
default:
|
207
|
//or retrieve parameters from web service based on SRS lookup
|
208
|
alert("unsupported map projection: "+this.srs);
|
209
|
}
|
210
|
|
211
|
this.matchSrs = function(otherSrs) {
|
212
|
if (this.srs == otherSrs.toUpperCase() ) return true;
|
213
|
return false;
|
214
|
}
|
215
|
|
216
|
}
|
217
|
|
218
|
function identity(coords) {
|
219
|
return coords;
|
220
|
}
|
221
|
|
222
|
/**
|
223
|
* Scene projection forward transformation.
|
224
|
* Forward trasnformation need reverse bilinear interpolation or orbit modelling
|
225
|
* (to be implemented)
|
226
|
* @param coords Lat/Long coords passed in
|
227
|
* @return map coordinates
|
228
|
*/
|
229
|
function ll2scene(coords) {
|
230
|
alert("ll2scene not defined");
|
231
|
//return new Array(124, 15+256); //for testing only,
|
232
|
return null;
|
233
|
}
|
234
|
|
235
|
/**
|
236
|
* Scene projection Inverse transformation.
|
237
|
* This is really a pixel representation with bi-linear interpolation of the corner coords.
|
238
|
* @param coords map coordinates passed in
|
239
|
* @return Lat/Long coords
|
240
|
*/
|
241
|
function scene2ll(coords) {
|
242
|
var xpct = (coords[0]-this.ul[0])/(this.lr[0]-this.ul[0]);
|
243
|
var ypct = (coords[1]-this.ul[1])/(this.lr[1]-this.ul[1]);
|
244
|
// alert("pct:"+xpct+":"+ypct);
|
245
|
var lon = bilinterp(xpct, ypct, this.cul[0], this.cur[0], this.cll[0], this.clr[0])
|
246
|
var lat = bilinterp(xpct, ypct, this.cul[1], this.cur[1], this.cll[1], this.clr[1])
|
247
|
return new Array(lon, lat);
|
248
|
}
|
249
|
|
250
|
/**
|
251
|
* Scene projection initialization function
|
252
|
* @param param array of the corner coordinates (which are in turn 2D point arrays)
|
253
|
* in the order upper-left, upper-right, lower-left, lower-right
|
254
|
*/
|
255
|
function sceneInit(param) {
|
256
|
this.cul = param[0];
|
257
|
this.cur = param[1];
|
258
|
this.cll = param[2];
|
259
|
this.clr = param[3];
|
260
|
}
|
261
|
|
262
|
/**
|
263
|
* Bilinear interpolation function to return an interpolated value for either
|
264
|
* x or y for some point located within a box where the XY of the corners are known.
|
265
|
* This should be applied to the x and y coordinates separately,
|
266
|
* ie. first interpolate for the x value, then the y.
|
267
|
* the a,b,c,d params are thus the x or y values at the corners.
|
268
|
* @param x distance from the left side of the box as a percentage
|
269
|
* @param y distance from the top of the box as a percentage
|
270
|
* @param a either x or y value at the UL corner of the box
|
271
|
* @param b either x or y value at the UR corner of the box
|
272
|
* @param c either x or y value at the LL corner of the box
|
273
|
* @param d either x or y value at the LR corner of the box
|
274
|
*/
|
275
|
function bilinterp(x, y, a, b, c, d) {
|
276
|
var top = x*(b-a) + a;
|
277
|
var bot = x*(d-c) + c;
|
278
|
//alert("top:"+top+" bot:"+bot);
|
279
|
return y*(bot-top) + top;
|
280
|
}
|
281
|
|
282
|
// pixel projection object definition
|
283
|
// a pixel representation
|
284
|
// forward transformation
|
285
|
function ll2pixel(coords) {
|
286
|
alert("ll2pixel not defined");
|
287
|
return null;
|
288
|
}
|
289
|
|
290
|
// inverse transformation
|
291
|
function pixel2ll(coords) {
|
292
|
alert("pixel2ll not defined");
|
293
|
// return new Array(lon, lat);
|
294
|
return null;
|
295
|
}
|
296
|
|
297
|
|
298
|
/****************************************************************************
|
299
|
The following code is a direct port of GCTPC coordinate transformation code
|
300
|
from C to Javascript. For more information go to http://edcftp.cr.usgs.gov/pub//software/gctpc/
|
301
|
Currently suppported projections include: Lambert Conformal Conic (LCC),
|
302
|
Lat/Long, Polar Stereographic, Transverse Mercator (including UTM).
|
303
|
Porting C to Javascript is fairly straightforward so other support for more
|
304
|
projections is easy to add.
|
305
|
*/
|
306
|
|
307
|
var PI = Math.PI;
|
308
|
var HALF_PI = PI*0.5;
|
309
|
var TWO_PI = PI*2.0;
|
310
|
var EPSLN = 1.0e-10;
|
311
|
var R2D = 57.2957795131;
|
312
|
var D2R =0.0174532925199;
|
313
|
var R = 6370997.0; // Radius of the earth (sphere)
|
314
|
|
315
|
|
316
|
// Initialize the Lambert Conformal conic projection
|
317
|
// -----------------------------------------------------------------
|
318
|
function lccinit(param) {
|
319
|
// array of: r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north
|
320
|
//double c_lat; /* center latitude */
|
321
|
//double c_lon; /* center longitude */
|
322
|
//double lat1; /* first standard parallel */
|
323
|
//double lat2; /* second standard parallel */
|
324
|
//double r_maj; /* major axis */
|
325
|
//double r_min; /* minor axis */
|
326
|
//double false_east; /* x offset in meters */
|
327
|
//double false_north; /* y offset in meters */
|
328
|
|
329
|
this.r_major = param[0];
|
330
|
this.r_minor = param[1];
|
331
|
var lat1 = param[2] * D2R;
|
332
|
var lat2 = param[3] * D2R;
|
333
|
this.center_lon = param[4] * D2R;
|
334
|
this.center_lat = param[5] * D2R;
|
335
|
this.false_easting = param[6];
|
336
|
this.false_northing = param[7];
|
337
|
|
338
|
// Standard Parallels cannot be equal and on opposite sides of the equator
|
339
|
if (Math.abs(lat1+lat2) < EPSLN) {
|
340
|
alert("Equal Latitiudes for St. Parallels on opposite sides of equator - lccinit");
|
341
|
return;
|
342
|
}
|
343
|
|
344
|
var temp = this.r_minor / this.r_major;
|
345
|
this.e = Math.sqrt(1.0 - temp*temp);
|
346
|
|
347
|
var sin1 = Math.sin(lat1);
|
348
|
var cos1 = Math.cos(lat1);
|
349
|
var ms1 = msfnz(this.e, sin1, cos1);
|
350
|
var ts1 = tsfnz(this.e, lat1, sin1);
|
351
|
|
352
|
var sin2 = Math.sin(lat2);
|
353
|
var cos2 = Math.cos(lat2);
|
354
|
var ms2 = msfnz(this.e, sin2, cos2);
|
355
|
var ts2 = tsfnz(this.e, lat2, sin2);
|
356
|
|
357
|
var ts0 = tsfnz(this.e, this.center_lat, Math.sin(this.center_lat));
|
358
|
|
359
|
if (Math.abs(lat1 - lat2) > EPSLN) {
|
360
|
this.ns = Math.log(ms1/ms2)/Math.log(ts1/ts2);
|
361
|
} else {
|
362
|
this.ns = sin1;
|
363
|
}
|
364
|
this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns));
|
365
|
this.rh = this.r_major * this.f0 * Math.pow(ts0, this.ns);
|
366
|
}
|
367
|
|
368
|
|
369
|
// Lambert Conformal conic forward equations--mapping lat,long to x,y
|
370
|
// -----------------------------------------------------------------
|
371
|
function ll2lcc(coords) {
|
372
|
|
373
|
var lon = coords[0];
|
374
|
var lat = coords[1];
|
375
|
|
376
|
// convert to radians
|
377
|
if ( lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) {
|
378
|
lat *= D2R;
|
379
|
lon *= D2R;
|
380
|
} else {
|
381
|
alert("*** Input out of range ***: lon: "+lon+" - lat: "+lat);
|
382
|
return null;
|
383
|
}
|
384
|
|
385
|
var con = Math.abs( Math.abs(lat) - HALF_PI);
|
386
|
var ts;
|
387
|
if (con > EPSLN) {
|
388
|
ts = tsfnz(this.e, lat, Math.sin(lat) );
|
389
|
rh1 = this.r_major * this.f0 * Math.pow(ts, this.ns);
|
390
|
} else {
|
391
|
con = lat * this.ns;
|
392
|
if (con <= 0) {
|
393
|
alert("Point can not be projected - ll2lcc");
|
394
|
return null;
|
395
|
}
|
396
|
rh1 = 0;
|
397
|
}
|
398
|
var theta = this.ns * adjust_lon(lon - this.center_lon);
|
399
|
var x = rh1 * Math.sin(theta) + this.false_easting;
|
400
|
var y = this.rh - rh1 * Math.cos(theta) + this.false_northing;
|
401
|
|
402
|
return new Array(x,y);
|
403
|
}
|
404
|
|
405
|
// Lambert Conformal Conic inverse equations--mapping x,y to lat/long
|
406
|
// -----------------------------------------------------------------
|
407
|
function lcc2ll(coords) {
|
408
|
|
409
|
var rh1, con, ts;
|
410
|
var lat, lon;
|
411
|
x = coords[0] - this.false_easting;
|
412
|
y = this.rh - coords[1] + this.false_northing;
|
413
|
if (this.ns > 0) {
|
414
|
rh1 = Math.sqrt (x * x + y * y);
|
415
|
con = 1.0;
|
416
|
} else {
|
417
|
rh1 = -Math.sqrt (x * x + y * y);
|
418
|
con = -1.0;
|
419
|
}
|
420
|
var theta = 0.0;
|
421
|
if (rh1 != 0) {
|
422
|
theta = Math.atan2((con * x),(con * y));
|
423
|
}
|
424
|
if ((rh1 != 0) || (this.ns > 0.0)) {
|
425
|
con = 1.0/this.ns;
|
426
|
ts = Math.pow((rh1/(this.r_major * this.f0)), con);
|
427
|
lat = phi2z(this.e, ts);
|
428
|
if (lat == -9999) return null;
|
429
|
} else {
|
430
|
lat = -HALF_PI;
|
431
|
}
|
432
|
lon = adjust_lon(theta/this.ns + this.center_lon);
|
433
|
return new Array(R2D*lon, R2D*lat);
|
434
|
}
|
435
|
|
436
|
//*******************************************************************************
|
437
|
//NAME POLAR STEREOGRAPHIC
|
438
|
// converted from the GCTPC package
|
439
|
//* Variables common to all subroutines in this code file
|
440
|
// static double r_major; /* major axis */
|
441
|
// static double r_minor; /* minor axis */
|
442
|
// static double e; /* eccentricity */
|
443
|
// static double e4; /* e4 calculated from eccentricity*/
|
444
|
// static double center_lon; /* center longitude */
|
445
|
// static double center_lat; /* center latitude */
|
446
|
// static double fac; /* sign variable */
|
447
|
// static double ind; /* flag variable */
|
448
|
// static double mcs; /* small m */
|
449
|
// static double tcs; /* small t */
|
450
|
// static double false_northing; /* y offset in meters */
|
451
|
// static double false_easting; /* x offset in meters */
|
452
|
|
453
|
|
454
|
//* Initialize the Polar Stereographic projection
|
455
|
function psinit(param) {
|
456
|
// array consisting of: r_maj,r_min,c_lon,c_lat,false_east,false_north)
|
457
|
//double c_lon; /* center longitude in degrees */
|
458
|
//double c_lat; /* center latitude in degrees */
|
459
|
//double r_maj; /* major axis */
|
460
|
//double r_min; /* minor axis */
|
461
|
//double false_east; /* x offset in meters */
|
462
|
//double false_north; /* y offset in meters */
|
463
|
|
464
|
this.r_major = param[0];
|
465
|
this.r_minor = param[1];
|
466
|
this.center_lon = param[2] * D2R;
|
467
|
this.center_lat = param[3] * D2R;
|
468
|
this.false_easting = param[4];
|
469
|
this.false_northing = param[5];
|
470
|
|
471
|
var temp = this.r_minor / this.r_major;
|
472
|
this.e = 1.0 - temp*temp;
|
473
|
this.e = Math.sqrt(this.e);
|
474
|
var con = 1.0 + this.e;
|
475
|
var com = 1.0 - this.e;
|
476
|
this.e4 = Math.sqrt( Math.pow(con,con) * Math.pow(com,com) );
|
477
|
this.fac = (this.center_lat < 0) ? -1.0 : 1.0;
|
478
|
this.ind = 0;
|
479
|
if (Math.abs(Math.abs(this.center_lat) - HALF_PI) > EPSLN) {
|
480
|
this.ind = 1;
|
481
|
var con1 = this.fac * this.center_lat;
|
482
|
var sinphi = Math.sin(con1);
|
483
|
this.mcs = msfnz(this.e, sinphi, Math.cos(con1));
|
484
|
this.tcs = tsfnz(this.e, con1, sinphi);
|
485
|
}
|
486
|
}
|
487
|
|
488
|
//* Polar Stereographic forward equations--mapping lat,long to x,y
|
489
|
// --------------------------------------------------------------*/
|
490
|
function ll2ps(coords) {
|
491
|
|
492
|
var lon = coords[0];
|
493
|
var lat = coords[1];
|
494
|
|
495
|
var con1 = this.fac * adjust_lon(lon - this.center_lon);
|
496
|
var con2 = this.fac * lat;
|
497
|
var sinphi = Math.sin(con2);
|
498
|
var ts = tsfnz(this.e, con2, sinphi);
|
499
|
if (this.ind != 0) {
|
500
|
rh = this.r_major * this.mcs * ts / this.tcs;
|
501
|
} else {
|
502
|
rh = 2.0 * this.r_major * ts / this.e4;
|
503
|
}
|
504
|
var x = this.fac * rh * Math.sin(con1) + this.false_easting;
|
505
|
var y = -this.fac * rh * Math.cos(con1) + this.false_northing;;
|
506
|
|
507
|
return new Array(x,y);
|
508
|
}
|
509
|
|
510
|
//* Polar Stereographic inverse equations--mapping x,y to lat/long
|
511
|
// --------------------------------------------------------------*/
|
512
|
function ps2ll(coords) {
|
513
|
|
514
|
x = (coords[0] - this.false_easting) * this.fac;
|
515
|
y = (coords[1] - this.false_northing) * this.fac;
|
516
|
var rh = Math.sqrt(x * x + y * y);
|
517
|
if (this.ind != 0) {
|
518
|
ts = rh * this.tcs/(this.r_major * this.mcs);
|
519
|
} else {
|
520
|
ts = rh * this.e4/(this.r_major * 2.0);
|
521
|
}
|
522
|
var lat = this.fac * phi2z(this.e, ts);
|
523
|
if (lat == -9999) return null;
|
524
|
var lon = 0;
|
525
|
if (rh == 0) {
|
526
|
lon = this.fac * this.center_lon;
|
527
|
} else {
|
528
|
lon = adjust_lon(this.fac * Math.atan2(x, -y) + this.center_lon);
|
529
|
}
|
530
|
return new Array(R2D*lon, R2D*lat);
|
531
|
}
|
532
|
|
533
|
// following constants added by Greenwood
|
534
|
function semi_minor(a, f) { return a-(a*(1/f)); }
|
535
|
|
536
|
var grs80 = new Array(6378137.0, 6356752.31414036); // r_maj, r_min
|
537
|
var wgs84 = new Array(6378137.0, 6356752.31424518);
|
538
|
var wgs72 = new Array(6378135.0, 6356750.52001609);
|
539
|
var intl = new Array(6378388.0, 6356911.94612795); // (f=297) from ESRI
|
540
|
|
541
|
var usfeet = 1200/3937; // US Survey foot
|
542
|
var feet = 0.3048; // International foot
|
543
|
|
544
|
// following functions from gctpc cproj.c for transverse mercator projections
|
545
|
function e0fn(x){return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));}
|
546
|
function e1fn(x){return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));}
|
547
|
function e2fn(x){return(0.05859375*x*x*(1.0+0.75*x));}
|
548
|
function e3fn(x){return(x*x*x*(35.0/3072.0));}
|
549
|
function mlfn(e0,e1,e2,e3,phi){return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));}
|
550
|
|
551
|
/**
|
552
|
Initialize Transverse Mercator projection
|
553
|
*/
|
554
|
function tminit(param) {
|
555
|
this.r_maj=param[0];
|
556
|
this.r_min=param[1];
|
557
|
this.scale_fact=param[2];
|
558
|
this.lon_center=param[3]*D2R;
|
559
|
this.lat_origin=param[4]*D2R;
|
560
|
this.false_easting=param[5];
|
561
|
this.false_northing=param[6];
|
562
|
var temp = this.r_min / this.r_maj;
|
563
|
this.es = 1.0 - Math.pow(temp,2);
|
564
|
// this.e = Math.sqrt(this.es);
|
565
|
this.e0 = e0fn(this.es);
|
566
|
this.e1 = e1fn(this.es);
|
567
|
this.e2 = e2fn(this.es);
|
568
|
this.e3 = e3fn(this.es);
|
569
|
this.ml0 = this.r_maj * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat_origin);
|
570
|
this.esp = this.es / (1.0 - this.es);
|
571
|
this.ind = (this.es < .00001) ? 1 : 0;
|
572
|
}
|
573
|
|
574
|
/**
|
575
|
Initialize UTM projection
|
576
|
*/
|
577
|
function utminit(param) {
|
578
|
this.r_maj=param[0];
|
579
|
this.r_min=param[1];
|
580
|
this.scale_fact=param[2];
|
581
|
var zone=param[3];
|
582
|
this.lat_origin = 0.0;
|
583
|
this.lon_center = ((6 * Math.abs(zone)) - 183) * D2R;
|
584
|
this.false_easting = 500000.0;
|
585
|
this.false_northing = (zone < 0) ? 10000000.0 : 0.0;
|
586
|
var temp = this.r_min / this.r_maj;
|
587
|
this.es = 1.0 - Math.pow(temp,2);
|
588
|
// this.e = Math.sqrt(this.es);
|
589
|
this.e0 = e0fn(this.es);
|
590
|
this.e1 = e1fn(this.es);
|
591
|
this.e2 = e2fn(this.es);
|
592
|
this.e3 = e3fn(this.es);
|
593
|
this.ml0 = this.r_maj * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat_origin);
|
594
|
this.esp = this.es / (1.0 - this.es);
|
595
|
this.ind = (this.es < .00001) ? 1 : 0;
|
596
|
} // utminit()
|
597
|
|
598
|
/**
|
599
|
Transverse Mercator Forward - long/lat to x/y
|
600
|
*/
|
601
|
function ll2tm(coords) {
|
602
|
var lon=coords[0]*D2R;
|
603
|
var lat=coords[1]*D2R;
|
604
|
var delta_lon = adjust_lon(lon - this.lon_center); /* Delta longitude (Given longitude - center */
|
605
|
var con; /* cone constant */
|
606
|
var x, y;
|
607
|
var sin_phi=Math.sin(lat);
|
608
|
var cos_phi=Math.cos(lat);
|
609
|
|
610
|
if (this.ind != 0) {
|
611
|
var b = cos_phi * Math.sin(delta_lon);
|
612
|
if ((Math.abs(Math.abs(b) - 1.0)) < .0000000001) {
|
613
|
alert("Error in ll2tm(): Point projects into infinity");
|
614
|
return(93);
|
615
|
} else {
|
616
|
x = .5 * this.r_maj * this.scale_fact * Math.log((1.0 + b)/(1.0 - b));
|
617
|
con = Math.acos(cos_phi * Math.cos(delta_lon)/Math.sqrt(1.0 - b*b));
|
618
|
if (lat < 0)
|
619
|
con = - con;
|
620
|
y = this.r_maj * this.scale_fact * (con - this.lat_origin);
|
621
|
}
|
622
|
} else {
|
623
|
var al = cos_phi * delta_lon;
|
624
|
var als = Math.pow(al,2);
|
625
|
var c = this.esp * Math.pow(cos_phi,2);
|
626
|
var tq = Math.tan(lat);
|
627
|
var t = Math.pow(tq,2);
|
628
|
con = 1.0 - this.es * Math.pow(sin_phi,2);
|
629
|
var n = this.r_maj / Math.sqrt(con);
|
630
|
var ml = this.r_maj * mlfn(this.e0, this.e1, this.e2, this.e3, lat);
|
631
|
|
632
|
x = this.scale_fact * n * al * (1.0 + als / 6.0 * (1.0 - t + c + als / 20.0 * (5.0 - 18.0 * t + Math.pow(t,2) + 72.0 * c - 58.0 * this.esp))) + this.false_easting;
|
633
|
y = this.scale_fact * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24.0 * (5.0 - t + 9.0 * c + 4.0 * Math.pow(c,2) + als / 30.0 * (61.0 - 58.0 * t + Math.pow(t,2) + 600.0 * c - 330.0 * this.esp))))) + this.false_northing;
|
634
|
|
635
|
switch(this.units){
|
636
|
case "usfeet":
|
637
|
x /= usfeet;
|
638
|
y /= usfeet
|
639
|
break;
|
640
|
case "feet":
|
641
|
x = x / feet;
|
642
|
y = y / feet;
|
643
|
break;
|
644
|
} // switch()
|
645
|
}
|
646
|
return new Array(x,y);
|
647
|
} // ll2tm()
|
648
|
|
649
|
/**
|
650
|
Transverse Mercator Inverse - x/y to long/lat
|
651
|
*/
|
652
|
function tm2ll(coords) {
|
653
|
var x=coords[0];
|
654
|
var y=coords[1];
|
655
|
var con, phi; /* temporary angles */
|
656
|
var delta_phi; /* difference between longitudes */
|
657
|
var i;
|
658
|
var max_iter = 6; /* maximun number of iterations */
|
659
|
var lat, lon;
|
660
|
|
661
|
if (this.ind != 0) { /* spherical form */
|
662
|
var f = exp(x/(this.r_maj * this.scale_fact));
|
663
|
var g = .5 * (f - 1/f);
|
664
|
var temp = this.lat_origin + y/(this.r_maj * this.scale_fact);
|
665
|
var h = cos(temp);
|
666
|
con = sqrt((1.0 - h * h)/(1.0 + g * g));
|
667
|
lat = asinz(con);
|
668
|
if (temp < 0)
|
669
|
lat = -lat;
|
670
|
if ((g == 0) && (h == 0)) {
|
671
|
lon = this.lon_center;
|
672
|
} else {
|
673
|
lon = adjust_lon(atan2(g,h) + this.lon_center);
|
674
|
}
|
675
|
} else { // ellipsoidal form
|
676
|
x = x - this.false_easting;
|
677
|
y = y - this.false_northing;
|
678
|
|
679
|
con = (this.ml0 + y / this.scale_fact) / this.r_maj;
|
680
|
phi = con;
|
681
|
for (i=0;;i++) {
|
682
|
delta_phi=((con + this.e1 * Math.sin(2.0*phi) - this.e2 * Math.sin(4.0*phi) + this.e3 * Math.sin(6.0*phi)) / this.e0) - phi;
|
683
|
phi += delta_phi;
|
684
|
if (Math.abs(delta_phi) <= EPSLN) break;
|
685
|
if (i >= max_iter) {
|
686
|
alert ("Error in tm2ll(): Latitude failed to converge");
|
687
|
return(95);
|
688
|
}
|
689
|
} // for()
|
690
|
if (Math.abs(phi) < HALF_PI) {
|
691
|
// sincos(phi, &sin_phi, &cos_phi);
|
692
|
var sin_phi=Math.sin(phi);
|
693
|
var cos_phi=Math.cos(phi);
|
694
|
var tan_phi = Math.tan(phi);
|
695
|
var c = this.esp * Math.pow(cos_phi,2);
|
696
|
var cs = Math.pow(c,2);
|
697
|
var t = Math.pow(tan_phi,2);
|
698
|
var ts = Math.pow(t,2);
|
699
|
con = 1.0 - this.es * Math.pow(sin_phi,2);
|
700
|
var n = this.r_maj / Math.sqrt(con);
|
701
|
var r = n * (1.0 - this.es) / con;
|
702
|
var d = x / (n * this.scale_fact);
|
703
|
var ds = Math.pow(d,2);
|
704
|
lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24.0 * (5.0 + 3.0 * t + 10.0 * c - 4.0 * cs - 9.0 * this.esp - ds / 30.0 * (61.0 + 90.0 * t + 298.0 * c + 45.0 * ts - 252.0 * this.esp - 3.0 * cs)));
|
705
|
lon = adjust_lon(this.lon_center + (d * (1.0 - ds / 6.0 * (1.0 + 2.0 * t + c - ds / 20.0 * (5.0 - 2.0 * c + 28.0 * t - 3.0 * cs + 8.0 * this.esp + 24.0 * ts))) / cos_phi));
|
706
|
} else {
|
707
|
lat = HALF_PI * sign(y);
|
708
|
lon = this.lon_center;
|
709
|
}
|
710
|
}
|
711
|
return new Array(lon*R2D, lat*R2D);
|
712
|
} // tm2ll()
|
713
|
|
714
|
|
715
|
// Function to compute the constant small m which is the radius of
|
716
|
// a parallel of latitude, phi, divided by the semimajor axis.
|
717
|
// -----------------------------------------------------------------
|
718
|
function msfnz(eccent, sinphi, cosphi) {
|
719
|
var con = eccent * sinphi;
|
720
|
return cosphi/(Math.sqrt(1.0 - con * con));
|
721
|
}
|
722
|
|
723
|
// Function to compute the constant small t for use in the forward
|
724
|
// computations in the Lambert Conformal Conic and the Polar
|
725
|
// Stereographic projections.
|
726
|
// -----------------------------------------------------------------
|
727
|
function tsfnz(eccent, phi, sinphi) {
|
728
|
var con = eccent * sinphi;
|
729
|
var com = .5 * eccent;
|
730
|
con = Math.pow(((1.0 - con) / (1.0 + con)), com);
|
731
|
return (Math.tan(.5 * (HALF_PI - phi))/con);
|
732
|
}
|
733
|
|
734
|
|
735
|
// Function to compute the latitude angle, phi2, for the inverse of the
|
736
|
// Lambert Conformal Conic and Polar Stereographic projections.
|
737
|
// ----------------------------------------------------------------
|
738
|
function phi2z(eccent, ts) {
|
739
|
var eccnth = .5 * eccent;
|
740
|
var con, dphi;
|
741
|
var phi = HALF_PI - 2 * Math.atan(ts);
|
742
|
for (i = 0; i <= 15; i++) {
|
743
|
con = eccent * Math.sin(phi);
|
744
|
dphi = HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) - phi;
|
745
|
phi += dphi;
|
746
|
if (Math.abs(dphi) <= .0000000001) return phi;
|
747
|
}
|
748
|
alert("Convergence error - phi2z");
|
749
|
return -9999;
|
750
|
}
|
751
|
|
752
|
// Function to return the sign of an argument
|
753
|
function sign(x) { if (x < 0.0) return(-1); else return(1);}
|
754
|
|
755
|
// Function to adjust longitude to -180 to 180; input in radians
|
756
|
function adjust_lon(x) {x=(Math.abs(x)<PI)?x:(x-(sign(x)*TWO_PI));return(x);}
|
757
|
|