001/*
002 * $Id: FlatFileReader.java,v 1.9 2011/03/24 16:06:32 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030package edu.wisc.ssec.mcidasv.data;
031
032import java.awt.Image;
033import java.awt.Toolkit;
034import java.io.*;
035import java.rmi.RemoteException;
036
037import ucar.unidata.data.BadDataException;
038import ucar.unidata.data.grid.GridUtil;
039import ucar.unidata.util.IOUtil;
040import visad.CoordinateSystem;
041import visad.Data;
042import visad.FlatField;
043import visad.FunctionType;
044import visad.Gridded2DSet;
045import visad.Linear2DSet;
046import visad.RealTupleType;
047import visad.RealType;
048import visad.VisADException;
049import visad.util.ImageHelper;
050
051import edu.wisc.ssec.mcidasv.data.HeaderInfo;
052import edu.wisc.ssec.mcidasv.data.hydra.LongitudeLatitudeCoordinateSystem;
053import edu.wisc.ssec.mcidasv.data.hydra.SwathNavigation;
054
055public class FlatFileReader {
056        
057    /** The url */
058        private String url = null;
059        
060        /** The dimensions */
061        private int lines = 0;
062        private int elements = 0;
063        private int strideLines = 0;
064        private int strideElements = 0;
065        private int band = 1;
066        private int bandCount = 1;
067        private String unit = "";
068        private int stride = 1;
069        
070        /** The nav dimensions */
071        private int navLines = 0;
072        private int navElements = 0;
073        
074        /** The data parameters */
075        private String interleave = HeaderInfo.kInterleaveSequential;
076        private boolean bigEndian = false;
077        private int offset = 0;
078        private String delimiter = "\\s+";
079        private int dataScale = 1;
080        
081        /** The nav parameters */
082        private double ulLat = Double.NaN;
083        private double ulLon = Double.NaN;
084        private double lrLat = Double.NaN;
085        private double lrLon = Double.NaN;
086        private String latFile = null;
087        private String lonFile = null;
088        private int latlonScale = 1;
089        private boolean eastPositive = false;
090                        
091        /** which format this object is representing */
092        private int myFormat = HeaderInfo.kFormatUnknown;
093        private int myNavigation = HeaderInfo.kNavigationUnknown;
094        
095        /** the actual floats read from the file */
096        float[] floatData = null;
097        
098        /** cache the nav info when possible */
099        Gridded2DSet navigationSet = null;
100        CoordinateSystem navigationCoords = null;
101        
102    /**
103     * Ctor for xml encoding
104     */
105    public FlatFileReader() {}
106
107    /**
108     * CTOR
109     *
110     * @param filename The filename
111     */
112    public FlatFileReader(String filename) {
113        this.url = filename;
114    }
115
116    /**
117     * CTOR
118     *
119     * @param filename The filename
120     * @param lines The number of lines
121     * @param elements The number of elements
122     * @param band The band
123     */
124    public FlatFileReader(String filename, int lines, int elements) {
125        this.url = filename;
126        this.lines = lines;
127        this.elements = elements;
128        setStride(1);
129    }
130    
131    /**
132     * @param delimiter The data value delimiter
133     * @param dataScale The data scale factor
134     */
135    public void setBinaryInfo(int format, String interleave, boolean bigEndian, int offset, int band, int bandCount) {
136        this.myFormat = format;
137        this.interleave = interleave;
138        this.bigEndian = bigEndian;
139        this.offset = offset;
140        this.band = band;
141        this.bandCount = bandCount;
142    }
143
144    /**
145     * @param delimiter The data value delimiter
146     * @param dataScale The data scale factor
147     */
148    public void setAsciiInfo(String delimiter, int dataScale) {
149        this.myFormat = HeaderInfo.kFormatASCII;
150        if (delimiter == null || delimiter.trim().length() == 0) delimiter="\\s+";
151        this.delimiter = delimiter;
152        this.dataScale = dataScale;
153    }
154
155    /**
156     * @param delimiter The data value delimiter
157     * @param dataScale The data scale factor
158     */
159    public void setImageInfo() {
160                this.myFormat = HeaderInfo.kFormatImage;
161    }
162
163    /**
164     * @param ulLat The upper left latitude
165     * @param ulLon The upper left longitude
166     * @param lrLat The lower right latitude
167     * @param lrLon The lower right longitude
168     */
169    public void setNavBounds(double ulLat, double ulLon, double lrLat, double lrLon) {
170        this.myNavigation = HeaderInfo.kNavigationBounds;
171        this.ulLat = ulLat;
172        this.ulLon = ulLon;
173        this.lrLat = lrLat;
174        this.lrLon = lrLon;
175        this.latlonScale = 1;
176    }
177    
178    /**
179     * @param ulLat The latitude file
180     * @param ulLon The longitude file
181     * @param latlonScale The navigation value scaling
182     */
183    public void setNavFiles(String latFile, String lonFile, int latlonScale) {
184        this.myNavigation = HeaderInfo.kNavigationFiles;
185        this.latFile = latFile;
186        this.lonFile = lonFile;
187        this.latlonScale = latlonScale;
188    }
189    
190    /**
191     * @param eastPositive
192     */
193    public void setEastPositive(boolean eastPositive) {
194        this.eastPositive = eastPositive;
195    }
196
197    /**
198     * @param stride
199     */
200    public void setStride(int stride) {
201        if (stride < 1) stride=1;
202        this.stride = stride;
203        this.strideElements = (int)Math.ceil((float)this.elements/(float)stride);
204                this.strideLines = (int)Math.ceil((float)this.lines/(float)stride);
205    }
206
207    /**
208     * @param unit
209     */
210    public void setUnit(String unit) {
211        if (unit.trim().equals("")) unit="";
212        this.unit = unit;
213    }
214
215    /**
216     * Read floats from a binary file
217     */
218    private void readFloatsFromBinary() {
219        System.out.println("FlatFileInfo.readFloatsFromBinary()");
220                        
221        int bytesEach = 1;
222                switch (this.myFormat) {
223                case HeaderInfo.kFormat1ByteUInt:
224                        bytesEach = 1;
225                        break;
226                case HeaderInfo.kFormat2ByteUInt:
227                        bytesEach = 2;
228                        break;
229                case HeaderInfo.kFormat2ByteSInt:
230                        bytesEach = 2;
231                        break;
232                case HeaderInfo.kFormat4ByteSInt:
233                        bytesEach = 4;
234                        break;
235                case HeaderInfo.kFormat4ByteFloat:
236                        bytesEach = 4;
237                        break;
238                default:
239                        System.err.println("FlatFileReader: Unrecognized binary format: " + this.myFormat);
240                        return;
241                }
242
243        int curPixel = 0;
244                int curElement = 0;
245                int curLine = 0;
246                int lastRead = 0;
247                int readEach = 8192;
248                int startPointer = 0;
249                int endPointer = -1;
250                int pixelPointer = 0;
251
252                int readPixels = this.strideElements * this.strideLines;
253        floatData = new float[readPixels];
254                byte[] readBytes = new byte[readEach];
255
256        try {            
257                FileInputStream fis = new FileInputStream(url);
258                
259                // byte boundaries
260                assert(readEach % 64 == 0);
261                
262                // assure we read the first time
263                assert(endPointer < 0);
264                
265                while ((curPixel < readPixels && curLine < lines && lastRead > 0) || curPixel == 0) {
266                        
267                        pixelPointer = this.offset;
268                        if (this.interleave.equals(HeaderInfo.kInterleaveSequential)) {
269                                // Skip to the right band
270                                pixelPointer += (this.band - 1) * (this.lines * this.elements * bytesEach);
271                                // Skip into the band
272                                pixelPointer += (curLine * this.elements * bytesEach) + (curElement * bytesEach);
273                        }
274                        else if (this.interleave.equals(HeaderInfo.kInterleaveByLine)) {
275                                // Skip to the right line
276                                pixelPointer += curLine * (this.bandCount * this.elements * bytesEach);
277                                // Skip into the line
278                                pixelPointer += ((this.band - 1) * this.elements * bytesEach) + (curElement * bytesEach);
279                        }
280                        else if (this.interleave.equals(HeaderInfo.kInterleaveByPixel)) {
281                                // Skip to the right line
282                                pixelPointer += curLine * (this.bandCount * this.elements * bytesEach);
283                                // Skip into the line
284                                pixelPointer += (curElement * bandCount * bytesEach) + ((this.band - 1) * bytesEach);
285                        }
286                        else {
287                                System.err.println("FlatFileReader: Unrecognized interleave type: " + this.interleave);
288                        }
289                        
290                        // We need data outside of our buffer
291                        if (pixelPointer > endPointer) {
292                                
293                                // Skip ahead to useful data
294                                int skipBytes = pixelPointer - endPointer - 1;
295                                if (skipBytes > 0) {
296//                                      System.out.println(" Skipping " + skipBytes + " bytes");
297                                        startPointer += lastRead + fis.skip(skipBytes);
298                                        endPointer = startPointer;
299                                }
300
301                                // Read more bytes 
302                                lastRead = fis.read(readBytes);
303                                if (startPointer != endPointer)
304                                        startPointer = endPointer + 1;
305                                endPointer = startPointer + lastRead - 1;
306//                              System.out.println(" Read " + lastRead + " bytes, from " + startPointer);
307
308                        }
309                        
310                        int readOffset = pixelPointer - startPointer;                                   
311                        switch (this.myFormat) {
312                        case HeaderInfo.kFormat1ByteUInt:
313                                floatData[curPixel++] = (float)bytesTo1ByteUInt(readBytes, readOffset);
314                                break;
315                        case HeaderInfo.kFormat2ByteUInt:
316                                floatData[curPixel++] = (float)bytesTo2ByteUInt(readBytes, readOffset);
317                                break;
318                        case HeaderInfo.kFormat2ByteSInt:
319                                floatData[curPixel++] = (float)bytesTo2ByteSInt(readBytes, readOffset);
320                                break;
321                        case HeaderInfo.kFormat4ByteSInt:
322                                floatData[curPixel++] = (float)bytesTo4ByteSInt(readBytes, readOffset);
323                                break;
324                        case HeaderInfo.kFormat4ByteFloat:
325                                floatData[curPixel++] = (float)bytesTo4ByteFloat(readBytes, readOffset);
326                                break;
327                        }
328
329                        curElement+=stride;
330                        if (curElement >= elements) {
331                                curElement = 0;
332                                curLine+=stride;
333                        }
334                }
335                
336            fis.close();
337            
338            System.out.println("  read " + curPixel + " floats (expected " + readPixels + ")");
339            
340        } catch (NumberFormatException exc) {
341                throw new BadDataException("Error parsing binary file");
342        } catch (Exception e) {
343                throw new BadDataException("Error reading binary file: " + url + "\n" + e);
344        }
345    }
346        
347    /**
348     * Read floats from an ASCII file
349     */
350    private void readFloatsFromAscii() {
351        System.out.println("FlatFileInfo.readFloatsFromAscii()");
352        
353        int curPixel = 0;
354                int curElement = 0;
355                int curLine = 0;
356                
357                int readPixels = this.strideElements * this.strideLines;
358        floatData = new float[readPixels];
359        
360        try {            
361                InputStream is = IOUtil.getInputStream(url, getClass());
362                BufferedReader in = new BufferedReader(new InputStreamReader(is));
363            String aLine;
364            
365            while ((aLine = in.readLine()) != null) {
366                aLine = aLine.trim();
367                String[] words = aLine.split(delimiter);
368                for (int i=0; i<words.length; i++) {
369                        
370                                if (curLine % stride == 0 && curElement % stride == 0) {
371                                        floatData[curPixel++] = Float.parseFloat(words[i]);
372                                }
373                                
374                                // Keep track of what element/line we are reading so we can stride appropriately
375                                curElement++;
376                                if (curElement >= elements) {
377                                        curElement = 0;
378                                        curLine++;
379                                }
380                                if (curLine > lines || curPixel > readPixels) {
381                                throw new BadDataException("Error parsing ASCII file: Bad dimensions");
382                                }
383
384                }                       
385            }
386            in.close();
387                        
388            System.out.println("  read " + curPixel + " floats (expected " + readPixels + ")");
389           
390        } catch (NumberFormatException exc) {
391                throw new BadDataException("Error parsing ASCII file");
392        } catch (Exception e) {
393                throw new BadDataException("Error reading ASCII file: " + url + "\n" + e);
394        }
395    }
396    
397    /**
398     * Make a FlatField from an Image
399     */
400    private Data getDataFromImage() {
401        System.out.println("FlatFileInfo.getDataFromImage()");
402        try {
403                floatData = new float[0];
404                InputStream is = IOUtil.getInputStream(url, getClass());
405                byte[] imageContent = IOUtil.readBytes(is);
406                Image image = Toolkit.getDefaultToolkit().createImage(imageContent);
407                ImageHelper ih = new ImageHelper();
408                image.getWidth(ih);
409                if (ih.badImage) {
410                        throw new IllegalStateException("Bad image: " + url);
411                }
412                
413            makeCoordinateSystem();
414
415                FlatField field = (FlatField) ucar.visad.Util.makeField(image,true);
416                        return GridUtil.setSpatialDomain(field, navigationSet);
417                        
418        } catch (Exception e) {
419                throw new BadDataException("Error reading image file: " + url + "\n" + e);
420        }
421    }
422
423    /**
424     * Make a Gridded2DSet from bounds
425     */
426    private Gridded2DSet getNavigationSetFromBounds() {
427        System.out.println("FlatFileInfo.getNavigationSetFromBounds()");
428            try {
429                this.navElements = this.strideElements;
430                this.navLines = this.strideLines;
431                int lonScale = this.latlonScale;
432                int latScale = this.latlonScale;
433                if (eastPositive) lonScale *= -1;
434                return new Linear2DSet(RealTupleType.SpatialEarth2DTuple,
435                                ulLon / lonScale, lrLon / lonScale, navElements,
436                                ulLat / latScale, lrLat / latScale, navLines);
437            } catch (Exception e) {
438                        throw new BadDataException("Error setting navigation bounds:\n" + e);
439            }
440    }
441
442    /**
443     * Make a Gridded2DSet from files
444     */
445    private Gridded2DSet getNavigationSetFromFiles() {
446        System.out.println("FlatFileInfo.getNavigationSetFromFiles()");
447        try {
448            float[][] lalo = new float[0][0];
449            
450            FlatFileReader lonData, latData;
451            
452            // ASCII nav files
453            if (this.myFormat == HeaderInfo.kFormatASCII) {
454                System.out.println("  ASCII nav file");
455                    
456                        this.navElements = this.elements;
457                        this.navLines = this.lines;
458                lalo = new float[2][navElements * navLines];
459                                                                        
460                        // Longitude band
461                        lonData = new FlatFileReader(lonFile, navLines, navElements);
462                        lonData.setAsciiInfo(delimiter, 1);
463                        
464                        // Latitude band
465                        latData = new FlatFileReader(latFile, navLines, navElements);
466                        latData.setAsciiInfo(delimiter, 1);
467                                            
468            }
469            
470            // Binary nav files
471            else {
472                
473                System.out.println("  Binary nav file");
474
475                // ENVI header for nav
476                EnviInfo enviLat = new EnviInfo(latFile);
477                EnviInfo enviLon = new EnviInfo(lonFile);
478                if (enviLat.isNavHeader() && enviLon.isNavHeader()) {
479                        System.out.println("    ENVI nav file");
480
481                        this.navElements = enviLat.getParameter(HeaderInfo.ELEMENTS, 0);
482                        this.navLines = enviLat.getParameter(HeaderInfo.LINES, 0);
483                    lalo = new float[2][navElements * navLines];
484                                                                        
485                        // Longitude band
486                        lonData = new FlatFileReader(enviLon.getLonBandFile(), 
487                                        enviLon.getParameter(HeaderInfo.LINES, 0),
488                                        enviLon.getParameter(HeaderInfo.ELEMENTS, 0));
489                        lonData.setBinaryInfo(
490                                        enviLon.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown),
491                                        enviLon.getParameter(HeaderInfo.INTERLEAVE, HeaderInfo.kInterleaveSequential),
492                                        enviLon.getParameter(HeaderInfo.BIGENDIAN, false),
493                                        enviLon.getParameter(HeaderInfo.OFFSET, 0),
494                                        enviLon.getLonBandNum(),
495                                        enviLon.getBandCount());
496                        
497                        // Latitude band
498                        latData = new FlatFileReader(enviLat.getLatBandFile(), 
499                                        enviLat.getParameter(HeaderInfo.LINES, 0),
500                                        enviLat.getParameter(HeaderInfo.ELEMENTS, 0));
501                        latData.setBinaryInfo(
502                                        enviLat.getParameter(HeaderInfo.DATATYPE, HeaderInfo.kFormatUnknown),
503                                        enviLat.getParameter(HeaderInfo.INTERLEAVE, HeaderInfo.kInterleaveSequential),
504                                        enviLat.getParameter(HeaderInfo.BIGENDIAN, false),
505                                        enviLat.getParameter(HeaderInfo.OFFSET, 0),
506                                        enviLat.getLatBandNum(),
507                                        enviLat.getBandCount());
508                        
509                }
510                
511                else {
512                        System.out.println("    AXFORM nav file");
513
514                        this.navElements = this.elements;
515                        this.navLines = this.lines;
516                    lalo = new float[2][navElements * navLines];
517                                                                        
518                        // Longitude band
519                        lonData = new FlatFileReader(lonFile, navLines, navElements);
520                        lonData.setBinaryInfo(HeaderInfo.kFormat2ByteUInt, HeaderInfo.kInterleaveSequential, bigEndian, offset, 1, 1);
521                        
522                        // Latitude band
523                        latData = new FlatFileReader(latFile, navLines, navElements);
524                        latData.setBinaryInfo(HeaderInfo.kFormat2ByteUInt, HeaderInfo.kInterleaveSequential, bigEndian, offset, 1, 1);
525                        
526                }
527                                
528            }
529                        
530                // Set the stride if the dimensions are the same and read the floats
531                if (this.lines == this.navLines && this.elements == this.navElements && stride != 1) {
532                        System.out.println("Setting stride for nav files: " + stride);
533                        lonData.setStride(this.stride);
534                        latData.setStride(this.stride);
535                        this.navElements = this.strideElements;
536                        this.navLines = this.strideLines;
537                lalo = new float[2][this.navElements * this.navLines];
538                }
539                lalo[0] = lonData.getFloats();
540                lalo[1] = latData.getFloats();
541                
542                // Take into account scaling and east positive
543                int latScale = this.latlonScale;
544                        int lonScale = this.latlonScale;
545                        if (eastPositive) lonScale = -1 * lonScale;
546                for (int i=0; i<lalo[0].length; i++) {
547                        lalo[0][i] = lalo[0][i] / (float)lonScale;
548                }
549                for (int i=0; i<lalo[1].length; i++) {
550                        lalo[1][i] = lalo[1][i] / (float)latScale;
551                }
552                
553            return new Gridded2DSet(RealTupleType.SpatialEarth2DTuple,
554                        lalo, navElements, navLines,
555                        null, null, null,
556                        false, false);
557            
558        } catch (NumberFormatException exc) {
559                throw new BadDataException("Error parsing ASCII navigation file");
560        } catch (Exception e) {
561                throw new BadDataException("Error setting navigation from file: " + url + "\n" + e);
562        }
563    }
564        
565    /**
566     * Create navigation info if it hasn't been built
567     */
568    private void makeCoordinateSystem() {
569        System.out.println("FlatFileInfo.makeCoordinateSystem()");
570        
571        if (navigationSet != null && navigationCoords != null) return;
572
573        switch (this.myNavigation) {
574        case HeaderInfo.kNavigationBounds:
575                navigationSet = getNavigationSetFromBounds();
576                break;
577        case HeaderInfo.kNavigationFiles:
578                navigationSet = getNavigationSetFromFiles();
579                break;
580                default:
581                        System.err.println("Unknown navigation format");
582        }
583        
584                // myElements, myLines: Nav dimensions
585                // this.elements, this.lines: Data dimensions
586                float ratioElements = (float)this.strideElements / (float)this.navElements;
587                float ratioLines = (float)this.strideLines / (float)this.navLines;
588                int[] geo_start = new int[2];
589                int[] geo_count = new int[2];
590                int[] geo_stride = new int[2];
591                try {
592                        Linear2DSet domainSet = SwathNavigation.getNavigationDomain(
593                                        0, strideElements-1, 1,
594                                        0, strideLines-1, 1, 
595                                        ratioElements, ratioLines, 0, 0, 
596                                        geo_start, geo_count, geo_stride);
597                                                
598//                      System.out.println("makeCoordinateSystem stats for " + url + ":");
599//                      System.out.println("  Elements: " + strideElements + ", Lines: " + strideLines);
600//                      System.out.println("  navElements: " + navElements + ", navLines: " + navLines);
601//                      System.out.println("  ratioElements: " + ratioElements + ", ratioLines: " + ratioLines);
602//                      System.out.println("  navigationSet: " + navigationSet.getLength(0) + " x " + navigationSet.getLength(1));
603//                      System.out.println("  geo_start: " + geo_start[0] + ", " + geo_start[1]);
604//                      System.out.println("  geo_count: " + geo_count[0] + ", " + geo_count[1]);
605//                      System.out.println("  geo_stride: " + geo_stride[0] + ", " + geo_stride[1]);
606//                      System.out.println("  domainSet: " + domainSet.getLength(0) + " x " + domainSet.getLength(1));
607//                      System.out.println("  domainSet.toString(): " + domainSet.toString());
608                        
609                        navigationCoords = new LongitudeLatitudeCoordinateSystem(domainSet, navigationSet);
610                } catch (Exception e) {
611                        // TODO Auto-generated catch block
612                        e.printStackTrace();
613                }
614    }
615
616    /**
617     * Return a valid data object for a DataSource
618     */
619    public Data getData() {
620        System.out.println("FlatFileInfo.getData()");
621
622        Data d = null;
623        FlatField field;
624
625        try {
626
627                switch (this.myFormat) {
628                case HeaderInfo.kFormatImage:
629                        d = getDataFromImage();
630                        break;
631                default:
632                        floatData = getFloats();
633                        field = getFlatField();
634//                      d = GridUtil.setSpatialDomain(field, navigationSet);
635                        d = field;
636                        break;
637                }
638
639        } catch (IOException e) {
640                // TODO Auto-generated catch block
641                e.printStackTrace();
642        } catch (VisADException e) {
643                // TODO Auto-generated catch block
644                e.printStackTrace();
645                }
646
647        return d;
648    }
649    
650    /**
651     * Return the array of floats making up the data
652     */
653    public float[] getFloats() {
654        System.out.println("FlatFileInfo.getFloats()");
655        
656        if (floatData != null) return floatData;
657        
658        switch (this.myFormat) {
659        case HeaderInfo.kFormatImage:
660                break;
661        case HeaderInfo.kFormatASCII:
662                readFloatsFromAscii();
663                break;
664                default:
665                readFloatsFromBinary();
666                        break;
667        }
668        
669        
670        
671                // DEBUG!
672//      File justName = new File(url);
673//      try {
674//              BufferedWriter out = new BufferedWriter(new FileWriter("/tmp/mcv/" + justName.getName()));
675//              for (int i=0; i<floatData.length; i++) {
676//                      if (i%strideElements==0) out.write("New line " + (i/strideElements) + " at element " + i + "\n");
677//                      out.write(floatData[i] + "\n");
678//              }
679//              out.close();
680//      } 
681//      catch (IOException e) { 
682//              System.out.println("Exception ");
683//      }
684
685                
686        
687        return floatData;
688    }
689        
690    /**
691     * float array -> flatfield
692     */
693    private FlatField getFlatField()
694    throws IOException, VisADException {
695        
696        makeCoordinateSystem();
697        
698//      RealType[]    unit                      = new RealType[] { RealType.Generic };
699//      RealTupleType unitType       = new RealTupleType(unit);
700        
701        RealType          unitType                      = RealType.getRealType(unit);
702
703        RealType      line              = RealType.getRealType("ImageLine");
704        RealType      element           = RealType.getRealType("ImageElement");
705        RealType[]    domain_components = { element, line };
706        RealTupleType image_domain      = new RealTupleType(domain_components, navigationCoords, null);
707        FunctionType  image_type                = new FunctionType(image_domain, unitType);
708        Linear2DSet   domain_set                = new Linear2DSet(image_domain,
709                        0.0, (float) (strideElements - 1.0), strideElements,
710                        0.0, (float) (strideLines - 1.0), strideLines);
711
712        FlatField    field      = new FlatField(image_type, domain_set);
713
714        float[][]    samples    = new float[][] { floatData };
715        try {
716            field.setSamples(samples, false);
717        } catch (RemoteException e) {
718            throw new VisADException("Couldn't finish FlatField initialization");
719        }
720        
721        return field;
722    }
723        
724    /**
725     * toString
726     *
727     * @return toString
728     */
729    public String toString() {
730        return "url: " + url + ", lines: " + lines + ", elements: " + elements;
731    }
732    
733    // byte[] conversion functions
734    // TODO: are these replicated elsewhere in McV?
735
736        private static int bytesTo1ByteUInt (byte[] bytes, int offset) {
737                return (int) ( bytes[offset] & 0xff );
738        }
739        
740        private static int bytesTo2ByteUInt (byte[] bytes, int offset) {
741                int accum = 0;
742                for ( int shiftBy = 0; shiftBy < 16; shiftBy += 8 ) {
743                        accum |= ( (long)( bytes[offset] & 0xff ) ) << shiftBy;
744                        offset++;
745                }
746                return (int)( accum );
747        }
748        
749        private static int bytesTo2ByteSInt (byte[] bytes, int offset) {
750                return (bytesTo2ByteUInt(bytes, offset)) - 32768;
751        }
752        
753        private static int bytesTo4ByteSInt (byte[] bytes, int offset) {
754                int accum = 0;
755                for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 ) {
756                        accum |= ( (long)( bytes[offset] & 0xff ) ) << shiftBy;
757                        offset++;
758                }
759                return (int)( accum );
760        }
761
762        private static float bytesTo4ByteFloat (byte[] bytes, int offset) {
763                int accum = 0;
764                for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 ) {
765                        accum |= ( (long)( bytes[offset] & 0xff ) ) << shiftBy;
766                        offset++;
767                }
768                return Float.intBitsToFloat(accum);
769        }       
770        
771        private static long bytesToLong (byte[] bytes) {
772                if (bytes.length != 4) return 0;
773                long accum = 0;
774                int i = 0;
775                for ( int shiftBy = 0; shiftBy < 32; shiftBy += 8 ) {
776                        accum |= ( (long)( bytes[i] & 0xff ) ) << shiftBy;
777                        i++;
778                }
779                return accum;
780        }
781        
782        private static double bytesToDouble (byte[] bytes) {
783                if (bytes.length != 8) return 0;
784                long accum = 0;
785                int i = 0;
786                for ( int shiftBy = 0; shiftBy < 64; shiftBy += 8 ) {
787                        accum |= ( (long)( bytes[i] & 0xff ) ) << shiftBy;
788                        i++;
789                }
790                return Double.longBitsToDouble(accum);
791        }
792
793}