001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.adt;
030
031public class Output {
032
033    static String[] Months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
034            "OCT", "NOV", "DEC" };
035
036    /** N/S hemisphere character value */
037    static String[] LatNS = { "N", "S" };
038
039    /** E/W hemisphere character value */
040    static String[] LonWE = { "W", "E" };
041
042    /** Rule 9 string array */
043    static String[] Rule9String = { "OFF   ", "ON    ", "WEAKEN" };
044
045    /** rapid dissipation string array */
046    static String[] RapidString = { "OFF   ", "FLAG  ", "ON    ", "ON    " };
047
048    /** BD curve value string array */
049    static String[] BDCatString = { "LOW CLD", "OFF WHT", "DK GRAY", "MD GRAY", "LT GRAY",
050            "BLACK  ", "WHITE  " };
051
052    /** ocean basin string array */
053    static String[] BasinString = { "ATLANTIC    ", "WEST PACIFIC", "EAST PACIFIC", "INDIAN      " };
054
055    /** eye scene type array */
056    static String[] EyeSceneTypes = { "EYE", "PINHOLE EYE", "LARGE EYE", "NONE" };
057
058    /** cloud scene type array */
059    static String[] CloudSceneTypes = { "UNIFORM CDO", "EMBEDDED CENTER", "IRREGULAR CDO",
060            "CURVED BAND", "SHEAR", "EYE" };
061
062    /** storm position type arrays */
063    static String[] AutoPosString = { "MANUAL", "FORECAST INTERPOLATION", "LAPLACIAN ANALYSIS",
064            "WARMEST PIXEL SEARCH", "SPIRAL ANALYSIS", "RING/SPIRAL COMBINATION",
065            "LINEAR EXTRAPOLATION", "NETCDF NOMINAL POSITION", "NOT AVAILABLE" };
066
067    static String[] AutoPosStringAbbr = { " MAN ", "FCST ", "LAPL ", "WARM ", "SPRL ", "COMBO",
068            "EXTRP", "NETCDF", " N/A " };
069
070    /** basin ID string array */
071    static String[] PW_BasinValues = { "ATLANTIC", "PACIFIC " };
072
073    /** Rule 8 string array */
074    static String[] Rule8String = { "NO LIMIT ", "0.5T/6hr ", "1.0T/6hr ", "1.7T/12hr",
075            "2.2T/18hr", "2.7T/24hr", "         ", "         ", "0.2T/hour", "0.5T/hour",
076            "NO LIMIT ", "0.5T/6hr ", "1.0T/6hr ", "2.7T/12hr", "3.2T/18hr", "3.7T/24hr",
077            "         ", "         ", "0.2T/hour", "0.5T/hour", "NO LIMIT ", "0.5T/6hr ",
078            "0.7T/6hr ", "1.2T/12hr", "1.7T/18hr", "2.2T/24hr", "         ", "         ",
079            "0.2T/hour", "0.5T/hour", "MW Adjst ", "MW ON    ", "MW ON    ", "MW HOLD  ",
080            "MW AdjEnd" };
081
082    public Output() {
083    }
084
085    public static String TextScreenOutput(String HistoryFileName) {
086
087        System.err.println("TextScreenOutput() in...");
088        String TextScreen_Return = "";
089
090        /* int LatNSval = 0; */
091        /* int LonWEval = 0; */
092        String SceneString = "";
093        String RadiusMaxWindString = "";
094        String SceneMaxCBString = "";
095        String SceneMaxCBLLString = "";
096        String SceneMaxCBString2 = "";
097        String SceneMaxCBLLString2 = "";
098        double PresWindValue_Pressure = -999.0;
099        double PresWindValue_Wind = -999.0;
100        boolean MaxCurvedBandTF = false;
101        boolean RadiusMaxWindTF = false;
102
103        /* convert Julian date/time to DateValue/month/year format */
104        int CurDate = History.IRCurrentRecord.date;
105        int CurTime = History.IRCurrentRecord.time;
106        double CurLatitudeValue = History.IRCurrentRecord.latitude;
107        double CurLongitudeValue = History.IRCurrentRecord.longitude;
108        int[] ReturnValues = Functions.adt_yddmy(CurDate);
109        int DateValue = ReturnValues[0];
110        int MonthValue = ReturnValues[1];
111        int YearValue = ReturnValues[2];
112
113        /* format character string for date output */
114        String DateString = String.format("%02d %3s %04d", DateValue, Months[MonthValue - 1],
115                YearValue);
116
117        /* format character string for time output */
118        String TimeString = String.format("  %06d UTC", CurTime);
119
120        /* convert xx.xxxx latitude format to degree/minute/second format */
121        int[] ReturnValues2A = adt_lldms(CurLatitudeValue);
122        int DegreeValue2A = ReturnValues2A[0];
123        int MinuteValue2A = ReturnValues2A[1];
124        int SecondValue2A = ReturnValues2A[2];
125        int LatNSval = (CurLatitudeValue < 0.0) ? 1 : 0;
126        /*
127         * LatNSval=0; if(CurLatitudeValue<0.0) { LatNSval=1; }
128         */
129        /* format character string for latitude output */
130        String LatitudeString = String.format("%3d:%02d:%02d %1s", DegreeValue2A, MinuteValue2A,
131                SecondValue2A, LatNS[LatNSval]);
132
133        int[] ReturnValues2B = adt_lldms(CurLongitudeValue);
134        int DegreeValue2B = ReturnValues2B[0];
135        int MinuteValue2B = ReturnValues2B[1];
136        int SecondValue2B = ReturnValues2B[2];
137        /*
138         * int LonWEval = (CurLongitudeValue<0.0) ? 1 : 0; old McIDAS-X
139         * conversion
140         */
141        int LonWEval = (CurLongitudeValue < 0.0) ? 0 : 1;
142        /*
143         * LonWEval=0; if(CurLongitudeValue<0.0) { LonWEval=1; }
144         */
145        /* format character string for latitude output */
146        String LongitudeString = String.format("%3d:%02d:%02d %1s", DegreeValue2B, MinuteValue2B,
147                SecondValue2B, LonWE[LonWEval]);
148
149        int[] ReturnValues3 = Functions.adt_oceanbasin(CurLatitudeValue, CurLongitudeValue);
150        int BasinIDValue = ReturnValues3[0];
151        int DomainID = ReturnValues3[1];
152
153        /* determine Dvorak pressure/wind speed in relation to final CI # */
154        double CurRawT = History.IRCurrentRecord.Traw;
155        double CurRawTorig = History.IRCurrentRecord.TrawO;
156        double CurFinalT = History.IRCurrentRecord.Tfinal;
157        double CurCI = History.IRCurrentRecord.CI;
158        double CurCIAdjP = History.IRCurrentRecord.CIadjp;
159        /* System.out.printf("ciadjp=%f\n",CurCIAdjP); */
160        PresWindValue_Pressure = Functions.adt_getpwval(0, CurCI, CurLatitudeValue,
161                CurLongitudeValue);
162        PresWindValue_Wind = Functions.adt_getpwval(1, CurCI, CurLatitudeValue, CurLongitudeValue);
163
164        boolean Vmax1or10TF = Env.Vmax1or10TF;
165
166        if (!Vmax1or10TF) {
167            /* convert 1-minute to 10-minute average Vmax for output */
168            PresWindValue_Wind = 0.88 * PresWindValue_Wind;
169        }
170
171        /* determine Rule 8 and Rule 9 screen output values */
172        int Rule8Value = History.IRCurrentRecord.rule8;
173        int Rule9Value = History.IRCurrentRecord.rule9;
174        int RapidIntenValue = History.IRCurrentRecord.rapiddiss;
175
176        double EyeTempValue = History.IRCurrentRecord.eyet;
177        double CloudTempValue = History.IRCurrentRecord.cloudt;
178
179        /* determine scenetype to be output to screen */
180        int EyeSceneTypeValue = History.IRCurrentRecord.eyescene;
181        int CloudSceneTypeValue = History.IRCurrentRecord.cloudscene;
182
183        if (CloudSceneTypeValue == 2) {
184            SceneString = String.format("%s", CloudSceneTypes[CloudSceneTypeValue]);
185        } else if (CloudSceneTypeValue == 3) {
186            double CurvedBandValue = ((double) (History.IRCurrentRecord.ringcbval - 1)) / 24.0;
187            double CurvedBandMaxValue = ((double) (History.IRCurrentRecord.ringcbvalmax - 1)) / 25.0;
188            int RingCB = History.IRCurrentRecord.ringcb;
189            SceneString = String.format("CURVED BAND with %4.2f ARC in %s", CurvedBandValue,
190                    BDCatString[RingCB]);
191            /*
192             * System.out.printf("curved band max=%f curved band=%f\n",
193             * CurvedBandMaxValue,CurvedBandValue);
194             */
195            if (CurvedBandMaxValue > CurvedBandValue) {
196                MaxCurvedBandTF = true;
197            }
198            if (MaxCurvedBandTF) {
199                SceneMaxCBString = String.format("Maximum CURVED BAND with %4.2f ARC in %s",
200                        CurvedBandMaxValue, BDCatString[RingCB]);
201                /*
202                 * convert xx.xxxx latitude format to degree/minute/second
203                 * format
204                 */
205                double CurvedBandMaxLatitude = History.IRCurrentRecord.ringcbvalmaxlat;
206                int[] ReturnValues4A = adt_lldms(CurvedBandMaxLatitude);
207                int DegreeValue4A = ReturnValues4A[0];
208                int MinuteValue4A = ReturnValues4A[1];
209                int SecondValue4A = ReturnValues4A[2];
210                LatNSval = (CurLatitudeValue < 0.0) ? 1 : 0;
211                /*
212                 * LatNSval=0; if(CurvedBandMaxLatitude<0.0) { LatNSval=1; }
213                 */
214                /* format character string for latitude output */
215                String CBMaxLatString = String.format("%3d:%02d:%02d %1s", DegreeValue4A,
216                        MinuteValue4A, SecondValue4A, LatNS[LatNSval]);
217
218                /*
219                 * convert xx.xxxx longitude format to degree/minute/second
220                 * format
221                 */
222                double CurvedBandMaxLongitude = History.IRCurrentRecord.ringcbvalmaxlon;
223                int[] ReturnValues4B = adt_lldms(CurvedBandMaxLongitude);
224                int DegreeValue4B = ReturnValues4B[0];
225                int MinuteValue4B = ReturnValues4B[1];
226                int SecondValue4B = ReturnValues4B[2];
227                /*
228                 * LonWEval = (CurLongitudeValue<0.0) ? 1 : 0; old conversion
229                 * for McIDAS-X
230                 */
231                LonWEval = (CurLongitudeValue < 0.0) ? 0 : 1;
232                /*
233                 * LonWEval=0; if(CurvedBandMaxLongitude<0.0) { LonWEval=1; }
234                 */
235                /* format character string for longitude output */
236                String CBMaxLonString = String.format("%3d:%02d:%02d %1s", DegreeValue4B,
237                        MinuteValue4B, SecondValue4B, LonWE[LonWEval]);
238                SceneMaxCBLLString = String.format(" at Lat:%12s  Lon:%12s", CBMaxLatString,
239                        CBMaxLonString);
240            }
241        } else if (CloudSceneTypeValue == 4) {
242            double CDOSizeValue = History.IRCurrentRecord.eyecdosize / 110.0;
243            if (CDOSizeValue < 1.30) {
244                SceneString = String.format("SHEAR (%4.2f^ TO DG)", CDOSizeValue);
245            } else {
246                SceneString = String.format("SHEAR (>1.25^ TO DG)");
247            }
248        } else {
249            if (EyeSceneTypeValue <= 2) {
250                SceneString = String.format("%s ", EyeSceneTypes[EyeSceneTypeValue]);
251                if (EyeSceneTypeValue <= 2) {
252                    RadiusMaxWindTF = true;
253                }
254                double RMWValue = History.IRCurrentRecord.rmw;
255                if (RMWValue < 0.0) {
256                    if (EyeSceneTypeValue == 1) {
257                        RadiusMaxWindString = String.format("<10");
258
259                    } else {
260                        RadiusMaxWindString = String.format("N/A");
261                    }
262                } else {
263                    RadiusMaxWindString = String.format("%3d", (int) RMWValue);
264                }
265            } else {
266                if ((Rule8Value == 31) || (Rule8Value == 32)) {
267                    SceneString = String.format("%s CLOUD REGION w/ MW EYE",
268                            CloudSceneTypes[CloudSceneTypeValue]);
269                } else {
270                    SceneString = String.format("%s CLOUD REGION",
271                            CloudSceneTypes[CloudSceneTypeValue]);
272                }
273            }
274        }
275
276        int NumRecsHistory = History.HistoryNumberOfRecords();
277        double InitStrengthValue = Env.InitRawTValue;
278        if ((HistoryFileName != null) && (NumRecsHistory == 0)) {
279            CloudSceneTypeValue = 0;
280            if (InitStrengthValue > 1.0) {
281                SceneString = String.format("USER DEFINED INITIAL CLASSIFICATION");
282            } else {
283                if (InitStrengthValue == 1.0) {
284                    SceneString = String.format("INITIAL CLASSIFICATION");
285                }
286            }
287        }
288        if (InitStrengthValue < 0.0) {
289            SceneString = String.format("USER REINITIALIZED CLASSIFICATION");
290        }
291        String SceneString2 = String.format("%s", SceneString);
292        if (MaxCurvedBandTF) {
293            SceneMaxCBString2 = String.format("%s", SceneMaxCBString);
294            SceneMaxCBLLString2 = String.format("%s", SceneMaxCBLLString);
295        }
296
297        int SatType = History.IRCurrentRecord.sattype;
298        int AutoPos = History.IRCurrentRecord.autopos;
299        int LandFlag = History.IRCurrentRecord.land;
300        int R34Value = History.IRCurrentRecord.r34;
301        int MSLPenvValue = History.IRCurrentRecord.MSLPenv;
302        double VZAValue = History.IRCurrentRecord.vza;
303        String SatelliteIDString = Functions.adt_sattypes(SatType);
304
305        String VersionString = Env.ADTVersion;
306        boolean LandFlagTF = Env.LandFlagTF;
307        boolean UseCKZTF = Env.UseCKZTF;
308
309        /* send results to the screen */
310
311        TextScreen_Return += String
312                .format("\n****************************************************\n\n");
313        TextScreen_Return += String
314                .format("                     UW - CIMSS                     \n");
315        TextScreen_Return += String.format("              ADVANCED DVORAK TECHNIQUE       \n");
316        TextScreen_Return += String.format("                  %17s                \n",
317                VersionString);
318        TextScreen_Return += String
319                .format("         Tropical Cyclone Intensity Algorithm       \n\n");
320        TextScreen_Return += String.format("             ----- Current Analysis ----- \n");
321        TextScreen_Return += String.format("     Date : %12s    Time : %12s\n", DateString,
322                TimeString);
323        TextScreen_Return += String.format("      Lat : %12s     Lon : %12s\n\n", LatitudeString,
324                LongitudeString);
325        if ((LandFlagTF) && (LandFlag == 1)) {
326            TextScreen_Return += String.format("              TROPICAL CYCLONE OVER LAND\n");
327            TextScreen_Return += String.format("              NO ADT ANALYSIS AVAILABLE\n");
328        } else {
329            if (Vmax1or10TF) {
330                TextScreen_Return += String.format("                CI# /Pressure/ Vmax\n");
331            } else {
332                TextScreen_Return += String.format("                CI# /Pressure/ Vmax(10-min)\n");
333            }
334            if (UseCKZTF) {
335                TextScreen_Return += String.format("                %3.1f /%6.1fmb/%5.1fkt\n\n",
336                        CurCI, PresWindValue_Pressure, PresWindValue_Wind);
337            } else {
338                TextScreen_Return += String.format("                %3.1f /%6.1fmb/%5.1fkt\n\n",
339                        CurCI, PresWindValue_Pressure + CurCIAdjP, PresWindValue_Wind);
340            }
341            if (HistoryFileName != null) {
342                TextScreen_Return += String.format("             Final T#  Adj T#  Raw T# \n");
343                TextScreen_Return += String.format("                %3.1f     %3.1f     %3.1f\n\n",
344                        CurFinalT, CurRawT, CurRawTorig);
345            }
346            if (!UseCKZTF) {
347                TextScreen_Return += String.format(
348                        "     Latitude bias adjustment to MSLP : %+5.1fmb\n\n", CurCIAdjP);
349            }
350            if (RadiusMaxWindTF) {
351                TextScreen_Return += String.format(
352                        " Estimated radius of max. wind based on IR :%3s km\n\n",
353                        RadiusMaxWindString);
354            }
355            TextScreen_Return += String.format(
356                    " Center Temp : %+5.1fC    Cloud Region Temp : %5.1fC\n", EyeTempValue,
357                    CloudTempValue);
358            TextScreen_Return += String.format("\n Scene Type : %s \n", SceneString2);
359            if (MaxCurvedBandTF) {
360                TextScreen_Return += String.format("              %s \n", SceneMaxCBString2);
361                TextScreen_Return += String.format("              %s \n", SceneMaxCBLLString2);
362            }
363            TextScreen_Return += String.format("\n Positioning Method : %s \n",
364                    AutoPosString[AutoPos]);
365            TextScreen_Return += String.format("\n Ocean Basin : %12s  \n",
366                    BasinString[BasinIDValue]);
367            TextScreen_Return += String.format(" Dvorak CI > MSLP Conversion Used : %8s  \n",
368                    PW_BasinValues[DomainID]);
369            if (HistoryFileName != null) {
370                TextScreen_Return += String.format("\n Tno/CI Rules : Constraint Limits : %9s\n",
371                        Rule8String[Rule8Value]);
372                TextScreen_Return += String.format("                   Weakening Flag : %6s\n",
373                        Rule9String[Rule9Value]);
374                TextScreen_Return += String.format("           Rapid Dissipation Flag : %6s\n",
375                        RapidString[RapidIntenValue]);
376            }
377            if (UseCKZTF) {
378                TextScreen_Return += String.format("\n C/K/Z MSLP Estimate Inputs :\n");
379                if (R34Value > 0.0) {
380                    TextScreen_Return += String.format("  - Average 34 knot radii : %4dkm\n",
381                            R34Value);
382                } else {
383                    TextScreen_Return += String.format("  - Average 34 knot radii : N/A\n");
384                }
385                TextScreen_Return += String.format("  - Environmental MSLP    : %4dmb\n",
386                        MSLPenvValue);
387            }
388            TextScreen_Return += String.format("\n Satellite Name : %7s \n", SatelliteIDString);
389            TextScreen_Return += String.format(" Satellite Viewing Angle : %4.1f degrees \n",
390                    VZAValue);
391        }
392        TextScreen_Return += String
393                .format("\n****************************************************\n\n");
394
395        System.err.println("TextScreenOutput() out, val: " + TextScreen_Return);
396        return TextScreen_Return;
397    }
398
399    /**
400     * Convert "degree.partial_degree" to degree/minute/second format.
401     *
402     * @param FullDegreeValue
403     *            Latitude/Longitude value to convert.
404     *
405     * @return Array whose values represent (in order): degrees, minutes, and
406     *         seconds.
407     */
408    public static int[] adt_lldms(double FullDegreeValue) {
409        int DegreeValue = (int) FullDegreeValue;
410        double MinuteValue = (FullDegreeValue - (double) DegreeValue) * 60.0;
411        double SecondValue = (MinuteValue - (double) ((int) MinuteValue)) * 60.0;
412        int DegreeReturn = Math.abs(DegreeValue);
413        int MinuteReturn = (int) Math.abs(MinuteValue);
414        int SecondReturn = (int) Math.abs(SecondValue);
415
416        return new int[] { DegreeReturn, MinuteReturn, SecondReturn };
417    }
418}