001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2026
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.hydra;
030
031import java.util.Arrays;
032import java.util.Map;
033import java.util.Objects;
034
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import edu.wisc.ssec.mcidasv.data.QualityFlag;
039import visad.util.Util;
040
041public class RangeProcessor {
042
043    private static final Logger logger =
044        LoggerFactory.getLogger(RangeProcessor.class);
045
046    static RangeProcessor createRangeProcessor(MultiDimensionReader reader,
047            Map<String, Object> metadata) throws Exception {
048        if (reader instanceof GranuleAggregation) {
049            return new AggregationRangeProcessor((GranuleAggregation) reader, metadata);
050        }
051
052        if (metadata.get("scale_name") == null) {
053            String product_name = (String) metadata.get(SwathAdapter.product_name);
054            if (Objects.equals(product_name, "IASI_L1C_xxx")) {
055                return new IASI_RangeProcessor();
056            }
057            return null;
058        } else {
059            String product_name = (String) metadata.get(ProfileAlongTrack.product_name);
060            if (Objects.equals(product_name, "2B-GEOPROF")) {
061                return new CloudSat_2B_GEOPROF_RangeProcessor(reader, metadata);
062            } else {
063                return new RangeProcessor(reader, metadata);
064            }
065        }
066    }
067
068    MultiDimensionReader reader;
069    Map<String, Object> metadata;
070
071    float[] scale = null;
072    float[] offset = null;
073    double[] missing = null;
074    double[] valid_range = null;
075    double valid_low = -Double.MAX_VALUE;
076    double valid_high = Double.MAX_VALUE;
077
078    boolean unpack = false;
079    boolean unsigned = false;
080    boolean rangeCheckBeforeScaling = true;
081
082    int scaleOffsetLen = 1;
083
084    String multiScaleDimName = SpectrumAdapter.channelIndex_name;
085    boolean hasMultiDimensionScale = false;
086
087    int multiScaleDimensionIndex = 0;
088
089    int soIndex = 0;
090
091    public RangeProcessor() {
092    }
093
094    public RangeProcessor(float scale, float offset, float valid_low, float valid_high,
095            float missing) {
096        this.scale = new float[] { scale };
097        this.offset = new float[] { offset };
098        this.missing = new double[] { missing };
099        this.valid_low = valid_low;
100        this.valid_high = valid_high;
101    }
102
103    public RangeProcessor(MultiDimensionReader reader, Map<String, Object> metadata,
104            String multiScaleDimName) throws Exception {
105        this(reader, metadata);
106        this.multiScaleDimName = multiScaleDimName;
107    }
108
109    public RangeProcessor(MultiDimensionReader reader, Map<String, Object> metadata)
110            throws Exception {
111        this.reader = reader;
112        this.metadata = metadata;
113
114        if (metadata.get("unpack") != null) {
115            unpack = true;
116        }
117
118        if (metadata.get("unsigned") != null) {
119            unsigned = true;
120        }
121
122        if (metadata.get("range_check_after_scaling") != null) {
123            String s = (String) metadata.get("range_check_after_scaling");
124            logger.debug("range_check_after_scaling: " + s);
125            rangeCheckBeforeScaling = false;
126        }
127
128        String array_name = (String) metadata.get("array_name");
129
130        scale = getAttributeAsFloatArray(array_name, (String) metadata.get("scale_name"));
131
132        offset = getAttributeAsFloatArray(array_name, (String) metadata.get("offset_name"));
133
134        if (scale != null) {
135            scaleOffsetLen = scale.length;
136
137            if (offset != null) {
138                if (scale.length != offset.length) {
139                    throw new Exception("RangeProcessor: scale and offset array lengths must be equal");
140                }
141            } else {
142                offset = new float[scaleOffsetLen];
143                Arrays.fill(offset, 0f);
144            }
145
146        }
147
148        missing = getAttributeAsDoubleArray(array_name, (String) metadata.get("fill_value_name"));
149
150        // if we are working with unsigned data, need to convert missing vals to unsigned too
151        if (unsigned) {
152            if (missing != null) {
153                for (int i = 0; i < missing.length; i++) {
154                    missing[i] = (float) Util.unsignedShortToInt((short) missing[i]);
155                }
156            }
157        }
158
159        String metaStr = (String) metadata.get("valid_range");
160        // attr name not supplied, so try the convention default
161        if (metaStr == null) {
162            metaStr = "valid_range";
163        }
164
165        valid_range = getAttributeAsDoubleArray(array_name, metaStr);
166        if (valid_range != null) {
167
168            valid_low = valid_range[0];
169            valid_high = valid_range[1];
170
171            if (valid_range[0] > valid_range[1]) {
172                valid_low = valid_range[1];
173                valid_high = valid_range[0];
174            }
175        } else {
176            metaStr = (String) metadata.get("valid_low");
177            if (metaStr == null) { // attr name not supplied, so try the
178                                   // convention default
179                metaStr = "valid_min";
180            }
181            double[] dblA = getAttributeAsDoubleArray(array_name, metaStr);
182            if (dblA != null) {
183                valid_low = dblA[0];
184            }
185
186            metaStr = (String) metadata.get("valid_high");
187            if (metaStr == null) { // attr name not supplied, so try the
188                                   // convention default
189                metaStr = "valid_max";
190            }
191            dblA = getAttributeAsDoubleArray(array_name, metaStr);
192            if (dblA != null) {
193                valid_high = dblA[0];
194            }
195        }
196
197        if (rangeCheckBeforeScaling) {
198            if (unsigned) {
199                if (valid_low != -Double.MAX_VALUE) {
200                    valid_low = (double) Util.unsignedShortToInt((short) valid_low);
201                }
202                if (valid_high != Double.MAX_VALUE) {
203                    valid_high = (double) Util.unsignedShortToInt((short) valid_high);
204                }
205            }
206        }
207
208        String str = (String) metadata.get("multiScaleDimensionIndex");
209        hasMultiDimensionScale = (str != null);
210        multiScaleDimensionIndex = (str != null) ? Integer.parseInt(str) : 0;
211    }
212
213    public float[] getAttributeAsFloatArray(String arrayName, String attrName) throws Exception {
214        float[] fltArray = null;
215        HDFArray arrayAttr = reader.getArrayAttribute(arrayName, attrName);
216
217        if (arrayAttr != null) {
218
219            if (arrayAttr.getType().equals(Float.TYPE)) {
220                float[] attr = (float[]) arrayAttr.getArray();
221                fltArray = new float[attr.length];
222                for (int k = 0; k < attr.length; k++)
223                    fltArray[k] = attr[k];
224            } else if (arrayAttr.getType().equals(Short.TYPE)) {
225                short[] attr = (short[]) arrayAttr.getArray();
226                fltArray = new float[attr.length];
227                for (int k = 0; k < attr.length; k++)
228                    fltArray[k] = (float) attr[k];
229            } else if (arrayAttr.getType().equals(Integer.TYPE)) {
230                int[] attr = (int[]) arrayAttr.getArray();
231                fltArray = new float[attr.length];
232                for (int k = 0; k < attr.length; k++)
233                    fltArray[k] = (float) attr[k];
234            } else if (arrayAttr.getType().equals(Double.TYPE)) {
235                double[] attr = (double[]) arrayAttr.getArray();
236                fltArray = new float[attr.length];
237                for (int k = 0; k < attr.length; k++)
238                    fltArray[k] = (float) attr[k];
239            }
240
241        }
242
243        return fltArray;
244    }
245
246    public double[] getAttributeAsDoubleArray(String arrayName, String attrName) throws Exception {
247        if (attrName == null) {
248            return null;
249        }
250
251        double[] dblArray = null;
252        HDFArray arrayAttr = null;
253        try {
254            arrayAttr = reader.getArrayAttribute(arrayName, attrName);
255        } catch (Exception e) {
256            String msg = String.format("Problem getting array attribute. " +
257                "arrayName: '%s', attrName: '%s'", arrayName, attrName);
258            logger.error(msg, e);
259        }
260
261        if (arrayAttr != null) {
262
263            if (arrayAttr.getType().equals(Float.TYPE)) {
264                float[] attr = (float[]) arrayAttr.getArray();
265                dblArray = new double[attr.length];
266                for (int k = 0; k < attr.length; k++)
267                    dblArray[k] = attr[k];
268            } else if (arrayAttr.getType().equals(Short.TYPE)) {
269                short[] attr = (short[]) arrayAttr.getArray();
270                dblArray = new double[attr.length];
271                for (int k = 0; k < attr.length; k++) {
272                    if (unsigned) {
273                        dblArray[k] = (double) Util.unsignedShortToInt((short) attr[k]);
274                    } else {
275                        dblArray[k] = (double) attr[k];
276                    }
277                }
278            } else if (arrayAttr.getType().equals(Integer.TYPE)) {
279                int[] attr = (int[]) arrayAttr.getArray();
280                dblArray = new double[attr.length];
281                for (int k = 0; k < attr.length; k++)
282                    dblArray[k] = (double) attr[k];
283            } else if (arrayAttr.getType().equals(Double.TYPE)) {
284                double[] attr = (double[]) arrayAttr.getArray();
285                dblArray = new double[attr.length];
286                for (int k = 0; k < attr.length; k++)
287                    dblArray[k] = (double) attr[k];
288            } else if (arrayAttr.getType().equals(Byte.TYPE)) {
289                byte[] attr = (byte[]) arrayAttr.getArray();
290                dblArray = new double[attr.length];
291                for (int k = 0; k < attr.length; k++)
292                    dblArray[k] = (double) attr[k];
293            } else if (arrayAttr.getType().equals(String.class)) {
294                String[] attr = (String[]) arrayAttr.getArray();
295                dblArray = new double[attr.length];
296                for (int k = 0; k < attr.length; k++)
297                    dblArray[k] = Double.valueOf(attr[0]);
298            }
299        }
300
301        return dblArray;
302    }
303
304    /**
305     * Process a range of data from an array of {@code byte} values where bytes
306     * are packed bit or multi-bit fields of quality flags. Based on info in a
307     * {@link QualityFlag} object passed in, we extract and return values for
308     * that flag.
309     * 
310     * @param values
311     *            Input byte values. Cannot be {@code null}.
312     * @param subset
313     *            Optional subset.
314     * @param qf
315     *            Quality flag.
316     *
317     * @return Processed range.
318     */
319
320    public float[] processRangeQualityFlag(int[] values, Map<String, double[]> subset, QualityFlag qf) {
321
322        if (subset != null) {
323            if (subset.get(multiScaleDimName) != null) {
324                soIndex = (int) (subset.get(multiScaleDimName))[0];
325            }
326        }
327
328        float[] newValues = new float[values.length];
329
330        float val = 0f;
331        int bitOffset = qf.getBitOffset();
332        int divisor = -1;
333
334        // map bit offset to a divisor
335        switch (bitOffset) {
336            case 1:
337                divisor = 2;
338                break;
339            case 2:
340                divisor = 4;
341                break;
342            case 3:
343                divisor = 8;
344                break;
345            case 4:
346                divisor = 16;
347                break;
348            case 5:
349                divisor = 32;
350                break;
351            case 6:
352                divisor = 64;
353                break;
354            case 7:
355                divisor = 128;
356                break;
357            default:
358                divisor = 1;
359                break;
360        }
361
362        // now map bit width to a mask
363        int numBits = qf.getNumBits();
364        int mask = -1;
365        switch (numBits) {
366            case 1:
367                mask = (int) 0x00000001;
368                break;
369            case 2:
370                mask = (int) 0x00000003;
371                break;
372            case 3:
373                mask = (int) 0x00000007;
374                break;
375            case 4:
376                mask = (int) 0x0000000F;
377                break;
378            case 5:
379                mask = (int) 0x0000001F;
380                break;
381            case 6:
382                mask = (int) 0x0000003F;
383                break;
384            case 7:
385                mask = (int) 0x0000007F;
386                break;
387            default:
388                mask = (int) 0x00000000;
389                break;
390        }
391
392        for (int k = 0; k < values.length; k++) {
393            val = (float) ((values[k] / divisor) & mask);
394            newValues[k] = val;
395        }
396
397        return newValues;
398    }
399
400    /**
401     * Process a range of data from an array of {@code byte} values.
402     * 
403     * @param values
404     *            Input {@code byte} values. Cannot be {@code null}.
405     * @param subset
406     *            Optional subset.
407     *
408     * @return Processed range.
409     */
410    
411    public float[] processRange(byte[] values, Map<String, double[]> subset) {
412
413        int multiScaleDimLen = 1;
414
415        if (subset != null) {
416            if (subset.get(multiScaleDimName) != null) {
417                double[] coords = subset.get(multiScaleDimName);
418                soIndex = (int) coords[0];
419                multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0);
420            }
421        }
422
423        float[] new_values = new float[values.length];
424
425        float val = 0f;
426        int i = 0;
427        boolean isMissing = false;
428
429        for (int k = 0; k < values.length; k++) {
430
431            val = (float) values[k];
432            if (unsigned) {
433                i = Util.unsignedByteToInt(values[k]);
434                val = (float) i;
435            }
436
437            // first, check the (possibly multiple) missing values
438            isMissing = false;
439            if (missing != null) {
440                for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
441                    if (val == missing[mvIdx]) {
442                        isMissing = true;
443                        break;
444                    }
445                }
446            }
447
448            if (isMissing) {
449                new_values[k] = Float.NaN;
450                continue;
451            }
452
453            if (rangeCheckBeforeScaling) {
454                if ((val < valid_low) || (val > valid_high)) {
455                    new_values[k] = Float.NaN;
456                    continue;
457                }
458            }
459
460            if (scale != null) {
461                if (unpack) {
462                    if (multiScaleDimLen == 1) {
463                        new_values[k] = (scale[soIndex] * val) + offset[soIndex];
464                    } else {
465                        new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k];
466                    }
467                } else {
468                    if (multiScaleDimLen == 1) {
469                        new_values[k] = scale[soIndex] * (val - offset[soIndex]);
470                    } else {
471                        new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]);
472                    }
473                }
474
475            } else {
476                new_values[k] = val;
477            }
478
479            // do valid range check AFTER scaling?
480            if (!rangeCheckBeforeScaling) {
481                if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
482                    new_values[k] = Float.NaN;
483                }
484            }
485        }
486        return new_values;
487    }
488
489    /**
490     * Process a range of data from an array of {@code short} values.
491     * 
492     * @param values
493     *            Input {@code short} values. Cannot be {@code null}.
494     * @param subset
495     *            Optional subset.
496     *
497     * @return Processed range.
498     */
499    
500    public float[] processRange(short[] values, Map<String, double[]> subset) {
501
502        int multiScaleDimLen = 1;
503
504        if (subset != null) {
505            if (subset.get(multiScaleDimName) != null) {
506                double[] coords = subset.get(multiScaleDimName);
507                soIndex = (int) coords[0];
508                multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0);
509            }
510        }
511
512        float[] new_values = new float[values.length];
513
514        float val = 0f;
515        int i = 0;
516        boolean isMissing = false;
517
518        for (int k = 0; k < values.length; k++) {
519
520            val = (float) values[k];
521            if (unsigned) {
522                i = Util.unsignedShortToInt((short)values[k]);
523                val = (float) i;
524            }
525
526            // first, check the (possibly multiple) missing values
527            isMissing = false;
528            if (missing != null) {
529                for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
530                    if (val == missing[mvIdx]) {
531                        isMissing = true;
532                        break;
533                    }
534                }
535            }
536
537            if (isMissing) {
538                new_values[k] = Float.NaN;
539                continue;
540            }
541
542            if (rangeCheckBeforeScaling) {
543                if ((val < valid_low) || (val > valid_high)) {
544                    new_values[k] = Float.NaN;
545                    continue;
546                }
547            }
548
549            if (scale != null) {
550                if (unpack) {
551                    if (multiScaleDimLen == 1) {
552                        new_values[k] = (scale[soIndex] * val) + offset[soIndex];
553                    } else {
554                        new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k];
555                    }
556                } else {
557                    if (multiScaleDimLen == 1) {
558                        new_values[k] = scale[soIndex] * (val - offset[soIndex]);
559                    } else {
560
561                        new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]);
562                    }
563                }
564            } else {
565                new_values[k] = val;
566            }
567
568            // do valid range check AFTER scaling?
569            if (!rangeCheckBeforeScaling) {
570                if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
571                    new_values[k] = Float.NaN;
572                }
573            }
574
575        }
576        return new_values;
577    }
578
579    public float[] processRangeUshorts(int[] values, Map<String, double[]> subset) {
580
581        int multiScaleDimLen = 1;
582
583        if (subset != null) {
584            if (subset.get(multiScaleDimName) != null) {
585                double[] coords = subset.get(multiScaleDimName);
586                soIndex = (int) coords[0];
587                multiScaleDimLen = (int) (coords[1] - coords[0] + 1.0);
588            }
589        }
590
591        float[] new_values = new float[values.length];
592
593        float val = 0f;
594        int i = 0;
595        boolean isMissing = false;
596
597        for (int k = 0; k < values.length; k++) {
598
599            val = (float) values[k];
600            if (unsigned) {
601                i = Util.unsignedShortToInt((short)values[k]);
602                val = (float) i;
603            }
604
605            // first, check the (possibly multiple) missing values
606            isMissing = false;
607            if (missing != null) {
608                for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
609                    if (val == missing[mvIdx]) {
610                        isMissing = true;
611                        break;
612                    }
613                }
614            }
615
616            if (isMissing) {
617                new_values[k] = Float.NaN;
618                continue;
619            }
620
621            if (rangeCheckBeforeScaling) {
622                if ((val < valid_low) || (val > valid_high)) {
623                    new_values[k] = Float.NaN;
624                    continue;
625                }
626            }
627
628            if (scale != null) {
629                if (unpack) {
630                    if (multiScaleDimLen == 1) {
631                        new_values[k] = (scale[soIndex] * val) + offset[soIndex];
632                    } else {
633                        new_values[k] = (scale[soIndex + k] * val) + offset[soIndex + k];
634                    }
635                } else {
636                    if (multiScaleDimLen == 1) {
637                        new_values[k] = scale[soIndex] * (val - offset[soIndex]);
638                    } else {
639
640                        new_values[k] = scale[soIndex + k] * (val - offset[soIndex + k]);
641                    }
642                }
643            } else {
644                new_values[k] = val;
645            }
646
647            // do valid range check AFTER scaling?
648            if (!rangeCheckBeforeScaling) {
649                if ((new_values[k] < valid_low) || (new_values[k] > valid_high)) {
650                    new_values[k] = Float.NaN;
651                }
652            }
653        }
654        return new_values;
655    }
656
657    /**
658     * Process a range of data from an array of {@code float} values.
659     * 
660     * @param values
661     *            Input {@code float} values. Cannot be {@code null}.
662     * @param subset
663     *            Optional subset.
664     *
665     * @return Processed array.
666     */
667    
668    public float[] processRange(float[] values, Map<String, double[]> subset) {
669
670        float[] new_values = null;
671
672        if ((missing != null) || (valid_range != null)) {
673            new_values = new float[values.length];
674        } else {
675            return values;
676        }
677
678        float val;
679
680        for (int k = 0; k < values.length; k++) {
681            val = values[k];
682            new_values[k] = val;
683
684            // first, check the (possibly multiple) missing values
685            if (missing != null) {
686                for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
687                    if (val == missing[mvIdx]) {
688                        new_values[k] = Float.NaN;
689                        break;
690                    }
691                }
692            }
693
694            if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) {
695                new_values[k] = Float.NaN;
696            }
697
698        }
699
700        return new_values;
701    }
702
703    /**
704     * Process a range of data from an array of {@code double} value.
705     * 
706     * @param values
707     *            Input {@code double} values. Cannot be {@code null}.
708     * @param subset
709     *            Optional subset.
710     *
711     * @return Processed array.
712     */
713    
714    public double[] processRange(double[] values, Map<String, double[]> subset) {
715
716        double[] new_values = null;
717
718        if ((missing != null) || (valid_range != null)) {
719            new_values = new double[values.length];
720        } else {
721            return values;
722        }
723
724        double val;
725
726        for (int k = 0; k < values.length; k++) {
727            val = values[k];
728            new_values[k] = val;
729
730            // first, check the (possibly multiple) missing values
731            if (missing != null) {
732                for (int mvIdx = 0; mvIdx < missing.length; mvIdx++) {
733                    if (val == missing[mvIdx]) {
734                        new_values[k] = Float.NaN;
735                        break;
736                    }
737                }
738            }
739
740            if ((valid_range != null) && ((val < valid_low) || (val > valid_high))) {
741                new_values[k] = Double.NaN;
742            }
743        }
744
745        return new_values;
746    }
747
748    public void setMultiScaleDimName(String multiScaleDimName) {
749        this.multiScaleDimName = multiScaleDimName;
750    }
751
752    public int getMultiScaleDimensionIndex() {
753        return multiScaleDimensionIndex;
754    }
755
756    public boolean hasMultiDimensionScale() {
757        return hasMultiDimensionScale;
758    }
759
760    public void setHasMultiDimensionScale(boolean yesno) {
761        hasMultiDimensionScale = yesno;
762    }
763
764    public void setMultiScaleIndex(int idx) {
765        this.soIndex = idx;
766    }
767
768    /**
769     * Should be generalized. For now works for short to float conversions
770     * 
771     * @param values
772     *            the set of input values to map
773     * @param lut
774     *            the lookup table for direct mapping input to output values
775     * @return output array as primitive floats
776     */
777
778    public Object processRangeApplyLUT(short[] values, float[] lut) {
779
780        float[] newValues = new float[values.length];
781
782        int lutLen = lut.length;
783
784        for (int i = 0; i < values.length; i++) {
785            int tmpVal = values[i];
786            if (tmpVal > 0) {
787                if (tmpVal < lutLen) {
788                    newValues[i] = lut[tmpVal];
789                } else {
790                    newValues[i] = Float.NaN;
791                }
792            } else {
793                newValues[i] = Float.NaN;
794            }
795        }
796
797        return newValues;
798    }
799
800    public Object processRangeUshortsApplyLUT(int[] values, float[] lut) {
801
802        float[] newValues = new float[values.length];
803
804        int lutLen = lut.length;
805
806        for (int i = 0; i < values.length; i++) {
807            int tmpVal = values[i];
808            if (tmpVal > 0) {
809                if (tmpVal < lutLen) {
810                    newValues[i] = lut[tmpVal];
811                } else {
812                    newValues[i] = Float.NaN;
813                }
814            } else {
815                newValues[i] = Float.NaN;
816            }
817        }
818
819        return newValues;
820    }
821
822}