public LP[] cvs; // conversion matrix public CTABLE Clone() { CTABLE ret = new CTABLE(); ret.id = id; ret.ll = ll; ret.del = del; ret.lim = lim; ret.cvs = cvs; return(ret); }
//********************************************************************** // pj_gridinfo_init_ntv2() // // Load a ntv2 (.gsb) file. //********************************************************************** static bool pj_gridinfo_init_ntv2(projCtx ctx, FileStream fid, PJ_GRIDINFO gilist) { byte[] header=new byte[11*16]; // -------------------------------------------------------------------- // Read the overview header. // -------------------------------------------------------------------- try { if(fid.Read(header, 0, header.Length)!=header.Length) { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } } catch { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Byte swap interesting fields if needed. // -------------------------------------------------------------------- if(!IS_LSB) { swap_words(header, 8, 4, 1); swap_words(header, 8+16, 4, 1); swap_words(header, 8+32, 4, 1); swap_words(header, 8+7*16, 8, 1); swap_words(header, 8+8*16, 8, 1); swap_words(header, 8+9*16, 8, 1); swap_words(header, 8+10*16, 8, 1); } // -------------------------------------------------------------------- // Get the subfile count out ... all we really use for now. // -------------------------------------------------------------------- int num_subfiles=BitConverter.ToInt32(header, 8+32); // ==================================================================== // Step through the subfiles, creating a PJ_GRIDINFO for each. // ==================================================================== for(int subfile=0; subfile<num_subfiles; subfile++) { // -------------------------------------------------------------------- // Read header. // -------------------------------------------------------------------- try { if(fid.Read(header, 0, header.Length)!=header.Length) { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } } catch { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } if(Encoding.ASCII.GetString(header, 0, 8).StartsWith("SUB_NAME")) { Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Byte swap interesting fields if needed. // -------------------------------------------------------------------- if(!IS_LSB) { swap_words(header, 8+16*4, 8, 1); swap_words(header, 8+16*5, 8, 1); swap_words(header, 8+16*6, 8, 1); swap_words(header, 8+16*7, 8, 1); swap_words(header, +8+16*8, 8, 1); swap_words(header, 8+16*9, 8, 1); swap_words(header, 8+16*10, 4, 1); } // -------------------------------------------------------------------- // Initialize a corresponding "ct" structure. // -------------------------------------------------------------------- CTABLE ct=new CTABLE(); ct.id=Encoding.ASCII.GetString(header, 8, 8); ct.ll.lam=-BitConverter.ToDouble(header, 7*16+8); // W_LONG ct.ll.phi=BitConverter.ToDouble(header, 4*16+8); // S_LAT LP ur; ur.lam=-BitConverter.ToDouble(header, 6*16+8); // E_LONG ur.phi=BitConverter.ToDouble(header, 5*16+8); // N_LAT ct.del.lam=BitConverter.ToDouble(header, 9*16+8); ct.del.phi=BitConverter.ToDouble(header, 8*16+8); ct.lim.lam=(int)(Math.Abs(ur.lam-ct.ll.lam)/ct.del.lam+0.5)+1; ct.lim.phi=(int)(Math.Abs(ur.phi-ct.ll.phi)/ct.del.phi+0.5)+1; #if DEBUG Console.Error.WriteLine("NTv2 {0} {1}x{2}: LL=({3},{4}) UR=({5},{6})", ct.id, ct.lim.lam, ct.lim.phi, ct.ll.lam/3600.0, ct.ll.phi/3600.0, ur.lam/3600.0, ur.phi/3600.0); #endif ct.ll.lam*=Proj.DEG_TO_RAD/3600.0; ct.ll.phi*=Proj.DEG_TO_RAD/3600.0; ct.del.lam*=Proj.DEG_TO_RAD/3600.0; ct.del.phi*=Proj.DEG_TO_RAD/3600.0; int gs_count=BitConverter.ToInt32(header, 8+16*10); if(gs_count!=ct.lim.lam*ct.lim.phi) { Console.Error.WriteLine("GS_COUNT({0}) does not match expected cells ({1}x{2}={3})", gs_count, ct.lim.lam, ct.lim.phi, ct.lim.lam*ct.lim.phi); Proj.pj_ctx_set_errno(ctx, -38); return false; } ct.cvs=null; PJ_GRIDINFO gi; // -------------------------------------------------------------------- // Create a new gridinfo for this if we aren't processing the // 1st subfile, and initialize our grid info. // -------------------------------------------------------------------- if(subfile==0) gi=gilist; else { gi=new PJ_GRIDINFO(); gi.gridname=gilist.gridname; gi.filename=gilist.filename; gi.next=null; } gi.ct=ct; gi.format="ntv2"; gi.grid_offset=fid.Position; // -------------------------------------------------------------------- // Attach to the correct list or sublist. // -------------------------------------------------------------------- if(Encoding.ASCII.GetString(header, 24, 4)=="NONE") { if(gi!=gilist) { PJ_GRIDINFO lnk=gilist; while(lnk.next!=null) lnk=lnk.next; lnk.next=gi; } } else { PJ_GRIDINFO gp=pj_gridinfo_parent(gilist, Encoding.ASCII.GetString(header, 24, 8)); if(gp==null) { #if DEBUG Console.Error.WriteLine("pj_gridinfo_init_ntv2(): failed to find parent {0} for {1}.", Encoding.ASCII.GetString(header, 24, 8), gi.ct.id); #endif PJ_GRIDINFO lnk=gilist; while(lnk.next!=null) lnk=lnk.next; lnk.next=gi; } else if(gp.child==null) gp.child=gi; else { PJ_GRIDINFO lnk=gp.child; while(lnk.next!=null) lnk=lnk.next; lnk.next=gi; } } // -------------------------------------------------------------------- // Seek past the data. // -------------------------------------------------------------------- fid.Seek(gs_count*16, SeekOrigin.Current); } return true; }
//********************************************************************** // pj_gridinfo_init_ntv1() // // Load an NTv1 style Canadian grid shift file. //********************************************************************** static bool pj_gridinfo_init_ntv1(projCtx ctx, FileStream fid, PJ_GRIDINFO gi) { byte[] header=new byte[176]; // -------------------------------------------------------------------- // Read the header. // -------------------------------------------------------------------- try { if(fid.Read(header, 0, header.Length)!=header.Length) { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } } catch { header=null; Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Regularize fields of interest. // -------------------------------------------------------------------- if(IS_LSB) { swap_words(header, 8, 4, 1); swap_words(header, 24, 8, 1); swap_words(header, 40, 8, 1); swap_words(header, 56, 8, 1); swap_words(header, 72, 8, 1); swap_words(header, 88, 8, 1); swap_words(header, 104, 8, 1); } if(BitConverter.ToInt32(header, 8)!=12) { Console.Error.WriteLine("NTv1 grid shift file has wrong record count, corrupt?"); Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Fill in CTABLE structure. // -------------------------------------------------------------------- CTABLE ct=new CTABLE(); ct.id="NTv1 Grid Shift File"; ct.ll.lam=-BitConverter.ToDouble(header, 72); ct.ll.phi=BitConverter.ToDouble(header, 24); LP ur; ur.lam=-BitConverter.ToDouble(header, 56); ur.phi=BitConverter.ToDouble(header, 40); ct.del.lam=BitConverter.ToDouble(header, 104); ct.del.phi=BitConverter.ToDouble(header, 88); ct.lim.lam=(int)(Math.Abs(ur.lam-ct.ll.lam)/ct.del.lam+0.5)+1; ct.lim.phi=(int)(Math.Abs(ur.phi-ct.ll.phi)/ct.del.phi+0.5)+1; #if DEBUG Console.Error.WriteLine("NTv1 {0}x{1}: LL=({2},{3}) UR=({4},{5})", ct.lim.lam, ct.lim.phi, ct.ll.lam, ct.ll.phi, ur.lam, ur.phi); #endif ct.ll.lam*=Proj.DEG_TO_RAD; ct.ll.phi*=Proj.DEG_TO_RAD; ct.del.lam*=Proj.DEG_TO_RAD; ct.del.phi*=Proj.DEG_TO_RAD; ct.cvs=null; gi.ct=ct; gi.grid_offset=fid.Position; gi.format="ntv1"; return true; }
//********************************************************************** // pj_gridinfo_init_gtx() // // Load a NOAA .gtx vertical datum shift file. //********************************************************************** static bool pj_gridinfo_init_gtx(projCtx ctx, FileStream fid, PJ_GRIDINFO gi) { byte[] header=new byte[40]; CTABLE ct; double xorigin, yorigin, xstep, ystep; int rows, columns; // -------------------------------------------------------------------- // Read the header. // -------------------------------------------------------------------- if(fid.Read(header, 0, 40)!=40) { Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Regularize fields of interest and extract. // -------------------------------------------------------------------- if(IS_LSB) { swap_words(header, 0, 8, 4); swap_words(header, 32, 4, 2); } yorigin=BitConverter.ToDouble(header, 0); xorigin=BitConverter.ToDouble(header, 8); ystep=BitConverter.ToDouble(header, 16); xstep=BitConverter.ToDouble(header, 24); rows=BitConverter.ToInt32(header, 32); columns=BitConverter.ToInt32(header, 36); if(xorigin<-360||xorigin>360 ||yorigin<-90||yorigin>90) { Console.WriteLine("gtx file header has invalid extents, corrupt?"); Proj.pj_ctx_set_errno(ctx, -38); return false; } // -------------------------------------------------------------------- // Fill in CTABLE structure. // -------------------------------------------------------------------- ct=new CTABLE(); ct.id="GTX Vertical Grid Shift File"; ct.ll.lam=xorigin; ct.ll.phi=yorigin; ct.del.lam=xstep; ct.del.phi=ystep; ct.lim.lam=columns; ct.lim.phi=rows; // some GTX files come in 0-360 and we shift them back into the // expected -180 to 180 range if possible. This does not solve // problems with grids spanning the dateline. if(ct.ll.lam>=180.0) ct.ll.lam-=360.0; #if !DEBUG if(ct.ll.lam>=0.0&&ct.ll.lam+ct.del.lam*ct.lim.lam>180.0) Console.Error.WriteLine("This GTX spans the dateline! This will cause problems."); Console.Error.WriteLine("GTX {0}x{1}: LL=({2},{3}) UR=({4},{5})", ct.lim.lam, ct.lim.phi, ct.ll.lam, ct.ll.phi, ct.ll.lam+(columns-1)*xstep, ct.ll.phi+(rows-1)*ystep); #endif ct.ll.lam*=Proj.DEG_TO_RAD; ct.ll.phi*=Proj.DEG_TO_RAD; ct.del.lam*=Proj.DEG_TO_RAD; ct.del.phi*=Proj.DEG_TO_RAD; ct.cvs=null; gi.ct=ct; gi.grid_offset=40; gi.format="gtx"; return true; }
//********************************************************************** // nad_ctable2_init() // // Read the header portion of a "ctable2" format grid. //********************************************************************** public static CTABLE nad_ctable2_init(projCtx ctx, Stream fid) { try { CTABLE ct=new CTABLE(); byte[] header=new byte[160]; // read the table header fid.Read(header, 0, 160); if(!IS_LSB) { swap_words(header, 96, 8, 4); swap_words(header, 128, 4, 2); } MemoryStream mem=new MemoryStream(header); BinaryReader br=new BinaryReader(mem); byte[] signature=new byte[16]; byte[] tmpID=new byte[80]; signature=br.ReadBytes(16); string sig=Encoding.ASCII.GetString(signature); if(sig.Substring(0, 9)!="CTABLE V2") { Proj.pj_log(ctx, PJ_LOG.ERROR, "ctable2 - wrong header!"); Proj.pj_ctx_set_errno(ctx, -38); return null; } // read the table header tmpID=br.ReadBytes(80); ct.ll.lam=br.ReadDouble(); ct.ll.phi=br.ReadDouble(); ct.del.lam=br.ReadDouble(); ct.del.phi=br.ReadDouble(); ct.lim.lam=br.ReadInt32(); ct.lim.phi=br.ReadInt32(); // do some minimal validation to ensure the structure isn't corrupt if(ct.lim.lam<1||ct.lim.lam>100000||ct.lim.phi<1||ct.lim.phi>100000) { Proj.pj_ctx_set_errno(ctx, -38); return null; } ct.id=Encoding.ASCII.GetString(tmpID); ct.id=ct.id.Trim(); // trim white space and newlines off id ct.cvs=null; return ct; } catch { Proj.pj_ctx_set_errno(ctx, -38); return null; } }
//********************************************************************** // nad_free() // // Free a CTABLE grid shift structure produced by nad_init(). //********************************************************************** public static void nad_free(CTABLE ct) { if(ct!=null) ct.cvs=null; }
//********************************************************************** // nad_ctable_load() // // Load the data portion of a ctable formatted grid. //********************************************************************** public static bool nad_ctable_load(projCtx ctx, CTABLE ct, Stream fid) { try { fid.Seek(80+16+16+8+4, SeekOrigin.Begin); // 80+16+16+8+4 ?= sizeof(struct CTABLE) // read all the actual shift values int a_size=ct.lim.lam*ct.lim.phi; ct.cvs=new LP[a_size]; BinaryReader br=new BinaryReader(fid); for(int i=0; i<a_size; i++) { ct.cvs[i].lam=br.ReadSingle(); ct.cvs[i].phi=br.ReadSingle(); } } catch { ct.cvs=null; #if DEBUG Console.Error.WriteLine("ctable loading failed on fread() - binary incompatible?"); #endif Proj.pj_ctx_set_errno(ctx, -38); return false; } return true; }
//********************************************************************** // nad_ctable_init() // // Read the header portion of a "ctable" format grid. //********************************************************************** public static CTABLE nad_ctable_init(projCtx ctx, Stream fid) { try { CTABLE ct=new CTABLE(); byte[] tmpID=new byte[80]; // read the table header fid.Read(tmpID, 0, 80); BinaryReader br=new BinaryReader(fid); ct.ll.lam=br.ReadDouble(); ct.ll.phi=br.ReadDouble(); ct.del.lam=br.ReadDouble(); ct.del.phi=br.ReadDouble(); ct.lim.lam=br.ReadInt32(); ct.lim.phi=br.ReadInt32(); br.ReadInt32(); // FLP* cvs // do some minimal validation to ensure the structure isn't corrupt if(ct.lim.lam<1||ct.lim.lam>100000||ct.lim.phi<1||ct.lim.phi>100000) { Proj.pj_ctx_set_errno(ctx, -38); return null; } ct.id=Encoding.ASCII.GetString(tmpID); ct.id=ct.id.Trim(); // trim white space and newlines off id ct.cvs=null; return ct; } catch { Proj.pj_ctx_set_errno(ctx, -38); return null; } }
//********************************************************************** // nad_ctable2_load() // // Load the data portion of a ctable2 formatted grid. //********************************************************************** public static bool nad_ctable2_load(projCtx ctx, CTABLE ct, Stream fid) { try { fid.Seek(160, SeekOrigin.Begin); // read all the actual shift values int a_size=ct.lim.lam*ct.lim.phi; ct.cvs=new LP[a_size]; BinaryReader br=new BinaryReader(fid); if(IS_LSB) { for(int i=0; i<a_size; i++) { ct.cvs[i].lam=br.ReadSingle(); ct.cvs[i].phi=br.ReadSingle(); } } else { byte[] buf=br.ReadBytes(a_size*8); swap_words(buf, 0, 4, a_size*2); using(BinaryReader br2=new BinaryReader(new MemoryStream(buf))) { for(int i=0; i<a_size; i++) { ct.cvs[i].lam=br2.ReadSingle(); ct.cvs[i].phi=br2.ReadSingle(); } } } return true; } catch { ct.cvs=null; #if DEBUG Proj.pj_log(ctx, PJ_LOG.ERROR, "ctable2 loading failed on fread() - binary incompatible?"); #endif Proj.pj_ctx_set_errno(ctx, -38); return false; } }
public static LP nad_cvt(LP @in, bool inverse, CTABLE ct) { const int MAX_TRY=9; if(@in.lam==Libc.HUGE_VAL) return @in; // normalize input to ll origin LP tb=@in; tb.lam-=ct.ll.lam; tb.phi-=ct.ll.phi; tb.lam=Proj.adjlon(tb.lam-Proj.PI)+Proj.PI; LP t=nad_intr(tb, ct); if(inverse) { LP del, dif; int i=MAX_TRY; if(t.lam==Libc.HUGE_VAL) return t; t.lam=tb.lam+t.lam; t.phi=tb.phi-t.phi; do { del=nad_intr(t, ct); // This case used to return failure, but I have // changed it to return the first order approximation // of the inverse shift. This avoids cases where the // grid shift *into* this grid came from another grid. // While we aren't returning optimally correct results // I feel a close result in this case is better than // no result. NFW // To demonstrate use -112.5839956 49.4914451 against // the NTv2 grid shift file from Canada. if(del.lam==Libc.HUGE_VAL) { #if DEBUG Console.Error.WriteLine("Inverse grid shift iteration failed, presumably at grid edge.\nUsing first approximation."); #endif break; } t.lam-=dif.lam=t.lam-del.lam-tb.lam; t.phi-=dif.phi=t.phi+del.phi-tb.phi; } while((i--)!=0&&Math.Abs(dif.lam)>TOL12&&Math.Abs(dif.phi)>TOL12); if(i<0) { #if DEBUG Console.Error.WriteLine("Inverse grid shift iterator failed to converge."); #endif t.lam=t.phi=Libc.HUGE_VAL; return t; } @in.lam=Proj.adjlon(t.lam+ct.ll.lam); @in.phi=t.phi+ct.ll.phi; } else { if(t.lam==Libc.HUGE_VAL) @in=t; else { @in.lam-=t.lam; @in.phi+=t.phi; } } return @in; }
// Determine nad table correction value public static LP nad_intr(LP t, CTABLE ct) { LP val, frct; ILP indx; double m00, m10, m01, m11; int f00, f10, f01, f11; int index; int @in; t.lam/=ct.del.lam; t.phi/=ct.del.phi; indx.lam=(int)Math.Floor(t.lam); indx.phi=(int)Math.Floor(t.phi); frct.lam=t.lam-indx.lam; frct.phi=t.phi-indx.phi; val.lam=val.phi=Libc.HUGE_VAL; if(indx.lam<0) { if(indx.lam==-1&&frct.lam>0.99999999999) { indx.lam++; frct.lam=0.0; } else return val; } else { @in=indx.lam+1; if(@in>=ct.lim.lam) { if(@in==ct.lim.lam&&frct.lam<1e-11) { indx.lam--; frct.lam=1.0; } else return val; } } if(indx.phi<0) { if(indx.phi==-1&&frct.phi>0.99999999999) { indx.phi++; frct.phi=0.0; } else return val; } else { @in=indx.phi+1; if(@in>=ct.lim.phi) { if(@in==ct.lim.phi&&frct.phi<1e-11) { indx.phi--; frct.phi=1.0; } else return val; } } index=indx.phi*ct.lim.lam+indx.lam; f00=index++; f10=index; index+=ct.lim.lam; f11=index--; f01=index; m11=m10=frct.lam; m00=m01=1.0-frct.lam; m11*=frct.phi; m01*=frct.phi; frct.phi=1.0-frct.phi; m00*=frct.phi; m10*=frct.phi; val.lam=m00*ct.cvs[f00].lam+m10*ct.cvs[f10].lam+m01*ct.cvs[f01].lam+m11*ct.cvs[f11].lam; val.phi=m00*ct.cvs[f00].phi+m10*ct.cvs[f10].phi+m01*ct.cvs[f01].phi+m11*ct.cvs[f11].phi; return val; }