001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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 http://www.gnu.org/licenses.
027 */
028package edu.wisc.ssec.mcidasv.data;
029
030import java.io.BufferedReader;
031import java.io.File;
032import java.io.FileNotFoundException;
033import java.io.FileReader;
034import java.io.IOException;
035import java.util.ArrayList;
036import java.util.List;
037
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import ucar.unidata.geoloc.projection.UtmProjection;
042
043public class EnviInfo extends HeaderInfo {
044
045        /** The url */
046        private String dataFile = "";
047        private boolean isEnvi = false;
048        private boolean hasBounds = false;
049        private static final Logger logger = LoggerFactory.getLogger(EnviInfo.class);
050        
051        // Map Info header field indices 
052        // See www.exelisvis.com/docs/ENVIHeaderFiles.html 
053        // for a description of this information
054        enum MapInfoIndex {
055                
056                MAP_INFO_IDX_PROJ_NAME(0), 
057                MAP_INFO_IDX_X_REF(1), 
058                MAP_INFO_IDX_Y_REF(2),
059                MAP_INFO_IDX_EASTING(3),
060                MAP_INFO_IDX_NORTHING(4),
061                MAP_INFO_IDX_X_SIZE(5),
062                MAP_INFO_IDX_Y_SIZE(6),
063                MAP_INFO_IDX_ZONE(7),
064                MAP_INFO_IDX_N_OR_S(8),
065                MAP_INFO_IDX_DATUM(9),
066                MAP_INFO_IDX_UNITS(10);
067                
068                private final int index;
069                
070                private MapInfoIndex(int idx) {
071                        index = idx;
072                }
073                
074                public int getIndex() {
075                        return index;
076                }
077        }
078
079        /**
080         * Ctor for xml encoding
081         */
082        public EnviInfo() {}
083
084        /**
085         * CTOR
086         *
087         * @param thisFile File to use.
088         */
089        public EnviInfo(File thisFile) {
090                this(thisFile.getAbsolutePath());
091        }
092        
093        /**
094         * CTOR
095         *
096         * @param filename The filename
097         */
098        public EnviInfo(String filename) {
099                super(filename);
100                this.dataFile = filename.replace(".hdr", ".img");
101        }
102
103        /**
104         * Is the file an ENVI header file?
105         */
106        public boolean isEnviHeader() {
107                parseHeader();
108                return isEnvi;
109        }
110
111        /**
112         * Can we find a matching ENVI data file?
113         */
114        public boolean hasEnviData() {
115                File testFile = new File(dataFile);
116                if (testFile.exists()) return true;
117                else return false;
118        }
119
120        /**
121         * Is this a navigation header file?
122         */
123        public boolean isNavHeader() {
124                parseHeader();
125                List bandNames = (List) getParameter(BANDNAMES, new ArrayList());
126                if (bandNames == null) return false;
127                if (bandNames.contains("Latitude") && bandNames.contains("Longitude")) return true;
128                return false;
129        }
130
131        /**
132         * Which band/file is latitude?
133         */
134        public int getLatBandNum() {
135                parseHeader();
136                List bandNames = (List) getParameter(BANDNAMES, new ArrayList());
137                for (int i=0; i<bandNames.size(); i++) {
138                        if (bandNames.get(i).equals("Latitude")) return i+1;
139                }
140                return -1;
141        }
142        
143        public String getLatBandFile() {
144                parseHeader();
145                List bandFiles = (List) getParameter(BANDFILES, new ArrayList());
146                int bandNum = getLatBandNum();
147                if (bandNum < 0) return "";
148                return (String)(bandFiles.get(bandNum-1));
149        }
150
151        /**
152         * Which band/file is longitude?
153         */
154        public int getLonBandNum() {
155                parseHeader();
156                List bandNames = (List) getParameter(BANDNAMES, new ArrayList());
157                for (int i=0; i<bandNames.size(); i++) {
158                        if (bandNames.get(i).equals("Longitude")) return i+1;
159                }
160                return -1;
161        }
162        
163        public String getLonBandFile() {
164                parseHeader();
165                List bandFiles = (List) getParameter(BANDFILES, new ArrayList());
166                int bandNum = getLonBandNum();
167                if (bandNum < 0) return "";
168                return (String)(bandFiles.get(bandNum-1));
169        }
170
171        /**
172         * Return a FlatField representing the data
173         */
174//      public FlatField getDataField() {
175//
176//      }
177
178        /**
179         * Return a Gridded2DSet representing navigation
180         */
181//      public Gridded2DSet getNavField() {
182//
183//      }
184
185        /**
186         * @return the hasBounds
187         */
188        public boolean isHasBounds() {
189                return hasBounds;
190        }
191
192        /**
193         * @param hasBounds the hasBounds to set
194         */
195        public void setHasBounds(boolean hasBounds) {
196                this.hasBounds = hasBounds;
197        }
198
199        /**
200         * Parse a potential ENVI header file
201         */
202        
203        protected void parseHeader() {
204                if (haveParsed()) return;
205                if (!doesExist()) {
206                        isEnvi = false;
207                        return;
208                }
209
210                try {
211                        BufferedReader br = new BufferedReader(new FileReader(getFilename()));
212                        String line;
213                        String parameter = "";
214                        String value = "";
215                        boolean inValue = false;
216
217                        List<String> bandNames = new ArrayList<String>();
218                        List bandFiles = new ArrayList();
219
220                        while ((line = br.readLine()) != null) {
221                                if (line.trim().equals("ENVI")) {
222                                        isEnvi = true;
223                                        continue;
224                                }
225                                if (!isEnvi) break;
226
227                                int indexOfEquals = line.indexOf("=");
228                                int indexOfOpen = line.indexOf("{");
229                                int indexOfClose = line.indexOf("}");
230                                if (indexOfEquals >= 0) {
231                                        parameter = line.substring(0, indexOfEquals).trim();
232                                        value = "";
233                                        inValue = false;
234                                }
235                                if (indexOfOpen >= 0) {
236                                        if (indexOfClose >= 0) {
237                                                value += line.substring(indexOfOpen+1, indexOfClose).trim();
238                                                inValue = false;
239                                        }
240                                        else {
241                                                value += line.substring(indexOfOpen+1).trim();
242                                                inValue = true;
243                                                continue;
244                                        }
245                                }
246                                else if (inValue) {
247                                        if (indexOfClose >= 0) {
248                                                value += line.substring(0, indexOfClose).trim();
249                                                inValue = false;
250                                        }
251                                        else {
252                                                value += line.trim();
253                                                continue;
254                                        }
255                                }
256                                else {
257                                        value += line.substring(indexOfEquals+1).trim();
258                                }
259
260                                if (parameter.equals("")) continue;
261
262                                if (parameter.equals("description")) {
263                                        setParameter(DESCRIPTION, value);
264                                }
265                                
266                                // TJJ Apr 2014
267                                // NOTE: method signatures in parent class should be modified or extended
268                                // I had to pass in an Integer object here in order to be able to retrieve
269                                // anything other than default values later (both "lines" and "samples")
270                                
271                                else if (parameter.equals("samples")) {
272                                        setParameter(ELEMENTS, new Integer(value));
273                                }
274                                else if (parameter.equals("lines")) {
275                                        setParameter(LINES, new Integer(value));
276                                }
277                                
278                                else if (parameter.equals("header offset")) {
279                                        setParameter(OFFSET, Integer.parseInt(value));
280                                }
281                                else if (parameter.equals("data type")) {
282                                        setParameter(DATATYPE, Integer.parseInt(value));
283                                }
284                                else if (parameter.equals("data ignore value") ||
285                                                parameter.equals("bad value")) {
286                                        setParameter(MISSINGVALUE, Float.parseFloat(value));                            
287                                }
288                                else if (parameter.equals("interleave")) {
289                                        setParameter(INTERLEAVE, value.toUpperCase());
290                                }
291                                else if (parameter.equals("map info")) {
292                                        logger.debug("Parsing Map Info, value: " + value);
293                                        
294                                        ArrayList<String> mapInfo = new ArrayList<String>();
295                                        String[] mapInfoSplit = value.split(",");
296                                        for (int i = 0; i < mapInfoSplit.length; i++) {
297                                                mapInfo.add(mapInfoSplit[i].trim());
298                                        }
299                                        
300                                        // See www.exelisvis.com/docs/ENVIHeaderFiles.html 
301                                        // for a description of this information
302                                        // this code handles UTM files
303                                        
304                                        String projName = mapInfo.get(MapInfoIndex.MAP_INFO_IDX_PROJ_NAME.getIndex());
305                                        
306                                        if (projName.equals("UTM")) {
307                                                
308                                                // zone and hemisphere
309                                                int utmZone = Integer.parseInt(mapInfo.get(MapInfoIndex.MAP_INFO_IDX_ZONE.getIndex()));
310                                                boolean utmN = false;
311                                                if (mapInfo.get(MapInfoIndex.MAP_INFO_IDX_N_OR_S.getIndex()).equals("North")) utmN = true;
312                                                UtmProjection utmp = new UtmProjection(utmZone, utmN);
313                                                
314                                                // Java UTM class default units km, adjust if necessary
315                                                float distFactor = 1.0f;
316                                                if (mapInfo.get(MapInfoIndex.MAP_INFO_IDX_UNITS.getIndex()).contains("Meters")) distFactor = 1000.0f;
317                                                
318                                                // figure out Lat/Lon bounding box from Northing/Easting,
319                                                // resolution, and grid size
320                                                float upperLeftX = Float.parseFloat(mapInfo.get(MapInfoIndex.MAP_INFO_IDX_EASTING.getIndex())) / distFactor;
321                                                float upperLeftY = Float.parseFloat(mapInfo.get(MapInfoIndex.MAP_INFO_IDX_NORTHING.getIndex())) / distFactor;
322                                                
323                                                // lines and samples were already seen
324                                                int numLines = getParameter(LINES, 0);
325                                                int numSamples = getParameter(ELEMENTS, 0);
326                                                
327                                                float xMag = Float.parseFloat(mapInfo.get(MapInfoIndex.MAP_INFO_IDX_X_SIZE.getIndex()));
328                                                float yMag = Float.parseFloat(mapInfo.get(MapInfoIndex.MAP_INFO_IDX_Y_SIZE.getIndex()));
329                                                
330                                                float lowerRightX = upperLeftX + ((numSamples * xMag) / distFactor);
331                                                float lowerRightY = upperLeftY + ((numLines * yMag) / distFactor);
332                                                
333                                                float [][] from = new float[2][2];
334                                                from [0][0] = upperLeftX;
335                                                from [1][0] = upperLeftY;
336                                                from [0][1] = lowerRightX;
337                                                from [1][1] = lowerRightY;
338                                                float [][] to = new float[2][2];
339                                                to = utmp.projToLatLon(from, to);
340                                                
341                                                // Need to check and see if we are correct in assuming which one is upper left
342                                                if (to[0][0] > to[0][1]) {
343                                                        setParameter("BOUNDS.ULLAT", "" + to[0][0]);
344                                                        setParameter("BOUNDS.ULLON", "" + to[1][0]);
345                                                        setParameter("BOUNDS.LRLAT", "" + to[0][1]);
346                                                        setParameter("BOUNDS.LRLON", "" + to[1][1]);
347                                                } else {
348                                                        from [0][0] = upperLeftX;
349                                                        from [1][0] = upperLeftY - ((numLines * yMag) / distFactor);
350                                                        from [0][1] = lowerRightX;
351                                                        from [1][1] = lowerRightY - ((numLines * yMag) / distFactor);
352                                                        to = utmp.projToLatLon(from, to);
353                                                        setParameter("BOUNDS.ULLAT", "" + to[0][1]);
354                                                        setParameter("BOUNDS.ULLON", "" + to[1][0]);
355                                                        setParameter("BOUNDS.LRLAT", "" + to[0][0]);
356                                                        setParameter("BOUNDS.LRLON", "" + to[1][1]);
357                                                }
358                                                hasBounds = true;
359                                        }
360                                }
361                                else if (parameter.equals("byte order")) {
362                                        boolean bigEndian = false;
363                                        if (value.equals("1")) bigEndian = true;
364                                        setParameter(BIGENDIAN, bigEndian);
365                                }
366                                else if (parameter.equals("bands")) {
367                                        if (bandNames.size() <= 0 && bandFiles.size() <= 0) {
368                                                int bandCount = Integer.parseInt(value);
369                                                for (int i=0; i<bandCount; i++) {
370                                                        bandNames.add("Band " + i+1);
371                                                        bandFiles.add(dataFile);
372                                                }
373                                                setParameter(BANDNAMES, bandNames);
374                                                setParameter(BANDFILES, bandFiles);
375                                        }
376                                }
377                                else if (parameter.equals("band names")) {
378                                        bandNames = new ArrayList<String>();
379                                        bandFiles = new ArrayList<String>();
380                                        String[] bandNamesSplit = value.split(",");
381                                        for (int i=0; i<bandNamesSplit.length; i++) {
382                                                bandNames.add(bandNamesSplit[i].trim());
383                                                bandFiles.add(dataFile);
384                                        }
385                                        setParameter(BANDNAMES, bandNames);
386                                        setParameter(BANDFILES, bandFiles);
387                                }
388
389                        }
390                        br.close();
391                } catch (FileNotFoundException fnfe) {
392                        fnfe.printStackTrace();
393                } catch (IOException ioe) {
394                        ioe.printStackTrace();
395                }
396
397        }
398
399}