001    /*
002     * $Id: MultiSpectralData.java,v 1.22 2012/02/19 17:35:41 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
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     */
030    
031    package edu.wisc.ssec.mcidasv.data.hydra;
032    
033    import java.awt.geom.Rectangle2D;
034    import java.rmi.RemoteException;
035    import java.util.ArrayList;
036    import java.util.HashMap;
037    import java.util.Iterator;
038    
039    import visad.CoordinateSystem;
040    import visad.FlatField;
041    import visad.FunctionType;
042    import visad.Gridded2DSet;
043    import visad.Linear1DSet;
044    import visad.Linear2DSet;
045    import visad.Real;
046    import visad.RealTuple;
047    import visad.RealTupleType;
048    import visad.RealType;
049    import visad.SampledSet;
050    import visad.Set;
051    import visad.SetType;
052    import visad.VisADException;
053    
054    public class MultiSpectralData extends MultiDimensionAdapter {
055    
056      SwathAdapter swathAdapter = null;
057      SpectrumAdapter spectrumAdapter = null;
058      CoordinateSystem cs = null;
059    
060      HashMap spectrumSelect = null;
061      HashMap swathSelect = null;
062    
063      String sensorName = null;
064      String platformName = null;
065      String paramName = null;
066      String inputParamName = null;
067      String name = null;
068    
069      public float init_wavenumber = 919.50f;
070      public String init_bandName = null;
071    
072      float[] dataRange = new float[] {180f, 320f};
073    
074      boolean hasBandNames = false;
075      ArrayList<String> bandNameList = null;
076      HashMap<String, Float> bandNameMap = null;
077    
078      
079      public MultiSpectralData(SwathAdapter swathAdapter, SpectrumAdapter spectrumAdapter,
080                               String inputParamName, String paramName, String sensorName, String platformName) {
081        this.swathAdapter = swathAdapter;
082        this.spectrumAdapter = spectrumAdapter;
083        this.paramName = paramName;
084        this.inputParamName = inputParamName;
085        this.name = swathAdapter.getArrayName();
086    
087        if (spectrumAdapter != null) {
088          this.spectrumSelect = spectrumAdapter.getDefaultSubset();
089          if (spectrumAdapter.hasBandNames()) {
090            hasBandNames = true;
091            bandNameList = spectrumAdapter.getBandNames();
092            bandNameMap = spectrumAdapter.getBandNameMap();
093          }
094          try {
095            setInitialWavenumber(getWavenumberFromChannelIndex(0));
096          } 
097          catch (Exception e) {
098            e.printStackTrace();
099            System.out.println("could not initialize initial wavenumber");
100          }
101        }
102    
103        if (swathAdapter != null) {
104          this.swathSelect = swathAdapter.getDefaultSubset();
105          if (spectrumAdapter != null) {
106            spectrumAdapter.setRangeProcessor(swathAdapter.getRangeProcessor());
107          }
108        }
109    
110        this.sensorName = sensorName;
111        this.platformName = platformName;
112      }
113    
114      public MultiSpectralData(SwathAdapter swathAdapter, SpectrumAdapter spectrumAdapter,
115                               String sensorName, String platformName) {
116        this(swathAdapter, spectrumAdapter, "Radiance", "BrightnessTemp", sensorName, platformName);
117      }
118    
119      public MultiSpectralData(SwathAdapter swathAdapter, SpectrumAdapter spectrumAdapter) {
120        this(swathAdapter, spectrumAdapter, null, null);
121      }
122    
123      public MultiSpectralData() {
124        this(null, null, null, null);
125      }
126    
127      public FlatField getSpectrum(int[] coords) 
128          throws Exception, VisADException, RemoteException {
129        if (coords == null) return null;
130        if (spectrumAdapter == null) return null;
131        spectrumSelect.put(SpectrumAdapter.x_dim_name, new double[] {(double)coords[0], (double)coords[0], 1.0});
132        spectrumSelect.put(SpectrumAdapter.y_dim_name, new double[] {(double)coords[1], (double)coords[1], 1.0});
133    
134        FlatField spectrum = spectrumAdapter.getData(spectrumSelect);
135        return convertSpectrum(spectrum, paramName);
136      }
137    
138      public FlatField getSpectrum(RealTuple location) 
139          throws Exception, VisADException, RemoteException {
140        if (spectrumAdapter == null) return null;
141        int[] coords = getSwathCoordinates(location, cs);
142        if (coords == null) return null;
143        spectrumSelect.put(SpectrumAdapter.x_dim_name, new double[] {(double)coords[0], (double)coords[0], 1.0});
144        spectrumSelect.put(SpectrumAdapter.y_dim_name, new double[] {(double)coords[1], (double)coords[1], 1.0});
145    
146        FlatField spectrum = spectrumAdapter.getData(spectrumSelect);
147        return convertSpectrum(spectrum, paramName);
148      }
149    
150      public FlatField getImage(HashMap subset) 
151        throws Exception, VisADException, RemoteException {
152        FlatField image = swathAdapter.getData(subset);
153        cs = ((RealTupleType) ((FunctionType)image.getType()).getDomain()).getCoordinateSystem();
154    
155        int channelIndex = (int) ((double[])subset.get(SpectrumAdapter.channelIndex_name))[0];
156        float channel = spectrumAdapter.getWavenumberFromChannelIndex(channelIndex);
157    
158        return convertImage(image, channel, paramName);
159      }
160    
161      public FlatField getImage(float channel, HashMap subset) 
162          throws Exception, VisADException, RemoteException {
163        if (spectrumAdapter == null) return getImage(subset);
164        int channelIndex = spectrumAdapter.getChannelIndexFromWavenumber(channel);
165        subset.put(SpectrumAdapter.channelIndex_name, new double[] {(double)channelIndex, (double)channelIndex, 1.0});
166        FlatField image = swathAdapter.getData(subset);
167        cs = ((RealTupleType) ((FunctionType)image.getType()).getDomain()).getCoordinateSystem();
168    
169        return convertImage(image, channel, paramName);
170      }
171    
172      public FlatField getData(Object subset) throws Exception {
173        return getImage((HashMap)subset);
174      }
175    
176      public Set makeDomain(Object subset) throws Exception {
177        throw new Exception("makeDomain unimplented");
178      } 
179    
180    
181      FlatField convertImage(FlatField image, float channel, String param) 
182                throws Exception {
183        FlatField new_image = null;
184        FunctionType f_type = (FunctionType)image.getType();
185        if (param.equals("BrightnessTemp")) { //- convert radiance to BrightnessTemp
186          FunctionType new_type = new FunctionType(f_type.getDomain(), RealType.getRealType("BrightnessTemp"));
187          new_image = new FlatField(new_type, image.getDomainSet());
188          float[][] values = image.getFloats(false);
189          float[] bt_values = values[0];
190          if (inputParamName == "Radiance") {
191            bt_values = radianceToBrightnessTemp(values[0], channel, platformName, sensorName);
192          }
193          new_image.setSamples(new float[][] {bt_values}, false);
194        }
195        else if (param.equals("Reflectance")) {
196          FunctionType new_type = new FunctionType(f_type.getDomain(), RealType.getRealType("Reflectance"));
197          new_image = new FlatField(new_type, image.getDomainSet());
198          new_image.setSamples(image.getFloats(false), false);
199        }
200        else {
201          new_image = image;
202        }
203        return new_image;
204      }
205    
206    
207      FlatField convertSpectrum(FlatField spectrum, String param) throws Exception {
208        FlatField new_spectrum = null;
209        FunctionType f_type = (FunctionType) spectrum.getType();
210    
211        if (param.equals("BrightnessTemp")) {
212          FunctionType new_type = new FunctionType(f_type.getDomain(), RealType.getRealType("BrightnessTemp"));
213          float[][] channels = ((SampledSet)spectrum.getDomainSet()).getSamples(false);
214          float[][] values = spectrum.getFloats(false);
215          float[] bt_values = values[0];
216          if (inputParamName == "Radiance") {
217            bt_values = radianceToBrightnessTempSpectrum(values[0], channels[0], platformName, sensorName);
218          }
219          new_spectrum = new FlatField(new_type, spectrum.getDomainSet());
220          new_spectrum.setSamples(new float[][] {bt_values}, true);
221        }
222        else if (param.equals("Reflectance")) {
223          FunctionType new_type = new FunctionType(f_type.getDomain(), RealType.getRealType("Reflectance"));
224          new_spectrum = new FlatField(new_type, spectrum.getDomainSet());
225          new_spectrum.setSamples(spectrum.getFloats(false), false);
226        }
227        else {
228          new_spectrum = spectrum;
229        }
230        return new_spectrum;
231      }
232    
233      protected void setDataRange(float[] range) {
234        dataRange = range;
235      }
236    
237      public float[] getDataRange() {
238        return dataRange;
239      }
240    
241      public String getParameter() {
242        return paramName;
243      }
244    
245      public String getName() {
246        return name;
247      }
248    
249      public CoordinateSystem getCoordinateSystem() {
250        return cs;
251      }
252    
253      public void setCoordinateSystem(CoordinateSystem cs) {
254        this.cs = cs;
255      }
256    
257      public boolean hasBandNames() {
258        return hasBandNames;
259      }
260    
261      public ArrayList<String> getBandNames() {
262        return bandNameList;
263      }
264    
265      public HashMap<String, Float> getBandNameMap() {
266        return bandNameMap;
267      }
268    
269      public String getBandNameFromWaveNumber(float channel) {
270        String bandName = null;
271        Iterator iter = bandNameMap.keySet().iterator();
272        while (iter.hasNext()) {
273           String key = (String) iter.next();
274           float mapVal = ((Float)bandNameMap.get(key)).floatValue();
275           if (channel == mapVal) {
276             bandName = key;
277             break;
278           }
279        }
280        return bandName;
281      }
282    
283      public void setInitialWavenumber(float val) {
284        init_wavenumber = val;
285        if (hasBandNames) {
286          init_bandName = getBandNameFromWaveNumber(init_wavenumber);
287        }
288      }
289    
290      public int[] getSwathCoordinates(RealTuple location, CoordinateSystem cs) 
291          throws VisADException, RemoteException {
292        if (location == null) return null;
293        if (cs == null) return null;
294        Real[] comps = location.getRealComponents();
295        //- trusted: latitude:0, longitude:1
296        float lon = (float) comps[1].getValue();
297        float lat = (float) comps[0].getValue();
298        if (lon < -180) lon += 360f;
299        if (lon > 180) lon -= 360f;
300        float[][] xy = cs.fromReference(new float[][] {{lon}, {lat}});
301        if ((Float.isNaN(xy[0][0])) || Float.isNaN(xy[1][0])) return null;
302        Set domain = swathAdapter.getSwathDomain();
303        int[] idx = domain.valueToIndex(xy);
304        xy = domain.indexToValue(idx);
305        int[] coords = new int[2];
306        coords[0] = (int) xy[0][0];
307        coords[1] = (int) xy[1][0];
308        if ((coords[0] < 0)||(coords[1] < 0)) return null;
309        return coords;
310      }
311    
312      public RealTuple getEarthCoordinates(float[] xy)
313          throws VisADException, RemoteException {
314        float[][] tup = cs.toReference(new float[][] {{xy[0]}, {xy[1]}});
315        return new RealTuple(RealTupleType.SpatialEarth2DTuple, new double[] {(double)tup[0][0], (double)tup[1][0]});
316      }
317    
318      public int getChannelIndexFromWavenumber(float channel) throws Exception {
319        return spectrumAdapter.getChannelIndexFromWavenumber(channel);
320      }
321    
322      public float getWavenumberFromChannelIndex(int index) throws Exception {
323        return spectrumAdapter.getWavenumberFromChannelIndex(index);
324      }
325    
326      public Rectangle2D getLonLatBoundingBox(CoordinateSystem cs) {
327        return null;
328      }
329    
330      public Rectangle2D getLonLatBoundingBox(HashMap subset) 
331          throws Exception {
332        Set domainSet = swathAdapter.makeDomain(subset);
333        return getLonLatBoundingBox(domainSet);
334      }
335    
336      public static Rectangle2D getLonLatBoundingBox(FlatField field) {
337        Set domainSet = field.getDomainSet();
338        return getLonLatBoundingBox(domainSet);
339      }
340    
341      public static Rectangle2D getLonLatBoundingBox(Set domainSet) {
342        CoordinateSystem cs = 
343          ((SetType)domainSet.getType()).getDomain().getCoordinateSystem();
344    
345        float start0, stop0, start1, stop1;
346        float minLon = Float.MAX_VALUE;
347        float minLat = Float.MAX_VALUE;
348        float maxLon = -Float.MAX_VALUE;
349        float maxLat = -Float.MAX_VALUE;
350    
351    
352        if (domainSet instanceof Linear2DSet) {
353          Linear1DSet lset = ((Linear2DSet)domainSet).getLinear1DComponent(0);
354          start0 = (float) lset.getFirst();
355          stop0 = (float) lset.getLast();
356          lset = ((Linear2DSet)domainSet).getLinear1DComponent(1);
357          start1 = (float) lset.getFirst();
358          stop1 = (float) lset.getLast();
359    
360          float x, y, del_x, del_y;
361          float lonA = Float.NaN;
362          float lonB = Float.NaN;
363          float lonC = Float.NaN;
364          float lonD = Float.NaN;
365          float latA = Float.NaN;
366          float latB = Float.NaN;
367          float latC = Float.NaN;
368          float latD = Float.NaN;
369          del_x = (stop0 - start0)/20;
370          del_y = (stop1 - start1)/20;
371    
372          x = start0;
373          y = start1;
374          try {
375            for (int j=0; j<21; j++) {
376              y = start1+j*del_y;
377              for (int i=0; i<21; i++) {
378                x = start0 + i*del_x;
379                float[][] lonlat = cs.toReference(new float[][] {{x}, {y}});
380                float lon = lonlat[0][0];
381                float lat = lonlat[1][0];
382                if (!Float.isNaN(lon) && !Float.isNaN(lat)) {
383                  lonA = lon;
384                  latA = lat;
385                  break;
386                }
387              }
388              for (int i=0; i<21; i++) {
389                x = stop0 - i*del_x;
390                float[][] lonlat = cs.toReference(new float[][] {{x}, {y}});
391                float lon = lonlat[0][0];
392                float lat = lonlat[1][0];
393                if (!Float.isNaN(lon) && !Float.isNaN(lat)) {
394                  lonB = lon;
395                  latB = lat;
396                  break;
397                }
398              }
399              if (!Float.isNaN(lonA) && !Float.isNaN(lonB)) {
400                break;
401              }
402            }
403    
404            for (int j=0; j<21; j++) {
405              y = stop1-j*del_y;
406              for (int i=0; i<21; i++) {
407                x = start0 + i*del_x;
408                float[][] lonlat = cs.toReference(new float[][] {{x}, {y}});
409                float lon = lonlat[0][0];
410                float lat = lonlat[1][0];
411                if (!Float.isNaN(lon) && !Float.isNaN(lat)) {
412                  lonC = lon;
413                  latC = lat;
414                  break;
415                }
416              }
417              for (int i=0; i<21; i++) {
418                x = stop0 - i*del_x;
419                float[][] lonlat = cs.toReference(new float[][] {{x}, {y}});
420                float lon = lonlat[0][0];
421                float lat = lonlat[1][0];
422                if (!Float.isNaN(lon) && !Float.isNaN(lat)) {
423                  lonD = lon;
424                  latD = lat;
425                  break;
426                }
427              }
428              if (!Float.isNaN(lonC) && !Float.isNaN(lonD)) {
429                break;
430              }
431             }
432    
433             float[][] corners = {{lonA,lonB,lonC,lonD},{latA,latB,latC,latD}};
434             for (int k=0; k<corners[0].length; k++) {
435                float lon = corners[0][k];
436                float lat = corners[1][k];
437                if (lon < minLon) minLon = lon;
438                if (lat < minLat) minLat = lat;
439                if (lon > maxLon) maxLon = lon;
440                if (lat > maxLat) maxLat = lat;
441             }
442           } catch (Exception e) {
443           }
444        }
445        else if (domainSet instanceof Gridded2DSet) {
446          int[] lens = ((Gridded2DSet)domainSet).getLengths();
447          start0 = 0f;
448          start1 = 0f;
449          stop0 = (float) lens[0];
450          stop1 = (float) lens[1];
451    
452          float x, y, del_x, del_y;
453          del_x = (stop0 - start0)/10;
454          del_y = (stop1 - start1)/10;
455          x = start0;
456          y = start1;
457          try {
458            for (int j=0; j<11; j++) {
459              y = start1+j*del_y;
460              for (int i=0; i<11; i++) {
461                x = start0+i*del_x;
462                float[][] lonlat = ((Gridded2DSet)domainSet).gridToValue(new float[][] {{x}, {y}});
463                float lon = lonlat[0][0];
464                float lat = lonlat[1][0];
465                if ((lon > 180 || lon < -180) || (lat > 90 || lat < -90)) continue;
466                if (lon < minLon) minLon = lon;
467                if (lat < minLat) minLat = lat;
468                if (lon > maxLon) maxLon = lon;
469                if (lat > maxLat) maxLat = lat;
470              }
471            }
472          } catch (Exception e) {
473          }
474        }
475        
476    
477        float del_lon = maxLon - minLon;
478        float del_lat = maxLat - minLat;
479    
480        return new Rectangle2D.Float(minLon, minLat, del_lon, del_lat);
481      }
482    
483      public float[] radianceToBrightnessTemp(float[] values, float channelValue) {
484        float c1=1.191066E-5f;           //- mW/m2/ster/cm^-4
485        float c2=1.438833f;              //- K*cm
486        float nu = channelValue;         //- nu: wavenumber
487        float B, K, BT;
488    
489        int n_values = values.length;
490        float[] new_values = new float[n_values];
491        for (int i=0; i<n_values;i++) {
492          B = values[i];
493          K = (c1*nu*nu*nu)/B;
494          if (K == 0.0) {
495            BT = B;
496          } 
497          else {
498            BT = c2*nu/((float) (Math.log((double)((c1*nu*nu*nu)/B)+1.0f)) );
499          }
500          if (BT < 0.01) BT = Float.NaN;
501          new_values[i] = BT;
502        }
503        return new_values;
504      }
505    
506      public float[] radianceToBrightnessTemp(float[] values, float channelValue, String platformName, String sensorName) 
507         throws Exception {
508        float[] new_values = null;
509    
510        if (sensorName == null) {
511          new_values = radianceToBrightnessTemp(values, channelValue);
512        }
513        else if (sensorName == "MODIS") {
514          int channelIndex = spectrumAdapter.getChannelIndexFromWavenumber(channelValue);
515          int band_number = MODIS_L1B_Utility.emissive_indexToBandNumber(channelIndex);
516          new_values = MODIS_L1B_Utility.modis_radiance_to_brightnessTemp(platformName, band_number, values);
517        }
518        return new_values;
519      }
520    
521      public float[] radianceToBrightnessTempSpectrum(float[] values, float[] channelValues) {
522        //- Converts radiances [mW/ster/m2/cm^-1] to BT [K]
523        //-  Input: nu  array of wavenmbers [cm^-1]
524        //-          B   radiances [mW/ster/m2/cm^-1]
525        //-  Output: bt brightness temperature in [K]
526        //-   Paolo Antonelli
527        //-   Wed Feb 25 16:43:05 CST 1998
528    
529        float c1=1.191066E-5f;           //- mW/m2/ster/cm^-4
530        float c2=1.438833f;              //- K*cm
531    
532        float nu;                        //- wavenumber
533        float B, BT;
534    
535        int n_values = values.length;
536        float[] new_values = new float[n_values];
537        for (int i=0; i<n_values; i++) {
538          nu = channelValues[i];
539          B = values[i];
540          BT = c2*nu/((float) (Math.log(((c1*nu*nu*nu)/B)+1.0f)) );
541          new_values[i] = BT;
542        }
543        return new_values;
544      }
545    
546    
547      public float[] radianceToBrightnessTempSpectrum(float[] values, float[] channelValues,
548                                     String platformName, String sensorName) 
549         throws Exception
550      {
551        float[] new_values = null;
552    
553        if (sensorName == null) {
554          new_values =  radianceToBrightnessTempSpectrum(values, channelValues);
555        }
556        else if (sensorName == "MODIS") {
557          new_values = new float[values.length];
558          for (int k=0; k<new_values.length; k++) {
559            int channelIndex = spectrumAdapter.getChannelIndexFromWavenumber(channelValues[k]);
560            int band_number = MODIS_L1B_Utility.emissive_indexToBandNumber(channelIndex);
561            float[] tmp = new float[1];
562            tmp[0] = values[k];
563            new_values[k] = (MODIS_L1B_Utility.modis_radiance_to_brightnessTemp(platformName, band_number, tmp))[0];
564          }
565        }
566    
567        return new_values;
568      }
569    
570      public HashMap getDefaultSubset() {
571        HashMap subset = swathAdapter.getDefaultSubset();
572        double chanIdx=0;
573    
574        try {
575           chanIdx = spectrumAdapter.getChannelIndexFromWavenumber(init_wavenumber);
576        }
577        catch (Exception e) {
578          System.out.println("couldn't get chanIdx, using zero");
579        }
580          
581        subset.put(SpectrumAdapter.channelIndex_name, new double[] {chanIdx, chanIdx, 1});
582        return subset;
583      }
584     
585    
586      public SpectrumAdapter getSpectrumAdapter() {
587        return spectrumAdapter;
588      }
589    }