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