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