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.data.cyclone;
030
031import java.rmi.RemoteException;
032import java.util.ArrayList;
033import java.util.Calendar;
034import java.util.GregorianCalendar;
035import java.util.Hashtable;
036import java.util.List;
037
038import ucar.unidata.data.DataCategory;
039import ucar.unidata.data.DataChoice;
040import ucar.unidata.data.DataSourceDescriptor;
041import ucar.unidata.data.DataSourceImpl;
042import ucar.unidata.data.DataUtil;
043import ucar.unidata.data.DirectDataChoice;
044import ucar.unidata.geoloc.Bearing;
045import ucar.unidata.util.DateUtil;
046import ucar.visad.Util;
047import visad.DateTime;
048import visad.Real;
049import visad.RealType;
050import visad.Unit;
051import visad.VisADException;
052import visad.georef.EarthLocation;
053import visad.georef.EarthLocationLite;
054
055/**
056 * Created by IntelliJ IDEA. User: yuanho Date: Apr 9, 2008 Time: 4:57:58 PM To
057 * change this template use File | Settings | File Templates.
058 */
059public abstract class StormDataSource extends DataSourceImpl {
060
061        /** _more_ */
062        public static final int CATEGORY_DB = 0; // - disturbance,
063
064        /** _more_ */
065        public static final int CATEGORY_TD = 1; // - tropical depression,
066
067        /** _more_ */
068        public static final int CATEGORY_TS = 2; // - tropical storm,
069
070        /** _more_ */
071        public static final int CATEGORY_TY = 3; // - typhoon,
072
073        /** _more_ */
074        public static final int CATEGORY_ST = 4; // - super typhoon,
075
076        /** _more_ */
077        public static final int CATEGORY_TC = 5; // - tropical cyclone,
078
079        /** _more_ */
080        public static final int CATEGORY_HU = 6; // - hurricane,
081
082        /** _more_ */
083        public static final int CATEGORY_SD = 7; // - subtropical depression,
084
085        /** _more_ */
086        public static final int CATEGORY_SS = 8; // - subtropical storm,
087
088        /** _more_ */
089        public static final int CATEGORY_EX = 9; // - extratropical systems,
090
091        /** _more_ */
092        public static final int CATEGORY_IN = 10; // - inland,
093
094        /** _more_ */
095        public static final int CATEGORY_DS = 11; // - dissipating,
096
097        /** _more_ */
098        public static final int CATEGORY_LO = 12; // - low,
099
100        /** _more_ */
101        public static final int CATEGORY_WV = 13; // - tropical wave,
102
103        /** _more_ */
104        public static final int CATEGORY_ET = 14; // - extrapolated,
105
106        /** _more_ */
107        public static final int CATEGORY_XX = 15; // - unknown.
108
109        /** _more_ */
110        // public static StormParam PARAM_DISTANCEERROR;
111
112        /** _more_ */
113        public static StormParam PARAM_MINPRESSURE;
114
115        /** _more_ */
116        public static StormParam PARAM_MAXWINDSPEED_KTS;
117
118        /** _more_ */
119        public static final int[] CATEGORY_VALUES = { CATEGORY_DB, CATEGORY_TD,
120                        CATEGORY_TS, CATEGORY_TY, CATEGORY_ST, CATEGORY_TC, CATEGORY_HU,
121                        CATEGORY_SD, CATEGORY_SS, CATEGORY_EX, CATEGORY_IN, CATEGORY_DS,
122                        CATEGORY_LO, CATEGORY_WV, CATEGORY_ET, CATEGORY_XX };
123
124        /** _more_ */
125        public static final String[] CATEGORY_NAMES = { "DB", "TD", "TS", "TY",
126                        "ST", "TC", "HU", "SD", "SS", "EX", "IN", "DS", "LO", "WV", "ET",
127                        "XX" };
128
129        /** _more_ */
130        public static final String ATTR_CATEGORY = "attr.category";
131
132        /** _more_ */
133        public static StormParam PARAM_STORMCATEGORY;
134
135        /** _more_ */
136        protected StormParam[] obsParams;
137
138        /** _more_ */
139        protected StormParam[] forecastParams;
140
141        /**
142         * _more_
143         * 
144         * @throws Exception
145         *             _more_
146         */
147        public StormDataSource() throws Exception {
148        }
149
150        /**
151         * _more_
152         * 
153         * @param descriptor
154         *            _more_
155         * @param name
156         *            _more_
157         * @param description
158         *            _more_
159         * @param properties
160         *            _more_
161         */
162        public StormDataSource(DataSourceDescriptor descriptor, String name,
163                        String description, Hashtable properties) {
164                super(descriptor, name, description, properties);
165        }
166
167        /**
168         * _more_
169         * 
170         * @return _more_
171         */
172        public boolean isEditable() {
173                return false;
174        }
175
176        /**
177         * _more_
178         * 
179         * @param dataChoice
180         *            _more_
181         * 
182         * @return _more_
183         */
184        public boolean canAddCurrentName(DataChoice dataChoice) {
185                return false;
186        }
187
188        /**
189         * _more_
190         * 
191         * @param id
192         *            _more_
193         * @param alias
194         *            _more_
195         * @param unit
196         *            _more_
197         * 
198         * @return _more_
199         */
200        protected static RealType makeRealType(String id, String alias, Unit unit) {
201                try {
202                        alias = alias
203                                        + "[unit:"
204                                        + ((unit == null) ? "null" : DataUtil.cleanName(unit
205                                                        .toString())) + "]";
206                        return ucar.visad.Util.makeRealType(id, alias, unit);
207                } catch (VisADException exc) {
208                        throw new RuntimeException(exc);
209                }
210        }
211
212        /**
213         * _more_
214         */
215        protected final void initAfter() {
216                try {
217                        incrOutstandingGetDataCalls();
218                        initializeStormData();
219                } finally {
220                        decrOutstandingGetDataCalls();
221                }
222        }
223
224        /**
225         * _more_
226         */
227        protected void initializeStormData() {
228        }
229
230        /**
231         * _more_
232         * 
233         * @throws VisADException
234         *             _more_
235         */
236        protected void initParams() throws VisADException {
237                if (PARAM_STORMCATEGORY == null) {
238                        PARAM_STORMCATEGORY = new StormParam(Util.makeRealType(
239                                        "stormcategory", "Storm_Category", null));
240                        PARAM_MINPRESSURE = new StormParam(makeRealType("minpressure",
241                                        "Min_Pressure", DataUtil.parseUnit("mb")));
242                        // PARAM_DISTANCEERROR =
243                        // new StormParam(Util.makeRealType("forecastlocationerror",
244                        // "Distance_Error", Util.parseUnit("km")), true,
245                        // false);
246                        PARAM_MAXWINDSPEED_KTS = new StormParam(makeRealType(
247                                        "maxwindspeedkts", "Max_Windspeed", DataUtil
248                                                        .parseUnit("kts")));
249
250                }
251        }
252
253        /**
254         * _more_
255         * 
256         * @param name
257         *            _more_
258         * 
259         * @return _more_
260         */
261        public int getCategory(String name) {
262                if (name == null) {
263                        return CATEGORY_XX;
264                }
265                for (int i = 0; i < CATEGORY_NAMES.length; i++) {
266                        if (name.equals(CATEGORY_NAMES[i])) {
267                                return CATEGORY_VALUES[i];
268                        }
269                }
270                return CATEGORY_XX;
271        }
272
273        /**
274         * _more_
275         * 
276         * @return _more_
277         */
278        public abstract List<StormInfo> getStormInfos();
279
280        /**
281         * _more_
282         * 
283         * @return _more_
284         */
285        public abstract String getId();
286
287        /**
288         * _more_
289         */
290        protected void doMakeDataChoices() {
291                List cats = DataCategory.parseCategories("stormtrack", false);
292                DataChoice choice = new DirectDataChoice(this, "stormtrack",
293                                "Storm Track", "Storm Track", cats, (Hashtable) null);
294                addDataChoice(choice);
295
296        }
297
298        /**
299         * Re-initialize the storm data.
300         */
301        public void reloadData() {
302                initializeStormData();
303                super.reloadData();
304        }
305
306        /**
307         * _more_
308         * 
309         * @param stormInfo
310         *            _more_
311         * @param waysToUse
312         *            _more_
313         * @param obsWay
314         *            _more_
315         * 
316         * @return _more_
317         * 
318         * @throws Exception
319         *             _more_
320         */
321        public StormTrackCollection getTrackCollection(StormInfo stormInfo,
322                        Hashtable<String, Boolean> waysToUse, Way obsWay) throws Exception {
323
324                try {
325                        incrOutstandingGetDataCalls();
326                        return getTrackCollectionInner(stormInfo, waysToUse, obsWay);
327                } finally {
328                        decrOutstandingGetDataCalls();
329                }
330
331        }
332
333        /**
334         * _more_
335         * 
336         * @return _more_
337         */
338        public String getWayName() {
339                return "Way";
340        }
341
342        /**
343         * _more_
344         * 
345         * @return _more_
346         */
347        public String getWaysName() {
348                return getWayName() + "s";
349        }
350
351        /**
352         * _more_
353         * 
354         * @param stormInfo
355         *            _more_
356         * @param waysToUse
357         *            _more_
358         * @param observationWay
359         *            _more_
360         * 
361         * @return _more_
362         * 
363         * @throws Exception
364         *             _more_
365         */
366
367        public abstract StormTrackCollection getTrackCollectionInner(
368                        StormInfo stormInfo, Hashtable<String, Boolean> waysToUse,
369                        Way observationWay) throws Exception;
370
371        /** _more_ */
372        private Hashtable seenWays = new Hashtable();
373
374        /** _more_ */
375        private List<Way> ways = new ArrayList();
376
377        /** _more_ */
378        private Hashtable<String, Way> wayMap = new Hashtable<String, Way>();
379
380        /**
381         * _more_
382         * 
383         * @param way
384         *            _more_
385         * 
386         * @return _more_
387         */
388        protected Way addWay(Way way) {
389                if (seenWays.get(way) == null) {
390                        seenWays.put(way, way);
391                        ways.add(way);
392                }
393                return way;
394        }
395
396        /**
397         * _more_
398         * 
399         * @param w
400         *            _more_
401         * @param name
402         *            _more_
403         * 
404         * @return _more_
405         */
406        protected Way getWay(String w, String name) {
407                Way way = wayMap.get(w);
408                if (way == null) {
409                        way = new Way(w, name);
410                        wayMap.put(w, way);
411                }
412                addWay(way);
413                return way;
414        }
415
416        /**
417         * _more_
418         * 
419         * @return _more_
420         */
421        public List<Way> getWays() {
422                return new ArrayList<Way>(ways);
423        }
424
425        /**
426         * _more_
427         * 
428         * @param stormId
429         *            _more_
430         * 
431         * @return _more_
432         */
433        public StormInfo getStormInfo(String stormId) {
434                List<StormInfo> stormInfos = getStormInfos();
435                for (StormInfo sInfo : stormInfos) {
436                        if (sInfo.getStormId().equals(stormId)) {
437                                return sInfo;
438                        }
439                }
440                return null;
441        }
442
443        /**
444         * _more_
445         * 
446         * @param dttm
447         *            _more_
448         * 
449         * @return _more_
450         * 
451         * @throws VisADException
452         *             _more_
453         */
454        public static int getYear(DateTime dttm) throws VisADException {
455                GregorianCalendar cal = new GregorianCalendar(DateUtil.TIMEZONE_GMT);
456                cal.setTime(ucar.visad.Util.makeDate(dttm));
457                return cal.get(Calendar.YEAR);
458        }
459
460        /**
461         * _more_
462         * 
463         * @param obsTrack
464         *            _more_
465         * @param fctTrack
466         *            _more_
467         * 
468         * @throws VisADException
469         *             _more_
470         */
471        public static void addDistanceError(StormTrack obsTrack, StormTrack fctTrack)
472                        throws VisADException {
473                List<StormTrackPoint> obsTrackPoints = obsTrack.getTrackPoints();
474                List<StormTrackPoint> fctTrackPoints = fctTrack.getTrackPoints();
475
476                for (StormTrackPoint stp : fctTrackPoints) {
477                        DateTime dt = stp.getTime();
478                        // StormTrackPoint stpObs = getClosestPoint(obsTrackPoints, dt);
479                        // double der = getDistance(stpObs, stp);
480                        // stp.addAttribute(PARAM_DISTANCEERROR.getReal(der));
481                }
482
483        }
484
485        /**
486         * _more_
487         * 
488         * @param obsTrack
489         *            _more_
490         * @param fctTrack
491         *            _more_
492         * @param param
493         *            _more_
494         * 
495         * @return _more_
496         * 
497         * @throws RemoteException
498         *             _more_
499         * @throws VisADException
500         *             _more_
501         */
502        public static StormTrack difference(StormTrack obsTrack,
503                        StormTrack fctTrack, StormParam param) throws VisADException,
504                        RemoteException {
505                List<StormTrackPoint> obsTrackPoints = obsTrack.getTrackPoints();
506                List<StormTrackPoint> fctTrackPoints = fctTrack.getTrackPoints();
507                List<StormTrackPoint> diffPoints = new ArrayList<StormTrackPoint>();
508
509                for (StormTrackPoint forecastPoint : fctTrackPoints) {
510                        Real forecastValue = forecastPoint.getAttribute(param);
511                        if (forecastValue == null) {
512                                continue;
513                        }
514                        DateTime forecastDttm = forecastPoint.getTime();
515                        StormTrackPoint[] range = getClosestPointRange(obsTrackPoints,
516                                        forecastDttm);
517                        if (range == null) {
518                                continue;
519                        }
520                        Real obsValue = null;
521                        if (range.length == 1) {
522                                // exact match:
523                                obsValue = range[0].getAttribute(param);
524                        } else {
525                                // Interpolate between the two points
526                                Real v1 = range[0].getAttribute(param);
527                                Real v2 = range[1].getAttribute(param);
528                                if ((v1 == null) || (v2 == null)) {
529                                        continue;
530                                }
531                                DateTime t1 = range[0].getTime();
532                                DateTime t2 = range[1].getTime();
533                                double percent = forecastDttm.getValue() - t1.getValue()
534                                                / (t2.getValue() - t1.getValue());
535
536                                double interpolatedValue = v2.getValue() + percent
537                                                * (v2.getValue() - v1.getValue());
538                                obsValue = v1.cloneButValue(interpolatedValue);
539                                System.err.println("interp %:" + percent + " v:" + obsValue
540                                                + " v1:" + v1 + " v2:" + v2 + "\n\tt1:" + t1 + " t2:"
541                                                + t2);
542                        }
543
544                        if (obsValue == null) {
545                                continue;
546                        }
547
548                        Real difference = (Real) forecastValue.__sub__(obsValue);
549                        StormTrackPoint newStormTrackPoint = new StormTrackPoint(
550                                        forecastPoint.getLocation(), forecastDttm, forecastPoint
551                                                        .getForecastHour(), new ArrayList<Real>());
552                        newStormTrackPoint.addAttribute(difference);
553                        diffPoints.add(newStormTrackPoint);
554                }
555                if (diffPoints.size() == 0) {
556                        return null;
557                }
558                return new StormTrack(fctTrack.getStormInfo(), fctTrack.getWay(),
559                                diffPoints, null);
560        }
561
562        /**
563         * _more_
564         * 
565         * @param aList
566         *            _more_
567         * @param dt
568         *            _more_
569         * 
570         * @return _more_
571         */
572        public static StormTrackPoint[] getClosestPointRange(
573                        List<StormTrackPoint> aList, DateTime dt) {
574                double timeToLookFor = dt.getValue();
575                int numPoints = aList.size();
576                double lastTime = -1;
577
578                for (int i = 0; i < numPoints; i++) {
579                        StormTrackPoint stp = aList.get(i);
580                        double currentTime = stp.getTime().getValue();
581                        if (timeToLookFor == currentTime) {
582                                return new StormTrackPoint[] { stp };
583                        }
584                        if (timeToLookFor < currentTime) {
585                                if (i == 0) {
586                                        return null;
587                                }
588                                if (timeToLookFor > lastTime) {
589                                        return new StormTrackPoint[] { aList.get(i - 1), stp };
590                                }
591                        }
592                        lastTime = currentTime;
593                }
594                return null;
595        }
596
597        /**
598         * _more_
599         * 
600         * @param aList
601         *            _more_
602         * @param dt
603         *            _more_
604         * 
605         * @return _more_
606         */
607        public static StormTrackPoint getClosestPoint(List<StormTrackPoint> aList,
608                        DateTime dt) {
609
610                int numPoints = aList.size();
611                StormTrackPoint stp1 = aList.get(0);
612                StormTrackPoint stp2 = aList.get(numPoints - 1);
613
614                double pValue = dt.getValue();
615                double minDiffLeft = 200000;
616                double minDiffRight = 200000;
617
618                for (int i = 0; i < numPoints; i++) {
619                        StormTrackPoint stp11 = aList.get(i);
620                        StormTrackPoint stp21 = aList.get(numPoints - i - 1);
621
622                        double p1Value = stp11.getTime().getValue();
623                        double p2Value = stp21.getTime().getValue();
624                        double diff1 = Math.abs(p1Value - pValue);
625                        double diff2 = Math.abs(p2Value - pValue);
626
627                        if ((pValue >= p1Value) && (diff1 < minDiffRight)) {
628                                if (pValue == p1Value) {
629                                        return stp11;
630                                }
631                                stp1 = stp11;
632                                minDiffRight = diff1;
633
634                        }
635
636                        if ((pValue <= p2Value) && (diff2 < minDiffLeft)) {
637                                if (pValue == p2Value) {
638                                        return stp21;
639                                }
640                                stp2 = stp21;
641                                minDiffLeft = diff2;
642                        }
643
644                }
645
646                double diff = minDiffLeft + minDiffRight;
647                EarthLocation el1 = stp1.getLocation();
648                EarthLocation el2 = stp1.getLocation();
649
650                double lat = ((diff - minDiffLeft) * el1.getLatitude().getValue() + (diff - minDiffRight)
651                                * el2.getLatitude().getValue())
652                                / diff;
653                double lon = ((diff - minDiffLeft) * el1.getLongitude().getValue() + (diff - minDiffRight)
654                                * el2.getLongitude().getValue())
655                                / diff;
656
657                EarthLocation el = new EarthLocationLite(new Real(RealType.Latitude,
658                                lat), new Real(RealType.Longitude, lon), null);
659
660                return new StormTrackPoint(el, dt, 0, null);
661
662        }
663
664        /**
665         * _more_
666         * 
667         * @return _more_
668         */
669        public boolean getIsObservationWayChangeable() {
670                return false;
671        }
672
673        /**
674         * _more_
675         * 
676         * @return _more_
677         */
678        public Way getDefaultObservationWay() {
679                return null;
680        }
681
682        /**
683         * _more_
684         * 
685         * @param p1
686         *            _more_
687         * @param p2
688         *            _more_
689         * 
690         * @return _more_
691         */
692        public static double getDistance(StormTrackPoint p1, StormTrackPoint p2) {
693
694                EarthLocation el1 = p1.getLocation();
695                EarthLocation el2 = p2.getLocation();
696
697                Bearing b = Bearing.calculateBearing(el1.getLatitude().getValue(), el1
698                                .getLongitude().getValue(), el2.getLatitude().getValue(), el2
699                                .getLongitude().getValue(), null);
700                return b.getDistance();
701
702        }
703}