001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
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 http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv.data.cyclone;
030    
031    import java.rmi.RemoteException;
032    import java.util.ArrayList;
033    import java.util.Calendar;
034    import java.util.GregorianCalendar;
035    import java.util.Hashtable;
036    import java.util.List;
037    
038    import ucar.unidata.data.DataCategory;
039    import ucar.unidata.data.DataChoice;
040    import ucar.unidata.data.DataSourceDescriptor;
041    import ucar.unidata.data.DataSourceImpl;
042    import ucar.unidata.data.DataUtil;
043    import ucar.unidata.data.DirectDataChoice;
044    import ucar.unidata.geoloc.Bearing;
045    import ucar.unidata.util.DateUtil;
046    import ucar.visad.Util;
047    import visad.DateTime;
048    import visad.Real;
049    import visad.RealType;
050    import visad.Unit;
051    import visad.VisADException;
052    import visad.georef.EarthLocation;
053    import 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     */
059    public 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    }