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